mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Chore, Docs] (...): drop the archived notes and move the skill under tools
- the archive freeze rule goes with the directory - code comments lose citations to the removed notes
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# Everything here is executed by bash/sh on Linux, Android or WSL: keep LF even on a Windows checkout
|
||||
# (core.autocrlf=true would otherwise hand sh a script full of $'\r').
|
||||
* text=auto eol=lf
|
||||
@@ -0,0 +1,183 @@
|
||||
---
|
||||
name: anland-mobilegl-plasma
|
||||
description: Bring up, rebuild, deploy, switch backends on, test and debug the "MobileGL + anland + KDE Plasma" stack - a rooted Android tablet whose anland APK embeds the MobileGL server, plus a Droidspaces Arch Linux ARM container running KWin/plasmashell/Chrome through the MobileGL client library and GBM backend - driven from this Windows machine (Git Bash, WSL archlinux, adb). Use it after a phone reboot, when changing server (NDK) or client (container glibc) MobileGL code or anland's KWin backend/patch, when switching Espryt/Magma, when validating zero-copy dma-buf presentation, and when chasing crashes, black windows, noise or software-rendering fallbacks in the Plasma session.
|
||||
---
|
||||
|
||||
# MobileGL on anland KDE Plasma
|
||||
|
||||
**Setting this up on a new machine or phone from source: `reproduce.md` next to this file.**
|
||||
Human-readable runbook (Chinese, same content): `reference/runbook-plasma.md`.
|
||||
Background: `reference/handoff-legacy-mobilegl-unified.md` (state, commits, known
|
||||
issues) and `reference/plan-ahb-dmabuf.md` (zero-copy design). All scripts are in `scripts/` next to this file;
|
||||
parameters are documented at the top of each script and in `scripts/env.sh`.
|
||||
|
||||
**The phone may be shared with another session.** Before anything that restarts the app, the daemon,
|
||||
the container session or replaces a library, run the read-only `scripts/device/status.sh` and make sure
|
||||
nobody else is mid-test. Use your own `ct.sh` channel names (others use c, c2, c3, xd, xr, gbm).
|
||||
|
||||
## Architecture (10 lines)
|
||||
|
||||
1. Phone: Lenovo TB321FU (Y700, Adreno 750, Android 15, kernel 6.1), rooted with **KernelSU** (`/data/adb/ksu`, `ksud`; `su -c` works). USB serial `HA27Q3LQ`, or wireless adb `ip:port`.
|
||||
2. anland APK `com.anland.consumer.mobilegl` (debuggable build of anland branch `legacy-mobilegl-unified`): UI process + private service process `:mobilegl` (`MobileGLWorker`), which loads the **server** `libMobileGL.so` (NDK, from MobileGL `feat/disaggregated`) and listens on abstract socket `@anland-mobilegl` from the first attached window on. One process serves every client (KWin, plasmashell, ksplash, Chrome...), one backend per process. `MobileGLWorker` is a **foreground service** (notification "Linux desktop is running" / action "Stop desktop"), so the server and the session outlive a hidden, locked or swiped-away window.
|
||||
3. Backend = the app setting "Renderer backend" (Settings > Connection > MobileGL desktop; file `/data/data/<pkg>/files/mobilegl-backend`, default DirectGLES; also `am start ... --es mobilegl_backend X`). Espryt = DirectGLES, Magma = DirectVulkan. The server reads it when its process starts; the app publishes it as `/data/local/tmp/anland-mobilegl/backend` and the session copies that into the container's `/etc/mobilegl/backend` (what every client reports; mismatch = "Hello.backendType disagrees with pinned backend"). `debug.mobilegl.backend`, if someone sets it, still overrides the setting until the next reboot.
|
||||
4. Experiment display daemon `/data/adb/modules/anland-daemon/display_daemon /data/local/tmp/anland-mobilegl/display.sock` (display/input broker), started by the app (below). The module only starts the original one on `/data/local/tmp/display_daemon.sock` at boot; leave that alone.
|
||||
5. Container `arch-kde-mgl` (Droidspaces 6.4.5, Arch Linux ARM aarch64 glibc, host net, `rootfs.img` mounted at `/mnt/Droidspaces/arch-kde-mgl` on the Android side). Bind mounts: `/data/local/tmp/anland-mobilegl` -> `/run/anland-mobilegl`, `/data/local/tmp/display_daemon.sock` -> `/run/display.sock`.
|
||||
6. `desktop-session.service` (User=swung0x48, **enabled**: the container boots straight into it, as the original image does) has drop-in `/etc/systemd/system/desktop-session.service.d/mobilegl.conf` (anland `producers/kde/Arch_v5/desktop-session-mobilegl.conf`, installed + enabled by `sync-build-kwin.sh`): `ExecStartPre=mobilegl-startup.sh wait` (until `@anland-mobilegl` listens in `/proc/net/unix` and the daemon socket exists), `ExecStartPre=+... sync-backend`, `Restart=always`, then `ExecStart=/opt/mobilegl/bin/mobilegl-startup.sh plasma`, which deletes the sticky software-renderer key, sets `MOBILEGL_IPC_PROBE_TIMEOUT_MS=0` session-wide (MobileGL never declines, so glvnd never falls through to another GL stack), writes a user drop-in for `plasma-kwin_wayland.service` (kwin_wayland_wrapper -> `/opt/mobilegl/kwin/bin/kwin_wayland` + `/opt/mobilegl/kwin/lib/libkwin.so.6.7.4`, `ANLAND_MOBILEGL=1`, `MOBILEGL_IPC_SURFACE=server`) and runs `startplasma-wayland`.
|
||||
7. KWin 6.7.4 with anland's `anland` backend renders straight into the anland Android Surface through the server. While the window is not on screen (consumer disconnected) the backend powers its output down (DPMS Off + RenderLoop inhibited: no compositing, no frame callbacks) and back up when the window returns. Every other GL process is `MOBILEGL_IPC_SURFACE=offscreen` and presents through KWin.
|
||||
8. Client = `/opt/mobilegl/lib/libMobileGL.so` (glibc aarch64), the container's **system-wide** GL vendor (section below): glvnd EGL vendor `/usr/share/glvnd/egl_vendor.d/10_mobilegl.json` (sorts before the distribution's `50_*.json`, which serves whenever MobileGL declines), GLX vendor `/usr/lib/libGLX_mobilegl.so.0`, settings from `/etc/mobilegl/client.conf` (`MOBILEGL_TRANSPORT=spawn MOBILEGL_IPC_DATA=shm MOBILEGL_IPC_CONTROL=unix:@anland-mobilegl`) + `/etc/mobilegl/backend`. No per-process environment is needed.
|
||||
9. Zero-copy windows: client `wl_egl_window` frames are server-allocated AHardwareBuffer "shared images" exported as dma-buf fds over `zwp_linux_dmabuf_v1`; KWin imports them with EGL and the server recognises its own buffer by inode. KWin gets a DRM device via `GBM_BACKEND=mobilegl` -> `/usr/lib/gbm/mobilegl_gbm.so` (node is only an identity, never ioctl'd). Without linux-dmabuf the client falls back to wl_shm readback.
|
||||
10. X11 (`reference/plan-x11-gpu.md`): Xwayland draws with **glamor on MobileGL** (an offscreen session; `GBM_BACKEND=mobilegl` on KWin's render node; window pixmaps are shared images handed to KWin as linux-dmabuf buffers; its `glFlush` publishes them through the server, `/tmp/mgl-xwayland.client.log`); `ANLAND_XWAYLAND_GLAMOR=0` in KWin's drop-in brings back the software `-shm` Xwayland. GLX windows present their frames as shared images through **DRI3 + Present** (log `presented through DRI3+Present shared images`), falling back to readback + MIT-SHM / `xcb_put_image` when the X server has no DRI3 (`MOBILEGL_GLX_PRESENT=readback` forces it per app). Chrome runs ANGLE-GLES over MobileGL EGL with the GPU in its own process (`mobilegl-startup.sh chrome`; `MOBILEGL_CHROME_GPU=in-process` opts out), presenting GBM dma-bufs over linux-dmabuf; after a device loss Chrome restarts that process and repaints.
|
||||
|
||||
## First-time prerequisites (Windows host)
|
||||
|
||||
- Git Bash, `adb` (SDK platform-tools), CMake >= 3.22 and Ninja on PATH (seen: CMake 3.27.6, Ninja 1.13.2), Python 3 + Pillow (only `rec.sh`).
|
||||
- Android NDK **27.2.12479018** at `%LOCALAPPDATA%/Android/Sdk/ndk/27.2.12479018` (override `ANDROID_NDK`).
|
||||
- WSL distro **archlinux** with clang, lld, llvm, cmake, ninja. **lld's major version must equal clang/llvm-libs'** (lld 23 on llvm 22 broke `ld.lld`; working set: all 22.1.6). Then export the sysroot once: `bash scripts/xsysroot/refresh-sysroot.sh` (-> `~/sysroots/arch-kde-mgl` + `~/sysroots/aarch64-arch.cmake`; re-run after container package upgrades, then `xbuild.sh --reconfigure`).
|
||||
- MobileGL worktree (never build in the shared main checkout): `GIT_LFS_SKIP_SMUDGE=1 git worktree add ...`, `git submodule update --init --recursive`, and copy `3rdparty/glslang/External/spirv-tools` from the main checkout (the submodule leaves it empty).
|
||||
- anland worktree on `legacy-mobilegl-unified` (KWin patch/backend, startup script, `mobilegl-tools/`).
|
||||
- Phone side that already exists and is NOT recreated by this skill: the APK (build: anland `consumers/anland_v5/android_consumer/MOBILEGL.md`, gradle `:app:assemblePlainDebug` with `MOBILEGL_DIST`, `-PmobileglApplicationId=com.anland.consumer.mobilegl -PanlandDefaultSocket=/data/local/tmp/anland-mobilegl/display.sock`, from Git Bash with `MSYS_NO_PATHCONV=1`; signed with this machine's `~/.android/debug.keystore`, so `adb install -r` works; reinstalling with another key changes the UID and needs the KernelSU grant again - `mobilegl-tools/ksu_grant.c`), the anland KernelSU modules (`anland-daemon`, `anland-awl`), the container with KWin's configured build tree `/root/mobilegl-build/kwin-6.7.4/build` and pristine `/root/gbm-dev/kwin-a/src`.
|
||||
- Install the helpers once (container must be running): `bash scripts/push-tools.sh` (device/*.sh -> `/data/local/tmp/anl`, `mgrun` -> container `/usr/local/bin`, `kwprep.sh` -> container `/root`).
|
||||
|
||||
Run host scripts from Git Bash as `bash scripts/<x>.sh`. Raw adb with device paths needs `MSYS_NO_PATHCONV=1` and the whole root command in one quoted string: `adb shell 'su -c "..."'`. A device script can also be run without pushing: `MSYS_NO_PATHCONV=1 adb shell 'su -c sh' < scripts/device/status.sh`.
|
||||
|
||||
## Starting, idling, stopping - no manual steps
|
||||
|
||||
Nothing starts at phone boot (container `run_at_boot=0`). **Opening the app** (launcher icon = `am start -n com.anland.consumer.mobilegl/com.anland.consumer.MainActivity`, no extras; the APK's default socket is the experiment's, `-PanlandDefaultSocket`) calls `MobileGLDesktop.ensureStarted()` - the single trigger - which starts the foreground service and runs the APK's `assets/mobilegl-desktop.sh up` as root (`su`): display daemon if absent (the script leaves the app's cgroup first, so it survives the app) -> backend published -> container started if stopped -> `systemctl start --no-block desktop-session`. The window waits for the daemon socket; the server listens once the Surface is attached; the session's `wait` step then lets Plasma start. A reopen with everything up only reattaches the window. Log: `/data/local/tmp/anland-mobilegl/desktop.log`, logcat `AnlandMobileGL` ("MobileGL desktop up done in N ms").
|
||||
|
||||
Hidden window, screen off, lock screen or swiped-away task: server and session stay (foreground service); KWin turns the workspace's DPMS off (logs "viewer gone: output powered down"; windows get xdg_toplevel `suspended`, no frame callbacks) and back on ("viewer back"). Client swaps are paced by frame callbacks (`MG_Impl/EGLImpl/FrameThrottle.h`): interval >= 1 waits like any Wayland EGL; interval 0 (Chrome) drops to ~1 fps once a callback goes unanswered for 200 ms.
|
||||
|
||||
Measured 2026-10-03 (Espryt, Chrome on webglsamples aquarium; % of one core, phone = all of Android): open-to-desktop cold 7.5-12.5 s (daemon 0.5, server 1.0, container 1.5, kwin ~5, plasmashell ~6-7), after a phone reboot 8.5 s, reopen of a running desktop 0.2 s; visible phone 249% / kwin 13 / chrome 99 / server 57 / GPU 22%, hidden 54 / 0 / 6 / 2 / 0%, locked 38 / 0 / 8 / 3 / 0%. Stop desktop 1.7 s. Wireless adb does not come back by itself after a reboot (re-enable it on the phone). **Stop desktop** (notification action, or `am startservice -n <pkg>/com.anland.consumer.MobileGLWorker -a com.anland.consumer.mobilegl.STOP`) runs `mobilegl-desktop.sh down` (session + container stopped, daemon stays), closes the windows, ends `:mobilegl`.
|
||||
|
||||
```sh
|
||||
adb connect <ip>:<port> # only for wireless debugging
|
||||
export MSYS_NO_PATHCONV=1
|
||||
adb shell 'su -c "sh /data/local/tmp/anl/bringup.sh"' # after a reboot: wake, dismiss keyguard, open the app, print the timeline
|
||||
bash scripts/desktop-verify.sh push cold reopen idle # timed cold start, swipe-away + reopen, CPU/GPU visible/hidden/locked
|
||||
adb shell 'su -c "sh /data/local/tmp/anl/status.sh"'
|
||||
```
|
||||
|
||||
`droidspaces` has no `status` command; `droidspaces show` lists running containers. `device/run-plasma.sh` is a developer restart (session + logind user, then the app), needed after a rebuild because KWin does not reconnect to a new server process. `device/desktop-reset.sh` takes everything down to the post-reboot state. The old manual sequence (setprop, start container, `restart-daemon.sh`, `run-plasma.sh`) is gone; `restart-daemon.sh` remains for daemon debugging only.
|
||||
|
||||
## Build and deploy loops
|
||||
|
||||
**Server (Android, NDK) -> APK lib**
|
||||
```sh
|
||||
bash scripts/build-android.sh # -> <worktree>/build-android/libMobileGL.so (RelWithDebInfo, unstripped)
|
||||
bash scripts/cyc.sh s1 # clear sticky SW-renderer key, push, swap into APK, restart session, screenshot $ANL_DIR/s1.png, print fatals
|
||||
```
|
||||
`device/swap.sh` overwrites `/data/app/.../lib/arm64/libMobileGL.so` keeping owner/mode/SELinux context (original saved as `/data/local/tmp/anland-mobilegl/libMobileGL.so.orig`); no re-signing. To swap without the session restart: push to `/data/local/tmp/anland-mobilegl/libMobileGL.new.so`, `su -c "sh /data/local/tmp/anl/swap.sh"`, then `run-plasma.sh` anyway (KWin does not reconnect to a new server process).
|
||||
|
||||
**Client + GBM backend (container glibc) via WSL cross-build**
|
||||
```sh
|
||||
bash scripts/xbuild.sh [--reconfigure] # WSL ~/mgl-xbuild-a64; ~150 s full, seconds incremental
|
||||
bash scripts/xdeploy.sh # rename-into-place install of libMobileGL.so + mobilegl_gbm.so, aliases, vendor JSONs, client.conf, GLX/GBM env drop-ins
|
||||
```
|
||||
Running clients keep the old inode; restart the client, or `run-plasma.sh` for KWin/plasmashell. Build server and client from the **same commit** (both stamp `git rev-parse HEAD`), else the logs WARN "compatible wire with different build". Fallback in-container build (slow): `scripts/sync-src.sh` + anland `mobilegl-tools/anland-build-client.sh`.
|
||||
|
||||
**KWin (anland backend / kwin.patch) - built inside the container**
|
||||
```sh
|
||||
bash scripts/kwin-stage.sh build # stage CR-stripped patch+backend+startup script, kwprep.sh, background sync-build-kwin.sh
|
||||
printf 'tail -5 /root/kwin-build.log\n' | bash scripts/ct.sh kw # wait for "== ALL DONE =="
|
||||
adb shell 'su -c "sh /data/local/tmp/anl/run-plasma.sh"'
|
||||
```
|
||||
`sync-build-kwin.sh` (anland `producers/kde/Arch_v5/`) copies only the backend sources, then `ninja -j4`, installs `kwin_wayland`, `libkwin.so.6.7.4`, the patched screencast plugin and `/opt/mobilegl/bin/mobilegl-startup.sh` by atomic rename. `kwprep.sh` is what applies changed `kwin.patch` hunks to core KWin files. Repo-root `libdisplay_producer/` is not synced by either. `kwin-stage.sh` is assembled from those two scripts and was not run as one unit yet.
|
||||
|
||||
**Unit tests (no device)** - in WSL (the MSVC build of this branch is broken):
|
||||
```sh
|
||||
MSYS2_ARG_CONV_EXCL='*' wsl -d archlinux -- bash /mnt/c/<repo>/tools/anland-mobilegl-plasma/scripts/wsl-test.sh MultiSessionTest TwinOwnershipTest
|
||||
MSYS2_ARG_CONV_EXCL='*' wsl -d archlinux -- bash -lc 'cd ~/mgl-anl-bld && ctest -R "MultiSession|TwinOwnership" -E DirectVulkan --output-on-failure'
|
||||
```
|
||||
`wsl-all.sh` builds and runs every unit test. Never run DirectVulkan integration tests; never two ctest runs at once.
|
||||
|
||||
## Switching backends
|
||||
|
||||
In the app: Settings > Connection > MobileGL desktop > Renderer backend, then Stop desktop and open the app. Or `adb shell 'su -c "sh /data/local/tmp/anl/switch.sh DirectGLES"'`, which does the same (saves the setting through the launch extra, STOPs the service, reopens; clears a stale `debug.mobilegl.backend`). No setprop, no file to edit in the container: the session copies the published backend into `/etc/mobilegl/backend` on its next start. `cyc.sh` keeps whatever is set.
|
||||
|
||||
## System-wide vendor and fallback
|
||||
|
||||
Installed by `xdeploy.sh` (and anland's `anland-build-client.sh`), so any process in the container gets MobileGL through the stock libglvnd/libgbm:
|
||||
|
||||
| File | Does |
|
||||
|---|---|
|
||||
| `/usr/share/glvnd/egl_vendor.d/10_mobilegl.json` | EGL vendor, asked before the system's `50_*.json` |
|
||||
| `/opt/mobilegl/share/glvnd/egl_vendor.d/50_mobilegl.json` | same JSON, for per-process forcing with `__EGL_VENDOR_LIBRARY_FILENAMES` |
|
||||
| `/etc/mobilegl/client.conf` | `KEY=value` defaults for every client (transport, data, endpoint); rewritten on deploy |
|
||||
| `/etc/mobilegl/backend` | the backend (the session's `sync-backend` step copies the app's choice in); wins over a `MOBILEGL_BACKEND_TYPE` line in client.conf |
|
||||
| `/etc/environment.d/10-mobilegl.conf` | `__GLX_VENDOR_LIBRARY_NAME=mobilegl`, `GBM_BACKEND=mobilegl` for the systemd user manager (all Plasma services and what they start, terminals included) |
|
||||
| `/etc/profile.d/mobilegl.sh` | the same two for login shells (`bash -l`, `su -l`, ssh, `droidspaces run bash -l`) |
|
||||
|
||||
Precedence is environment > `/etc/mobilegl/backend` (backend only) > client.conf > built-in; `MOBILEGL_CONFIG_FILE` / `MOBILEGL_BACKEND_FILE` name other files (empty = off); a server process never reads them.
|
||||
|
||||
**Fallback.** Before bringing anything up the client probes the configured endpoint (one non-blocking connect, `MOBILEGL_IPC_PROBE_TIMEOUT_MS`, default 250, 0 = off; a refused abstract socket answers in ~0.2 ms). With no server: the EGL vendor returns `EGL_NO_DISPLAY` and no devices (glvnd moves on to the other vendor), the GLX vendor fails `__glx_Main` (libGLX falls back to the vendor Xwayland names), and `mobilegl_gbm` fails `create_device` (libgbm falls back to the device's own backend). Client queries (`eglQueryString(EGL_NO_DISPLAY)`, `eglGetProcAddress`) never bring a session up. MobileGL also steps aside for a GBM device another backend created. It no longer steps aside for `LIBGL_ALWAYS_SOFTWARE=1` (918d40dd): that is Mesa's software-renderer selection, not a reason to disable MobileGL's entry points, so a process that sets it (a software Xwayland included) is still served by MobileGL while a server answers. MobileGL never opens an X connection to a display its own process serves (`MG_Util/X11/DisplayGuard.h`; `MOBILEGL_X11_DIAL=0` forbids all), which is what made a glamor Xwayland deadlock on itself before. Show it without stopping the real server:
|
||||
```sh
|
||||
sed 's|^MOBILEGL_IPC_CONTROL=.*|MOBILEGL_IPC_CONTROL=unix:@nobody|' /etc/mobilegl/client.conf > /tmp/down.conf
|
||||
sudo -u swung0x48 env -i HOME=/home/swung0x48 PATH=/usr/bin XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-0 \
|
||||
MOBILEGL_CONFIG_FILE=/tmp/down.conf eglinfo -B -p wayland # vendor = the other one, ~0.1 s
|
||||
```
|
||||
|
||||
## Test clients and how to verify
|
||||
|
||||
X11 on the GPU has its own driver, `scripts/x11-verify.sh` (preflight, measure before/after with glxgears at `vblank_mode=0` on both GLX paths (presentation throughput only: glxgears is fixed-function GL, which MobileGL does not implement, so its window is black by design), deploy-client/-kwin/-server, check, apps, stop); `scripts/device/x11-measure.sh` refuses to load the phone below 40% battery.
|
||||
|
||||
Inside the container as root (`printf '...' | bash scripts/ct.sh <chan>`), `mgrun offscreen <cmd>` runs a client as swung0x48 in the live session through a login shell, with nothing but the surface mode and the log path set (log `/tmp/mgl-run.client.log`, override base with `MGLOG=/tmp/x.log`); a plain `env -i ... bash -l -c '<cmd>'` as swung0x48 gets MobileGL the same way:
|
||||
```sh
|
||||
mgrun offscreen eglinfo -B ; mgrun offscreen glxinfo -B
|
||||
timeout 25 mgrun offscreen glmark2-es2-wayland -s 1280x720 --run-forever > /tmp/gm.out 2>&1 &
|
||||
sleep 8; grep -a "presented through" /tmp/mgl-run.client.log
|
||||
```
|
||||
Chrome: `bash scripts/chrome-launch.sh <tag> [flags]` (log `/tmp/mgl-chrome-<tag>.client.log`, screenshot `ch-<tag>.png`; `CHROME_GPU=in-process` puts the GPU back in the browser, which cannot recover from a device loss).
|
||||
Video: Chrome decodes in hardware. The GPU process's render node has a VA-API driver in the container (`/usr/lib/dri/msm_drm_drv_video.so`, DroidSpaces' V4L2 driver): Chrome allocates its frames with `vaCreateSurfaces` and exports them (`vaExportSurfaceHandle`, one fd, R8 + GR88 layers) - the driver's own buffers, never GBM ones - and imports each as an NV12 (P010 for 10-bit) EGLImage bound to `GL_TEXTURE_EXTERNAL_OES`. MobileGL takes such a FOREIGN YUV dma-buf through its labelled CPU-copy fallback: the server maps it and copies it into a YUV AHardwareBuffer of its own (`AHardwareBuffer_lockPlanes`) at the reader's first use in each frame (any present, publishing flush or native fence), then converts that into the texture's RGBA level on the GPU (Espryt: `GL_EXT_YUV_target` with the import's colour hints, `MOBILEGL_YUV_DRIVER_CONVERSION=1` for the driver's external sampler; Magma: `VkSamplerYcbcrConversion` on the AHB's external format). A server-allocated NV12/P010 buffer (GBM `gbm_bo_create(NV12)`) needs no copy. `MOBILEGL_CHROME_VIDEO_DECODE=software` in `mobilegl-startup.sh chrome` brings back `--disable-accelerated-video-decode`. Check a video with `scripts/probes/cdpmedia.py <port> <s>` in the container (CDP `Media.enable`: `kVideoDecoderName = VaapiVideoDecoder`, `kIsPlatformVideoDecoder = true`; `CDP_NAVIGATE=<url>` / `CDP_EXPR=<js>`), which needs Chrome started with `--remote-debugging-port` and a non-default `--user-data-dir` (a fresh profile also needs `--password-store=basic`, else page loads wait forever on KWallet), and grep Chrome's log for `exit_code` (a GPU-process restart). `scripts/probes/yuvprobe.c` pixel-checks foreign NV12/P010 imports, their per-frame refresh and a GBM NV12 buffer (`mgrun offscreen mgl-yuvprobe`, exit 0 = pass). KWin's own NV12 path (it splits a YUV buffer into R8 + GR88 imports) is not served: MobileGL imports no single-channel dma-bufs, so KWin advertises no YUV format over linux-dmabuf. A vendor-agnostic replacement driver (MobileVA: VA-API on Android MediaCodec, decoding into server YUV images with no CPU copy, the current driver kept as an A/B fallback) is planned, not built.
|
||||
Device loss: `setprop debug.mobilegl.inject_device_lost_pid <pid>` (as root) loses the server session of the client whose pid - as it sees itself in the container - is `<pid>`, once per value, on any backend and whether or not it presents; `debug.mobilegl.inject_device_lost_at N` instead loses whichever session makes the N-th device check (Espryt presents and read-backs only). `bash scripts/chrome-recover.sh <in-process|process> [rounds]` checks that Chrome repaints after such a loss without a restart (battery-guarded, stops below 40%). `bash scripts/kwin-recover.sh [rounds]` loses KWin's own session (default twice) and checks that KWin restarts compositing on a fresh session - same KWin, plasmashell and client processes, the screen still animating (glmark2 on Wayland, glxgears on X11, `CHROME=1` adds Chrome) - which needs the anland backend + kwin.patch reset handling deployed (`kwin-stage.sh build`).
|
||||
Probes: `scripts/probes/texfmt.c` (texture formats keep alpha?), `dmahash.c` (hash a process's dma-bufs), `yuvprobe.c` (YUV dma-buf sampling, `gcc -O1 /root/yuvprobe.c -o /usr/local/bin/mgl-yuvprobe -lEGL -lGLESv2 -lgbm -lm`), `cdpmedia.py` (which decoder a Chrome page uses); build in the container, e.g. `gcc -O1 /root/texfmt.c -o /usr/local/bin/mgl-texfmt -lEGL`, run with `mgrun offscreen`.
|
||||
GPU hangs: a shader that never ends stops the WHOLE GPU (every process, MobileGL or not) until the kernel resets it - ~2 s, or ~10 s ("Preemption Fault" in dmesg) when a higher-priority context waits; nothing in user space shortens one stall. What the server does is end the guilty session so it cannot hang the GPU again (Magma: event-bracketed submissions + `mgl-gpu-watch`, budget `MOBILEGL_GPU_HANG_BUDGET_MS` / `debug.mobilegl.gpu_hang_budget_ms`, default 2000; Espryt: the driver's GUILTY reset status, which it reports after the ~2 s reset only). Repro: `scripts/probes/hangclient.c` (Wayland + GL 3.3 core; `-H 60 -N 1 -E 60` hangs every 60 frames, `-H -1` is a frame-gap observer; build with wayland-scanner's xdg-shell files, `-lwayland-client -lwayland-egl -lEGL -lOpenGL -lm`), and `gpuprobe.c` (NDK, plain adb shell; `gpuprobe 30 high` is a high-priority GLES context outside MobileGL - it both measures the device-wide stall and forces the 10 s preemption path).
|
||||
|
||||
A result counts only if ALL hold:
|
||||
- client log says `presented through linux-dmabuf shared images` (not `wl_shm`; `could not be presented through a linux-dmabuf shared image` = fell back);
|
||||
- `bash scripts/shot.sh g 2 1` gives two different hashes while the client animates (a "fast" score can mean frames are not shown);
|
||||
- `adb logcat -b crash -d` and `adb logcat -d | grep -E "Fatal signal|Abort message|E MobileGL"` are clean (a frozen window is usually a dead server);
|
||||
- for plasmashell verdicts, the sticky software renderer is off (below) and `/tmp/mgl-plasmashell.client.log` is freshly written.
|
||||
Reference (glmark2-es2-wayland 1280x720, zero-copy vs wl_shm): Espryt 187 vs 49, Magma 158 vs 34. Rerun any surprising pass.
|
||||
|
||||
## Logs
|
||||
|
||||
| What | Where |
|
||||
|---|---|
|
||||
| KWin client side | container `/tmp/mobilegl-compositor.client.log` |
|
||||
| plasmashell / ksplash | `/tmp/mgl-plasmashell.client.log`, `/tmp/mgl-ksplash.client.log` (user drop-ins `~/.config/systemd/user/plasma-{plasmashell,ksplash}.service.d/mobilegl-log.conf`) |
|
||||
| mgrun / Chrome | `/tmp/mgl-run.client.log`, `/tmp/mgl-chrome-<tag>.client.log` (+ `/tmp/chrome-<tag>-out.log`) |
|
||||
| Server | `adb logcat -s MobileGL`; file `/data/data/com.anland.consumer.mobilegl/files/mobilegl-server.server.log` |
|
||||
| Native crashes | `adb logcat -b crash -d`; container `coredumpctl list` |
|
||||
| Session / KWin stderr | container `journalctl -u desktop-session --no-pager`, `journalctl --no-pager --since -2min \| grep kwin_wayland` |
|
||||
| Display daemon | `/data/local/tmp/anland-mobilegl/display-daemon.log` |
|
||||
| Desktop start/stop (app's su script) | `/data/local/tmp/anland-mobilegl/desktop.log`, `container-start.log`; logcat `-s AnlandMobileGL` |
|
||||
| Session wait / backend sync | container `journalctl -u desktop-session --no-pager` ("waiting for the MobileGL server ...") |
|
||||
|
||||
`MOBILEGL_LOG_FILE_PATH=/tmp/x.log` produces `/tmp/x.client.log` (and `.server.log` for forwarded lines); files are truncated on open, so one base per process. Client builds log at INFO. Symbolize server frames with `$ANDROID_NDK/toolchains/llvm/prebuilt/windows-x86_64/bin/llvm-addr2line.exe -Cfe build-android/libMobileGL.so <offset>`.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **pkill -f self-match**: a `ct.sh`/`droidspaces run bash -lc "<script>"` process carries the script in its argv; `pkill -f chrome` kills the runner. Use `pkill -x`.
|
||||
- **Root-owned /tmp files**: a log created by a root-run client cannot be reopened by swung0x48 later (no log appears). `rm -f` it as root first.
|
||||
- **Sticky Plasma software renderer**: after any server crash Plasma writes `[QtQuickRendererSettings] SceneGraphBackend=software` into `~swung0x48/.config/kdeglobals`; plasmashell then renders on the CPU and screenshots still look fine (tray icon "正在使用的软件渲染器"). `mobilegl-startup.sh plasma` deletes it at every session start; within a running session that hit a crash, restart the session (`run-plasma.sh`) before any verdict.
|
||||
- **Lock screen**: autolock is off (`kscreenlockerrc [Daemon] Autolock=false`); a real lock can only be left by typing the password (logind lock/unlock is not supported for this session type - `device/unlock.sh` is unverified). Test the greeter with `/usr/lib/kscreenlocker_greet --testing` and kill it.
|
||||
- **CRLF**: `core.autocrlf=true` here; anland's backend sources and `sync-build-kwin.sh` are CRLF in the Windows checkout. Strip CR before anything runs on Linux/Android (`tr -d '\r'`; `ct.sh`, `push-tools.sh`, `kwin-stage.sh` do). This skill has a `.gitattributes` forcing LF.
|
||||
- **Display daemon**: the app starts it; the session's `wait` step keeps KWin from starting without it (a KWin that cannot reach it logs "failed to connect to display daemon at /run/anland-mobilegl/display.sock" and exits in ~3 s). Anything started from the app's `su` must leave the app's cgroup (`echo $$ > /sys/fs/cgroup/cgroup.procs`) or a force-stop kills it.
|
||||
- **Device power/connection**: low battery or the screen turning off (30 s default) can kill the SurfaceView - `svc power stayon true`; wireless adb `ip:port` changes per pairing - never pin `-s HA27Q3LQ`, use `ANDROID_SERIAL` if several devices are attached.
|
||||
- **Container vs phone paths**: container `/tmp` is a different place from the phone's `/data/local/tmp`; move files via `/mnt/Droidspaces/arch-kde-mgl/root` (container `/root`) or `/data/local/tmp/anland-mobilegl` (= container `/run/anland-mobilegl`).
|
||||
- **Never overwrite a mapped .so in place** (container or APK while running): copy to a new inode and `mv -f`; the APK swap force-stops the app first.
|
||||
- **Input**: plain `adb shell input` is blocked - use `su -c input`; anland grabs the touchscreen, so if `input tap` does nothing use `input mouse tap x y`. Never `debuggerd -b` a container process (signal 35 kills glibc processes).
|
||||
- **linux-dmabuf**: the client must use `create` + roundtrip, never `create_immed` (KWin answers a failed immediate import with a protocol error that kills the client).
|
||||
- **Cross-session bugs**: noise / another window's pixels after some client starts or exits = process-global state in the multi-session server (handles are per client, FBO/VAO per context). Repro with `kscreenlocker_greet --testing`.
|
||||
- `/etc/environment` leaks the baseline session's driver variables through pam_env (and, via `/usr/lib/environment.d/99-environment.conf`, into the user manager); `mobilegl-startup.sh` unsets them - keep that when editing it.
|
||||
- **Shared log path**: Xwayland inherits KWin's `MOBILEGL_LOG_FILE_PATH`; any line a process logs opens (truncates) that file. MobileGL stays silent in processes it does not serve, but give any extra client its own `MOBILEGL_LOG_FILE_PATH`.
|
||||
- **Stale per-process overrides**: an `__EGL_VENDOR_LIBRARY_FILENAMES` / `MOBILEGL_*` left in a unit drop-in or the user manager's environment (`systemctl --user show-environment`) still wins over the system files; `run-plasma.sh` (terminate-user) clears the manager's.
|
||||
- `handoff` mentions a `kwin_wayland` logging shim and `/tmp/mgl-kwin-<pid>.log`: stale - `/opt/mobilegl/kwin/bin/kwin_wayland` is now the real binary and logs to `/tmp/mobilegl-compositor.client.log`.
|
||||
|
||||
## House rules
|
||||
|
||||
- Work in a git worktree, never in the shared main checkout or its build dirs; worktrees need `GIT_LFS_SKIP_SMUDGE=1`.
|
||||
- Commit as `CONTRIBUTING.md` describes (`[Type] (Scope): content`, no trailer), with `git commit --no-verify`.
|
||||
- Never run the DirectVulkan integration tests (`ctest -E DirectVulkan`).
|
||||
- No external project names ("Mesa", "Zink", ...) in source code or comments; describe the technique.
|
||||
- Vendor-agnostic only on the zero-copy path: AHB, EGL_ANDROID_*, VK_ANDROID AHB import, sync_file, dma-buf, libgbm ABI - no kgsl/gralloc layouts/QCOM modifiers.
|
||||
- Identity caches key on monotonic lifetime ids, never GL names or pointers.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Reference material
|
||||
|
||||
Frozen copies of MobileGL planning documents this skill depends on. They lived in the MobileGL
|
||||
repository under `docs/archive/notes/anland/` and were removed from the tree during the
|
||||
`feat/disaggregated` cleanup, so the skill carries them here.
|
||||
|
||||
Source: MobileGL `feat/disaggregated`, commit `b5d64175` (2026-10-19). These are background
|
||||
reading, not operational instructions; do not edit them here.
|
||||
|
||||
| File | Original path |
|
||||
|---|---|
|
||||
| `handoff-legacy-mobilegl-unified.md` | `docs/archive/notes/anland/handoff-legacy-mobilegl-unified.md` |
|
||||
| `plan-ahb-dmabuf.md` | `docs/archive/notes/anland/plan-ahb-dmabuf.md` |
|
||||
| `plan-x11-gpu.md` | `docs/archive/notes/anland/plan-x11-gpu.md` |
|
||||
| `runbook-plasma.md` | `docs/archive/notes/anland/runbook-plasma.md` |
|
||||
@@ -0,0 +1,186 @@
|
||||
# Handoff: anland 5.x + MobileGL unified server
|
||||
|
||||
Updated 2026-10-04. This replaces the 2026-10-02 (post-P14) handoff; git history keeps the old text.
|
||||
|
||||
## What this is
|
||||
|
||||
MobileGL runs disaggregated on a rooted Android tablet:
|
||||
- **Client:** the glibc library in the Droidspaces container `arch-kde-mgl`, where it is the system-wide glvnd EGL/GLX vendor and the GBM backend.
|
||||
- **Transport:** the client streams pipe records over abstract socket `@anland-mobilegl`.
|
||||
- **Server:** runs in-process in the anland APK's `:mobilegl` foreground service.
|
||||
- **Backends:** Espryt (DirectGLES) or Magma (DirectVulkan), chosen in the app settings.
|
||||
|
||||
KDE Plasma runs on top with zero-copy windows (server-allocated AHB dma-bufs over linux-dmabuf). Everything is vendor-agnostic: AHB, sync_file, EGL_ANDROID_*, VK_ANDROID_*; no kgsl or qcom specifics.
|
||||
|
||||
## Remotes and heads
|
||||
|
||||
- MobileGL: <https://github.com/MobileGL-Dev/MobileGL> `feat/disaggregated`, head `2d424146`. Integration worktree: `.claude/worktrees/anland-unified`.
|
||||
- anland: <https://github.com/MobileGL-Dev/anland> `legacy-mobilegl-unified`, head `41ec066`. Integration worktree: `anland/.claude/worktrees/mgl-unified`.
|
||||
- Version: 26.10 (`MobileGL/Config.h`, `android-plugin/app/build.gradle.kts`).
|
||||
|
||||
## Where to start reading
|
||||
|
||||
| Topic | Doc |
|
||||
|---|---|
|
||||
| Reproduce the whole setup on a new machine or phone | `.claude/skills/anland-mobilegl-plasma/reproduce.md` (and `SKILL.md`) |
|
||||
| Day-to-day bring-up and checks | `runbook-plasma.md` |
|
||||
| Zero-copy windows | `plan-ahb-dmabuf.md` |
|
||||
| X11 on the GPU (glamor, DRI3/Present GLX) | `plan-x11-gpu.md` |
|
||||
| glmark2 performance: baseline and the optimization handoff | `../../../Performance/glmark2-baseline.md`, `../../../Performance/glmark2-handoff.md` |
|
||||
| VA-API driver on MediaCodec (plan only, nothing implemented) | `../../../MobileVA/plan.md` |
|
||||
|
||||
## State of the desktop (verified on the device, 2026-10-04)
|
||||
|
||||
- **App-driven bring-up:** opening the anland app starts daemon → server → container → Plasma. A foreground service keeps it alive, the server idles while the app is hidden (KWin DPMS off), and the notification has a "Stop desktop" action.
|
||||
- **Both backends** run the desktop: KWin, plasmashell, Kickoff, notifications, the lock screen, resize on extra-keys-bar toggle, and suspend/resume on window loss.
|
||||
- **Buffer age and damage** work end to end on both backends.
|
||||
- **glvnd vendor:** MobileGL is the system-wide vendor (`10_mobilegl.json`, `/etc/mobilegl/client.conf`). It steps aside for `LIBGL_ALWAYS_SOFTWARE`, foreign GBM devices, and when no server answers (falls through in about 0.1 s).
|
||||
- **X11:** Xwayland glamor on MobileGL. GLX presents through DRI3 + Present shared images, with a MIT-SHM readback fallback.
|
||||
- **Chrome:**
|
||||
- runs its GPU in a separate process with real EGL native fences, with no flicker;
|
||||
- recovers from device loss (as do KWin and plasmashell);
|
||||
- Shadertoy-style GPU faults are contained per session;
|
||||
- a hanging client is cut off on Magma (watchdog `MOBILEGL_GPU_HANG_BUDGET_MS`) and on Espryt (GUILTY reset).
|
||||
- **Chrome video:** hardware decode works through the container's VA-API driver. NV12/P010 dma-bufs are sampled via `GL_OES_EGL_image_external` on both backends. Foreign YUV buffers are CPU-copied into server YUV images, and 4K60 is fine. `MOBILEGL_CHROME_VIDEO_DECODE=software` opts out.
|
||||
- **Device state at handoff:**
|
||||
- MobileGL anland on Magma, Plasma up, server/client build `bb31f374`; later commits are docs, CI and a DirectGLES multi-session fix that is not yet deployed;
|
||||
- clocks NOT pinned (restored); the original anland APK is stopped;
|
||||
- pin and restore helpers: `su -c "sh /data/local/tmp/anl/pbclk.sh pin|restore"`.
|
||||
|
||||
## Work streams and where each one stopped
|
||||
|
||||
### 1. glmark2 performance (stopped at a handoff)
|
||||
Details: `docs/Performance/glmark2-handoff.md`.
|
||||
- **Now:** MobileGL beats the stock kgsl stack in windowed glmark2 on both backends (es2 800x600: Espryt ~1887, Magma ~1213, stock ~1100). Fullscreen is still about half of stock (Espryt ~561, Magma ~404, stock ~1177).
|
||||
- **Next, ranked:**
|
||||
1. render straight into the window's buffers (removes the fullscreen copy);
|
||||
2. non-blocking present;
|
||||
3. merge Magma's per-frame submissions;
|
||||
4. Magma descriptor and pipeline caching;
|
||||
5. profile terrain;
|
||||
6. Espryt per-draw state sync;
|
||||
7. non-blocking fences.
|
||||
- Bench scripts: `tools/device_bench/anland_glmark2/`.
|
||||
|
||||
### 2. GitHub Actions CI (stopped mid-way, no handoff from its agent)
|
||||
- **Background:** CI had been red since about 2026-09-26 (Test workflow) and since 2026-10-02 (APK workflow retraces). Most runs show `cancelled` only because each newer push cancels them.
|
||||
- **Landed:**
|
||||
|
||||
| Commit | What |
|
||||
|---|---|
|
||||
| `eb71b669` | The pull and verify builds compile again |
|
||||
| `c1e957cb` | Retired the P3a/P4a untouched-region gates; those phases closed and later work legitimately changed the regions |
|
||||
| `78c5f8da` | Census records `Refuse{Recover}` as a local refusal |
|
||||
| `32d27b14` | Real bug: a session's backend registration no longer trips the strict apply-thread pin while another session is applying (DirectGLES) |
|
||||
| `2d424146` | Failed ctest entries are annotated with their failure lines, so failures can be read from the check-run annotations without a signed-in log reader |
|
||||
|
||||
- **Run on `2d424146`:** in progress at handoff. Gates, include-closure and flatc are green; the builds and integration jobs were still running.
|
||||
- **Still open:**
|
||||
- the APK workflow's **retrace failures** (Retrace and validate), on DirectGLES for minecraft-1.21.11-main-menu, 1.21.4 startup/in-world/main-menu, 1.17-main-menu-854 and OpenRA, and on DirectVulkan for 1.17-main-menu-854;
|
||||
- they first appeared around `5accb098` / `8da082f8` (2026-10-02); last green APK run `2f64ede8`;
|
||||
- not yet bisected. Candidates:
|
||||
- the clear-colour default (0,0,0,0), which is GL-correct, so judge any golden against the spec;
|
||||
- anonymous-struct sampler handling in glslang;
|
||||
- sampler CSO publishing;
|
||||
- the scissor-shadow fix;
|
||||
- the inactive-fragment-output location fix;
|
||||
- the external-texture and YUV changes;
|
||||
- the perf commits.
|
||||
- **User guidance on judging checks:**
|
||||
- phase-era process gates may be overly strict, and superseded ones may be removed with a reason;
|
||||
- retraces and OpenGL-level checks must pass;
|
||||
- a feature a backend does not implement (e.g. iterationRP) may be excluded narrowly per backend.
|
||||
- **Reading CI without `gh`:** `curl https://api.github.com/repos/MobileGL-Dev/MobileGL/actions/runs?branch=feat/disaggregated`, then `/actions/runs/<id>/jobs` and `/check-runs/<job>/annotations`. Raw logs need sign-in.
|
||||
|
||||
### 3. Chrome hardware video (landed) → vendor-agnostic VA-API (planned only)
|
||||
- **Container driver:** `/usr/lib/dri/msm_drm_drv_video.so` is an unpackaged third-party, Qualcomm-only driver. It drives V4L2 at `/dev/video32` with Qualcomm modes and copies frames on the CPU itself. libva picks it because the render node's DRM driver name is `msm_drm`.
|
||||
- **Plan in `docs/MobileVA/plan.md`:** a new `MobileVA/` directory; MediaCodec decode in the `:mobilegl` process; H.264 first, then VP9, AV1, HEVC/10-bit; the old driver kept as A/B fallback.
|
||||
- **Prerequisites the plan found:**
|
||||
- per-plane R8/GR88 dma-buf import, which mpv's GL path and KWin's NV12 path need;
|
||||
- YUV sampling paths that wait on the shared image's write fence (they appear not to).
|
||||
|
||||
### 4. MobileGL work landed since the previous handoff (newest first, grouped)
|
||||
- **Perf:**
|
||||
- `bb31f374` Magma keeps render passes open across draws;
|
||||
- `e5ee31b0` android-30 for native TLS;
|
||||
- `915882b2` record timing only with stats on;
|
||||
- `963f4d81` Magma command buffer reuse;
|
||||
- `d627fca3` the server wakes a parked client only when its sequence is applied;
|
||||
- `2de0d6dd` client-array buffer reuse;
|
||||
- `66b47a10` glFinish waits for the GPU, plus ES OES strings.
|
||||
- **YUV:** `7218bacb` NV12/P010 shared images, `GL_OES_EGL_image_external`, Espryt `GL_EXT_YUV_target`, Magma Ycbcr conversion.
|
||||
- **Device loss and hangs:**
|
||||
- `287e1df8` cut off the session that hangs the GPU;
|
||||
- `7823bcd9` client fresh-session recovery;
|
||||
- `90e63c9e` robustness extensions;
|
||||
- `58c3d9bd` per-session device-loss latch;
|
||||
- `36190b7c`, `cf763b40`, `1dd888cb`, `f22ae625`, `f52a5467` loss and teardown edge cases.
|
||||
- **Multi-thread and multi-context fixes:** `a9f22ebb`, `25a76c1b`, `393002a4`, `a84104a4`, `2acc6c41`, `8024b645`.
|
||||
- **Chrome:**
|
||||
- `4f6f4125` real EGL native fences;
|
||||
- `30977c18` opt-in ES identity for ES contexts (the GPU process on ANGLE);
|
||||
- `5accb098` glslang anonymous-struct samplers (Shadertoy page fault).
|
||||
- **X11:**
|
||||
- `198d6fea` GLX via DRI3/Present;
|
||||
- `08f23543` glFlush implicit sync for glamor;
|
||||
- `5de6ecd4`, `7fd965ce` shared-image upload and publish;
|
||||
- `cd58f048` inactive fragment output location;
|
||||
- `316ffe02` never dial its own X display.
|
||||
- **glvnd:** `69e96f5b`, `4095624f`, `4f679d66`, `3d69c375`, `552dd3d3`.
|
||||
- **Present:**
|
||||
- `74919a3b` / `7090e31d` buffer age and damage;
|
||||
- Espryt fixes `815217cc` (scissor shadow is per-session), `c822a9fe`, `228bd24c`;
|
||||
- `1ff7ca53` / `caa00995` frame-callback pacing;
|
||||
- `5a7b23f6` / `258b9460` / `16b07cf4` suspend, resume and resize.
|
||||
- **Shared images:** `38842985`, `3d17e8c1`, `350fc699`, `67226176`, `b5cab441`, `7520e53b`.
|
||||
- **Misc:**
|
||||
- `8da082f8` clear colour starts at (0,0,0,0);
|
||||
- `51c37bcb` sampler param conversion;
|
||||
- `15f9b27a` sampler CSO per share group;
|
||||
- `239fc228` gl_FragCoord access chains;
|
||||
- `bfa7987c` Magma declines invalid SPIR-V;
|
||||
- `6125f941` surfaceless/no-config EGL;
|
||||
- `4633ca22` frame-stats log gated (`MOBILEGL_SERVER_FRAME_STATS`);
|
||||
- `2a1889b8` gtest discovery at ctest start.
|
||||
|
||||
### 5. anland work landed (newest first)
|
||||
- `41ec066` / `f60b261`: Chrome video decode modes.
|
||||
- `e48874c`: KWin recovers from its own session loss.
|
||||
- `b9f8162` / `cf07bc7`: Chrome GPU process by default.
|
||||
- `3d8c4dd` / `5d30340`: Xwayland glamor.
|
||||
- `89894f1` / `a55e975`: DPMS off while hidden.
|
||||
- `74bd2d3`: the app starts the desktop, foreground service.
|
||||
- `0fd5d01` / `144f55c`: APK build guards.
|
||||
- `90c44d7`: system-wide vendor.
|
||||
- `23fc73f`: buffer age and damage in KWin.
|
||||
- `9773d1e` / `2dfda4a`: resize follow.
|
||||
- `f9e41df`: GBM device for linux-dmabuf.
|
||||
- `5f15240`: backend choice.
|
||||
|
||||
## Known open issues (rough priority)
|
||||
|
||||
1. **CI:** retrace failures (see stream 2) and whatever the `2d424146` run still shows red.
|
||||
2. **Fullscreen performance** at about half of stock (see stream 1).
|
||||
3. **Espryt's ~10 s Preemption-Fault hang path:** the driver doesn't report the reset to the server's contexts, so a hanging client stalls the desktop for 10 s per hang there.
|
||||
4. **YUV gaps:**
|
||||
- YUV sampling doesn't wait on the write fence (suspected);
|
||||
- no single-channel (R8/GR88) dma-buf import, so KWin advertises no YUV formats and mpv's GL interop won't work;
|
||||
- the CPU copy for foreign YUV buffers is a fallback, not zero-copy.
|
||||
5. **Chrome** doesn't use buffer age (full-frame repaints). The Magma text-caret frame costs about 24 ms.
|
||||
6. **A rare Magma X11 rectangle glitch.** The readback GLX fallback is slower with glamor.
|
||||
7. **Two GoldControllerTest failures** on Windows hosts. MSVC builds of this branch are broken; build tests in WSL with clang.
|
||||
8. **Leftovers:**
|
||||
- container probe files under `~swung0x48/yuvtest` and the local `v.html` tabs in the Chrome profile;
|
||||
- an empty directory `.claude/worktrees/quirky-poincare-df3f3e` (locked by Windows);
|
||||
- WSL `~/mgl-espryt*` / `~/mgl-magma*` directories of unknown owner (kept).
|
||||
|
||||
## Rules this work follows
|
||||
|
||||
- **Commits:** `--no-verify`, one short sentence, no Co-Authored-By trailer. No external project names in code or comments.
|
||||
- **Tests:** never run the DirectVulkan integration tests (`ctest -E DirectVulkan`); never run two ctest suites at once.
|
||||
- **Worktrees:** one worktree and build directory per task; remove the worktree, branch and build directories right after merging. Keep `anland-unified`, `mgl-unified`, WSL `~/mgl-xbuild-a64`, `~/mgl-anl-bld` and `~/sysroots`. Don't touch `wgl-host` or `~/repos/mobilegl-cts-gl33`.
|
||||
- **Core profile only:** don't use fixed-function test apps such as glxgears.
|
||||
- **Device:**
|
||||
- stop device work while the user is using the phone;
|
||||
- on USB power, watch the battery (stop below 40%);
|
||||
- wireless adb needs re-enabling after a reboot.
|
||||
@@ -0,0 +1,95 @@
|
||||
# 计划:AHB 当 dma-buf,server 当分配器(窗口零拷贝)(2026-10-02)
|
||||
|
||||
> 接 [`handoff-legacy-mobilegl-unified.md`](handoff-legacy-mobilegl-unified.md)。调研 + 设备探针,未动代码。硬约束:**厂商中立**——只依赖 Android 公开设施(AHardwareBuffer、EGL_ANDROID_*、VK_ANDROID_external_memory_android_hardware_buffer、sync_file)和通用 Linux 设施(dma-buf、SCM_RIGHTS、libgbm 后端 ABI、linux-dmabuf 协议),不碰 kgsl / gralloc 私有布局 / 厂商修饰符。
|
||||
|
||||
## 现状:每个窗口每帧 GPU→CPU→GPU
|
||||
|
||||
KWin 自己(`MOBILEGL_IPC_SURFACE=server`)直接画在 Android Surface 上,没问题。其余所有 Wayland 客户端:
|
||||
|
||||
1. **生产端**(`EGLImpl.cpp:210` → `WaylandWindow.cpp:398`):server 渲染进 pbuffer;`eglSwapBuffers` 时客户端 `ReadPixels` 整帧,按 ~2 MiB 回复槽切带,**每带一次阻塞往返**;server `glReadPixels`/`vkCmdCopyImageToBuffer`+等待+重排 → 回复槽 memcpy → 客户端 memcpy → BGRA 换序+翻转写进 `wl_shm`。CPU 整帧遍历:Espryt 4 次,Magma ~6 次。
|
||||
2. **消费端**(KWin 也是 MobileGL 客户端):`glTexSubImage2D` 上传 wl_shm → 解包 malloc+拷贝 → 层影子拷贝 → **整层**(不是脏区)进 SEG_STAGE → server `AdoptRun` 再拷贝 → 驱动上传。又 4 次。
|
||||
3. linux-dmabuf 根本没开:KWin 6.7 的 `EglBackend::initWayland` 要求 `drmDevice()`,anland 后端在 MobileGL 模式下给空设备,`kwin.patch` 让它直接退回 wl_shm(还连带了 screencast 的空指针崩溃补丁)。
|
||||
|
||||
2560×1412 的窗口一帧 14.5 MB,上面合计 ~8–10 次整帧 CPU 遍历 + 若干阻塞往返。
|
||||
|
||||
## 目标形状
|
||||
|
||||
```
|
||||
client(GL) ──渲染──▶ [AHB] ◀──采样── KWin(GL) 全在 server 进程里:同一块 AHB,两个会话各自导入
|
||||
│ ▲ dma-buf fd CPU 拷贝 0 次;fd 每块缓冲只传一次
|
||||
└─ wl_buffer(linux-dmabuf, fd) ─▶ KWin ─ eglCreateImage(DMA_BUF, fd) ─▶ server 按身份认回 AHB
|
||||
```
|
||||
|
||||
关键点:**生产者和消费者都是同一个 server 的会话**。所以"导入 dma-buf"不需要驱动支持 `EGL_EXT_image_dma_buf_import`(Adreno 的 Android EGL 就没有)——server 只需认出"这是我发出去的那块 AHB",再走 Android 标准导入:Espryt 用 `eglGetNativeClientBufferANDROID` + `EGL_NATIVE_BUFFER_ANDROID`,Magma 用 `VK_ANDROID_external_memory_android_hardware_buffer`。两者都是 Android 规范要求的,厂商中立。
|
||||
|
||||
## 设备探针(HA27Q3LQ,内核 6.1,Android 15)
|
||||
|
||||
- AHB(RGBA8,GPU 用途,有/无 CPU 用途)的 native_handle = 2 个 fd,都是 `/dmabuf:` 文件(图像 + 32 KB 元数据)。
|
||||
- 从 Android 侧(`shell` 域)经 SCM_RIGHTS 发给容器进程(`droidspacesd` 域):收到、`mmap`、像素逐点正确;两边 `st_ino` 相同 → **按 inode 认身份可行**。
|
||||
- 容器里有 `/dev/dri/renderD128`(显示驱动 msm_drm,组 `droidspaces-gpu`),Mesa 26.3 的 libgbm 会按 `GBM_BACKEND` 或驱动名加载 `/usr/lib/gbm/<name>_gbm.so`(ABI v1,`gbm_backend_abi.h` 已装)。**但这个节点是厂商的,别的机器未必有**——见下面"薄节点"。
|
||||
- KWin 6.7:`RenderDevice::open(path)` = `DrmDevice`(open+fstat+`gbm_create_device`) → `eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm)`;`initWayland` 的 tranche 设备号取 `renderDevNode()` 否则 `drmDevice->deviceId()`;dma-buf 导入走 `eglCreateImage(EGL_LINUX_DMA_BUF_EXT)`,格式表走 `eglQueryDmaBufFormats/ModifiersEXT`。
|
||||
|
||||
## 厂商中立的几条规则
|
||||
|
||||
1. **只分配 AHB**,只用公开 NDK 拿 fd:`AHardwareBuffer_sendHandleToUnixSocket` 发到 server 自己的 socketpair,`recvmsg` 取 SCM_RIGHTS 里的 fd(不依赖 LL-NDK 的 `getNativeHandle`,也不解析 handle 里的 int)。
|
||||
2. **不假设哪个 fd 是图像**:发 fd[0] 作 plane 0,身份登记 handle 里的所有 fd。
|
||||
3. **不假设布局**:默认通告 `DRM_FORMAT_MOD_INVALID`("驱动定义布局",正好就是 AHB 的语义)。`LINEAR` 只在启动自检通过时才通告:分配带 CPU 用途的 AHB,`lock` 写图案,`mmap` fd[0] 按 `describe().stride` 读回比对。自检失败就不通告,CPU 映射走 server(`AHardwareBuffer_lock` + 拷贝)。
|
||||
4. **身份**:首选 `st_ino`(启动时分配两块确认 inode 互异——老内核所有 dma-buf 共用一个 anon inode);不唯一时退到 `DMA_BUF_SET_NAME` 打 `mgl:<id>` 标签、读 fdinfo 的 `name:`。非本 server 分配的 dma-buf → `EGL_BAD_MATCH`(KWin 会回退 shm)。
|
||||
5. **同步只用 sync_file**:`EGL_ANDROID_native_fence_sync`、`VK_KHR_external_semaphore_fd`(SYNC_FD)、必要时 `DMA_BUF_IOCTL_EXPORT/IMPORT_SYNC_FILE`(通用 Linux ≥5.20/6.0)。不用 DRM syncobj(要真 DRM 驱动)。
|
||||
6. **薄节点**:GBM 后端用 `GBM_BACKEND=mobilegl` 选中(`/usr/lib/gbm/mobilegl_gbm.so`),不装成 `msm_drm_gbm.so`。它对设备 fd 不发任何 ioctl,只当"打开了某个设备"的令牌;真正的通道是到 server 的连接。节点路径由 anland 配置(`MOBILEGL_GBM_NODE`):有可访问的 DRM 节点就借来当身份(只 open/fstat),没有就用占位节点。MobileGL 现有的假 EGL device 里写死的 `/dev/dri/renderD128` 默认值同样改成跟这个配置走。
|
||||
|
||||
## 归属:这事主要是 MobileGL 的
|
||||
|
||||
| 部分 | 在哪 | 内容 |
|
||||
|---|---|---|
|
||||
| 分配器 + 身份登记 | **MobileGL server**(Android 侧) | AHB 分配、fd 导出、inode 登记、会话生命周期/引用计数、隐式同步(server 看得见两端,就当"内核") |
|
||||
| 后端导入 | **MobileGL Espryt / Magma** | AHB → EGLImage → 纹理/渲染缓冲;AHB → VkImage(每会话各自 VkDevice,各导一次) |
|
||||
| 生产端 WSI | **MobileGL 客户端 EGL**(glibc) | `wl_egl_window` 改为 AHB 交换链 + `zwp_linux_dmabuf_v1`,没有该 global 时退回现在的 wl_shm |
|
||||
| 消费端 EGL | **MobileGL 客户端 EGL** | `EGL_EXT_image_dma_buf_import(_modifiers)`、`eglQueryDmaBuf*`、真 `eglCreateImage`(现在是空壳)、`glEGLImageTargetTexture2DOES`/`RenderbufferStorageOES` |
|
||||
| GBM 后端 | **MobileGL 新组件** `mobilegl_gbm.so`(glibc) | gbm ABI v1:`bo_create*` → server 分配,`bo_import`,`bo_map`,格式查询;外加 EGL 的真 GBM 平台(现在 GBM 窗口被拒) |
|
||||
| 合成器 | anland | 后端打开薄节点建 `RenderDevice`;撤掉 `initWayland` 跳过 dma-buf 与 screencast 两处补丁;启动脚本设 `GBM_BACKEND`/`MOBILEGL_GBM_NODE` |
|
||||
|
||||
ANativeWindow 只剩 KWin 输出那一块,不变。
|
||||
|
||||
## 分阶段
|
||||
|
||||
**P0 去风险(小)**
|
||||
- server 真实所在域 `untrusted_app` → 容器 `droidspacesd` 传 dma-buf fd 过 SELinux(ashmem 段已经能过,dma-buf 类别要实测;看 `avc: denied`)。
|
||||
- 确认 libgbm 在 `GBM_BACKEND` 指定时不对 fd 发 ioctl(读 Mesa 26.3 `backend.c` 或拿 `/dev/null` 实测)。
|
||||
- 确认 KWin 拿非 DRM 的设备号(占位节点)时 tranche/feedback 不出事。
|
||||
- Magma 跨 VkDevice 同一 AHB 两次导入可用;Espryt 同一 AHB 两个 context 的 EGLImage。
|
||||
|
||||
**P1 server 分配器 + 登记表**
|
||||
- `SharedImageRegistry`:`Allocate(w,h,format,usage) → {id, fds, stride, modifier}`;按 inode(或 name 标签)查找;归属会话 + 引用计数;会话死掉释放(键用单调 id,不用客户端句柄——P14 的跨会话句柄冲突教训)。
|
||||
- 线协议新动词:分配 / 释放 / 按 fd 导入(fd 走现成的 `ITransport::ShareFd`)。
|
||||
- 单元测试:注册、身份、跨会话释放只放自己的。
|
||||
|
||||
**P2 生产端:wl_egl_window 走 AHB 交换链**
|
||||
- 先做最稳的一步:server 照旧渲染进 pbuffer/目标,`eglSwapBuffers` 时**GPU blit**(带 Y 翻转)到当前空闲 AHB——CPU 零拷贝,只多一次 GPU 拷贝。之后再让默认帧缓冲直接落在 AHB 上(Magma 已有默认 FB 变换机制;Espryt 用 FBO 冒充默认 FB)。
|
||||
- 客户端:绑定 `zwp_linux_dmabuf_v1`,每块 AHB 建一次 `wl_buffer`(`create_immed`,`ABGR8888`/`XBGR8888` = GL RGBA8 内存序,不再换序);按 `wl_buffer.release` 轮转 3 块;resize 重建。没 global → 现路径。
|
||||
- 同步第一版:swap 返回前 server CPU 等自己的渲染完成(现在 readback 本来就全等,不退步);server 记录每块的"最后消费者 fence",生产者重用前等它。
|
||||
|
||||
**P3 消费端:KWin 导入**
|
||||
- 客户端 EGL 通告上述扩展;`eglCreateImage(EGL_LINUX_DMA_BUF_EXT)` 把 fd 发给 server → 认身份 → 后端导入;`glEGLImageTargetTexture2DOES` 绑到 KWin 的纹理。
|
||||
- anland:后端建真 `DrmDevice`(薄节点 + `mobilegl_gbm.so`),撤补丁,linux-dmabuf 起来。这一步和 P2 合起来就是第一个可见成果:plasmashell/Qt/GTK/glmark2-wayland 零拷贝。
|
||||
|
||||
**P4 GBM 后端**
|
||||
- `mobilegl_gbm.so` + EGL 的 GBM 平台(`eglGetPlatformDisplay(GBM)`、`eglCreateImage` 从 gbm bo)。KWin 的 `GbmGraphicsBufferAllocator` 随之可用 → screencast 走 dma-buf,screencast 补丁可撤。
|
||||
- 格式:ARGB8888 等 AHB 没有的通道序,按 RGBA8 分配,server 记"声明格式",导入时用纹理 swizzle 对齐语义(两端都是 MobileGL,server 都看得见)。
|
||||
|
||||
**P5 同步与性能**
|
||||
- 去掉 swap 时的 CPU 等待:sync_file 在 server 内做 GPU 等(Espryt `EGL_ANDROID_native_fence_sync` + `eglWaitSyncKHR`;Magma sync-fd 信号量跨会话设备)。
|
||||
- 可选 `DMA_BUF_IOCTL_IMPORT_SYNC_FILE` 给外部 CPU 映射者隐式同步。
|
||||
- `EGL_EXT_buffer_age` + 真实损伤区,Qt 局部重绘。
|
||||
|
||||
**P6 其它消费者(按需)**
|
||||
- Xwayland glamor 跑在 MobileGL 的 GBM 平台上(现在强制 llvmpipe + `-shm`)。
|
||||
- Chrome 原生 GBM 路径(它会 `drmGetDeviceFromDevId` 枚举节点——占位节点下可能不行,是风险项;现在的 wl_egl_window 路径在 P2 后已经零拷贝)。
|
||||
|
||||
## 风险
|
||||
|
||||
- SELinux:dma-buf fd 从 `untrusted_app` 进 `droidspacesd`(P0 第一项)。
|
||||
- 方向:GL 默认帧缓冲自下而上、`wl_buffer` 自上而下。P2 的 blit 顺手翻转;直接渲染时可选用 `wl_surface.set_buffer_transform(FLIPPED_180)`(竖直翻转,KWin 着色器里免费),需实测 KWin 处理正确。
|
||||
- 每会话一个 VkDevice(Magma):同一 AHB 在两个设备上,同步只能靠 sync_file 或 CPU 等。
|
||||
- 生命周期:客户端崩溃时 KWin 仍持有它的 `wl_buffer`/EGLImage——AHB 引用要由导入者也持有,不能随生产会话一起释放。
|
||||
- 资源:每窗口 3 块 AHB(2560×1412 约 44 MB),比现在的 pbuffer + 3 块 shm 少不了多少,但不再需要回复槽带宽。
|
||||
@@ -0,0 +1,174 @@
|
||||
# Plan: X11 apps on the GPU, zero-copy (Xwayland glamor + GLX DRI3/Present) (2026-10-03)
|
||||
|
||||
> Follows [`plan-ahb-dmabuf.md`](plan-ahb-dmabuf.md) (the Wayland half: server-allocated AHB
|
||||
> "shared images" exported as dma-bufs). Same hard rules: vendor-neutral (AHB, dma-buf, sync_file,
|
||||
> the libgbm backend ABI, linux-dmabuf, DRI3/Present), no layout assumptions (modifier INVALID).
|
||||
|
||||
## Today
|
||||
|
||||
```
|
||||
GLX app --glXSwapBuffers--> ReadPixels (server->client CPU) --xcb_put_image--> Xwayland (-shm, llvmpipe)
|
||||
Xwayland --wl_shm--> KWin --glTexSubImage2D upload--> server
|
||||
```
|
||||
Two full-frame CPU trips per frame for every X11 GL app, and every ordinary X11 app is drawn by
|
||||
pixman/llvmpipe and uploaded again by KWin.
|
||||
|
||||
## Target
|
||||
|
||||
```
|
||||
GLX app --render--> pbuffer --GPU copy (PresentToSharedImage)--> [AHB image]
|
||||
--DRI3 PixmapFromBuffer(s)(dma-buf, once per buffer)--> Xwayland pixmap (glamor: gbm_bo_import -> EGLImage)
|
||||
--Present PresentPixmap--> Xwayland flips it (attaches the pixmap's dma-buf wl_buffer) or GPU-copies it
|
||||
Xwayland (glamor on MobileGL EGL/GBM) --linux-dmabuf wl_buffer (window pixmaps are shared images)--> KWin
|
||||
```
|
||||
Every hop is a session of the same MobileGL server sampling or writing the same AHardwareBuffer.
|
||||
CPU copies: zero.
|
||||
|
||||
## Stage 1 - Xwayland on MobileGL (glamor)
|
||||
|
||||
What Xwayland 24.1.13 (`hw/xwayland/xwayland-glamor-gbm.c`, `glamor/glamor.c`) needs, and where it is:
|
||||
|
||||
| Need | Status |
|
||||
|---|---|
|
||||
| render node from linux-dmabuf v4 feedback `main_device` (drmGetDeviceFromDevId) | KWin's thin node: must be a real DRM render node (`MOBILEGL_GBM_NODE`=/dev/dri/renderD128). `/dev/null` makes glamor fail -> shm fallback |
|
||||
| `gbm_create_device(fd)` with `GBM_BACKEND=mobilegl` | `mobilegl_gbm.so` (exists) |
|
||||
| EGL client ext `EGL_KHR/MESA_platform_gbm`, `eglGetPlatformDisplay(GBM, gbm)` | exists; the foreign-device decline keys on the backend name `mobilegl` |
|
||||
| `eglCreateContext(EGL_NO_CONFIG_KHR, core 3.1)` + surfaceless `eglMakeCurrent` | cherry-picked from the surfaceless branch (6125f941), to be dropped on rebase |
|
||||
| GL >= 2.1 desktop, `GL_ARB_vertex_array_object`, `GL_OES_EGL_image`; renderer not llvmpipe | exists on a split client |
|
||||
| `EGL_EXT_image_dma_buf_import(_modifiers)` -> dmabuf_capable | exists (shared images) |
|
||||
| `gbm_bo_create_with_modifiers2(XRGB8888/ARGB8888, {INVALID, LINEAR})`, `gbm_bo_get_fd_for_plane`, `_handle_for_plane`, `_stride_for_plane`, `_offset`, `_modifier`, `gbm_bo_import(FD / FD_MODIFIER)` | exists; now covered by a host test through the real libgbm loader |
|
||||
| `eglCreateImageKHR(EGL_LINUX_DMA_BUF_EXT)` of its own bos, `glEGLImageTargetTexture2DOES` | exists (server identifies its image by inode) |
|
||||
| KWin's tranche lists XRGB8888/ARGB8888 with modifier INVALID | KWin adds INVALID+LINEAR when the EGL reports no modifiers (ours) |
|
||||
| **implicit sync**: glamor renders into window pixmaps and commits after `glFlush`, assuming the kernel orders KWin's read after it | **new**: see below |
|
||||
| **Xwayland must never dial its own display** | **new**: see below |
|
||||
|
||||
### Implicit sync by flush (new shared_image op `Flush`)
|
||||
|
||||
The server already orders an image between sessions: a writer publishes a sync_file per write
|
||||
(`PublishWrite`), a reader waits on it at its first use and publishes its frame's read fence at
|
||||
its frame boundary (swap). glamor has neither a swap nor an explicit write: it renders into the
|
||||
image through an FBO and calls `glFlush` before committing.
|
||||
|
||||
- Client: `glFlush`/`glFinish` in a process that has bound a shared image to a texture
|
||||
(`glEGLImageTargetTexture2DOES`) and does not own the server window (not KWin) send the new
|
||||
in-stream `shared_image Flush` op and wait for its reply. Policy is a pure function
|
||||
(`SharedImageFlushPolicy`), overridable with `MOBILEGL_SHARED_IMAGE_FLUSH_SYNC=0/1`.
|
||||
- Server: `BackendObject::PublishSharedImageAccesses()` - every image the session used since its
|
||||
last boundary gets the flush's fence as a write (generation moves) and as a read.
|
||||
Espryt: `ExportFence` (EGL_ANDROID_native_fence_sync), Magma: release held images, submit with
|
||||
the exportable semaphore, export it. A session that has flushed this way also waits (GPU) for
|
||||
the pending read fences of an image before its first use in a frame (write-after-read).
|
||||
- The reply comes after the fence is published, so KWin (which samples after Xwayland's commit,
|
||||
which follows the reply) always finds the new generation.
|
||||
|
||||
### X-connection safety (`MG_Util/X11/DisplayGuard`)
|
||||
|
||||
One rule, one function: `MayDialX11Display(name)` is false when this process **serves** that
|
||||
display: it holds a listening unix socket named `/tmp/.X11-unix/X<n>` (path or abstract) per
|
||||
`/proc/net/unix` + `/proc/self/fd`, or its executable is an X server (Xwayland, Xorg, Xvfb, ...).
|
||||
`MOBILEGL_X11_DIAL=0` turns all dialing off. Every place MobileGL opens an X connection asks it
|
||||
first: EGLState's default-visual query (eglGetDisplay of a non-X native display has platform NONE,
|
||||
so the old "only on X displays" test still dialed for `eglGetDisplay(gbm_device)`), DirectGLES's
|
||||
visual query and window-surface display, Magma's Xlib surface. GLX never opens a connection (it
|
||||
uses the application's). KWin's launcher also drops `DISPLAY` from Xwayland's environment.
|
||||
|
||||
### KWin (anland `kwin.patch`, `mobilegl-startup.sh`)
|
||||
|
||||
`ANLAND_MOBILEGL=1` and `ANLAND_XWAYLAND_GLAMOR != 0`: Xwayland starts **without** `-shm`, with
|
||||
`MOBILEGL_IPC_SURFACE=offscreen`, its own `MOBILEGL_LOG_FILE_PATH=/tmp/mgl-xwayland`, `GBM_BACKEND`
|
||||
kept, no `LIBGL_ALWAYS_SOFTWARE`/`GALLIUM_DRIVER`, no `DISPLAY`. Otherwise the old `-shm` +
|
||||
software environment. If glamor cannot initialise, Xwayland itself falls back to shm rendering.
|
||||
|
||||
## Stage 2 - GLX presentation through DRI3 + Present
|
||||
|
||||
Per GLX window drawable (`GLXImpl/X11Present.{h,cpp}`, logic behind two small interfaces so the
|
||||
host tests drive it with fakes):
|
||||
|
||||
- **Path selection** (`SelectPresentPath`): DRI3 >= 1.0 and Present >= 1.0 on the connection,
|
||||
shared images available, depth 24/32, `MOBILEGL_GLX_PRESENT` not `readback` -> `Dri3Present`;
|
||||
else MIT-SHM >= 1.2 with fd passing -> `ShmPutImage`; else `PutImage` (today's bands).
|
||||
- **Buffers**: up to 3 (4 when Xwayland holds all three past a 1 s wait) shared images of the
|
||||
window's format (depth 24 -> XRGB8888, 32 -> ARGB8888: exactly what glamor imports them as), each
|
||||
imported once as a pixmap (`PixmapFromBuffers` with modifier INVALID on DRI3 >= 1.2, else
|
||||
`PixmapFromBuffer`, checked). A buffer is busy from its PresentPixmap until its `IdleNotify`.
|
||||
- **Present**: `PresentToSharedImage` (GPU copy, rows top first, returns once submitted with the
|
||||
write fence published), then `PresentPixmap` with an XFixes update region from the swap's
|
||||
damage (flipped to X's top-left origin; none = whole), serial per frame. Events arrive on a
|
||||
special-event queue (`xcb_register_for_special_xge`), never in the application's queue.
|
||||
- **Pacing**: `FrameThrottle` (the Wayland rules) on `CompleteNotify`: interval >= 1 waits for the
|
||||
previous frame's completion, `target_msc = last_msc + interval`; interval 0 uses
|
||||
`PresentOptionAsync`, unpaced while completions come, ~1 fps once one is 200 ms overdue.
|
||||
- **Errors**: a refused `PixmapFromBuffer`, a `PresentPixmap` error (checked lazily, no round
|
||||
trip), or the window's destruction (`ConfigureNotify` with `PresentWindowDestroyed`) latch the
|
||||
drawable onto the readback path for good, logged once.
|
||||
- **Resize**: `ConfigureNotify` (and the existing throttled `XGetGeometry`) resize the pbuffer;
|
||||
buffers of the old size are dropped as they come back idle.
|
||||
- **Fallback** (no DRI3/Present: Xwayland `-shm`, a remote X server): readback into a MIT-SHM
|
||||
segment (memfd, `xcb_shm_attach_fd`) + `xcb_shm_put_image`; without MIT-SHM, today's bands.
|
||||
- `GLX_EXT_buffer_age` (`GLX_BACK_BUFFER_AGE_EXT`) answers the EGL buffer age of the drawable.
|
||||
|
||||
## Tests (host, WSL)
|
||||
|
||||
`X11DisplayGuardTest` (display names, /proc parsing, the Xwayland case), `X11PresentTest` (path
|
||||
selection, rotation, idle/complete/configure handling, pacing, resize, error latching, fake xcb
|
||||
and fake images), `SharedImageSync` additions (accesses publish as write + read),
|
||||
`SharedImageFlushPolicyTest`, `GbmGlamorTest` (the real libgbm loader with `GBM_BACKEND=mobilegl`
|
||||
over a fake shared-image library: glamor's create/export/import calls).
|
||||
|
||||
## Device verification (phase B)
|
||||
|
||||
`scripts/x11/` in the skill: deploy, check Xwayland's glamor log lines and its dma-buf wl_buffers in
|
||||
KWin (`WAYLAND_DEBUG`-free: `/proc/<kwin>/fdinfo` dma-buf count and the server log's import lines),
|
||||
glxgears/glxinfo/an X11 GL app before/after (fps, `top` CPU of app/Xwayland/KWin/server), and
|
||||
ordinary X apps (xterm, xeyes) for glamor rendering.
|
||||
|
||||
## Risks
|
||||
|
||||
- `drmGetDeviceFromDevId` on the borrowed render node inside the container (sysfs visibility).
|
||||
- Adreno: cross-context ordering relies on the Flush op's fence; a missed flush shows as tearing,
|
||||
not corruption of other windows.
|
||||
- The extra round trip per Xwayland `glFlush` (glamor flushes once per main-loop wakeup).
|
||||
- DRI3 clients that are not MobileGL (Mesa Vulkan WSI) hand Xwayland dma-bufs the server never
|
||||
allocated: `PixmapFromBuffers` fails with BadAlloc for them, as on any driver mismatch.
|
||||
|
||||
## Device results (2026-10-03, TB321FU / Adreno 750, x11-gpu 95aa618d server + client)
|
||||
|
||||
glxgears 1280x720, % of one core, `x11-verify.sh measure`; "before" = the old stack (`-shm`
|
||||
llvmpipe Xwayland, GLX readback + PutImage).
|
||||
|
||||
> glxgears is fixed-function GL (glBegin/glEnd display lists, glLight), which MobileGL does not
|
||||
> implement (Core Profile only), so its window stays black. These numbers measure presentation
|
||||
> throughput of empty frames through each path, not rendering. Check X11 rendering with a
|
||||
> core-profile or shader-based GLX client, and verify it from pixels (mgl-xgrab / XGetImage).
|
||||
|
||||
| run | fps | phone | glxgears | Xwayland | kwin | server | GPU |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| before, unpaced | 160 | 229% | 28% | 42% | 71% | 37% | 15% |
|
||||
| Espryt DRI3+Present, interval 1 | 98-101 | 210% | 12% | 5% | 28% | 32% | 12% |
|
||||
| Espryt DRI3+Present, vblank_mode=0 | 1440-1780 | 296% | 29% | 29% | 36% | 62% | 72% |
|
||||
| Espryt readback + MIT-SHM on glamor | 46 | 168-188% | 5% | 78% | 11% | 35% | 5% |
|
||||
| Magma DRI3+Present, interval 1 | 105-112 | 210% | 12% | 5% | 24% | 43% | 16% |
|
||||
| Magma DRI3+Present, vblank_mode=0 | 940-970 | 282% | 24% | 18% | 33% | 60% | 77% |
|
||||
| Magma readback + MIT-SHM on glamor | 27 | 161% | 2% | 50% | 7% | 70% | 5% |
|
||||
|
||||
- Glamor engages on both backends: Xwayland runs without `-shm`, offers DRI3 1.2 + Present 1.2,
|
||||
its windows are linux-dmabuf shared images; GLX windows log `presented through DRI3+Present`.
|
||||
- Found on the device and fixed: uploads into a texture bound to a shared image were dropped
|
||||
(Espryt) - an X server's PutImage into a window pixmap showed black windows. They are now
|
||||
drained at the producer's flush and written into the image (RGB-converted for X formats, which
|
||||
the driver holds as RGB8).
|
||||
- The forced readback path is slower on a glamor Xwayland than on the old llvmpipe one (glamor
|
||||
uploads every frame through the server); it is only the fallback now.
|
||||
- Fixed (cd58f048): after a hide/show or lock/unlock round, X11 Qt windows kept stale or black
|
||||
regions in their window pixmap. Root cause: every glamor copy and fill drew nothing. glamor's
|
||||
fragment shaders name gl_FragColor only in the inactive arm of a version guard and write their
|
||||
own `frag_color`; the legacy lowering still declared `mg_FragColor` ahead of it, and the IO
|
||||
resolver gave that dead output location 0 and `frag_color` location 1, which no draw buffer
|
||||
reads. Uploads (PutImage) still landed, so windows looked right until Xwayland copied between
|
||||
window buffers (it swaps the window pixmap on every commit and reallocates buffers after a
|
||||
hide), and then showed old content or zeros. Inactive fragment outputs now take the top of the
|
||||
draw-buffer range. Host test: ProgramTest.InactiveLoweredFragColorDoesNotDisplaceTheActiveOutput.
|
||||
On the device, an X client checking its own XGetImage against a CPU model (PutImage, CopyArea
|
||||
and FillRectangle; MIT-SHM and plain; depth 24 and 32) sees 0 bad pixels through hide/show/lock
|
||||
on both backends. One run in about 20 on Magma (no SHM, depth 24) left a 2751-pixel rect
|
||||
mismatched; it did not come back in the repeats.
|
||||
@@ -0,0 +1,178 @@
|
||||
# Runbook:在这台 Windows 机器 + 平板上起 / 迭代 MobileGL + anland + KDE Plasma(2026-10-02)
|
||||
|
||||
> 操作手册,不是设计笔记。配套 Claude Code skill:`.claude/skills/anland-mobilegl-plasma/`(`SKILL.md` 是本文的精简版,`scripts/` 是本文引用的全部脚本,每个脚本开头注明了参数)。现状、提交与已知问题见 [`handoff-legacy-mobilegl-unified.md`](handoff-legacy-mobilegl-unified.md),零拷贝的设计见 [`plan-ahb-dmabuf.md`](plan-ahb-dmabuf.md)。下文路径与命令按 2026-10-02 设备上的实际状态核对过;标"未验证"的是没跑通过或只从旧记录推出来的。
|
||||
|
||||
**设备可能正被别的会话使用。** 凡是会重启 app / daemon / 容器会话、或替换库的操作,先跑只读的 `scripts/device/status.sh` 看一眼,确认没人在测;`ct.sh` 的通道名用自己的(别人在用 c、c2、c3、xd、xr、gbm)。
|
||||
|
||||
## 架构
|
||||
|
||||
- **平板**:Lenovo TB321FU(Y700,Adreno 750,Android 15,内核 6.1),**KernelSU** root(`/data/adb/ksu`、`ksud`;不是 Magisk),`su -c` 可用。USB 序列号 `HA27Q3LQ`,也常走无线调试 `ip:port`。
|
||||
- **anland APK** `com.anland.consumer.mobilegl`(anland 分支 `legacy-mobilegl-unified` 的 debuggable 构建):UI 进程 + 私有 Service 进程 `:mobilegl`(`MobileGLWorker`)。后者加载 **server** `libMobileGL.so`(NDK 构建,MobileGL 分支 `feat/disaggregated`),从第一个 attach 上来的窗口起监听抽象 socket `@anland-mobilegl`。一个进程服务所有 client(KWin、plasmashell、ksplash、Chrome……),一个进程只服务一种后端。`MobileGLWorker` 是**前台服务**(通知"Linux 桌面正在运行",带"停止桌面"按钮),窗口隐藏、锁屏、从最近任务划掉之后 server 和会话都还在。
|
||||
- **后端**:app 设置"渲染后端"(设置 > 连接 > MobileGL 桌面;存在 `/data/data/<包名>/files/mobilegl-backend`,默认 DirectGLES;启动参数 `--es mobilegl_backend X` 也会写它)。Espryt = DirectGLES,Magma = DirectVulkan。server 进程启动时读一次;app 把它发布成 `/data/local/tmp/anland-mobilegl/backend`,会话启动前拷进容器的 `/etc/mobilegl/backend`——所有 client 按这个文件报后端,不一致 server 拒绝("Hello.backendType disagrees with pinned backend")。如果有人设了 `debug.mobilegl.backend`,它在下次重启前仍然覆盖设置。
|
||||
- **实验用 display daemon**:`/data/adb/modules/anland-daemon/display_daemon /data/local/tmp/anland-mobilegl/display.sock`(显示 / 输入中介),由 app 拉起(见下)。模块的 `service.sh` 开机只起原版那个(`/data/local/tmp/display_daemon.sock`),原版不要动。
|
||||
- **容器** `arch-kde-mgl`:Droidspaces 6.4.5,Arch Linux ARM(aarch64 glibc),host 网络;Android 侧 `rootfs.img` 挂在 `/mnt/Droidspaces/arch-kde-mgl`。bind mount:`/data/local/tmp/anland-mobilegl` → `/run/anland-mobilegl`,`/data/local/tmp/display_daemon.sock` → `/run/display.sock`。
|
||||
- **会话**:`desktop-session.service`(User=swung0x48,**已 enable**:容器一启动就进会话,和原版镜像一样)。drop-in `/etc/systemd/system/desktop-session.service.d/mobilegl.conf`(anland 的 `producers/kde/Arch_v5/desktop-session-mobilegl.conf`,由 `sync-build-kwin.sh` 安装并 enable):先 `mobilegl-startup.sh wait`(等 `/proc/net/unix` 里 `@anland-mobilegl` 在监听、daemon socket 存在),再以 root 跑 `sync-backend`,`Restart=always`,ExecStart 是 `/opt/mobilegl/bin/mobilegl-startup.sh plasma`——它删掉粘滞的软件渲染键,整个会话设 `MOBILEGL_IPC_PROBE_TIMEOUT_MS=0`(MobileGL 永不拒绝,glvnd 不会把 KWin / plasmashell 交给别的 GL 栈),它每次写 `plasma-kwin_wayland.service` 的用户 drop-in(经 `kwin_wayland_wrapper` 起 `/opt/mobilegl/kwin/bin/kwin_wayland` + `/opt/mobilegl/kwin/lib/libkwin.so.6.7.4`,`ANLAND_MOBILEGL=1`、`MOBILEGL_IPC_SURFACE=server`),然后 `startplasma-wayland`。
|
||||
- **KWin 6.7.4** 带 anland 的 `anland` 后端,经 server 直接画进 anland 的 Android Surface。窗口不在屏幕上(consumer 断开)时后端把输出关掉(DPMS Off + RenderLoop inhibit:不合成、不发 frame callback),窗口回来再打开。其余 GL 进程都是 `MOBILEGL_IPC_SURFACE=offscreen`,经 KWin 合成上屏。
|
||||
- **client 库** `/opt/mobilegl/lib/libMobileGL.so`(glibc aarch64),是容器里**全系统**的 GL vendor(见下节):EGL vendor `/usr/share/glvnd/egl_vendor.d/10_mobilegl.json`(排在发行版的 `50_*.json` 前面,MobileGL 拒绝时由它接手)、GLX vendor `/usr/lib/libGLX_mobilegl.so.0`,配置来自 `/etc/mobilegl/client.conf`(`MOBILEGL_TRANSPORT=spawn MOBILEGL_IPC_DATA=shm MOBILEGL_IPC_CONTROL=unix:@anland-mobilegl`)+ `/etc/mobilegl/backend`。进程不需要任何专门的环境变量。
|
||||
- **零拷贝窗口**:client 的 `wl_egl_window` 帧是 server 分配的 AHardwareBuffer(shared image),以 dma-buf fd 经 `zwp_linux_dmabuf_v1` 交给 KWin;KWin 用 EGL 导入,server 按 inode 认出是自己发出的那块。KWin 的 DRM 设备来自 `GBM_BACKEND=mobilegl` → `/usr/lib/gbm/mobilegl_gbm.so`(节点只当身份,从不 ioctl)。没有 linux-dmabuf 时退回 wl_shm 回读。
|
||||
- **X11**(Xwayland)走 MobileGL 的 GLX vendor,每帧回读 + `xcb_put_image`。Chrome 用 `mobilegl-startup.sh chrome`:ANGLE GLES 跑在 MobileGL EGL 上,GPU 在浏览器进程内。
|
||||
|
||||
## 首次准备(Windows 主机)
|
||||
|
||||
- Git Bash、`adb`(SDK platform-tools)、PATH 上有 CMake ≥ 3.22 与 Ninja(本机 CMake 3.27.6、Ninja 1.13.2);`rec.sh` 另需 Python 3 + Pillow。
|
||||
- Android NDK **27.2.12479018**,默认 `%LOCALAPPDATA%/Android/Sdk/ndk/27.2.12479018`(`ANDROID_NDK` 可覆盖)。
|
||||
- WSL 发行版 **archlinux**:clang、lld、llvm、cmake、ninja。**lld 的大版本必须等于 clang / llvm-libs**(曾经 lld 23 配 llvm 22,`ld.lld` 直接跑不起来;现在全是 22.1.6)。然后导出一次 sysroot:`bash scripts/xsysroot/refresh-sysroot.sh`(得到 WSL 里的 `~/sysroots/arch-kde-mgl` 与 `~/sysroots/aarch64-arch.cmake`,glibc 2.43、gcc 16;容器升级 glibc / gcc / vulkan / libdrm / gbm 后要重跑,再 `xbuild.sh --reconfigure`)。
|
||||
- MobileGL 用独立 worktree(主 checkout 和它的构建目录是共用的,别动):`GIT_LFS_SKIP_SMUDGE=1 git worktree add ...`,`git submodule update --init --recursive`,再从主 checkout 拷 `3rdparty/glslang/External/spirv-tools`(submodule 初始化后是空的,configure 会报找不到 SPIR-V tools)。
|
||||
- anland worktree,分支 `legacy-mobilegl-unified`(KWin patch / 后端源码、启动脚本、`mobilegl-tools/`)。
|
||||
- 设备侧已经存在、本 runbook **不负责从零搭**的:APK(按 anland `consumers/anland_v5/android_consumer/MOBILEGL.md`,gradle `:app:assemblePlainDebug`,`MOBILEGL_DIST` 指向 server 库,`-PmobileglApplicationId=com.anland.consumer.mobilegl`;换签名重装会换 UID,要重做 KernelSU 授权,见 `mobilegl-tools/ksu_grant.c`)、anland 的 KernelSU 模块(`anland-daemon`、`anland-awl`)、容器本身,以及容器里配置好的 KWin 构建目录 `/root/mobilegl-build/kwin-6.7.4/build` 和原版 KWin 源码 `/root/gbm-dev/kwin-a/src`。从零重建这些未验证。
|
||||
- 装一次辅助脚本(容器要在跑):`bash scripts/push-tools.sh`——`device/*.sh` 到 `/data/local/tmp/anl`,`mgrun` 到容器 `/usr/local/bin`,`kwprep.sh` 到容器 `/root`。
|
||||
|
||||
主机脚本都在 Git Bash 里 `bash scripts/<x>.sh` 运行。直接敲 adb 时,带设备路径要加 `MSYS_NO_PATHCONV=1`(否则 `/data/...` 被改写成 Git 安装目录),root 命令整个包进一层引号:`adb shell 'su -c "..."'`。设备脚本也可以不推送直接跑:`MSYS_NO_PATHCONV=1 adb shell 'su -c sh' < scripts/device/status.sh`。
|
||||
|
||||
## 启动、闲置、停止——没有手动步骤
|
||||
|
||||
开机什么都不起(容器 `run_at_boot=0`)。**打开 app**(桌面图标,等同 `am start -n com.anland.consumer.mobilegl/com.anland.consumer.MainActivity`,不带参数;APK 的默认 socket 是实验 daemon 那个,见 `-PanlandDefaultSocket`)会调 `MobileGLDesktop.ensureStarted()`——唯一的触发点——启动前台服务,并以 root(`su`)跑 APK 里的 `assets/mobilegl-desktop.sh up`:daemon 不在就起(脚本先离开 app 的 cgroup,app 被杀时 daemon 和容器不受牵连)→ 发布后端 → 容器没跑就启动 → `systemctl start --no-block desktop-session`。窗口等 daemon socket 出现;Surface attach 后 server 开始监听;会话的 `wait` 放行,Plasma 起来。全都在跑时再打开只是重新挂上窗口。日志:`/data/local/tmp/anland-mobilegl/desktop.log`,logcat `AnlandMobileGL`("MobileGL desktop up done in N ms")。
|
||||
|
||||
窗口隐藏、熄屏、锁屏、从最近任务划掉:server 与会话都留着(前台服务);KWin 记 "viewer gone: output powered down" 然后闲置,回来时记 "viewer back"。**停止桌面**(通知按钮,或 `am startservice -n <包名>/com.anland.consumer.MobileGLWorker -a com.anland.consumer.mobilegl.STOP`)跑 `mobilegl-desktop.sh down`(停会话、停容器,daemon 留着),关窗口,结束 `:mobilegl` 进程。
|
||||
|
||||
```sh
|
||||
adb connect <ip>:<port> # 只有无线调试才需要
|
||||
export MSYS_NO_PATHCONV=1
|
||||
adb shell 'su -c "sh /data/local/tmp/anl/bringup.sh"' # 重启后:点亮、解开锁屏、打开 app、打印各步耗时
|
||||
bash scripts/desktop-verify.sh push cold reopen idle # 冷启动计时、划掉再打开、可见/隐藏/锁屏的 CPU 与 GPU
|
||||
adb shell 'su -c "sh /data/local/tmp/anl/status.sh"'
|
||||
```
|
||||
|
||||
`droidspaces` 没有 `status` 子命令,`droidspaces show` 列出在跑的容器。`device/run-plasma.sh` 是开发用的重启(会话 + logind 用户,再重开 app),重新构建之后需要——KWin 不会重连新的 server 进程。`device/desktop-reset.sh` 把一切停到"刚重启"的状态。以前的手动序列(setprop、起容器、`restart-daemon.sh`、`run-plasma.sh`)不再需要;`restart-daemon.sh` 只留作调试 daemon。
|
||||
|
||||
## 构建与部署循环
|
||||
|
||||
### server(Android,NDK)→ 替换 APK 里的库
|
||||
|
||||
```sh
|
||||
bash scripts/build-android.sh # → <worktree>/build-android/libMobileGL.so(RelWithDebInfo,不 strip,约 320 MB,留着给 addr2line)
|
||||
bash scripts/cyc.sh s1 # 删粘滞软件渲染键 → 推库 → 换进 APK → 重启会话 → 截图 $ANL_DIR/s1.png → 打印 fatal
|
||||
```
|
||||
|
||||
`device/swap.sh` 直接 root 覆盖 `/data/app/.../lib/arm64/libMobileGL.so`,保留 owner / 权限 / SELinux context,第一次运行把原件存为 `/data/local/tmp/anland-mobilegl/libMobileGL.so.orig`;不重签 APK(重签要重装,UID 会变,授权要重做)。只换库不跑 cyc:推到 `/data/local/tmp/anland-mobilegl/libMobileGL.new.so`,`su -c "sh /data/local/tmp/anl/swap.sh"`,之后照样要 `run-plasma.sh`——server 进程换了,KWin 不会重连。
|
||||
|
||||
### client + GBM 后端(容器 glibc),WSL 交叉编译
|
||||
|
||||
```sh
|
||||
bash scripts/xbuild.sh [--reconfigure] # WSL ~/mgl-xbuild-a64;全量约 150 秒,增量几秒
|
||||
bash scripts/xdeploy.sh # 新 inode 改名就位安装 libMobileGL.so + mobilegl_gbm.so,刷新别名、GLX 链接、vendor JSON、client.conf 和 GLX/GBM 环境 drop-in
|
||||
```
|
||||
|
||||
已经在跑的 client 继续映射旧 inode:重启那个 client,KWin / plasmashell 就 `run-plasma.sh`。server 与 client **同一个 commit** 构建(两边 stamp 都取 `git rev-parse HEAD`),否则日志里 WARN "compatible wire with different build"。容器内构建是备用路径(慢):`scripts/sync-src.sh` 同步改动的文件,再在容器里跑 anland 的 `mobilegl-tools/anland-build-client.sh`。
|
||||
|
||||
### KWin(anland 后端 / kwin.patch),在容器里编
|
||||
|
||||
```sh
|
||||
bash scripts/kwin-stage.sh build # 去 CR 后暂存 patch + 后端 + 启动脚本,跑 kwprep.sh,后台起 sync-build-kwin.sh
|
||||
printf 'tail -5 /root/kwin-build.log\n' | bash scripts/ct.sh kw # 等到 "== ALL DONE =="
|
||||
adb shell 'su -c "sh /data/local/tmp/anl/run-plasma.sh"'
|
||||
```
|
||||
|
||||
两处暂存是因为两个容器脚本读的地方不同:`kwprep.sh` 读容器 `/root/kwsync.tgz`,把 `kwin.patch` 重新打到原版 KWin 源码的副本上,只把内容变了的文件拷进活树(保持 ninja 增量);anland 的 `sync-build-kwin.sh`(`producers/kde/Arch_v5/`)读 `/run/anland-mobilegl/kwin-sync/`,只同步后端源码,`ninja -j4`,再以原子改名安装 `kwin_wayland`、`libkwin.so.6.7.4`、打过补丁的 screencast 插件和 `/opt/mobilegl/bin/mobilegl-startup.sh`,不重启合成器。仓库根的 `libdisplay_producer/` 两个脚本都不同步。`kwin-stage.sh` 是把这两步拼起来的,还没整体跑过。
|
||||
|
||||
### 单元测试(不用设备)
|
||||
|
||||
这个分支的 MSVC 构建是坏的,在 WSL 里编:
|
||||
|
||||
```sh
|
||||
MSYS2_ARG_CONV_EXCL='*' wsl -d archlinux -- bash /mnt/c/<worktree>/.claude/skills/anland-mobilegl-plasma/scripts/wsl-test.sh MultiSessionTest TwinOwnershipTest
|
||||
MSYS2_ARG_CONV_EXCL='*' wsl -d archlinux -- bash -lc 'cd ~/mgl-anl-bld && ctest -R "MultiSession|TwinOwnership" -E DirectVulkan --output-on-failure'
|
||||
```
|
||||
|
||||
`wsl-all.sh` 编并跑全部单元测试。DirectVulkan 集成测试永远不要跑;两个 ctest 不要同时跑。
|
||||
|
||||
## 切换后端
|
||||
|
||||
在 app 里:设置 > 连接 > MobileGL 桌面 > 渲染后端,然后通知里"停止桌面",再打开 app。或者 `adb shell 'su -c "sh /data/local/tmp/anl/switch.sh DirectGLES"'`,做的是同一件事(用启动参数保存设置、STOP 服务、重新打开;顺手清掉残留的 `debug.mobilegl.backend`)。不用 setprop,也不用改容器里的文件:会话下次启动时把发布的后端拷进 `/etc/mobilegl/backend`。`cyc.sh` 不改后端。
|
||||
|
||||
## 全系统 vendor 与回退
|
||||
|
||||
`xdeploy.sh`(以及 anland 的 `anland-build-client.sh`)安装下面这些,容器里任何进程经原版 libglvnd / libgbm 就用上 MobileGL:
|
||||
|
||||
| 文件 | 作用 |
|
||||
|---|---|
|
||||
| `/usr/share/glvnd/egl_vendor.d/10_mobilegl.json` | EGL vendor,先于系统的 `50_*.json` 被问 |
|
||||
| `/opt/mobilegl/share/glvnd/egl_vendor.d/50_mobilegl.json` | 同一份 JSON,留给 `__EGL_VENDOR_LIBRARY_FILENAMES` 单进程强制 |
|
||||
| `/etc/mobilegl/client.conf` | 所有 client 的 `KEY=value` 默认值(transport、data、endpoint),每次部署重写 |
|
||||
| `/etc/mobilegl/backend` | 后端(`switch.sh` 写);比 client.conf 里的 `MOBILEGL_BACKEND_TYPE` 优先 |
|
||||
| `/etc/environment.d/10-mobilegl.conf` | `__GLX_VENDOR_LIBRARY_NAME=mobilegl`、`GBM_BACKEND=mobilegl`,给 systemd 用户管理器(所有 Plasma 服务及其子进程,包括终端) |
|
||||
| `/etc/profile.d/mobilegl.sh` | 同样两项,给登录 shell(`bash -l`、`su -l`、ssh、`droidspaces run bash -l`) |
|
||||
|
||||
优先级:环境变量 > `/etc/mobilegl/backend`(只管后端)> client.conf > 内置默认;`MOBILEGL_CONFIG_FILE` / `MOBILEGL_BACKEND_FILE` 可指向别的文件(设为空即关闭);server 进程不读这些文件。
|
||||
|
||||
**回退**:真正拉起之前 client 先探一次配置的 endpoint(一次非阻塞 connect,`MOBILEGL_IPC_PROBE_TIMEOUT_MS` 默认 250,0 关闭;被拒的抽象 socket 约 0.2 ms 就有结果)。没有 server 时:EGL vendor 返回 `EGL_NO_DISPLAY`、设备数 0(glvnd 换下一个 vendor),GLX vendor 的 `__glx_Main` 失败(libGLX 回退到 Xwayland 报的 vendor),`mobilegl_gbm` 的 `create_device` 失败(libgbm 回退到设备自己的后端)。客户端查询(`eglQueryString(EGL_NO_DISPLAY)`、`eglGetProcAddress`)从不拉起会话。对别的后端创建的 GBM 设备,MobileGL 同样让开。`LIBGL_ALWAYS_SOFTWARE=1` 不再让它让开(918d40dd):那只是 Mesa 的软件渲染器选择,不是关掉 MobileGL 入口的理由;只要有 server,带这个变量的进程(包括被 KWin 补丁设了它的 Xwayland)照样由 MobileGL 服务。不停真 server 的演示:
|
||||
```sh
|
||||
sed 's|^MOBILEGL_IPC_CONTROL=.*|MOBILEGL_IPC_CONTROL=unix:@nobody|' /etc/mobilegl/client.conf > /tmp/down.conf
|
||||
sudo -u swung0x48 env -i HOME=/home/swung0x48 PATH=/usr/bin XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-0 \
|
||||
MOBILEGL_CONFIG_FILE=/tmp/down.conf eglinfo -B -p wayland # vendor 变成另一个,约 0.1 s
|
||||
```
|
||||
|
||||
## 跑测试 client 与验证
|
||||
|
||||
在容器里以 root 执行(`printf '...' | bash scripts/ct.sh <通道>`),`mgrun offscreen <命令>` 以 swung0x48 身份在当前会话里起一个 client(日志 `/tmp/mgl-run.client.log`,`MGLOG=/tmp/x.log` 可换):
|
||||
|
||||
```sh
|
||||
mgrun offscreen eglinfo -B ; mgrun offscreen glxinfo -B
|
||||
timeout 25 mgrun offscreen glmark2-es2-wayland -s 1280x720 --run-forever > /tmp/gm.out 2>&1 &
|
||||
sleep 8; grep -a "presented through" /tmp/mgl-run.client.log
|
||||
```
|
||||
|
||||
Chrome:`bash scripts/chrome-launch.sh <tag> [参数]`(日志 `/tmp/mgl-chrome-<tag>.client.log`,截图 `ch-<tag>.png`)。探针:`scripts/probes/texfmt.c`(各纹理格式是否保住 alpha)、`dmahash.c`(对某进程的 dma-buf 取样哈希);放进容器 `/root` 编,例如 `gcc -O1 /root/texfmt.c -o /usr/local/bin/mgl-texfmt -lEGL`,用 `mgrun offscreen` 跑(之前的确切编译参数未验证)。
|
||||
|
||||
视频:Chrome 走硬解(VaapiVideoDecoder)。容器的 VA-API 驱动(`msm_drm_drv_video.so`,V4L2 解码)自己分配 surface 并导出 NV12 dma-buf(单 fd,R8 + GR88 两层),Chrome 以 NV12/P010 EGLImage 绑到 `GL_TEXTURE_EXTERNAL_OES`。这种**外来** YUV dma-buf 平台导入不了,MobileGL 走明确标注的 CPU 拷贝兜底:server 映射它、每帧首次使用时拷进自己的 YUV AHB(`AHardwareBuffer_lockPlanes`),再在 GPU 上转换进纹理的 RGBA 层(Espryt:`GL_EXT_YUV_target` + 导入时的色彩 hint,`MOBILEGL_YUV_DRIVER_CONVERSION=1` 改用驱动的 external sampler;Magma:AHB external format 上的 `VkSamplerYcbcrConversion`)。server 自己分配的 NV12/P010(GBM)零拷贝。`MOBILEGL_CHROME_VIDEO_DECODE=software` 恢复 `--disable-accelerated-video-decode`。检查:容器里 `python3 scripts/probes/cdpmedia.py <port> <秒>`(CDP `Media.enable`,`kVideoDecoderName`),`scripts/probes/yuvprobe.c` 像素校验外来 NV12/P010 导入、逐帧刷新和 GBM NV12。KWin 自己的 NV12 路径(拆成 R8 + GR88 两个导入)不支持,KWin 不通告 YUV 格式。
|
||||
|
||||
结论成立要同时满足:
|
||||
|
||||
- client 日志是 `presented through linux-dmabuf shared images`,不是 `wl_shm`;出现 `could not be presented through a linux-dmabuf shared image` 说明退回了;
|
||||
- client 在动的时候 `bash scripts/shot.sh g 2 1` 两张截图哈希不同——dma-buf 下分数"很快"可能是帧根本没上屏;
|
||||
- `adb logcat -b crash -d` 与 `adb logcat -d | grep -E "Fatal signal|Abort message|E MobileGL"` 干净——窗口冻住多半是 server 死了;
|
||||
- 对 plasmashell 下结论前,粘滞软件渲染已关(见下),且 `/tmp/mgl-plasmashell.client.log` 是新写的。
|
||||
|
||||
参考(glmark2-es2-wayland 1280x720,零拷贝 vs wl_shm):Espryt 187 vs 49,Magma 158 vs 34。出乎意料的通过要复跑。
|
||||
|
||||
## 日志
|
||||
|
||||
| 内容 | 位置 |
|
||||
|---|---|
|
||||
| KWin 的 client 侧 | 容器 `/tmp/mobilegl-compositor.client.log` |
|
||||
| plasmashell / ksplash | `/tmp/mgl-plasmashell.client.log`、`/tmp/mgl-ksplash.client.log`(用户 drop-in `~/.config/systemd/user/plasma-{plasmashell,ksplash}.service.d/mobilegl-log.conf`) |
|
||||
| mgrun / Chrome | `/tmp/mgl-run.client.log`、`/tmp/mgl-chrome-<tag>.client.log`(+ `/tmp/chrome-<tag>-out.log`) |
|
||||
| server | `adb logcat -s MobileGL`;文件 `/data/data/com.anland.consumer.mobilegl/files/mobilegl-server.server.log` |
|
||||
| native 崩溃 | `adb logcat -b crash -d`;容器里 `coredumpctl list` |
|
||||
| 会话 / KWin stderr | 容器 `journalctl -u desktop-session --no-pager`、`journalctl --no-pager --since -2min \| grep kwin_wayland` |
|
||||
| display daemon | `/data/local/tmp/anland-mobilegl/display-daemon.log` |
|
||||
|
||||
`MOBILEGL_LOG_FILE_PATH=/tmp/x.log` 实际写 `/tmp/x.client.log`(server 转发的行进 `.server.log`),打开时截断,所以每个进程一个 base。client 构建的日志级别是 INFO。server 栈帧用 `$ANDROID_NDK/toolchains/llvm/prebuilt/windows-x86_64/bin/llvm-addr2line.exe -Cfe build-android/libMobileGL.so <偏移>` 符号化。
|
||||
|
||||
## 常见坑
|
||||
|
||||
- **pkill -f 误杀自己**:`ct.sh` / `droidspaces run bash -lc "<脚本>"` 的进程参数里带着整段脚本,`pkill -f chrome` 会先把执行者杀掉。用 `pkill -x`。
|
||||
- **/tmp 里 root 的文件**:root 跑 client 留下的日志,之后 swung0x48 打不开(日志不出现)。先以 root `rm -f`。
|
||||
- **Plasma 软件渲染是粘滞的**:server 一崩,Plasma 就把 `[QtQuickRendererSettings] SceneGraphBackend=software` 写进 `~swung0x48/.config/kdeglobals`,之后 plasmashell 全用 CPU 渲染,截图照样正常(托盘里有"正在使用的软件渲染器"图标)。`mobilegl-startup.sh plasma` 每次会话启动都会删掉它;一个已经崩过的会话里,下结论前先重启会话(`run-plasma.sh`)。
|
||||
- **锁屏**:自动锁屏已关(`kscreenlockerrc` 的 `[Daemon] Autolock=false`);真锁上只能输密码——这种 logind 会话不支持 lock-session,`device/unlock.sh` 的 unlock-session 能否解开未验证。测 greeter 用 `/usr/lib/kscreenlocker_greet --testing` 再杀掉。
|
||||
- **CRLF**:本机 `core.autocrlf=true`,anland 的后端源码和 `sync-build-kwin.sh` 在 Windows checkout 里是 CRLF。凡是要在 Linux / Android 上执行的先 `tr -d '\r'`(`ct.sh`、`push-tools.sh`、`kwin-stage.sh` 已处理);skill 目录有 `.gitattributes` 强制 LF。
|
||||
- **display daemon**:由 app 拉起;会话的 `wait` 步骤保证 KWin 不会在它之前启动(连不上时 KWin 报 "failed to connect to display daemon at /run/anland-mobilegl/display.sock",约 3 秒退出)。从 app 的 `su` 拉起的东西必须先离开 app 的 cgroup(`echo $$ > /sys/fs/cgroup/cgroup.procs`),否则 force-stop 会一并杀掉。
|
||||
- **电量 / 连接**:电量低或熄屏(默认 30 秒)可能让 SurfaceView 被回收——`svc power stayon true`;无线调试的端口每次配对都变——脚本里不要写死 `-s HA27Q3LQ`,多设备时设 `ANDROID_SERIAL`。
|
||||
- **容器与 Android 路径**:容器的 `/tmp` 和平板的 `/data/local/tmp` 不是一个地方;文件经 `/mnt/Droidspaces/arch-kde-mgl/root`(= 容器 `/root`)或 `/data/local/tmp/anland-mobilegl`(= 容器 `/run/anland-mobilegl`)中转。
|
||||
- **正在映射的 .so 不要原地覆盖**(容器里或运行中的 APK):拷成新 inode 再 `mv -f`;APK 换库前先 force-stop。
|
||||
- **输入注入**:普通 `adb shell input` 被拦,要 `su -c input`;anland 抓了触摸屏,`input tap` 无效时用 `input mouse tap x y`。不要对容器进程用 `debuggerd -b`(信号 35 会直接杀掉 glibc 进程)。
|
||||
- **linux-dmabuf**:client 必须 `create` + roundtrip,不能 `create_immed`(KWin 对失败的立即导入回协议错误,直接断开 client)。
|
||||
- **跨会话 bug**:某个 client 起来或退出后出现噪点 / 别的窗口的残影,先怀疑多会话 server 里的进程级状态(handle 是每 client 各自的,FBO / VAO 是每 context 的)。复现用 `kscreenlocker_greet --testing`。
|
||||
- `/etc/environment` 经 pam_env(以及 `/usr/lib/environment.d/99-environment.conf` 进用户管理器)带进基线会话的驱动变量,`mobilegl-startup.sh` 里把它们 unset 了,改脚本时保留。
|
||||
- **共用日志路径**:Xwayland 继承 KWin 的 `MOBILEGL_LOG_FILE_PATH`,进程写第一行日志就会打开(截断)那个文件。MobileGL 在不服务的进程里不写日志,但额外起的 client 要给自己的 `MOBILEGL_LOG_FILE_PATH`。
|
||||
- **残留的单进程覆盖**:unit drop-in 或用户管理器环境(`systemctl --user show-environment`)里遗留的 `__EGL_VENDOR_LIBRARY_FILENAMES` / `MOBILEGL_*` 仍比系统文件优先;`run-plasma.sh`(terminate-user)会清掉管理器里的。
|
||||
- handoff 里提到的 `kwin_wayland` 日志 shim 与 `/tmp/mgl-kwin-<pid>.log` 已过时:`/opt/mobilegl/kwin/bin/kwin_wayland` 现在就是真二进制,KWin 的 client 日志在 `/tmp/mobilegl-compositor.client.log`。
|
||||
|
||||
## 规矩
|
||||
|
||||
- 在 git worktree 里干活,不碰共用的主 checkout 和它的构建目录;worktree 要 `GIT_LFS_SKIP_SMUDGE=1`。
|
||||
- 提交用 `git commit --no-verify`,一句话的短消息,**不加** `Co-Authored-By`。
|
||||
- DirectVulkan 集成测试不跑(`ctest -E DirectVulkan`)。
|
||||
- 源码和注释里不写外部项目名("Mesa"、"Zink" 等),只描述手法。
|
||||
- 零拷贝路径只用厂商中立的设施:AHB、EGL_ANDROID_*、VK_ANDROID 的 AHB 导入、sync_file、dma-buf、libgbm ABI;不碰 kgsl / gralloc 私有布局 / 高通修饰符。
|
||||
- 身份缓存用单调递增的生命期 id 作键,不用 GL 名字或指针。
|
||||
@@ -0,0 +1,183 @@
|
||||
# Reproducing the MobileGL + anland + KDE Plasma setup from source
|
||||
|
||||
This is the from-scratch path for a NEW development machine and/or a NEW phone. `SKILL.md` covers the
|
||||
day-to-day loop once everything below exists. Paths and names that are specific to the original
|
||||
machine are listed in step 9: set them in `scripts/env.sh` (or the environment) before running anything.
|
||||
|
||||
## 0. What you need
|
||||
|
||||
**Phone.**
|
||||
- arm64 Android 11+ with **root**. KernelSU is what this was built on; the scripts only need `su -c`.
|
||||
- A Vulkan 1.1 driver with `VK_ANDROID_external_memory_android_hardware_buffer` for Magma, and/or GLES 3.2 + EGL_ANDROID_native_fence_sync for Espryt. Nothing vendor-specific is used: no kgsl and no vendor ioctls, only AHardwareBuffer, sync_file and the vendor's public GLES/Vulkan.
|
||||
- About 20 GB free for the container image.
|
||||
|
||||
**Development host.**
|
||||
- The scripts are written for **Windows + Git Bash** with WSL (`archlinux` distro) for the unit tests and the cross build.
|
||||
- A Linux host works too: drop the `cygpath`/`MSYS_NO_PATHCONV` handling in `env.sh`, and run the WSL parts natively.
|
||||
- Tools:
|
||||
- `adb`, CMake >= 3.22, Ninja, Python 3 (+ Pillow for screenshots/recording).
|
||||
- Android NDK **27.2.12479018**.
|
||||
- JDK 17 and Android SDK platform 36 (for the APK).
|
||||
- In the Linux environment: clang/lld/llvm of ONE major version (a mismatched lld breaks `ld.lld`), cmake, ninja.
|
||||
|
||||
## 1. Sources
|
||||
|
||||
```sh
|
||||
# MobileGL: branch feat/disaggregated (everything here is on it)
|
||||
git clone https://github.com/MobileGL-Dev/MobileGL.git && cd MobileGL
|
||||
git checkout feat/disaggregated
|
||||
GIT_LFS_SKIP_SMUDGE=1 git submodule update --init --recursive
|
||||
# 3rdparty/glslang is the MobileGL-Dev fork (main); its External/spirv-tools must be populated
|
||||
# (glslang's own update_glslang_sources.py, or copy it from another checkout).
|
||||
# Keep builds out of a shared checkout: work in a worktree (GIT_LFS_SKIP_SMUDGE=1 git worktree add ...).
|
||||
|
||||
# anland: branch legacy-mobilegl-unified (KWin backend + patch, startup script, APK, daemon module)
|
||||
git clone https://github.com/MobileGL-Dev/anland.git && cd anland # the fork that carries this branch
|
||||
git checkout legacy-mobilegl-unified
|
||||
```
|
||||
|
||||
Build the server (APK) and the client (container) from the **same MobileGL commit**: both stamp
|
||||
`git rev-parse HEAD`, and a mismatch logs "compatible wire with different build".
|
||||
|
||||
## 2. Phone base: root, Droidspaces, display daemon
|
||||
|
||||
1. **Root** with KernelSU (or another `su` provider).
|
||||
2. **Droidspaces:** install the module and its app. Containers live under `/data/local/Droidspaces/Containers/<name>/`, and the CLI is `/data/local/Droidspaces/bin/droidspaces`.
|
||||
3. **anland display daemon** (KernelSU/Magisk module `anland-daemon`):
|
||||
- Build it from the anland repo with `build_daemon_android.sh`, then package it with `build_magisk_module.sh`, which uses `magisk_module/`. Flash it and reboot.
|
||||
- The module's `service.sh` starts the original daemon on `/data/local/tmp/display_daemon.sock`.
|
||||
- The MobileGL session uses a SECOND instance of the same binary on `/data/local/tmp/anland-mobilegl/display.sock`: see `scripts/device/restart-daemon.sh`, which also chmods the directory and socket.
|
||||
|
||||
## 3. The container
|
||||
|
||||
Use an **Arch Linux ARM (aarch64, glibc) KDE Plasma Wayland rootfs** that has a systemd `desktop-session.service` running the desktop as a normal user with `PAMName=login`. A ready-made one is the Arch KDE "Wayland" image from Droidspaces RootFS Desktop Builder (see anland `doc/UserManual/anland_guide.md`, "Use prebuilt packages or a RootFS"). Or install `plasma-desktop`, `plasma-workspace`, `libglvnd`, `xorg-xwayland`, `base-devel`, `cmake` and `ninja` on plain Arch Linux ARM and write the service yourself.
|
||||
|
||||
Import it into Droidspaces as `arch-kde-mgl`. The default name is in `env.sh`. Reference `container.config` keys:
|
||||
```
|
||||
enable_gpu_mode=1
|
||||
net_mode=host
|
||||
bind_mounts=/data/local/tmp/display_daemon.sock:/run/display.sock,/data/local/tmp/anland-mobilegl:/run/anland-mobilegl
|
||||
run_at_boot=0
|
||||
```
|
||||
- **Host networking:** it is required. The MobileGL server listens on the ABSTRACT socket `@anland-mobilegl`, which crosses into the container only when the network namespace is shared.
|
||||
- **The anland-mobilegl directory:** create `/data/local/tmp/anland-mobilegl` on the phone before the first start, mode 777.
|
||||
- **Desktop user:** the default is `swung0x48` (`MGL_DESKTOP_USER`). Create your own and point `desktop-session.service` at it.
|
||||
- **Starting it:** `droidspaces -C .../container.config start`. Android root sees the rootfs at `/mnt/Droidspaces/<name>`, which `CT_ROOT` in `env.sh` relies on.
|
||||
- **Container tools:** install them with `bash scripts/push-tools.sh` once the container runs.
|
||||
|
||||
## 4. MobileGL server and the anland APK
|
||||
|
||||
```sh
|
||||
bash scripts/build-android.sh # -> <worktree>/build-android/libMobileGL.so
|
||||
```
|
||||
The script runs, with the NDK toolchain:
|
||||
```
|
||||
-DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-30 -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
-DMOBILEGL_PIPE_PUSH=ON -DMOBILEGL_BUILD_DISAGGREGATED=ON -DMOBILEGL_BUILD_DISAGGREGATED_INPROC=ON
|
||||
-DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF
|
||||
```
|
||||
|
||||
APK, from anland `consumers/anland_v5/android_consumer` (details in its `MOBILEGL.md`):
|
||||
```sh
|
||||
mkdir -p dist/arm64-v8a && cp <worktree>/build-android/libMobileGL.so dist/arm64-v8a/
|
||||
MOBILEGL_DIST=$PWD/dist ./gradlew :app:assemblePlainDebug \
|
||||
-PanlandNdkVersion=27.2.12479018 -PmobileglApplicationId=com.anland.consumer.mobilegl \
|
||||
-PanlandDefaultSocket=/data/local/tmp/anland-mobilegl/display.sock
|
||||
adb install -r app/build/outputs/apk/plain/debug/app-plain-debug.apk
|
||||
```
|
||||
- **Grant root:** the app needs root for its helper; approve it in KernelSU.
|
||||
- **Reinstall with another signing key:** that changes the UID, so grant root again (anland `mobilegl-tools/ksu_grant.c` does it from a shell). Don't uninstall to fix a signature clash: that wipes the app data.
|
||||
- **Later server-only changes:** swap just the library into the installed APK (`scripts/cyc.sh` / `device/swap.sh`) instead of rebuilding the APK.
|
||||
|
||||
## 5. MobileGL client in the container
|
||||
|
||||
Pick one of these two:
|
||||
- **Cross build** (fast, from WSL or Linux):
|
||||
1. `bash scripts/xsysroot/refresh-sysroot.sh` exports the container's headers and libraries to `~/sysroots/arch-kde-mgl` and installs `~/sysroots/aarch64-arch.cmake`. Re-run it after container package upgrades.
|
||||
2. `bash scripts/xbuild.sh`, then `bash scripts/xdeploy.sh`.
|
||||
- **Native build in the container** (slow, no WSL): `scripts/sync-src.sh` copies the worktree to `/root/mobilegl-unified-src`, then run anland `producers/kde/Arch_v5/mobilegl-tools/anland-build-client.sh` inside (g++, `-DMOBILEGL_BUILD_DISAGGREGATED=ON`, Release).
|
||||
|
||||
Either route installs the same files, renamed into place so running clients keep their old mapping:
|
||||
|
||||
| File | What it is |
|
||||
|---|---|
|
||||
| `/opt/mobilegl/lib/libMobileGL.so` (+ `libEGL`/`libGL` aliases) | the client library |
|
||||
| `/usr/share/glvnd/egl_vendor.d/10_mobilegl.json` | system-wide EGL vendor; sorts before the distribution's `50_*.json`, which serves whenever MobileGL declines (no server reachable) |
|
||||
| `/opt/mobilegl/share/glvnd/egl_vendor.d/50_mobilegl.json` | per-process forcing |
|
||||
| `/usr/lib/libGLX_mobilegl.so.0` | GLX vendor |
|
||||
| `/usr/lib/gbm/mobilegl_gbm.so` | GBM backend (zero-copy window buffers) |
|
||||
| `/etc/mobilegl/client.conf` | `MOBILEGL_TRANSPORT=spawn`, `MOBILEGL_IPC_DATA=shm`, `MOBILEGL_IPC_CONTROL=unix:@anland-mobilegl` |
|
||||
| `/etc/mobilegl/backend` | `DirectVulkan` (Magma) or `DirectGLES` (Espryt) |
|
||||
| `/etc/environment.d/10-mobilegl.conf`, `/etc/profile.d/mobilegl.sh` | `__GLX_VENDOR_LIBRARY_NAME=mobilegl`, `GBM_BACKEND=mobilegl` |
|
||||
|
||||
## 6. KWin 6.7.4 with the anland backend, and Xwayland
|
||||
|
||||
The MobileGL session runs a rebuilt KWin from `/opt/mobilegl/kwin`, so the packaged one stays untouched.
|
||||
|
||||
1. **Source tree:** in the container, unpack KWin 6.7.4 to `/root/mobilegl-build/kwin-6.7.4`, apply anland `producers/kde/Arch_v5/kwin.patch`, and overlay `producers/kde/anland_backend_Arch_v5/src/backends/anland` into `src/backends/anland`. Also take the current `common/` and `libdisplay_producer/`, as `mobilegl.md` there says.
|
||||
2. **Configure:**
|
||||
```sh
|
||||
cmake -S kwin-6.7.4 -B kwin-6.7.4/build -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib -DBUILD_TESTING=OFF
|
||||
```
|
||||
3. **Build and install:** run `producers/kde/Arch_v5/sync-build-kwin.sh` (`ninja -j4`). It installs `kwin_wayland` + `libkwin.so.6.7.4` into `/opt/mobilegl/kwin/{bin,lib}`, the patched screencast plugin in place (the original is kept beside it), and `/opt/mobilegl/bin/mobilegl-startup.sh`.
|
||||
- From the dev host, `bash scripts/kwin-stage.sh build` does steps 1–3 incrementally: `kwprep.sh` applies changed patch hunks.
|
||||
4. **Xwayland:** the anland-patched Xwayland 24.1.13 comes from `producers/kde/Arch_v5/build.sh` (makepkg, as a normal user; it builds the anland KWin and Xwayland packages). A prebuilt anland rootfs already has it.
|
||||
5. **Session hook:** add `/etc/systemd/system/desktop-session.service.d/mobilegl.conf`:
|
||||
```ini
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=/opt/mobilegl/bin/mobilegl-startup.sh plasma
|
||||
Environment=ANLAND_SOCKET=/run/anland-mobilegl/display.sock
|
||||
Environment=QT_LOGGING_RULES=kwin_*.info=true
|
||||
```
|
||||
`mobilegl-startup.sh plasma` writes the user drop-in for `plasma-kwin_wayland.service` (the `/opt/mobilegl/kwin` binary, `ANLAND_MOBILEGL=1`, `MOBILEGL_IPC_SURFACE=server`, `GBM_BACKEND=mobilegl`) and runs `startplasma-wayland`.
|
||||
|
||||
## 7. Bring it up
|
||||
|
||||
Nothing has to be started by hand, and the order no longer matters: **open the app** (its launcher
|
||||
icon, or `am start -n com.anland.consumer.mobilegl/com.anland.consumer.MainActivity` with no extras).
|
||||
That is the single trigger. The app starts its foreground service and runs its bundled
|
||||
`mobilegl-desktop.sh up` as root, which:
|
||||
1. starts the display daemon if it is not running;
|
||||
2. publishes the backend (the app setting "Renderer backend", default DirectGLES) to
|
||||
`/data/local/tmp/anland-mobilegl/backend`;
|
||||
3. starts the container if it is stopped;
|
||||
4. starts `desktop-session`, which waits for the server and the daemon socket, copies the backend into
|
||||
`/etc/mobilegl/backend` and runs Plasma.
|
||||
|
||||
Cold start to a desktop takes about 8-12 s. `scripts/device/bringup.sh` wakes the phone, dismisses the
|
||||
keyguard, opens the app and prints the timeline. To change the backend, use the app setting and then
|
||||
Stop desktop and reopen, or run `scripts/device/switch.sh DirectGLES|DirectVulkan`. Do not use
|
||||
`setprop debug.mobilegl.backend`: it overrides the setting until the next reboot. **Stop desktop**
|
||||
(the notification action) stops the session and the container.
|
||||
|
||||
**After a phone reboot:** wireless adb does not come back by itself. Re-enable it on the phone
|
||||
(Developer options > Wireless debugging), then `adb connect <ip>:<port>`; the port changes. After
|
||||
that, open the app as above. See SKILL.md "Starting, idling, stopping".
|
||||
|
||||
## 8. Verify
|
||||
|
||||
- **Plasma:** it appears in the app. The tray must NOT show the "software renderer" icon; if it does, delete `SceneGraphBackend=software` from the user's `kdeglobals` and restart.
|
||||
- **Plain shell:** in a plain container shell as the desktop user, `eglinfo -B` names MobileGL, and `glxinfo -B` from a login shell does too.
|
||||
- **Wayland client:** `timeout 25 mgrun offscreen glmark2-es2-wayland -s 1280x720`. The log must say `presented through linux-dmabuf shared images`.
|
||||
- **Chrome:** `bash scripts/chrome-launch.sh t1` gives windows with correct icons.
|
||||
- **Resize and lock:** BACK toggles the extra-keys bar (resize); lock/unlock resumes the desktop. `rtest.sh` in the scratch dir checks both.
|
||||
- **Unit tests:** `bash scripts/wsl-all.sh` in WSL. NEVER run the DirectVulkan integration tests.
|
||||
|
||||
## 9. Machine-specific values to change
|
||||
|
||||
| Value | Original | Where |
|
||||
|---|---|---|
|
||||
| MobileGL worktree | this skill's worktree | `MGL_WORKTREE` (auto) |
|
||||
| scratch dir | `%LOCALAPPDATA%/Temp/anl` | `ANL_DIR` |
|
||||
| NDK | `<SDK>/ndk/27.2.12479018` | `ANDROID_NDK` |
|
||||
| anland worktree | `%USERPROFILE%/AndroidStudioProjects/anland/.claude/worktrees/mgl-unified` | `ANLAND_WORKTREE` (`kwin-stage.sh`) |
|
||||
| APK id | `com.anland.consumer.mobilegl` | `MGL_PKG` |
|
||||
| container | `arch-kde-mgl` | `MGL_CONTAINER` |
|
||||
| desktop user | `swung0x48` | `MGL_DESKTOP_USER` + `desktop-session.service` |
|
||||
| WSL distro | `archlinux` | `WSL_DISTRO` (`xbuild.sh`, `wsl-*.sh`) |
|
||||
| cross build dir | WSL `~/mgl-xbuild-a64` | `MGL_XBUILD_DIR` |
|
||||
| phone | Lenovo TB321FU, Adreno 750 | nothing: no serial is pinned; with several devices set `ANDROID_SERIAL` |
|
||||
|
||||
**Thermals:** a phone vendor's thermal daemon may throttle charging while the screen is on during long sessions. That is not part of this setup.
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/build-android.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# build-android.sh - build the Android SERVER libMobileGL.so (NDK, arm64-v8a) that the anland APK embeds.
|
||||
# Output: $MGL_WORKTREE/build-android/libMobileGL.so (RelWithDebInfo, unstripped, ~320 MB; keep it for
|
||||
# llvm-addr2line, it is what cyc.sh pushes). Stamp = worktree HEAD, so the container client
|
||||
# (xbuild.sh, same stamp) and the server agree on the build ("compatible wire with different build"
|
||||
# WARN otherwise). Env: MGL_WORKTREE, ANDROID_NDK, BUILD_DIR (default build-android), JOBS (12).
|
||||
# Log level is compiled to INFO. android-30 = the APK's minSdk: thread_local is then the platform's
|
||||
# own TLS rather than the emulated one (which cost 7-12% of an apply thread on the API 26 build).
|
||||
set -euo pipefail
|
||||
. "$(dirname "$0")/env.sh"
|
||||
W=$MGL_WORKTREE
|
||||
B=$W/${BUILD_DIR:-build-android}
|
||||
STAMP=$(git -C "$W" rev-parse HEAD)
|
||||
if [ ! -f "$B/build.ninja" ]; then
|
||||
cmake -S "$W" -B "$B" -G Ninja -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK/build/cmake/android.toolchain.cmake" \
|
||||
-DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-30 -DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DMOBILEGL_PIPE_PUSH=ON -DMOBILEGL_BUILD_DISAGGREGATED=ON -DMOBILEGL_BUILD_DISAGGREGATED_INPROC=ON \
|
||||
-DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF \
|
||||
-DCMAKE_CXX_FLAGS=-DMOBILEGL_LOG_ACTIVE_LEVEL=MOBILEGL_LOG_LEVEL_INFO \
|
||||
-DMOBILEGL_BUILD_STAMP="$STAMP"
|
||||
else
|
||||
cmake "$B" -DMOBILEGL_BUILD_STAMP="$STAMP" >/dev/null
|
||||
fi
|
||||
cmake --build "$B" --target MobileGL -j"${JOBS:-12}"
|
||||
ls -la "$B/libMobileGL.so"
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/chrome-launch.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# chrome-launch.sh <tag> [extra chrome flags...] - (re)start Chrome inside the running Plasma session
|
||||
# through mobilegl-startup.sh's `chrome` mode (ANGLE GLES over MobileGL EGL), client log
|
||||
# /tmp/mgl-chrome-<tag>.client.log, then print its "presented through" line and save a screenshot
|
||||
# to $ANL_DIR/ch-<tag>.png. Kills the running chrome first (pkill -x, never -f). MobileGL is the
|
||||
# container's system-wide vendor (10_mobilegl.json + /etc/mobilegl/client.conf), so only the log is set here.
|
||||
# Env: WAIT (s before checking, default 20), CHANNEL (ct.sh channel, default chrome),
|
||||
# CHROME_GPU (process, the default: the GPU in its own process, which presents
|
||||
# through GBM dma-bufs and is restarted by Chrome after a device loss; in-process opts out -
|
||||
# needs the anland startup script that knows MOBILEGL_CHROME_GPU; in process mode the GPU process and the browser
|
||||
# write the one client log, the GPU process last); CHROME_GPU_SANDBOX=on keeps its GPU sandbox.
|
||||
set -euo pipefail
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/env.sh"
|
||||
TAG=${1:?usage: chrome-launch.sh <tag> [chrome flags...]}; shift
|
||||
bash "$here/ct.sh" "${CHANNEL:-chrome}" <<IN
|
||||
pkill -x chrome; sleep 2
|
||||
U=\$(id -u $MGL_DESKTOP_USER)
|
||||
rm -f /tmp/mgl-chrome-$TAG.client.log /tmp/chrome-$TAG-out.log
|
||||
sudo -u $MGL_DESKTOP_USER env XDG_RUNTIME_DIR=/run/user/\$U WAYLAND_DISPLAY=wayland-0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/\$U/bus MOBILEGL_LOG_FILE_PATH=/tmp/mgl-chrome-$TAG.log MOBILEGL_CHROME_GPU=${CHROME_GPU:-process} MOBILEGL_CHROME_GPU_SANDBOX=${CHROME_GPU_SANDBOX:-off} bash -c 'nohup /opt/mobilegl/bin/mobilegl-startup.sh chrome --restore-last-session $* > /tmp/chrome-$TAG-out.log 2>&1 &'
|
||||
sleep ${WAIT:-20}
|
||||
grep -ah 'presented through' /tmp/mgl-chrome-$TAG.client.log | head -3
|
||||
IN
|
||||
adb_ shell "su -c 'screencap -p $DEV_SHARE/ch-$TAG.png'"
|
||||
adb_ pull "$DEV_SHARE/ch-$TAG.png" "$ANL_DIR/ch-$TAG.png" >/dev/null && echo "screenshot: $ANL_DIR/ch-$TAG.png"
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/chrome-recover.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# chrome-recover.sh <in-process|process> [rounds] - does Chrome repaint after a GPU device loss,
|
||||
# without a restart? Starts Chrome (chrome-launch.sh, CHROME_GPU=<mode>) on an animated WebGL page,
|
||||
# then per round: screenshots (the page must animate), loses the session of Chrome's GPU client with
|
||||
# the server's pid-targeted debug knob (debug.mobilegl.inject_device_lost_pid; the pid as Chrome sees
|
||||
# itself in the container), waits, and screenshots again. A round PASSES when the server latched
|
||||
# that pid's session, the browser process is the same one (no restart), a GPU client is drawing on a
|
||||
# NEW server session, and the screen still animates. Stops early below MIN_BATTERY percent.
|
||||
#
|
||||
# in-process the GPU in the browser process (the startup script's default today): the target is
|
||||
# the browser itself; recovery needs ANGLE to make a new native context.
|
||||
# process the GPU in its own process (MOBILEGL_CHROME_GPU=process): the target is the GPU
|
||||
# process; Chrome restarts it after the loss, so the next round's target is a new pid.
|
||||
#
|
||||
# Deploy first (same MobileGL commit on both sides; see SKILL.md):
|
||||
# bash scripts/build-android.sh && bash scripts/cyc.sh pre # server -> APK, restarts the session
|
||||
# bash scripts/xbuild.sh && bash scripts/xdeploy.sh # client -> container
|
||||
# STARTUP=<anland worktree>/producers/kde/Arch_v5/mobilegl-startup.sh bash scripts/chrome-recover.sh deploy-startup
|
||||
# Env: ROUNDS (default 2), SETTLE (s after the loss, default 8), MIN_BATTERY (default 40),
|
||||
# CHANNEL (ct.sh channel, default crec), TAG (log tag, default crec-<mode>).
|
||||
set -euo pipefail
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/env.sh"
|
||||
MODE=${1:?usage: chrome-recover.sh <in-process|process|deploy-startup> [rounds]}
|
||||
ROUNDS=${2:-${ROUNDS:-2}}
|
||||
SETTLE=${SETTLE:-8}
|
||||
MIN_BATTERY=${MIN_BATTERY:-40}
|
||||
CH=${CHANNEL:-crec}
|
||||
TAG=${TAG:-crec-$MODE}
|
||||
PAGE=/tmp/mgl-spin.html
|
||||
|
||||
ct() { bash "$here/ct.sh" "$CH"; }
|
||||
|
||||
battery() { adb_ shell dumpsys battery | tr -d '\r' | awk '/ level:/ {print $2}'; }
|
||||
check_battery() {
|
||||
local level; level=$(battery)
|
||||
echo "battery: $level%"
|
||||
if [ -n "$level" ] && [ "$level" -lt "$MIN_BATTERY" ]; then
|
||||
echo "STOP: battery below $MIN_BATTERY%"; cleanup; exit 3
|
||||
fi
|
||||
}
|
||||
cleanup() { adb_ shell "su -c 'setprop debug.mobilegl.inject_device_lost_pid 0'" || true; }
|
||||
|
||||
if [ "$MODE" = deploy-startup ]; then
|
||||
# The anland startup script that knows MOBILEGL_CHROME_GPU, installed by rename (a running
|
||||
# session keeps its open copy).
|
||||
src=${STARTUP:?STARTUP=<path to the anland mobilegl-startup.sh>}
|
||||
tr -d '\r' < "$src" > "$ANL_DIR/mobilegl-startup.sh"
|
||||
adb_ push "$ANL_DIR/mobilegl-startup.sh" "$DEV_SHARE/mobilegl-startup.sh.new" >/dev/null
|
||||
ct <<'IN'
|
||||
install -m 755 /run/anland-mobilegl/mobilegl-startup.sh.new /opt/mobilegl/bin/mobilegl-startup.sh.new
|
||||
mv -f /opt/mobilegl/bin/mobilegl-startup.sh.new /opt/mobilegl/bin/mobilegl-startup.sh
|
||||
grep -c MOBILEGL_CHROME_GPU /opt/mobilegl/bin/mobilegl-startup.sh
|
||||
IN
|
||||
exit 0
|
||||
fi
|
||||
case "$MODE" in in-process|process) ;; *) echo "mode must be in-process or process" >&2; exit 2;; esac
|
||||
trap cleanup EXIT
|
||||
|
||||
check_battery
|
||||
adb_ shell "su -c 'sh $DEV_TOOLS/status.sh'" || true
|
||||
cleanup
|
||||
|
||||
# The page: WebGL clears that change colour every frame plus a moving block, so a frame that
|
||||
# reaches the screen changes the screenshot.
|
||||
ct <<IN
|
||||
cat > $PAGE <<'HTML'
|
||||
<!doctype html><html><body style="margin:0;background:#000">
|
||||
<canvas id=c width=640 height=360 style="width:100vw;height:100vh"></canvas>
|
||||
<script>
|
||||
const gl = document.getElementById('c').getContext('webgl');
|
||||
let t = 0, lost = 0;
|
||||
document.getElementById('c').addEventListener('webglcontextlost', e => { e.preventDefault(); lost++; });
|
||||
function frame() {
|
||||
t++;
|
||||
if (gl && !gl.isContextLost()) {
|
||||
gl.clearColor((t % 60) / 60, ((t + 20) % 60) / 60, ((t + 40) % 60) / 60, 1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.enable(gl.SCISSOR_TEST); gl.scissor((t * 7) % 600, 100, 40, 160);
|
||||
gl.clearColor(1, 1, 1, 1); gl.clear(gl.COLOR_BUFFER_BIT); gl.disable(gl.SCISSOR_TEST);
|
||||
}
|
||||
document.title = 'spin ' + t + ' lost ' + lost;
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
frame();
|
||||
</script></body></html>
|
||||
HTML
|
||||
chmod 644 $PAGE
|
||||
IN
|
||||
|
||||
WAIT=${WAIT:-20} CHROME_GPU=$MODE CHANNEL=$CH bash "$here/chrome-launch.sh" "$TAG" --new-window "file://$PAGE"
|
||||
|
||||
# The target pid inside the container: the browser (in-process) or the GPU process.
|
||||
target_pid() {
|
||||
ct <<'IN' | tr -d '\r' | tail -1
|
||||
for p in $(pgrep -x chrome); do
|
||||
args=$(tr '\0' ' ' < /proc/$p/cmdline)
|
||||
case "$args" in
|
||||
*--type=gpu-process*) echo "gpu $p" ;;
|
||||
*--type=*) ;;
|
||||
*) echo "browser $p" ;;
|
||||
esac
|
||||
done | sort > /tmp/mgl-crec-pids
|
||||
cat /tmp/mgl-crec-pids >&2
|
||||
echo "$(awk '/^browser/ {print $2; exit}' /tmp/mgl-crec-pids) $(awk '/^gpu/ {print $2; exit}' /tmp/mgl-crec-pids)"
|
||||
IN
|
||||
}
|
||||
|
||||
pass=0
|
||||
for round in $(seq 1 "$ROUNDS"); do
|
||||
echo "=== round $round/$ROUNDS ($MODE)"
|
||||
check_battery
|
||||
read -r browser gpu < <(target_pid)
|
||||
if [ "$MODE" = process ]; then target=$gpu; else target=$browser; fi
|
||||
echo "browser=$browser gpu=${gpu:-none} target=$target"
|
||||
if [ -z "$target" ]; then echo "FAIL: no target process"; break; fi
|
||||
bash "$here/shot.sh" "$TAG-r$round-pre" 2 1
|
||||
adb_ logcat -c
|
||||
adb_ shell "su -c 'setprop debug.mobilegl.inject_device_lost_pid $target'"
|
||||
sleep "$SETTLE"
|
||||
adb_ shell "su -c 'setprop debug.mobilegl.inject_device_lost_pid 0'"
|
||||
bash "$here/shot.sh" "$TAG-r$round-post" 3 1 | tee "$ANL_DIR/$TAG-r$round-post.md5"
|
||||
latched=$(adb_ logcat -d | grep -c "injected:pid=$target" || true)
|
||||
started=$(adb_ logcat -d | grep -c "session #[0-9]* started" || true)
|
||||
adb_ logcat -d | grep -E "inject_device_lost_pid|SessionLatch|session #[0-9]+ (started|pid)" | cut -c1-220 | head -12 || true
|
||||
read -r browser2 gpu2 < <(target_pid)
|
||||
echo "after: browser=$browser2 gpu=${gpu2:-none}"
|
||||
ct <<IN || true
|
||||
echo "--- client log ($TAG):"; grep -aE "DEVICE LOST|RECOVERED|GL_UNKNOWN_CONTEXT_RESET|failed to make|session #" /tmp/mgl-chrome-$TAG.client.log | tail -8
|
||||
echo "--- chrome stderr:"; grep -aiE "context lost|GPU process|make.*current|lost" /tmp/chrome-$TAG-out.log | tail -8
|
||||
IN
|
||||
adb_ logcat -b crash -d | tail -5
|
||||
hashes=$(awk '{print $1}' "$ANL_DIR/$TAG-r$round-post.md5" | sort -u | wc -l)
|
||||
verdict=PASS
|
||||
[ "$latched" -ge 1 ] || { echo " - the server never latched pid $target"; verdict=FAIL; }
|
||||
[ "$browser2" = "$browser" ] || { echo " - the browser process changed ($browser -> $browser2): Chrome restarted"; verdict=FAIL; }
|
||||
[ "$started" -ge 1 ] || { echo " - no new server session started after the loss"; verdict=FAIL; }
|
||||
if [ "$MODE" = process ]; then
|
||||
[ -n "$gpu2" ] && [ "$gpu2" != "$gpu" ] || { echo " - no new GPU process"; verdict=FAIL; }
|
||||
fi
|
||||
[ "$hashes" -ge 2 ] || { echo " - the screen does not change after the loss (frozen)"; verdict=FAIL; }
|
||||
echo "round $round: $verdict"
|
||||
[ "$verdict" = PASS ] && pass=$((pass + 1))
|
||||
[ "$verdict" = PASS ] || break
|
||||
done
|
||||
echo "=== $pass/$ROUNDS rounds passed ($MODE); screenshots in $ANL_DIR/$TAG-r*-*.png"
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/container/kwprep.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# kwprep.sh - (container, as root) bring the live KWin tree /root/mobilegl-build/kwin-6.7.4 in line with
|
||||
# anland's kwin.patch + anland backend sources, touching ONLY files whose content changed (keeps the
|
||||
# ninja build incremental). It re-applies the whole patch to a pristine copy of KWin's src/
|
||||
# (/root/gbm-dev/kwin-a/src) in /root/kp and copies every patched file that differs into the live tree,
|
||||
# then copies changed backend files. Build + install afterwards with anland's sync-build-kwin.sh.
|
||||
#
|
||||
# Input: /root/kwsync.tgz, a tar made in the anland worktree's producers/kde directory:
|
||||
# tar czf kwsync.tgz Arch_v5/kwin.patch Arch_v5/mobilegl-startup.sh anland_backend_Arch_v5/src/backends/anland/
|
||||
# and pushed to the container's /root (e.g. via /mnt/Droidspaces/arch-kde-mgl/root/ on the Android side).
|
||||
# The pristine tree /root/gbm-dev/kwin-a/src was set up by hand earlier (unverified that it is exactly
|
||||
# upstream 6.7.4); patch hunks that fail are reported by `patch`.
|
||||
set -e
|
||||
rm -rf /root/kwsync && mkdir -p /root/kwsync && tar xzf /root/kwsync.tgz -C /root/kwsync
|
||||
P=/root/kwsync/Arch_v5/kwin.patch
|
||||
rm -rf /root/kp && mkdir -p /root/kp && cp -a /root/gbm-dev/kwin-a/src /root/kp/
|
||||
cd /root/kp && patch -p1 -s < $P
|
||||
LIVE=/root/mobilegl-build/kwin-6.7.4
|
||||
FILES=$(grep -E '^\+\+\+ b/' $P | sed 's|^+++ b/||' | awk '{print $1}' | sort -u)
|
||||
for f in $FILES; do
|
||||
if [ -f "/root/kp/$f" ] && ! cmp -s "/root/kp/$f" "$LIVE/$f"; then cp "/root/kp/$f" "$LIVE/$f"; echo "updated $f"; fi
|
||||
done
|
||||
for f in /root/kwsync/anland_backend_Arch_v5/src/backends/anland/*; do
|
||||
# libdisplay_producer is a symlink in git; a Windows checkout turns it into a small text file. The live
|
||||
# tree has the real directory (repo-root libdisplay_producer/), which neither script syncs.
|
||||
[ -f "$f" ] && [ "$(basename $f)" != libdisplay_producer ] || continue
|
||||
b=$(basename $f); if ! cmp -s "$f" "$LIVE/src/backends/anland/$b"; then cp "$f" "$LIVE/src/backends/anland/$b"; echo "updated anland/$b"; fi
|
||||
done
|
||||
echo PREP_DONE
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# mgrun <surface: offscreen|server> <command...> - (container, as root) run one MobileGL client as the
|
||||
# desktop user inside the running Plasma session. Installed as /usr/local/bin/mgrun (push-tools.sh).
|
||||
# mgrun offscreen glmark2-es2-wayland -s 1280x720 # Wayland client, presents through KWin
|
||||
# mgrun offscreen eglinfo -B ; mgrun offscreen glxinfo -B # glxinfo goes through Xwayland + GLX vendor
|
||||
# "server" is only for a client that owns the Android Surface itself (KWin does); everything else is
|
||||
# offscreen. MobileGL is the container's system-wide vendor, so nothing selects it here: EGL comes from
|
||||
# /usr/share/glvnd/egl_vendor.d/10_mobilegl.json, transport/endpoint from /etc/mobilegl/client.conf,
|
||||
# the backend from /etc/mobilegl/backend (must match the server), and the GLX vendor name / GBM backend
|
||||
# from /etc/profile.d/mobilegl.sh (sourced by the login shell below). Client log:
|
||||
# ${MGLOG:-/tmp/mgl-run.log} -> the library writes /tmp/mgl-run.client.log (truncated per run; give
|
||||
# concurrent clients different MGLOG values). Env: MGLOG, DESKTOP_USER (swung0x48).
|
||||
S=$1; shift
|
||||
DU=${DESKTOP_USER:-swung0x48}
|
||||
U=$(id -u "$DU")
|
||||
cd /tmp
|
||||
exec sudo -u "$DU" env XDG_RUNTIME_DIR=/run/user/$U WAYLAND_DISPLAY=wayland-0 DISPLAY=:0 \
|
||||
XAUTHORITY=$(ls /run/user/$U/xauth_* 2>/dev/null | head -1) \
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$U/bus QT_QPA_PLATFORM=wayland \
|
||||
MOBILEGL_IPC_SURFACE=$S MOBILEGL_LOG_FILE_PATH=${MGLOG:-/tmp/mgl-run.log} \
|
||||
bash -lc 'exec "$@"' mgrun "$@"
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/ct.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# ct.sh <channel> [script-file] - run a bash script as ROOT inside the container (login shell),
|
||||
# reading the script from the file or from stdin. Generic form of the old c.sh/c2.sh/c3.sh.
|
||||
#
|
||||
# printf '%s\n' 'ls -la /tmp/*.log' | bash ct.sh mychan
|
||||
# bash ct.sh mychan ./something.cmd
|
||||
#
|
||||
# How: the script is written to $ANL_DIR/<channel>.cmd, pushed to $DEV_TOOLS/<channel>.cmd together with
|
||||
# a one-line <channel>.sh that does `droidspaces --name=<container> run bash -lc "$(cat <channel>.cmd)"`,
|
||||
# and that runs under `su`. Quoting through adb + su + droidspaces is thereby avoided entirely.
|
||||
#
|
||||
# Pick a channel name of your own: other sessions on the same phone use c, c2, c3, xd, xr, gbm...;
|
||||
# writing to their channel files races with them. CR characters are stripped (Windows checkouts).
|
||||
# Inside, run user-side commands with `sudo -u <desktop user> env XDG_RUNTIME_DIR=/run/user/<uid> ...`.
|
||||
# Never `pkill -f <word>` in a channel script: the `bash -lc "<script>"` process carries the whole
|
||||
# script on its command line and kills itself. Use `pkill -x <name>`.
|
||||
set -euo pipefail
|
||||
. "$(dirname "$0")/env.sh"
|
||||
ch=${1:?usage: ct.sh <channel> [script-file]}
|
||||
src=${2:--}
|
||||
if [ "$src" = - ]; then tr -d '\r' > "$ANL_DIR/$ch.cmd"; else tr -d '\r' < "$src" > "$ANL_DIR/$ch.cmd"; fi
|
||||
printf '%s\n' "$DS_BIN --name=$MGL_CONTAINER run bash -lc \"\$(cat $DEV_TOOLS/$ch.cmd)\"" > "$ANL_DIR/$ch.sh"
|
||||
adb_ shell "mkdir -p $DEV_TOOLS"
|
||||
adb_ push "$ANL_DIR/$ch.cmd" "$DEV_TOOLS/$ch.cmd" >/dev/null
|
||||
adb_ push "$ANL_DIR/$ch.sh" "$DEV_TOOLS/$ch.sh" >/dev/null
|
||||
adb_ shell "su -c 'sh $DEV_TOOLS/$ch.sh'"
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/cyc.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# cyc.sh <shot-name> - one server iteration: clear Plasma's sticky software-renderer key, push the
|
||||
# NDK-built server lib, swap it into the installed APK (device/swap.sh), restart app + Plasma session
|
||||
# (device/run-plasma.sh, ~90 s), screenshot to $ANL_DIR/<shot-name>.png, print fatal logcat lines.
|
||||
# Env: SERVER_LIB (default $MGL_WORKTREE/build-android/libMobileGL.so), WAIT (s after the session
|
||||
# restart, default 15), N (logcat lines, default 20), CHANNEL (ct.sh channel, default cyc).
|
||||
# Needs device/swap.sh + device/run-plasma.sh in $DEV_TOOLS (push-tools.sh). RESTARTS THE SESSION.
|
||||
set -euo pipefail
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/env.sh"
|
||||
shot=${1:?usage: cyc.sh <shot-name>}
|
||||
LIB=${SERVER_LIB:-$MGL_WORKTREE/build-android/libMobileGL.so}
|
||||
printf '%s\n' "sudo -u $MGL_DESKTOP_USER kwriteconfig6 --file kdeglobals --group QtQuickRendererSettings --key SceneGraphBackend --delete; rm -f /tmp/mgl-plasmashell.client.log" \
|
||||
| bash "$here/ct.sh" "${CHANNEL:-cyc}"
|
||||
adb_ push "$LIB" "$DEV_SHARE/libMobileGL.new.so" >/dev/null
|
||||
adb_ logcat -c
|
||||
adb_ shell "su -c 'sh $DEV_TOOLS/swap.sh >/dev/null && sh $DEV_TOOLS/run-plasma.sh >/dev/null 2>&1; sleep ${WAIT:-15}; screencap -p $DEV_SHARE/$shot.png'"
|
||||
adb_ pull "$DEV_SHARE/$shot.png" "$ANL_DIR/$shot.png" >/dev/null && echo "screenshot: $ANL_DIR/$shot.png"
|
||||
adb_ logcat -d | grep -E "Fatal signal|FATAL|Abort message|Magma wire buffer|E MobileGL" | cut -c1-260 | head -"${N:-20}" || true
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/desktop-verify.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# desktop-verify.sh <phase>... - check that the MobileGL desktop starts, idles and stops by itself
|
||||
# (anland app: foreground service + mobilegl-desktop.sh; container: desktop-session.service).
|
||||
# Phases run in the order given; screenshots go to $ANL_DIR/dv-<phase>.png.
|
||||
#
|
||||
# push install device/*.sh into $DEV_TOOLS (push-tools.sh's device half)
|
||||
# reset everything down: app force-stopped (its service with it), container stopped,
|
||||
# experiment display daemon killed - the state after a phone reboot
|
||||
# cold reset, then open the app like its icon does and time each piece (desktop-timeline.sh)
|
||||
# open open the app without resetting (after a reboot: the reboot was the reset)
|
||||
# chrome (re)start Chrome in the session on an animated WebGL page (kills a running Chrome)
|
||||
# idle CPU/GPU for visible, hidden (HOME), locked (screen off), visible again (measure.sh)
|
||||
# reopen swipe the app away (remove its task), check KWin survives, open it again, time it
|
||||
# stop Stop desktop from the notification (the service's STOP action) and check it all ends
|
||||
# rtest lock/unlock + window resize regression ($ANL_DIR/rtest.sh)
|
||||
# Env: SECS (idle window, default 20), URL (chrome page).
|
||||
set -euo pipefail
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/env.sh"
|
||||
SECS=${SECS:-20}
|
||||
URL=${URL:-https://webglsamples.org/aquarium/aquarium.html}
|
||||
SOCK=$DEV_SHARE/display.sock
|
||||
su_() { adb_ shell "su -c '$*'"; }
|
||||
shot() {
|
||||
su_ "screencap -p $DEV_SHARE/dv-$1.png"
|
||||
adb_ pull "$DEV_SHARE/dv-$1.png" "$ANL_DIR/dv-$1.png" > /dev/null && echo "screenshot $ANL_DIR/dv-$1.png"
|
||||
}
|
||||
kwin_log() { # the anland backend's lines since <time>
|
||||
printf '%s\n' "journalctl --no-pager --since '$1' 2>/dev/null | grep -a -E 'viewer (gone|back)|consumer (reconnected|disconnected)|failed to connect' | tail -${2:-8}" |
|
||||
bash "$here/ct.sh" dv 2>/dev/null | grep -v pushed || true
|
||||
}
|
||||
task_id() {
|
||||
su_ "am stack list" | tr -d '\r' | grep -m1 "$MGL_PKG/" | sed -n 's/.*taskId=\([0-9]*\).*/\1/p'
|
||||
}
|
||||
wake() {
|
||||
su_ "input keyevent KEYCODE_WAKEUP"; sleep 1; su_ "wm dismiss-keyguard"; sleep 2
|
||||
}
|
||||
|
||||
for phase in "$@"; do
|
||||
echo "=== $phase"
|
||||
case $phase in
|
||||
push)
|
||||
for f in "$here"/device/*.sh; do
|
||||
tr -d '\r' < "$f" > "$ANL_DIR/push.tmp"
|
||||
adb_ push "$ANL_DIR/push.tmp" "$DEV_TOOLS/$(basename "$f")" > /dev/null
|
||||
done
|
||||
rm -f "$ANL_DIR/push.tmp"; echo "device scripts in $DEV_TOOLS" ;;
|
||||
reset)
|
||||
su_ "sh $DEV_TOOLS/desktop-reset.sh" ;;
|
||||
cold)
|
||||
bash "$0" reset
|
||||
wake
|
||||
su_ "sh $DEV_TOOLS/desktop-timeline.sh 240"
|
||||
sleep 3; shot cold ;;
|
||||
open)
|
||||
wake
|
||||
su_ "sh $DEV_TOOLS/desktop-timeline.sh 300"
|
||||
sleep 3; shot open ;;
|
||||
chrome)
|
||||
WAIT=15 bash "$here/chrome-launch.sh" dv "$URL" ;;
|
||||
idle)
|
||||
su_ "sh $DEV_TOOLS/measure.sh visible $SECS"
|
||||
t=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
|
||||
su_ "input keyevent KEYCODE_HOME"; sleep 4
|
||||
su_ "sh $DEV_TOOLS/measure.sh hidden $SECS"
|
||||
su_ "am start -n $MGL_PKG/com.anland.consumer.MainActivity >/dev/null"; sleep 5
|
||||
su_ "input keyevent KEYCODE_SLEEP"; sleep 4
|
||||
su_ "sh $DEV_TOOLS/measure.sh locked $SECS"
|
||||
wake
|
||||
su_ "am start -n $MGL_PKG/com.anland.consumer.MainActivity >/dev/null"; sleep 5
|
||||
su_ "sh $DEV_TOOLS/measure.sh visible2 $SECS"
|
||||
shot idle
|
||||
kwin_log "$t" 12 ;;
|
||||
reopen)
|
||||
t=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
|
||||
id=$(task_id); echo "removing task $id"
|
||||
[ -n "$id" ] && su_ "am stack remove $id"
|
||||
sleep 8
|
||||
su_ "echo server: \$(pidof $MGL_PKG:mobilegl) ui: \$(pidof $MGL_PKG) kwin: \$(pidof kwin_wayland) plasmashell: \$(pidof plasmashell)"
|
||||
su_ "sh $DEV_TOOLS/measure.sh closed 10"
|
||||
su_ "sh $DEV_TOOLS/desktop-timeline.sh 120"
|
||||
sleep 3; shot reopen
|
||||
kwin_log "$t" ;;
|
||||
stop)
|
||||
su_ "am startservice -n $MGL_PKG/com.anland.consumer.MobileGLWorker -a com.anland.consumer.mobilegl.STOP" || true
|
||||
sleep 25
|
||||
su_ "echo server: \$(pidof $MGL_PKG:mobilegl) ui: \$(pidof $MGL_PKG); $DS_BIN show"
|
||||
su_ "tail -3 $DEV_SHARE/desktop.log" ;;
|
||||
rtest)
|
||||
bash "$ANL_DIR/rtest.sh" dv ;;
|
||||
*)
|
||||
echo "unknown phase $phase" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
@@ -0,0 +1,15 @@
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/device/bringup.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# bringup.sh - (Android root shell) bring the MobileGL desktop up after a phone reboot. There is
|
||||
# nothing left to do by hand: opening the anland app (its launcher icon, or this am start) starts
|
||||
# the display daemon, the MobileGL server, the container and its Plasma session. Prints the timeline.
|
||||
# Run: su -c "sh /data/local/tmp/anl/bringup.sh" Env: as desktop-timeline.sh.
|
||||
input keyevent KEYCODE_WAKEUP
|
||||
wm dismiss-keyguard
|
||||
sh "$(dirname "$0")/desktop-timeline.sh" 300
|
||||
@@ -0,0 +1,25 @@
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/device/desktop-reset.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# desktop-reset.sh - (Android root shell) take the MobileGL desktop fully down, as after a phone
|
||||
# reboot: the app force-stopped (its foreground service and server with it), the container
|
||||
# stopped, the experiment display daemon killed. Leaves the original anland daemon alone.
|
||||
# Run: su -c "sh /data/local/tmp/anl/desktop-reset.sh" Env: PKG, CONTAINER, SOCK, DS.
|
||||
P=${PKG:-com.anland.consumer.mobilegl}
|
||||
C=${CONTAINER:-arch-kde-mgl}
|
||||
SOCK=${SOCK:-/data/local/tmp/anland-mobilegl/display.sock}
|
||||
D=${DS:-/data/local/Droidspaces/bin/droidspaces}
|
||||
am force-stop $P
|
||||
$D --name=$C stop > /dev/null 2>&1
|
||||
for p in $(pidof display_daemon); do
|
||||
tr '\0' ' ' < /proc/$p/cmdline | grep -qF " $SOCK " && kill $p
|
||||
done
|
||||
rm -f $SOCK
|
||||
sleep 2
|
||||
echo "app: $(pidof $P) server: $(pidof $P:mobilegl) daemon: $(ls $SOCK 2>/dev/null)"
|
||||
$D show
|
||||
@@ -0,0 +1,38 @@
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/device/desktop-timeline.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# desktop-timeline.sh [timeout-s] - (Android root shell) open the anland app the way its launcher
|
||||
# icon does (am start, no extras) and print when each piece of the MobileGL desktop comes up,
|
||||
# in seconds after the open: display daemon socket, the app's MobileGL server listening, the
|
||||
# container, kwin_wayland, plasmashell, and the splash (ksplashqml) gone = desktop on screen.
|
||||
# Pieces that already run show 0.0. Read-only apart from the am start.
|
||||
# Run: su -c "sh /data/local/tmp/anl/desktop-timeline.sh 180" Env: PKG, CONTAINER, SOCK, DS.
|
||||
T=${1:-180}
|
||||
P=${PKG:-com.anland.consumer.mobilegl}
|
||||
C=${CONTAINER:-arch-kde-mgl}
|
||||
SOCK=${SOCK:-/data/local/tmp/anland-mobilegl/display.sock}
|
||||
D=${DS:-/data/local/Droidspaces/bin/droidspaces}
|
||||
now() { awk '{print $1}' /proc/uptime; }
|
||||
since() { awk -v a="$1" -v b="$(now)" 'BEGIN{printf "%.1f", b - a}'; }
|
||||
listening() { grep -q '00010000 0001 01 [0-9]* @anland-mobilegl$' /proc/net/unix; }
|
||||
t0=$(now)
|
||||
am start -n $P/com.anland.consumer.MainActivity > /dev/null
|
||||
for k in daemon server container kwin shell splash_seen shown; do eval "t_$k="; done
|
||||
while :; do
|
||||
[ -z "$t_daemon" ] && [ -S "$SOCK" ] && t_daemon=$(since $t0) && echo "daemon socket +${t_daemon}s"
|
||||
[ -z "$t_server" ] && listening && t_server=$(since $t0) && echo "server listening +${t_server}s"
|
||||
[ -z "$t_container" ] && [ -n "$($D --name=$C pid 2>/dev/null | tr -dc 0-9)" ] && t_container=$(since $t0) && echo "container up +${t_container}s"
|
||||
[ -z "$t_kwin" ] && pidof kwin_wayland > /dev/null && t_kwin=$(since $t0) && echo "kwin_wayland +${t_kwin}s"
|
||||
[ -z "$t_shell" ] && pidof plasmashell > /dev/null && t_shell=$(since $t0) && echo "plasmashell +${t_shell}s"
|
||||
[ -z "$t_splash_seen" ] && pidof ksplashqml > /dev/null && t_splash_seen=1
|
||||
if [ -n "$t_shell" ] && [ -z "$t_shown" ] && ! pidof ksplashqml > /dev/null; then
|
||||
t_shown=$(since $t0); echo "splash gone +${t_shown}s (desktop on screen)"; break
|
||||
fi
|
||||
[ "$(since $t0 | cut -d. -f1)" -ge "$T" ] && { echo "TIMEOUT after ${T}s"; break; }
|
||||
sleep 0.3
|
||||
done
|
||||
@@ -0,0 +1,55 @@
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/device/measure.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# measure.sh <label> [seconds] - (Android root shell) CPU and GPU load of the MobileGL desktop over a
|
||||
# window: % of ONE core (so 800 = all 8 cores) for the whole phone, KWin (+Xwayland), plasmashell,
|
||||
# Chrome (all its processes), the app's :mobilegl server and its UI process, plus the mean of the
|
||||
# kgsl gpu_busy_percentage samples (1/s). The container's processes are visible from Android's
|
||||
# /proc (Droidspaces only gives them their own pid namespace).
|
||||
# Run: su -c "sh /data/local/tmp/anl/measure.sh visible 20" Env: PKG.
|
||||
L=${1:-sample}
|
||||
N=${2:-20}
|
||||
P=${PKG:-com.anland.consumer.mobilegl}
|
||||
GPU=/sys/class/kgsl/kgsl-3d0/gpu_busy_percentage
|
||||
|
||||
pids_named() { # exact process names (comm or argv0 basename)
|
||||
for n in "$@"; do pidof "$n"; done
|
||||
}
|
||||
pids_matching() { # argv contains the word
|
||||
for d in /proc/[0-9]*; do
|
||||
{ tr '\0' ' ' < $d/cmdline; } 2>/dev/null | grep -q "$1" && echo ${d#/proc/}
|
||||
done
|
||||
}
|
||||
ticks() { # utime+stime of the pids, in clock ticks
|
||||
t=0
|
||||
for p in "$@"; do
|
||||
[ -r /proc/$p/stat ] || continue
|
||||
v=$(sed 's/^.*) //' /proc/$p/stat | awk '{print $12 + $13}')
|
||||
t=$((t + v))
|
||||
done
|
||||
echo $t
|
||||
}
|
||||
total() { awk '/^cpu /{print $2+$3+$4+$6+$7+$8}' /proc/stat; }
|
||||
|
||||
KW="$(pids_named kwin_wayland Xwayland)"
|
||||
SH="$(pids_named plasmashell)"
|
||||
CH="$(pids_matching /opt/google/chrome/)"
|
||||
SV="$(pidof $P:mobilegl)"
|
||||
UI="$(pidof $P)"
|
||||
|
||||
a0=$(total); k0=$(ticks $KW); s0=$(ticks $SH); c0=$(ticks $CH); v0=$(ticks $SV); u0=$(ticks $UI)
|
||||
g=0; i=0
|
||||
while [ $i -lt $N ]; do
|
||||
sleep 1
|
||||
b=$(cat $GPU 2>/dev/null | tr -dc '0-9'); g=$((g + ${b:-0})); i=$((i + 1))
|
||||
done
|
||||
a1=$(total); k1=$(ticks $KW); s1=$(ticks $SH); c1=$(ticks $CH); v1=$(ticks $SV); u1=$(ticks $UI)
|
||||
# CLK_TCK is 100: ticks per second == percent of one core.
|
||||
pct() { echo $(( ($2 - $1) / N )); }
|
||||
printf '%-10s %3ss phone %4s%% kwin %3s%% plasmashell %3s%% chrome %3s%% server %3s%% app-ui %3s%% gpu %3s%%\n' \
|
||||
"$L" "$N" "$(pct $a0 $a1)" "$(pct $k0 $k1)" "$(pct $s0 $s1)" "$(pct $c0 $c1)" "$(pct $v0 $v1)" "$(pct $u0 $u1)" "$((g / N))"
|
||||
@@ -0,0 +1,28 @@
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/device/restart-daemon.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# restart-daemon.sh - (Android root shell) (re)start the EXPERIMENT's anland display daemon on $SOCK.
|
||||
# It is not started at boot (the anland-daemon module's service.sh only starts the original daemon on
|
||||
# /data/local/tmp/display_daemon.sock, which must be left alone). Without it KWin logs
|
||||
# "failed to connect to display daemon at /run/anland-mobilegl/display.sock" and the session dies in ~3 s.
|
||||
# The container sees $SOCK as /run/anland-mobilegl/display.sock (bind mount in container.config);
|
||||
# uid 1000 in the container must connect, hence 777 dir / 666 socket.
|
||||
# Env: SOCK, DAEMON (/data/adb/modules/anland-daemon/display_daemon).
|
||||
SOCK=${SOCK:-/data/local/tmp/anland-mobilegl/display.sock}
|
||||
DAEMON=${DAEMON:-/data/adb/modules/anland-daemon/display_daemon}
|
||||
DIR=$(dirname $SOCK)
|
||||
for p in $(ps -A -o PID,ARGS | grep "display_daemon $SOCK" | grep -v grep | awk '{print $1}'); do kill $p; done
|
||||
sleep 1
|
||||
rm -f $SOCK
|
||||
mkdir -p $DIR
|
||||
nohup $DAEMON $SOCK > $DIR/display-daemon.log 2>&1 &
|
||||
sleep 1
|
||||
chmod 777 $DIR
|
||||
chmod 666 $SOCK
|
||||
echo "daemon restarted:"
|
||||
ps -A -o PID,ARGS | grep "display_daemon $SOCK" | grep -v grep
|
||||
@@ -0,0 +1,35 @@
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/device/run-plasma.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# run-plasma.sh - (Android root shell) DEVELOPER restart of the whole MobileGL desktop, e.g. after a
|
||||
# server, client or KWin rebuild: ends the Plasma session (and the user's logind session, which takes
|
||||
# a while to wind down), then restarts the anland app; opening it brings up the server, starts
|
||||
# desktop-session.service again and the session waits for the server. Normal use needs none of this:
|
||||
# opening the app is enough. ~60 s. Prints the unit status, plasma user units and recent coredumps.
|
||||
# Run: su -c "sh /data/local/tmp/anl/run-plasma.sh"
|
||||
# Env: PKG, CONTAINER, DESKTOP_USER (swung0x48), DS (droidspaces binary).
|
||||
D=${DS:-/data/local/Droidspaces/bin/droidspaces}
|
||||
P=${PKG:-com.anland.consumer.mobilegl}
|
||||
C=${CONTAINER:-arch-kde-mgl}
|
||||
U=${DESKTOP_USER:-swung0x48}
|
||||
$D --name=$C run bash -lc "
|
||||
systemctl stop desktop-session.service 2>/dev/null
|
||||
loginctl terminate-user $U 2>/dev/null; sleep 30
|
||||
systemctl reset-failed desktop-session.service
|
||||
rm -f /tmp/mobilegl-compositor.log
|
||||
date +%s > /tmp/plasma-start
|
||||
"
|
||||
am force-stop $P
|
||||
am start -n $P/com.anland.consumer.MainActivity > /dev/null
|
||||
sleep 30
|
||||
$D --name=$C run bash -lc "
|
||||
systemctl --no-pager status desktop-session.service | head -8
|
||||
UID_=\$(id -u $U)
|
||||
sudo -u $U XDG_RUNTIME_DIR=/run/user/\$UID_ systemctl --user --no-pager list-units --all 'plasma*' 2>&1 | head -30
|
||||
coredumpctl --no-pager list --since '-2min' 2>&1 | tail -5
|
||||
"
|
||||
@@ -0,0 +1,21 @@
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/device/status.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# status.sh - (Android root shell) READ-ONLY health check of the whole stack.
|
||||
# Run: su -c "sh /data/local/tmp/anl/status.sh" Env: PKG, CONTAINER, DS, SOCK.
|
||||
D=${DS:-/data/local/Droidspaces/bin/droidspaces}
|
||||
P=${PKG:-com.anland.consumer.mobilegl}
|
||||
C=${CONTAINER:-arch-kde-mgl}
|
||||
SOCK=${SOCK:-/data/local/tmp/anland-mobilegl/display.sock}
|
||||
echo "battery: $(dumpsys battery | grep ' level:' | tr -d ' ') $(dumpsys power | grep -o 'mStayOn=[a-z]*')"
|
||||
echo "backend: app setting=$(cat /data/data/$P/files/mobilegl-backend 2>/dev/null || echo 'DirectGLES (default)') published=$(cat ${SOCK%/*}/backend 2>/dev/null) container /etc/mobilegl/backend=$(cat /mnt/Droidspaces/$C/etc/mobilegl/backend 2>/dev/null) prop override=$(getprop debug.mobilegl.backend)"
|
||||
echo "desktop service log: $(tail -1 ${SOCK%/*}/desktop.log 2>/dev/null)"
|
||||
echo "app pid: $(pidof $P) server (:mobilegl) pid: $(pidof $P:mobilegl)"
|
||||
ps -A -o PID,ARGS | grep "display_daemon $SOCK" | grep -v grep || echo "experiment display daemon NOT running"
|
||||
$D show
|
||||
$D --name=$C run bash -lc 'echo "desktop-session: $(systemctl is-active desktop-session.service)"; pgrep -a -x kwin_wayland; pgrep -a -x plasmashell; grep -A1 QtQuickRendererSettings /home/*/.config/kdeglobals 2>/dev/null || echo "kdeglobals: no sticky software renderer"'
|
||||
@@ -0,0 +1,25 @@
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/device/swap.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# swap.sh - (Android root shell) replace the server libMobileGL.so inside the INSTALLED anland APK with
|
||||
# $SHARE/libMobileGL.new.so, keeping owner, mode and SELinux context; force-stops the app. The first
|
||||
# run backs the APK's original lib up to $SHARE/libMobileGL.so.orig. No re-signing/reinstall (that
|
||||
# would change the app UID and lose its root grant). Run: su -c "sh /data/local/tmp/anl/swap.sh"
|
||||
# Env: PKG (com.anland.consumer.mobilegl), SHARE (/data/local/tmp/anland-mobilegl).
|
||||
set -e
|
||||
P=${PKG:-com.anland.consumer.mobilegl}
|
||||
SHARE=${SHARE:-/data/local/tmp/anland-mobilegl}
|
||||
D=$(dirname $(pm path $P | sed 's/package://'))/lib/arm64
|
||||
ls -laZ $D
|
||||
[ -f $SHARE/libMobileGL.so.orig ] || cp $D/libMobileGL.so $SHARE/libMobileGL.so.orig
|
||||
am force-stop $P
|
||||
CTX=$(ls -Z $D/libMobileGL.so | awk '{print $1}')
|
||||
OWN=$(stat -c %u:%g $D/libMobileGL.so)
|
||||
cp $SHARE/libMobileGL.new.so $D/libMobileGL.so
|
||||
chown $OWN $D/libMobileGL.so; chmod 755 $D/libMobileGL.so; chcon $CTX $D/libMobileGL.so
|
||||
ls -laZ $D/libMobileGL.so
|
||||
@@ -0,0 +1,25 @@
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/device/switch.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# switch.sh <DirectGLES|DirectVulkan> - (Android root shell) change the MobileGL backend the anland
|
||||
# app's server runs (Espryt = DirectGLES, Magma = DirectVulkan). The choice is the app's setting
|
||||
# (Settings > Connection > MobileGL desktop, saved in the app's files/mobilegl-backend); this does
|
||||
# what a user does there: save it, Stop desktop, open the app again. The new server publishes the
|
||||
# backend and the container's session copies it to /etc/mobilegl/backend before it starts, so
|
||||
# server and clients agree. ~30-60 s. No setprop: debug.mobilegl.backend, if set, still overrides
|
||||
# the setting until the next reboot - this clears it.
|
||||
# Env: PKG, SOCK.
|
||||
B=${1:?usage: switch.sh DirectGLES|DirectVulkan}
|
||||
P=${PKG:-com.anland.consumer.mobilegl}
|
||||
[ -n "$(getprop debug.mobilegl.backend)" ] && setprop debug.mobilegl.backend ""
|
||||
if pidof $P:mobilegl > /dev/null; then
|
||||
am startservice -n $P/com.anland.consumer.MobileGLWorker -a com.anland.consumer.mobilegl.STOP > /dev/null
|
||||
i=0; while pidof $P:mobilegl > /dev/null && [ $i -lt 60 ]; do sleep 1; i=$((i + 1)); done
|
||||
fi
|
||||
am start -n $P/com.anland.consumer.MainActivity --es mobilegl_backend $B > /dev/null
|
||||
echo "backend $B saved; the desktop is starting (watch: sh $(dirname $0)/status.sh)"
|
||||
@@ -0,0 +1,16 @@
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/device/unlock.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# unlock.sh - (Android root shell) ask logind to unlock the desktop user's session. UNVERIFIED that this
|
||||
# helps here: the handoff notes this logind session type does not support lock-session (a real lock
|
||||
# screen then needs the password). Prevention: [Daemon] Autolock=false in ~/.config/kscreenlockerrc.
|
||||
# Env: DS, CONTAINER, DESKTOP_USER.
|
||||
D=${DS:-/data/local/Droidspaces/bin/droidspaces}
|
||||
C=${CONTAINER:-arch-kde-mgl}
|
||||
U=${DESKTOP_USER:-swung0x48}
|
||||
$D --name=$C run bash -lc "S=\$(loginctl list-sessions --no-legend | awk '\$3==\"$U\"{print \$1}' | head -1); loginctl unlock-session \$S; echo unlocked \$S"
|
||||
@@ -0,0 +1,55 @@
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/device/x11-measure.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# x11-measure.sh <label> <app-process-name> [seconds] - (Android root shell) CPU of the X11 path while
|
||||
# an X11 app runs: % of ONE core (800 = all 8 cores) for the whole phone, the app, Xwayland,
|
||||
# kwin_wayland, the app's :mobilegl server; mean kgsl gpu_busy_percentage; and the number of dma-buf
|
||||
# descriptors Xwayland and KWin hold (window pixmaps / client buffers as dma-bufs). Refuses to run
|
||||
# below 40% battery: the phone drains faster under load than USB charges it.
|
||||
# Run: su -c "sh /data/local/tmp/anl/x11-measure.sh glamor glxgears 10" Env: PKG, MIN_BATTERY.
|
||||
L=${1:-sample}
|
||||
APP=${2:-glxgears}
|
||||
N=${3:-10}
|
||||
P=${PKG:-com.anland.consumer.mobilegl}
|
||||
GPU=/sys/class/kgsl/kgsl-3d0/gpu_busy_percentage
|
||||
level=$(dumpsys battery | sed -n 's/^ *level: *//p')
|
||||
if [ "${level:-0}" -lt "${MIN_BATTERY:-40}" ]; then
|
||||
echo "battery ${level}% < ${MIN_BATTERY:-40}%: not loading the phone; charge first" >&2
|
||||
exit 3
|
||||
fi
|
||||
ticks() {
|
||||
t=0
|
||||
for p in "$@"; do
|
||||
[ -r /proc/$p/stat ] || continue
|
||||
v=$(sed 's/^.*) //' /proc/$p/stat | awk '{print $12 + $13}')
|
||||
t=$((t + v))
|
||||
done
|
||||
echo $t
|
||||
}
|
||||
total() { awk '/^cpu /{print $2+$3+$4+$6+$7+$8}' /proc/stat; }
|
||||
dmabufs() { # dma-buf descriptors held by the pids (Android kernels link them to /dmabuf:...)
|
||||
n=0
|
||||
for p in "$@"; do n=$((n + $(ls -l /proc/$p/fd 2>/dev/null | grep -c dmabuf))); done
|
||||
echo $n
|
||||
}
|
||||
AP="$(pidof $APP)"
|
||||
XW="$(pidof Xwayland)"
|
||||
KW="$(pidof kwin_wayland)"
|
||||
SV="$(pidof $P:mobilegl)"
|
||||
[ -n "$AP" ] || { echo "no running process named $APP" >&2; exit 2; }
|
||||
a0=$(total); p0=$(ticks $AP); x0=$(ticks $XW); k0=$(ticks $KW); v0=$(ticks $SV)
|
||||
g=0; i=0
|
||||
while [ $i -lt $N ]; do
|
||||
sleep 1
|
||||
b=$(cat $GPU 2>/dev/null | tr -dc '0-9'); g=$((g + ${b:-0})); i=$((i + 1))
|
||||
done
|
||||
a1=$(total); p1=$(ticks $AP); x1=$(ticks $XW); k1=$(ticks $KW); v1=$(ticks $SV)
|
||||
pct() { echo $(( ($2 - $1) / N )); }
|
||||
printf '%-12s %3ss phone %4s%% %s %3s%% Xwayland %3s%% kwin %3s%% server %3s%% gpu %3s%% dmabufs Xwayland %s kwin %s battery %s%%\n' \
|
||||
"$L" "$N" "$(pct $a0 $a1)" "$APP" "$(pct $p0 $p1)" "$(pct $x0 $x1)" "$(pct $k0 $k1)" "$(pct $v0 $v1)" "$((g / N))" \
|
||||
"$(dmabufs $XW)" "$(dmabufs $KW)" "$level"
|
||||
@@ -0,0 +1,49 @@
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/env.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# env.sh - shared parameters for the host-side (Git Bash) scripts of the anland-mobilegl-plasma skill.
|
||||
# Sourced, never run. Every value can be overridden from the environment before calling a script.
|
||||
#
|
||||
# MGL_WORKTREE MobileGL worktree (Windows form C:/...). Default: the worktree this skill lives in.
|
||||
# ANL_DIR Local scratch dir: channel files, screenshots, xdist/. Default: %LOCALAPPDATA%/Temp/anl
|
||||
# ANDROID_NDK NDK used for the Android server lib. Default: <SDK>/ndk/27.2.12479018
|
||||
# MGL_PKG Anland APK that embeds the server. Default: com.anland.consumer.mobilegl
|
||||
# MGL_CONTAINER Droidspaces container. Default: arch-kde-mgl
|
||||
# MGL_DESKTOP_USER Desktop user inside the container. Default: swung0x48
|
||||
# DS_BIN droidspaces binary on the phone. Default: /data/local/Droidspaces/bin/droidspaces
|
||||
# DEV_TOOLS Phone dir for device/*.sh and channel files. Default: /data/local/tmp/anl
|
||||
# DEV_SHARE Phone dir bind-mounted into the container as /run/anland-mobilegl (screenshots,
|
||||
# server lib staging, display daemon socket). Default: /data/local/tmp/anland-mobilegl
|
||||
# ANDROID_SERIAL Honoured by adb itself; leave unset when only one device is attached
|
||||
# (USB serial or a wireless "ip:port").
|
||||
|
||||
_skill_scripts=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
if [ -z "${MGL_WORKTREE:-}" ]; then
|
||||
MGL_WORKTREE=$(cd "$_skill_scripts/../../../.." && pwd -W 2>/dev/null || pwd)
|
||||
fi
|
||||
: "${ANL_DIR:=$(cygpath -m "${LOCALAPPDATA:-C:/Users/$USERNAME/AppData/Local}" 2>/dev/null || echo /tmp)/Temp/anl}"
|
||||
: "${ANDROID_NDK:=$(cygpath -m "${LOCALAPPDATA:-C:/Users/$USERNAME/AppData/Local}" 2>/dev/null)/Android/Sdk/ndk/27.2.12479018}"
|
||||
: "${MGL_PKG:=com.anland.consumer.mobilegl}"
|
||||
: "${MGL_CONTAINER:=arch-kde-mgl}"
|
||||
: "${MGL_DESKTOP_USER:=swung0x48}"
|
||||
: "${DS_BIN:=/data/local/Droidspaces/bin/droidspaces}"
|
||||
: "${DEV_TOOLS:=/data/local/tmp/anl}"
|
||||
: "${DEV_SHARE:=/data/local/tmp/anland-mobilegl}"
|
||||
# The container rootfs as Android root sees it (the container's /tmp is NOT visible here).
|
||||
CT_ROOT=/mnt/Droidspaces/$MGL_CONTAINER
|
||||
export MGL_WORKTREE ANL_DIR ANDROID_NDK MGL_PKG MGL_CONTAINER MGL_DESKTOP_USER DS_BIN DEV_TOOLS DEV_SHARE CT_ROOT
|
||||
|
||||
# Git Bash would rewrite /data/... arguments into C:/Program Files/Git/data/...; keep them literal.
|
||||
# Consequence: pass LOCAL paths to Windows tools (adb, cmake, git) in C:/ form, never /c/.
|
||||
export MSYS_NO_PATHCONV=1
|
||||
adb_() { MSYS2_ARG_CONV_EXCL='*' adb "$@"; }
|
||||
|
||||
# C:/x/y -> /mnt/c/x/y (for WSL).
|
||||
win2wsl() { local p=$1 d; d=$(printf '%s' "${p:0:1}" | tr 'A-Z' 'a-z'); printf '/mnt/%s%s' "$d" "${p:2}"; }
|
||||
|
||||
mkdir -p "$ANL_DIR"
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/kwin-recover.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# kwin-recover.sh [rounds] - does the desktop keep working when KWin's OWN MobileGL session is lost?
|
||||
# Starts animated clients (glmark2-es2-wayland: Wayland, zero-copy shared images; glxgears: X11 through
|
||||
# Xwayland's GLX; with CHROME=1 also Chrome on an animated WebGL page), then per round loses the server
|
||||
# session of KWin with the pid-targeted debug knob (debug.mobilegl.inject_device_lost_pid, KWin's pid as
|
||||
# it sees itself in the container), waits, and checks. A round PASSES when:
|
||||
# - the server latched KWin's session (the server log file: "injected:pid=<kwin>"; logcat is too small);
|
||||
# - KWin, plasmashell and every test client are the SAME processes (nothing restarted);
|
||||
# - KWin restarted compositing on a fresh session (its journal: "compositing restarted on a fresh
|
||||
# MobileGL session"; its client log: one more "RECOVERED");
|
||||
# - the screen still animates (shot.sh hashes differ) and the clients keep presenting;
|
||||
# - no native crash (logcat -b crash).
|
||||
# Between rounds the knob is reset to 0 while the clients run, which re-arms it for the same pid.
|
||||
# Stops early below MIN_BATTERY percent (the phone drains faster under load than USB charges it).
|
||||
#
|
||||
# Deploy first (same MobileGL commit on both sides; see SKILL.md):
|
||||
# bash scripts/build-android.sh && bash scripts/cyc.sh pre # server -> APK, restarts the session
|
||||
# bash scripts/xbuild.sh && bash scripts/xdeploy.sh # client -> container
|
||||
# ANLAND_WORKTREE=<anland worktree> bash scripts/kwin-stage.sh build # kwin.patch + anland backend
|
||||
# (wait for "== ALL DONE ==" in /root/kwin-build.log), then: adb shell 'su -c "sh /data/local/tmp/anl/run-plasma.sh"'
|
||||
# Env: ROUNDS (default 2), SETTLE (s after the loss, default 10), REARM (s with the knob at 0 between
|
||||
# rounds, default 4), MIN_BATTERY (default 40), CHROME (1: add Chrome), CHANNEL (ct.sh channel,
|
||||
# default krec), TAG (screenshot/log tag, default krec).
|
||||
set -euo pipefail
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/env.sh"
|
||||
ROUNDS=${1:-${ROUNDS:-2}}
|
||||
SETTLE=${SETTLE:-10}
|
||||
REARM=${REARM:-4}
|
||||
MIN_BATTERY=${MIN_BATTERY:-40}
|
||||
CH=${CHANNEL:-krec}
|
||||
TAG=${TAG:-krec}
|
||||
COMPOSITOR_LOG=/tmp/mobilegl-compositor.client.log
|
||||
SERVER_LOG=/data/data/$MGL_PKG/files/mobilegl-server.server.log
|
||||
|
||||
ct() { bash "$here/ct.sh" "$CH"; }
|
||||
|
||||
battery() { adb_ shell dumpsys battery | tr -d '\r' | awk '/ level:/ {print $2}'; }
|
||||
stop_clients() {
|
||||
ct <<'IN' || true
|
||||
pkill -x glmark2-es2-way 2>/dev/null; pkill -x glmark2-es2-wayland 2>/dev/null; pkill -x glxgears 2>/dev/null
|
||||
true
|
||||
IN
|
||||
}
|
||||
cleanup() {
|
||||
adb_ shell "su -c 'setprop debug.mobilegl.inject_device_lost_pid 0'" || true
|
||||
stop_clients
|
||||
}
|
||||
check_battery() {
|
||||
local level; level=$(battery)
|
||||
echo "battery: $level%"
|
||||
if [ -n "$level" ] && [ "$level" -lt "$MIN_BATTERY" ]; then
|
||||
echo "STOP: battery below $MIN_BATTERY%"; exit 3
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
check_battery
|
||||
adb_ shell "su -c 'sh $DEV_TOOLS/status.sh'" || true
|
||||
adb_ shell "su -c 'setprop debug.mobilegl.inject_device_lost_pid 0'"
|
||||
|
||||
# The clients, each with its own log (mgrun's MGLOG), in the background for the whole run.
|
||||
ct <<'IN'
|
||||
rm -f /tmp/mgl-krec-gm.client.log /tmp/mgl-krec-gx.client.log /tmp/krec-gm.out /tmp/krec-gx.out
|
||||
MGLOG=/tmp/mgl-krec-gm.log nohup timeout 900 mgrun offscreen glmark2-es2-wayland -s 960x540 --run-forever \
|
||||
> /tmp/krec-gm.out 2>&1 < /dev/null &
|
||||
MGLOG=/tmp/mgl-krec-gx.log nohup timeout 900 mgrun offscreen glxgears -info > /tmp/krec-gx.out 2>&1 < /dev/null &
|
||||
sleep 8
|
||||
echo "glmark2: $(pgrep -x glmark2-es2-way || pgrep -x glmark2-es2-wayland) glxgears: $(pgrep -x glxgears)"
|
||||
grep -aE "presented through|could not be presented" /tmp/mgl-krec-gm.client.log | head -2
|
||||
grep -aE "presented through|readback" /tmp/mgl-krec-gx.client.log | head -2
|
||||
IN
|
||||
if [ "${CHROME:-0}" = 1 ]; then
|
||||
ct <<'IN'
|
||||
cat > /tmp/mgl-krec-spin.html <<'HTML'
|
||||
<!doctype html><html><body style="margin:0;background:#000">
|
||||
<canvas id=c width=640 height=360 style="width:100vw;height:100vh"></canvas>
|
||||
<script>
|
||||
const gl = document.getElementById('c').getContext('webgl'); let t = 0;
|
||||
function frame() { t++; if (gl && !gl.isContextLost()) { gl.clearColor((t % 60) / 60, 0.2, 0.6, 1); gl.clear(gl.COLOR_BUFFER_BIT); }
|
||||
requestAnimationFrame(frame); }
|
||||
frame();
|
||||
</script></body></html>
|
||||
HTML
|
||||
chmod 644 /tmp/mgl-krec-spin.html
|
||||
IN
|
||||
WAIT=${WAIT:-20} CHANNEL=$CH bash "$here/chrome-launch.sh" "$TAG" --new-window "file:///tmp/mgl-krec-spin.html"
|
||||
fi
|
||||
|
||||
# KWin, plasmashell and the clients, as pids inside the container; how often KWin recovered so far.
|
||||
snapshot() {
|
||||
ct <<IN | tr -d '\r' | tail -1
|
||||
k=\$(pgrep -x kwin_wayland | head -1); p=\$(pgrep -x plasmashell | head -1)
|
||||
g=\$(pgrep -x glmark2-es2-way || pgrep -x glmark2-es2-wayland); x=\$(pgrep -x glxgears | head -1)
|
||||
c=\$(pgrep -x chrome | head -1)
|
||||
r=\$(grep -ac "RECOVERED" $COMPOSITOR_LOG 2>/dev/null || true)
|
||||
f=\$(grep -ac "FPS" /tmp/krec-gm.out 2>/dev/null || true)
|
||||
echo "\${k:-none} \${p:-none} \${g:-none} \${x:-none} \${c:-none} \${r:-0} \${f:-0}"
|
||||
IN
|
||||
}
|
||||
|
||||
pass=0
|
||||
for round in $(seq 1 "$ROUNDS"); do
|
||||
echo "=== round $round/$ROUNDS"
|
||||
check_battery
|
||||
read -r kwin plasma gm gx chrome recovered fps < <(snapshot)
|
||||
echo "kwin=$kwin plasmashell=$plasma glmark2=$gm glxgears=$gx chrome=$chrome recoveries=$recovered fps-lines=$fps"
|
||||
if [ "$kwin" = none ]; then echo "FAIL: KWin is not running"; break; fi
|
||||
bash "$here/shot.sh" "$TAG-r$round-pre" 2 1
|
||||
srvlines=$(adb_ shell "su -c 'wc -l < $SERVER_LOG'" | tr -dc '0-9')
|
||||
adb_ logcat -b crash -c || true
|
||||
adb_ shell "su -c 'setprop debug.mobilegl.inject_device_lost_pid $kwin'"
|
||||
sleep "$SETTLE"
|
||||
adb_ shell "su -c 'setprop debug.mobilegl.inject_device_lost_pid 0'"
|
||||
bash "$here/shot.sh" "$TAG-r$round-post" 3 1 | tee "$ANL_DIR/$TAG-r$round-post.md5"
|
||||
read -r kwin2 plasma2 gm2 gx2 chrome2 recovered2 fps2 < <(snapshot)
|
||||
echo "after: kwin=$kwin2 plasmashell=$plasma2 glmark2=$gm2 glxgears=$gx2 chrome=$chrome2 recoveries=$recovered2 fps-lines=$fps2"
|
||||
adb_ shell "su -c 'tail -n +$((srvlines + 1)) $SERVER_LOG'" > "$ANL_DIR/$TAG-r$round-server.log"
|
||||
latched=$(grep -c "injected:pid=$kwin" "$ANL_DIR/$TAG-r$round-server.log" || true)
|
||||
grep -aE "inject_device_lost_pid|SessionLatch|session #[0-9]+ (started|pid)|owner=server|lease" \
|
||||
"$ANL_DIR/$TAG-r$round-server.log" | cut -c1-220 | head -14 || true
|
||||
ct <<IN || true
|
||||
echo "--- KWin's MobileGL client log:"; grep -aE "DEVICE LOST|RECOVERED|GL_UNKNOWN_CONTEXT_RESET|re-created on the fresh|Refuse" $COMPOSITOR_LOG | tail -6
|
||||
echo "--- KWin journal:"; journalctl --no-pager --since -40s 2>/dev/null | grep -aE "kwin_wayland" | grep -aiE "reset|restarted|MobileGL swap|context|fatal|abort" | tail -10
|
||||
echo "--- clients:"; grep -aE "DEVICE LOST|lost|Fatal" /tmp/mgl-krec-gm.client.log /tmp/mgl-krec-gx.client.log | tail -4
|
||||
IN
|
||||
crashes=$(adb_ logcat -b crash -d | grep -c "Fatal signal\|Abort message" || true)
|
||||
hashes=$(awk '{print $1}' "$ANL_DIR/$TAG-r$round-post.md5" | sort -u | wc -l)
|
||||
verdict=PASS
|
||||
[ "$latched" -ge 1 ] || { echo " - the server never latched KWin's session (pid $kwin)"; verdict=FAIL; }
|
||||
[ "$kwin2" = "$kwin" ] || { echo " - KWin is a different process ($kwin -> $kwin2): it restarted"; verdict=FAIL; }
|
||||
[ "$plasma2" = "$plasma" ] || { echo " - plasmashell restarted ($plasma -> $plasma2)"; verdict=FAIL; }
|
||||
[ "$gm2" = "$gm" ] && [ "$gx2" = "$gx" ] || { echo " - a test client restarted or died"; verdict=FAIL; }
|
||||
[ "${CHROME:-0}" != 1 ] || [ "$chrome2" = "$chrome" ] || { echo " - Chrome restarted"; verdict=FAIL; }
|
||||
[ "$recovered2" -gt "$recovered" ] || { echo " - KWin's client log shows no new RECOVERED session"; verdict=FAIL; }
|
||||
[ "$fps2" -gt "$fps" ] || { echo " - glmark2 stopped reporting frames"; verdict=FAIL; }
|
||||
[ "$hashes" -ge 2 ] || { echo " - the screen does not change after the loss (frozen)"; verdict=FAIL; }
|
||||
[ "$crashes" -eq 0 ] || { echo " - native crash(es):"; adb_ logcat -b crash -d | tail -8; verdict=FAIL; }
|
||||
echo "round $round: $verdict"
|
||||
[ "$verdict" = PASS ] && pass=$((pass + 1))
|
||||
[ "$verdict" = PASS ] || break
|
||||
# Re-arm the knob for the same pid: a session must observe 0 before the next value counts.
|
||||
sleep "$REARM"
|
||||
done
|
||||
echo "=== $pass/$ROUNDS rounds passed; screenshots in $ANL_DIR/$TAG-r*-*.png"
|
||||
[ "$pass" = "$ROUNDS" ]
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/kwin-stage.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# kwin-stage.sh [build] - stage anland's KWin changes (kwin.patch, anland backend sources,
|
||||
# mobilegl-startup.sh, sync-build-kwin.sh) for the in-container KWin rebuild; with "build", also run
|
||||
# kwprep.sh (apply changed patch hunks / backend files to the live tree) and start sync-build-kwin.sh
|
||||
# (ninja -j4 + atomic install into /opt/mobilegl/kwin, screencast plugin, startup script) in the
|
||||
# background, log /root/kwin-build.log. KWin is NOT restarted: run device/run-plasma.sh afterwards.
|
||||
# Poll: printf 'tail -5 /root/kwin-build.log\n' | bash ct.sh kw
|
||||
#
|
||||
# Two staging places, because the two container scripts read different ones:
|
||||
# /root/kwsync.tgz <- kwprep.sh (installed by push-tools.sh)
|
||||
# $DEV_SHARE/kwin-sync = /run/anland-mobilegl/kwin-sync <- sync-build-kwin.sh (as kwin-sync/sync-build.sh)
|
||||
# All files are CR-stripped: the Windows checkout of anland has CRLF backend sources and a CRLF
|
||||
# sync-build-kwin.sh (bash then fails on $'\r').
|
||||
# Assembled from kwprep.sh + sync-build-kwin.sh; not exercised as one unit yet.
|
||||
# Env: ANLAND_WORKTREE (anland checkout on branch legacy-mobilegl-unified, Windows form),
|
||||
# CHANNEL (ct.sh channel, default kw).
|
||||
set -euo pipefail
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/env.sh"
|
||||
: "${ANLAND_WORKTREE:=$(cygpath -m "$USERPROFILE" 2>/dev/null)/AndroidStudioProjects/anland/.claude/worktrees/mgl-unified}"
|
||||
K=$ANLAND_WORKTREE/producers/kde
|
||||
st=$ANL_DIR/kwstage
|
||||
rm -rf "$st"; mkdir -p "$st/kwsync/Arch_v5" "$st/kwsync/anland_backend_Arch_v5/src/backends/anland" "$st/kwin-sync/anland" "$st/kwin-sync/misc"
|
||||
lf() { tr -d '\r' < "$1" > "$2"; }
|
||||
for f in "$K"/anland_backend_Arch_v5/src/backends/anland/*; do
|
||||
b=$(basename "$f"); [ "$b" = libdisplay_producer ] && continue
|
||||
lf "$f" "$st/kwsync/anland_backend_Arch_v5/src/backends/anland/$b"
|
||||
lf "$f" "$st/kwin-sync/anland/$b"
|
||||
done
|
||||
for f in kwin.patch mobilegl-startup.sh desktop-session-mobilegl.conf; do
|
||||
lf "$K/Arch_v5/$f" "$st/kwsync/Arch_v5/$f"
|
||||
lf "$K/Arch_v5/$f" "$st/kwin-sync/misc/$f"
|
||||
done
|
||||
lf "$K/Arch_v5/sync-build-kwin.sh" "$st/kwin-sync/sync-build.sh"
|
||||
(cd "$st/kwsync" && tar --force-local -czf ../kwsync.tgz Arch_v5 anland_backend_Arch_v5)
|
||||
tar --force-local -C "$st" -czf "$st/kwin-sync.tgz" kwin-sync
|
||||
adb_ push "$st/kwsync.tgz" "$DEV_TOOLS/kwsync.tgz" >/dev/null
|
||||
adb_ push "$st/kwin-sync.tgz" "$DEV_TOOLS/kwin-sync.tgz" >/dev/null
|
||||
adb_ shell "su -c 'cp $DEV_TOOLS/kwsync.tgz $CT_ROOT/root/kwsync.tgz && tar xzf $DEV_TOOLS/kwin-sync.tgz -C $DEV_SHARE && chmod -R a+rX $DEV_SHARE/kwin-sync && rm -f $DEV_TOOLS/kwsync.tgz $DEV_TOOLS/kwin-sync.tgz && echo staged'"
|
||||
if [ "${1:-}" = build ]; then
|
||||
bash "$here/ct.sh" "${CHANNEL:-kw}" <<'IN'
|
||||
bash /root/kwprep.sh || exit 1
|
||||
nohup bash /run/anland-mobilegl/kwin-sync/sync-build.sh > /root/kwin-build.log 2>&1 < /dev/null &
|
||||
echo "kwin build started (pid $!), log /root/kwin-build.log; it ends with '== ALL DONE =='"
|
||||
IN
|
||||
fi
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/probes/cdpmedia.py
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# cdpmedia.py [port] [seconds] - ask a Chrome started with --remote-debugging-port=<port> (and a
|
||||
# non-default --user-data-dir) which video decoder its first page's players use, and evaluate
|
||||
# `window.__mglStats` / the element `#t` text of the page. Standard library only (no websocket
|
||||
# module in the container): a minimal RFC 6455 client.
|
||||
# python3 cdpmedia.py 9333 6
|
||||
# Prints kVideoDecoderName / kIsPlatformVideoDecoder / kVideoTracks properties and media errors as
|
||||
# they arrive, then the page's stats text.
|
||||
import base64, json, os, socket, struct, sys, time, urllib.request
|
||||
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else 9333
|
||||
seconds = float(sys.argv[2]) if len(sys.argv) > 2 else 6.0
|
||||
targets = json.load(urllib.request.urlopen(f"http://127.0.0.1:{port}/json"))
|
||||
page = next(t for t in targets if t.get("type") == "page")
|
||||
url = page["webSocketDebuggerUrl"]
|
||||
path = url.split(f"127.0.0.1:{port}", 1)[1]
|
||||
sock = socket.create_connection(("127.0.0.1", port))
|
||||
key = base64.b64encode(os.urandom(16)).decode()
|
||||
sock.sendall((f"GET {path} HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"
|
||||
f"Sec-WebSocket-Key: {key}\r\nSec-WebSocket-Version: 13\r\n\r\n").encode())
|
||||
response = b""
|
||||
while b"\r\n\r\n" not in response:
|
||||
response += sock.recv(4096)
|
||||
buffer = response.split(b"\r\n\r\n", 1)[1]
|
||||
|
||||
|
||||
def send(message):
|
||||
data = json.dumps(message).encode()
|
||||
header = bytes([0x81])
|
||||
mask = os.urandom(4)
|
||||
if len(data) < 126:
|
||||
header += bytes([0x80 | len(data)])
|
||||
elif len(data) < 65536:
|
||||
header += bytes([0x80 | 126]) + struct.pack(">H", len(data))
|
||||
else:
|
||||
header += bytes([0x80 | 127]) + struct.pack(">Q", len(data))
|
||||
sock.sendall(header + mask + bytes(b ^ mask[i % 4] for i, b in enumerate(data)))
|
||||
|
||||
|
||||
def recv_exact(n):
|
||||
global buffer
|
||||
while len(buffer) < n:
|
||||
chunk = sock.recv(65536)
|
||||
if not chunk:
|
||||
raise EOFError
|
||||
buffer += chunk
|
||||
out, buffer = buffer[:n], buffer[n:]
|
||||
return out
|
||||
|
||||
|
||||
def recv():
|
||||
first, second = recv_exact(2)
|
||||
length = second & 0x7F
|
||||
if length == 126:
|
||||
length = struct.unpack(">H", recv_exact(2))[0]
|
||||
elif length == 127:
|
||||
length = struct.unpack(">Q", recv_exact(8))[0]
|
||||
payload = recv_exact(length)
|
||||
return json.loads(payload) if first & 0x0F == 1 else None
|
||||
|
||||
|
||||
send({"id": 1, "method": "Media.enable"})
|
||||
# Players created before the domain was enabled report nothing: reload so the page makes new ones.
|
||||
if os.environ.get("CDP_NAVIGATE"):
|
||||
send({"id": 3, "method": "Page.navigate", "params": {"url": os.environ["CDP_NAVIGATE"]}})
|
||||
elif os.environ.get("CDP_RELOAD", "1") == "1":
|
||||
send({"id": 3, "method": "Page.reload"})
|
||||
deadline = time.time() + seconds
|
||||
sock.settimeout(0.5)
|
||||
seen = set()
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
message = recv()
|
||||
except socket.timeout:
|
||||
continue
|
||||
if not message or "method" not in message:
|
||||
continue
|
||||
if message["method"] == "Media.playerPropertiesChanged":
|
||||
for prop in message["params"]["properties"]:
|
||||
if prop["name"] in ("kVideoDecoderName", "kIsPlatformVideoDecoder", "kVideoTracks", "kResolution"):
|
||||
line = f"{prop['name']} = {prop['value']}"
|
||||
if line not in seen:
|
||||
seen.add(line)
|
||||
print(line)
|
||||
elif message["method"] in ("Media.playerErrorsRaised",):
|
||||
print("media error:", json.dumps(message["params"])[:300])
|
||||
send({"id": 2, "method": "Runtime.evaluate",
|
||||
"params": {"expression": os.environ.get("CDP_EXPR", "(document.getElementById('t')||{}).textContent"), "returnByValue": True}})
|
||||
sock.settimeout(3)
|
||||
while True:
|
||||
message = recv()
|
||||
if message and message.get("id") == 2:
|
||||
print("page:", message.get("result", {}).get("result", {}).get("value"))
|
||||
break
|
||||
@@ -0,0 +1,58 @@
|
||||
// MobileGL - tools/anland-mobilegl-plasma/scripts/probes/dmahash.c
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
// dmahash <pid> : for each dma-buf fd of <pid> at least 1 MB, print inode, size and a sampled checksum.
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
char dir[64];
|
||||
snprintf(dir, sizeof dir, "/proc/%s/fd", argv[1]);
|
||||
DIR* d = opendir(dir);
|
||||
if (!d) {
|
||||
perror("opendir");
|
||||
return 1;
|
||||
}
|
||||
struct dirent* e;
|
||||
while ((e = readdir(d))) {
|
||||
char p[128], l[128] = {0};
|
||||
snprintf(p, sizeof p, "%s/%s", dir, e->d_name);
|
||||
if (readlink(p, l, sizeof l - 1) <= 0 || strncmp(l, "/dmabuf", 7) != 0) continue;
|
||||
int fd = open(p, O_RDONLY);
|
||||
if (fd < 0) continue;
|
||||
struct stat st;
|
||||
fstat(fd, &st);
|
||||
if (st.st_size < (1 << 20)) {
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
const uint8_t* m = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (m == MAP_FAILED) {
|
||||
printf("fd %s ino %lu mmap failed\n", e->d_name, (unsigned long)st.st_ino);
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
uint64_t h = 1469598103934665603ull;
|
||||
unsigned nz = 0;
|
||||
for (off_t i = 0; i < st.st_size; i += 64) {
|
||||
h = (h ^ m[i]) * 1099511628211ull;
|
||||
nz += m[i] != 0;
|
||||
}
|
||||
printf("fd %s ino %lu size %ld hash %016llx nz %u\n", e->d_name, (unsigned long)st.st_ino, (long)st.st_size,
|
||||
(unsigned long long)h, nz);
|
||||
munmap((void*)m, st.st_size);
|
||||
close(fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// MobileGL - tools/anland-mobilegl-plasma/scripts/probes/gpuprobe.c
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
// gpuprobe - a plain Android process (outside MobileGL) that measures whether the GPU keeps executing
|
||||
// other contexts' work: a pbuffer GLES context clears + draws and glFinish()es every ~10 ms, printing any
|
||||
// round trip over 50 ms with a CLOCK_REALTIME timestamp. Usage: gpuprobe <seconds> [priority: low|med|high]
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#include <GLES3/gl3.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef EGL_CONTEXT_PRIORITY_LEVEL_IMG
|
||||
#define EGL_CONTEXT_PRIORITY_LEVEL_IMG 0x3100
|
||||
#define EGL_CONTEXT_PRIORITY_HIGH_IMG 0x3101
|
||||
#define EGL_CONTEXT_PRIORITY_MEDIUM_IMG 0x3102
|
||||
#define EGL_CONTEXT_PRIORITY_LOW_IMG 0x3103
|
||||
#endif
|
||||
|
||||
static double now_ms(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
return ts.tv_sec * 1e3 + ts.tv_nsec / 1e6;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int seconds = argc > 1 ? atoi(argv[1]) : 20;
|
||||
EGLint prio = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
|
||||
if (argc > 2 && !strcmp(argv[2], "high")) prio = EGL_CONTEXT_PRIORITY_HIGH_IMG;
|
||||
if (argc > 2 && !strcmp(argv[2], "low")) prio = EGL_CONTEXT_PRIORITY_LOW_IMG;
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
EGLDisplay d = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
eglInitialize(d, NULL, NULL);
|
||||
const EGLint ca[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_RED_SIZE, 8,
|
||||
EGL_NONE};
|
||||
EGLConfig c;
|
||||
EGLint n;
|
||||
eglChooseConfig(d, ca, &c, 1, &n);
|
||||
const EGLint pa[] = {EGL_WIDTH, 256, EGL_HEIGHT, 256, EGL_NONE};
|
||||
EGLSurface s = eglCreatePbufferSurface(d, c, pa);
|
||||
const EGLint xa[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_CONTEXT_PRIORITY_LEVEL_IMG, prio, EGL_NONE};
|
||||
EGLContext x = eglCreateContext(d, c, EGL_NO_CONTEXT, xa);
|
||||
if (x == EGL_NO_CONTEXT) {
|
||||
printf("no ctx 0x%x\n", eglGetError());
|
||||
return 1;
|
||||
}
|
||||
eglMakeCurrent(d, s, s, x);
|
||||
EGLint got = 0;
|
||||
eglQueryContext(d, x, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &got);
|
||||
const double t0 = now_ms();
|
||||
double maxRt = 0;
|
||||
long iters = 0, slow = 0;
|
||||
printf("[probe] start %.0f prio=0x%x\n", t0, got);
|
||||
while (now_ms() - t0 < seconds * 1000.0) {
|
||||
const double a = now_ms();
|
||||
glClearColor((iters & 1) ? 1.f : 0.f, 0.f, 0.f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glFinish();
|
||||
const double rt = now_ms() - a;
|
||||
if (rt > maxRt) maxRt = rt;
|
||||
if (rt > 50) {
|
||||
++slow;
|
||||
printf("[probe] glFinish %.0f ms ending at %.0f\n", rt, now_ms());
|
||||
}
|
||||
++iters;
|
||||
usleep(10000);
|
||||
}
|
||||
printf("[probe] END iters=%ld max=%.0f ms slow=%ld\n", iters, maxRt, slow);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
// MobileGL - tools/anland-mobilegl-plasma/scripts/probes/hangclient.c
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
// hangclient - a Wayland xdg-toplevel, EGL + desktop GL 3.3 core client that animates and, on request,
|
||||
// submits fragment work that never ends (the driver has to reset it). Prints per-frame gaps so the same
|
||||
// binary measures "other app" frame pacing (frame callbacks come from the compositor) when run with -H -1.
|
||||
//
|
||||
// hangclient -T tag [-H hang_at_frame] [-N hang_repeat] [-I iters] [-S seconds] [-W w] [-X h] [-F]
|
||||
// -H n frame at which the hanging draw is submitted (-1: never; default -1)
|
||||
// -N k how many consecutive hanging frames (default 1)
|
||||
// -E e re-arm a hang every e frames after the first (0: once)
|
||||
// -I i loop count of the hanging shader (default 2^31-1)
|
||||
// -S s run time in seconds (default 30)
|
||||
// -F glFinish after every frame (puts the wait inside the client's own session)
|
||||
#define GL_GLEXT_PROTOTYPES 1
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#include <GL/glcorearb.h>
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-egl.h>
|
||||
#include "xdg-shell-client-protocol.h"
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static struct wl_compositor* g_comp;
|
||||
static struct xdg_wm_base* g_wm;
|
||||
static int g_configured, g_closed;
|
||||
static int g_w = 480, g_h = 360;
|
||||
|
||||
static void reg_global(void* d, struct wl_registry* r, uint32_t name, const char* iface, uint32_t ver) {
|
||||
(void)d;
|
||||
if (!strcmp(iface, "wl_compositor"))
|
||||
g_comp = wl_registry_bind(r, name, &wl_compositor_interface, 4);
|
||||
else if (!strcmp(iface, "xdg_wm_base"))
|
||||
g_wm = wl_registry_bind(r, name, &xdg_wm_base_interface, 1);
|
||||
}
|
||||
static void reg_remove(void* d, struct wl_registry* r, uint32_t n) {
|
||||
(void)d;
|
||||
(void)r;
|
||||
(void)n;
|
||||
}
|
||||
static const struct wl_registry_listener reg_l = {reg_global, reg_remove};
|
||||
static void wm_ping(void* d, struct xdg_wm_base* wm, uint32_t s) {
|
||||
(void)d;
|
||||
xdg_wm_base_pong(wm, s);
|
||||
}
|
||||
static const struct xdg_wm_base_listener wm_l = {wm_ping};
|
||||
static void xs_conf(void* d, struct xdg_surface* s, uint32_t serial) {
|
||||
(void)d;
|
||||
xdg_surface_ack_configure(s, serial);
|
||||
g_configured = 1;
|
||||
}
|
||||
static const struct xdg_surface_listener xs_l = {xs_conf};
|
||||
static void tl_conf(void* d, struct xdg_toplevel* t, int32_t w, int32_t h, struct wl_array* st) {
|
||||
(void)d;
|
||||
(void)t;
|
||||
(void)st;
|
||||
(void)w;
|
||||
(void)h;
|
||||
}
|
||||
static void tl_close(void* d, struct xdg_toplevel* t) {
|
||||
(void)d;
|
||||
(void)t;
|
||||
g_closed = 1;
|
||||
}
|
||||
static const struct xdg_toplevel_listener tl_l = {tl_conf, tl_close};
|
||||
|
||||
static double now_ms(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
return ts.tv_sec * 1e3 + ts.tv_nsec / 1e6;
|
||||
}
|
||||
|
||||
static GLuint sh(GLenum type, const char* src) {
|
||||
GLuint s = glCreateShader(type);
|
||||
glShaderSource(s, 1, &src, NULL);
|
||||
glCompileShader(s);
|
||||
GLint ok = 0;
|
||||
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
|
||||
if (!ok) {
|
||||
char log[2048];
|
||||
glGetShaderInfoLog(s, sizeof log, NULL, log);
|
||||
fprintf(stderr, "shader: %s\n", log);
|
||||
exit(2);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
const char* tag = "hc";
|
||||
int hangAt = -1, hangN = 1, every = 0, seconds = 30, finish = 0;
|
||||
long iters = 2147483647L;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (!strcmp(argv[i], "-T") && i + 1 < argc)
|
||||
tag = argv[++i];
|
||||
else if (!strcmp(argv[i], "-H") && i + 1 < argc)
|
||||
hangAt = atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "-N") && i + 1 < argc)
|
||||
hangN = atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "-E") && i + 1 < argc)
|
||||
every = atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "-I") && i + 1 < argc)
|
||||
iters = atol(argv[++i]);
|
||||
else if (!strcmp(argv[i], "-S") && i + 1 < argc)
|
||||
seconds = atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "-W") && i + 1 < argc)
|
||||
g_w = atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "-X") && i + 1 < argc)
|
||||
g_h = atoi(argv[++i]);
|
||||
else if (!strcmp(argv[i], "-F"))
|
||||
finish = 1;
|
||||
}
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
struct wl_display* dpy = wl_display_connect(NULL);
|
||||
if (!dpy) {
|
||||
fprintf(stderr, "no wayland\n");
|
||||
return 1;
|
||||
}
|
||||
struct wl_registry* reg = wl_display_get_registry(dpy);
|
||||
wl_registry_add_listener(reg, ®_l, NULL);
|
||||
wl_display_roundtrip(dpy);
|
||||
if (!g_comp || !g_wm) {
|
||||
fprintf(stderr, "no compositor/xdg_wm_base\n");
|
||||
return 1;
|
||||
}
|
||||
xdg_wm_base_add_listener(g_wm, &wm_l, NULL);
|
||||
struct wl_surface* surf = wl_compositor_create_surface(g_comp);
|
||||
struct xdg_surface* xs = xdg_wm_base_get_xdg_surface(g_wm, surf);
|
||||
xdg_surface_add_listener(xs, &xs_l, NULL);
|
||||
struct xdg_toplevel* tl = xdg_surface_get_toplevel(xs);
|
||||
xdg_toplevel_add_listener(tl, &tl_l, NULL);
|
||||
xdg_toplevel_set_title(tl, tag);
|
||||
wl_surface_commit(surf);
|
||||
while (!g_configured)
|
||||
wl_display_dispatch(dpy);
|
||||
|
||||
EGLDisplay ed = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, dpy, NULL);
|
||||
if (!eglInitialize(ed, NULL, NULL)) {
|
||||
fprintf(stderr, "eglInitialize\n");
|
||||
return 1;
|
||||
}
|
||||
eglBindAPI(EGL_OPENGL_API);
|
||||
const EGLint ca[] = {EGL_SURFACE_TYPE,
|
||||
EGL_WINDOW_BIT,
|
||||
EGL_RENDERABLE_TYPE,
|
||||
EGL_OPENGL_BIT,
|
||||
EGL_RED_SIZE,
|
||||
8,
|
||||
EGL_GREEN_SIZE,
|
||||
8,
|
||||
EGL_BLUE_SIZE,
|
||||
8,
|
||||
EGL_NONE};
|
||||
EGLConfig cfg;
|
||||
EGLint n = 0;
|
||||
if (!eglChooseConfig(ed, ca, &cfg, 1, &n) || n < 1) {
|
||||
fprintf(stderr, "no config\n");
|
||||
return 1;
|
||||
}
|
||||
const EGLint xa[] = {
|
||||
EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 3, EGL_CONTEXT_OPENGL_PROFILE_MASK,
|
||||
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, EGL_NONE};
|
||||
EGLContext ctx = eglCreateContext(ed, cfg, EGL_NO_CONTEXT, xa);
|
||||
if (ctx == EGL_NO_CONTEXT) {
|
||||
fprintf(stderr, "no context 0x%x\n", eglGetError());
|
||||
return 1;
|
||||
}
|
||||
struct wl_egl_window* ew = wl_egl_window_create(surf, g_w, g_h);
|
||||
EGLSurface es = eglCreatePlatformWindowSurface(ed, cfg, ew, NULL);
|
||||
eglMakeCurrent(ed, es, es, ctx);
|
||||
eglSwapInterval(ed, 1);
|
||||
|
||||
const char* vs = "#version 330 core\nlayout(location=0) in vec2 p; uniform float t; out vec2 uv;\n"
|
||||
"void main(){ uv=p*0.5+0.5; gl_Position=vec4(p*0.4+vec2(0.5*sin(t),0.0),0.0,1.0);}";
|
||||
const char* fs = "#version 330 core\nin vec2 uv; uniform int iters; uniform float t; out vec4 c;\n"
|
||||
"void main(){ float a=uv.x; for(int i=0;i<iters;++i){ a=fract(a*1.0001+0.37+uv.y); }\n"
|
||||
" c=vec4(uv, 0.5+0.5*sin(t)+a*1e-6, 1.0);}";
|
||||
GLuint prog = glCreateProgram();
|
||||
glAttachShader(prog, sh(GL_VERTEX_SHADER, vs));
|
||||
glAttachShader(prog, sh(GL_FRAGMENT_SHADER, fs));
|
||||
glLinkProgram(prog);
|
||||
GLint ok = 0;
|
||||
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
|
||||
if (!ok) {
|
||||
fprintf(stderr, "link failed\n");
|
||||
return 2;
|
||||
}
|
||||
GLint uIt = glGetUniformLocation(prog, "iters"), uT = glGetUniformLocation(prog, "t");
|
||||
GLuint vao, vbo;
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
const float q[] = {-1, -1, 1, -1, -1, 1, 1, 1};
|
||||
glGenBuffers(1, &vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof q, q, GL_STATIC_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
|
||||
const double t0 = now_ms();
|
||||
double last = t0, maxGap = 0;
|
||||
long frames = 0, gaps100 = 0;
|
||||
printf("[%s] start %.0f renderer=%s hangAt=%d N=%d every=%d iters=%ld\n", tag, t0, glGetString(GL_RENDERER), hangAt,
|
||||
hangN, every, iters);
|
||||
while (!g_closed && now_ms() - t0 < seconds * 1000.0) {
|
||||
wl_display_dispatch_pending(dpy);
|
||||
const double t = (now_ms() - t0) / 1000.0;
|
||||
int hang = 0;
|
||||
if (hangAt >= 0 && frames >= hangAt) {
|
||||
long rel = frames - hangAt;
|
||||
if (every > 0) rel %= every;
|
||||
hang = rel < hangN;
|
||||
}
|
||||
glViewport(0, 0, g_w, g_h);
|
||||
glClearColor(0.5f + 0.5f * (float)sin(t * 3.0), 0.2f, 0.4f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glUseProgram(prog);
|
||||
glUniform1i(uIt, hang ? (int)iters : 0);
|
||||
glUniform1f(uT, (float)t);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
if (hang) printf("[%s] HANG submitted frame %ld at %.0f\n", tag, frames, now_ms());
|
||||
if (finish) glFinish();
|
||||
if (!eglSwapBuffers(ed, es)) printf("[%s] swap failed 0x%x at %.0f\n", tag, eglGetError(), now_ms());
|
||||
const GLenum rs = glGetError();
|
||||
if (rs != GL_NO_ERROR) printf("[%s] glGetError 0x%x at %.0f\n", tag, rs, now_ms());
|
||||
const double nowv = now_ms(), gap = nowv - last;
|
||||
last = nowv;
|
||||
++frames;
|
||||
if (gap > maxGap) maxGap = gap;
|
||||
if (gap > 100.0) {
|
||||
++gaps100;
|
||||
printf("[%s] GAP %.0f ms ending at %.0f (frame %ld)\n", tag, gap, nowv, frames);
|
||||
}
|
||||
if (frames % 120 == 0)
|
||||
printf("[%s] frame %ld t=%.1fs fps=%.1f\n", tag, frames, (nowv - t0) / 1e3, frames * 1e3 / (nowv - t0));
|
||||
}
|
||||
const double total = now_ms() - t0;
|
||||
printf("[%s] END frames=%ld fps=%.1f maxGap=%.0f ms gaps>100ms=%ld\n", tag, frames, frames * 1e3 / total, maxGap,
|
||||
gaps100);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// MobileGL - tools/anland-mobilegl-plasma/scripts/probes/texfmt.c
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
/* texfmt: does a texture keep its alpha? One opaque red texel and one transparent texel per
|
||||
* format, sampled across a 64x64 ES3 pbuffer cleared white with SRC_ALPHA blending. Expect the
|
||||
* left half red and the right half WHITE; a black right half means the alpha was lost. */
|
||||
#include <EGL/egl.h>
|
||||
#include <GLES3/gl3.h>
|
||||
#include <GLES2/gl2ext.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#define P(T, n) T n = (T)eglGetProcAddress(#n)
|
||||
static const char* vs =
|
||||
"#version 300 es\nlayout(location=0) in vec2 p; out vec2 uv; void main(){uv=p*0.5+0.5; gl_Position=vec4(p,0,1);}";
|
||||
static const char* fs =
|
||||
"#version 300 es\nprecision mediump float; uniform sampler2D t; uniform int mode; in vec2 uv; out vec4 o;"
|
||||
"void main(){vec4 c=texture(t,uv); o = mode==1 ? vec4(1.0,0.0,0.0,c.a) : (mode==2 ? vec4(c.r,0.0,0.0,c.r) : c);}";
|
||||
int main(void) {
|
||||
EGLDisplay d = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
eglInitialize(d, 0, 0);
|
||||
eglBindAPI(EGL_OPENGL_ES_API);
|
||||
EGLint ca[] = {EGL_SURFACE_TYPE,
|
||||
EGL_PBUFFER_BIT,
|
||||
EGL_RENDERABLE_TYPE,
|
||||
EGL_OPENGL_ES3_BIT,
|
||||
EGL_RED_SIZE,
|
||||
8,
|
||||
EGL_GREEN_SIZE,
|
||||
8,
|
||||
EGL_BLUE_SIZE,
|
||||
8,
|
||||
EGL_ALPHA_SIZE,
|
||||
8,
|
||||
EGL_NONE};
|
||||
EGLConfig c;
|
||||
EGLint n;
|
||||
eglChooseConfig(d, ca, &c, 1, &n);
|
||||
EGLint xa[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
|
||||
EGLContext x = eglCreateContext(d, c, EGL_NO_CONTEXT, xa);
|
||||
EGLint pa[] = {EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE};
|
||||
EGLSurface s = eglCreatePbufferSurface(d, c, pa);
|
||||
eglMakeCurrent(d, s, s, x);
|
||||
P(PFNGLCREATESHADERPROC, glCreateShader);
|
||||
P(PFNGLSHADERSOURCEPROC, glShaderSource);
|
||||
P(PFNGLCOMPILESHADERPROC, glCompileShader);
|
||||
P(PFNGLCREATEPROGRAMPROC, glCreateProgram);
|
||||
P(PFNGLATTACHSHADERPROC, glAttachShader);
|
||||
P(PFNGLLINKPROGRAMPROC, glLinkProgram);
|
||||
P(PFNGLUSEPROGRAMPROC, glUseProgram);
|
||||
P(PFNGLGETUNIFORMLOCATIONPROC, glGetUniformLocation);
|
||||
P(PFNGLUNIFORM1IPROC, glUniform1i);
|
||||
P(PFNGLGENBUFFERSPROC, glGenBuffers);
|
||||
P(PFNGLBINDBUFFERPROC, glBindBuffer);
|
||||
P(PFNGLBUFFERDATAPROC, glBufferData);
|
||||
P(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer);
|
||||
P(PFNGLENABLEVERTEXATTRIBARRAYPROC, glEnableVertexAttribArray);
|
||||
P(PFNGLDRAWARRAYSPROC, glDrawArrays);
|
||||
P(PFNGLCLEARCOLORPROC, glClearColor);
|
||||
P(PFNGLCLEARPROC, glClear);
|
||||
P(PFNGLREADPIXELSPROC, glReadPixels);
|
||||
P(PFNGLVIEWPORTPROC, glViewport);
|
||||
P(PFNGLGENTEXTURESPROC, glGenTextures);
|
||||
P(PFNGLBINDTEXTUREPROC, glBindTexture);
|
||||
P(PFNGLTEXIMAGE2DPROC, glTexImage2D);
|
||||
P(PFNGLTEXPARAMETERIPROC, glTexParameteri);
|
||||
P(PFNGLENABLEPROC, glEnable);
|
||||
P(PFNGLBLENDFUNCPROC, glBlendFunc);
|
||||
P(PFNGLGETERRORPROC, glGetError);
|
||||
P(PFNGLTEXSTORAGE2DPROC, glTexStorage2D);
|
||||
P(PFNGLTEXSUBIMAGE2DPROC, glTexSubImage2D);
|
||||
P(PFNGLGETSTRINGPROC, glGetString);
|
||||
printf("GL_VERSION %s\n", glGetString(GL_VERSION));
|
||||
printf("BGRA ext: %s\n",
|
||||
strstr((const char*)glGetString(GL_EXTENSIONS), "GL_EXT_texture_format_BGRA8888") ? "yes" : "no");
|
||||
GLuint v = glCreateShader(GL_VERTEX_SHADER), f = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glShaderSource(v, 1, &vs, 0);
|
||||
glCompileShader(v);
|
||||
glShaderSource(f, 1, &fs, 0);
|
||||
glCompileShader(f);
|
||||
GLuint pr = glCreateProgram();
|
||||
glAttachShader(pr, v);
|
||||
glAttachShader(pr, f);
|
||||
glLinkProgram(pr);
|
||||
glUseProgram(pr);
|
||||
float q[] = {-1, -1, 1, -1, -1, 1, 1, 1};
|
||||
GLuint vbo;
|
||||
glGenBuffers(1, &vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof q, q, GL_STATIC_DRAW);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, 0);
|
||||
glEnableVertexAttribArray(0);
|
||||
GLint lm = glGetUniformLocation(pr, "mode");
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
struct {
|
||||
const char* name;
|
||||
GLint ifmt;
|
||||
GLenum fmt;
|
||||
int bpp;
|
||||
int mode;
|
||||
int storage;
|
||||
GLenum sized;
|
||||
unsigned char px[8];
|
||||
} cases[] = {
|
||||
{"RGBA/TexImage", GL_RGBA, GL_RGBA, 4, 0, 0, 0, {255, 0, 0, 255, 0, 0, 0, 0}},
|
||||
{"BGRA_EXT/TexImage", GL_BGRA_EXT, GL_BGRA_EXT, 4, 0, 0, 0, {0, 0, 255, 255, 0, 0, 0, 0}},
|
||||
{"BGRA8_EXT/TexStorage+Sub", 0, GL_BGRA_EXT, 4, 0, 1, GL_BGRA8_EXT, {0, 0, 255, 255, 0, 0, 0, 0}},
|
||||
{"RGBA8/TexStorage+Sub", 0, GL_RGBA, 4, 0, 1, GL_RGBA8, {255, 0, 0, 255, 0, 0, 0, 0}},
|
||||
{"ALPHA", GL_ALPHA, GL_ALPHA, 1, 1, 0, 0, {255, 0}},
|
||||
{"LUMINANCE_ALPHA", GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, 2, 1, 0, 0, {255, 255, 0, 0}},
|
||||
{"R8/TexStorage+Sub", 0, GL_RED, 1, 2, 1, GL_R8, {255, 0}},
|
||||
};
|
||||
for (unsigned i = 0; i < sizeof cases / sizeof cases[0]; ++i) {
|
||||
GLuint t;
|
||||
glGenTextures(1, &t);
|
||||
glBindTexture(GL_TEXTURE_2D, t);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
while (glGetError()) {}
|
||||
if (cases[i].storage) {
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, cases[i].sized, 2, 1);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, cases[i].fmt, GL_UNSIGNED_BYTE, cases[i].px);
|
||||
} else {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, cases[i].ifmt, 2, 1, 0, cases[i].fmt, GL_UNSIGNED_BYTE, cases[i].px);
|
||||
}
|
||||
GLenum err = glGetError();
|
||||
glUniform1i(lm, cases[i].mode);
|
||||
glViewport(0, 0, 64, 64);
|
||||
glClearColor(1, 1, 1, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
unsigned char l[4], r[4];
|
||||
glReadPixels(16, 32, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, l);
|
||||
glReadPixels(48, 32, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, r);
|
||||
const int ok = l[0] > 200 && l[1] < 60 && l[2] < 60 && r[0] > 200 && r[1] > 200 && r[2] > 200;
|
||||
printf("%-26s err=0x%04x left=%3d,%3d,%3d right=%3d,%3d,%3d %s\n", cases[i].name, err, l[0], l[1], l[2], r[0],
|
||||
r[1], r[2], ok ? "ok" : "BAD");
|
||||
}
|
||||
/* ANGLE's emulation of ALPHA8 / LUMINANCE_ALPHA8 on a desktop-GL driver: R8 / RG8 + swizzle. */
|
||||
P(PFNGLTEXPARAMETERIPROC, glTexParameteri2);
|
||||
struct {
|
||||
const char* name;
|
||||
GLenum sized, fmt;
|
||||
int bpp;
|
||||
GLint sw[4];
|
||||
unsigned char px[4];
|
||||
unsigned char wantL[3], wantR[3];
|
||||
} sz[] = {
|
||||
{"R8 swizzled as ALPHA8",
|
||||
GL_R8,
|
||||
GL_RED,
|
||||
1,
|
||||
{GL_ZERO, GL_ZERO, GL_ZERO, GL_RED},
|
||||
{255, 0},
|
||||
{0, 0, 0},
|
||||
{255, 255, 255}},
|
||||
{"RG8 swizzled as LUM_ALPHA",
|
||||
GL_RG8,
|
||||
GL_RG,
|
||||
2,
|
||||
{GL_RED, GL_RED, GL_RED, GL_GREEN},
|
||||
{255, 255, 0, 0},
|
||||
{255, 255, 255},
|
||||
{255, 255, 255}},
|
||||
{"RG8 as LUM_ALPHA (lum 0)",
|
||||
GL_RG8,
|
||||
GL_RG,
|
||||
2,
|
||||
{GL_RED, GL_RED, GL_RED, GL_GREEN},
|
||||
{0, 255, 0, 0},
|
||||
{0, 0, 0},
|
||||
{255, 255, 255}},
|
||||
};
|
||||
glUniform1i(lm, 0);
|
||||
for (unsigned i = 0; i < sizeof sz / sizeof sz[0]; ++i) {
|
||||
GLuint t;
|
||||
glGenTextures(1, &t);
|
||||
glBindTexture(GL_TEXTURE_2D, t);
|
||||
glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, sz[i].sized, 2, 1);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, sz[i].fmt, GL_UNSIGNED_BYTE, sz[i].px);
|
||||
glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, sz[i].sw[0]);
|
||||
glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, sz[i].sw[1]);
|
||||
glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, sz[i].sw[2]);
|
||||
glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, sz[i].sw[3]);
|
||||
GLenum err = glGetError();
|
||||
glClearColor(1, 1, 1, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
unsigned char l[4], r[4];
|
||||
glReadPixels(16, 32, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, l);
|
||||
glReadPixels(48, 32, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, r);
|
||||
int ok = 1;
|
||||
for (int k = 0; k < 3; ++k)
|
||||
ok = ok && abs(l[k] - sz[i].wantL[k]) < 40 && abs(r[k] - sz[i].wantR[k]) < 40;
|
||||
printf("%-26s err=0x%04x left=%3d,%3d,%3d right=%3d,%3d,%3d %s\n", sz[i].name, err, l[0], l[1], l[2], r[0],
|
||||
r[1], r[2], ok ? "ok" : "BAD");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
// MobileGL - tools/anland-mobilegl-plasma/scripts/probes/yuvprobe.c
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
// yuvprobe.c - pixel check of YUV dma-buf sampling through MobileGL (GL_OES_EGL_image_external).
|
||||
//
|
||||
// gcc -O1 yuvprobe.c -o /usr/local/bin/mgl-yuvprobe -lEGL -lGLESv2 -lgbm
|
||||
// mgrun offscreen mgl-yuvprobe # exit 0 = every check passed
|
||||
//
|
||||
// What it does, in an ES 3.0 context on a pbuffer:
|
||||
// 1. FOREIGN NV12 (and P010): a dma-buf from /dev/dma_heap/system - a buffer the MobileGL server
|
||||
// did not allocate, as a video decoder's would be - with padded pitches and a gap before the
|
||||
// chroma plane, filled with four vertical bars (red, green, blue, white) in BT.709 narrow
|
||||
// range. Imported with eglCreateImage(EGL_LINUX_DMA_BUF_EXT) and its colour hints, bound to
|
||||
// GL_TEXTURE_EXTERNAL_OES, drawn through a samplerExternalOES shader into an RGBA8 FBO and read
|
||||
// back: each bar must come out its colour. The buffer is then rewritten with the bars in
|
||||
// reverse order and, after a native fence (what ends a frame for an offscreen producer), drawn
|
||||
// again: the new order must show (the server refreshes a foreign buffer at each frame).
|
||||
// 2. GBM NV12: gbm_bo_create(NV12) through the mobilegl GBM backend (a server-allocated YUV
|
||||
// buffer, zero-copy), its planes printed, imported and sampled once (content undefined; the
|
||||
// check is that every call succeeds).
|
||||
// Also prints the dma-buf formats/modifiers EGL lists for NV12/P010.
|
||||
#define _GNU_SOURCE
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#include <GLES3/gl3.h>
|
||||
#include <GLES2/gl2ext.h>
|
||||
#include <fcntl.h>
|
||||
#include <gbm.h>
|
||||
#include <linux/dma-buf.h>
|
||||
#include <linux/dma-heap.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef DRM_FORMAT_NV12
|
||||
#define DRM_FORMAT_NV12 0x3231564E
|
||||
#endif
|
||||
#ifndef DRM_FORMAT_P010
|
||||
#define DRM_FORMAT_P010 0x30313050
|
||||
#endif
|
||||
|
||||
static int g_failures = 0;
|
||||
#define CHECK(cond, ...) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
printf("FAIL: " __VA_ARGS__); \
|
||||
printf("\n"); \
|
||||
++g_failures; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static PFNEGLCREATEIMAGEKHRPROC pCreateImage;
|
||||
static PFNEGLDESTROYIMAGEKHRPROC pDestroyImage;
|
||||
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC pTargetTexture;
|
||||
static PFNEGLQUERYDMABUFFORMATSEXTPROC pQueryFormats;
|
||||
static PFNEGLQUERYDMABUFMODIFIERSEXTPROC pQueryModifiers;
|
||||
static PFNEGLCREATESYNCKHRPROC pCreateSync;
|
||||
static PFNEGLDESTROYSYNCKHRPROC pDestroySync;
|
||||
static EGLDisplay g_display;
|
||||
|
||||
enum {
|
||||
W = 256,
|
||||
H = 128,
|
||||
BARS = 4
|
||||
};
|
||||
static const unsigned char kBars[BARS][3] = {{255, 0, 0}, {0, 255, 0}, {0, 0, 255}, {255, 255, 255}};
|
||||
|
||||
// RGB -> BT.709 narrow-range codes at `bits` (8 or 10).
|
||||
static void Encode709(const unsigned char rgb[3], int bits, int out[3]) {
|
||||
const double kr = 0.2126, kb = 0.0722, kg = 1.0 - kr - kb;
|
||||
const double r = rgb[0] / 255.0, g = rgb[1] / 255.0, b = rgb[2] / 255.0;
|
||||
const double y = kr * r + kg * g + kb * b;
|
||||
const double cb = (b - y) / (2.0 * (1.0 - kb)), cr = (r - y) / (2.0 * (1.0 - kr));
|
||||
const double scale = (double)(1 << (bits - 8));
|
||||
out[0] = (int)lround((16.0 + 219.0 * y) * scale);
|
||||
out[1] = (int)lround((128.0 + 224.0 * cb) * scale);
|
||||
out[2] = (int)lround((128.0 + 224.0 * cr) * scale);
|
||||
}
|
||||
|
||||
// Writes the bars (`reverse`d or not) into a semi-planar buffer: 1 byte samples, or P010's 16-bit
|
||||
// samples with the 10 significant bits high.
|
||||
static void FillBars(uint8_t* base, uint32_t pitchY, uint32_t offsetC, uint32_t pitchC, int bytes, int reverse) {
|
||||
const int bits = bytes == 1 ? 8 : 10;
|
||||
for (int y = 0; y < H; ++y) {
|
||||
for (int x = 0; x < W; ++x) {
|
||||
int bar = x * BARS / W;
|
||||
if (reverse) bar = BARS - 1 - bar;
|
||||
int code[3];
|
||||
Encode709(kBars[bar], bits, code);
|
||||
uint8_t* py = base + (size_t)y * pitchY + (size_t)x * bytes;
|
||||
if (bytes == 1)
|
||||
py[0] = (uint8_t)code[0];
|
||||
else
|
||||
*(uint16_t*)py = (uint16_t)(code[0] << 6);
|
||||
if ((x & 1) == 0 && (y & 1) == 0) {
|
||||
uint8_t* pc = base + offsetC + (size_t)(y / 2) * pitchC + (size_t)(x / 2) * 2 * bytes;
|
||||
if (bytes == 1) {
|
||||
pc[0] = (uint8_t)code[1];
|
||||
pc[1] = (uint8_t)code[2];
|
||||
} else {
|
||||
((uint16_t*)pc)[0] = (uint16_t)(code[1] << 6);
|
||||
((uint16_t*)pc)[1] = (uint16_t)(code[2] << 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static GLuint Compile(GLenum type, const char* source) {
|
||||
GLuint shader = glCreateShader(type);
|
||||
glShaderSource(shader, 1, &source, NULL);
|
||||
glCompileShader(shader);
|
||||
GLint ok = 0;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
|
||||
if (!ok) {
|
||||
char log[2048];
|
||||
glGetShaderInfoLog(shader, sizeof(log), NULL, log);
|
||||
printf("shader: %s\n", log);
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
static GLuint g_program, g_fbo, g_rgba;
|
||||
|
||||
static void SetUpDraw(void) {
|
||||
const char* vs = "#version 300 es\n"
|
||||
"out vec2 uv;\n"
|
||||
"void main() {\n"
|
||||
" vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));\n"
|
||||
" gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);\n"
|
||||
" uv = p;\n"
|
||||
"}\n";
|
||||
const char* fs = "#version 300 es\n"
|
||||
"#extension GL_OES_EGL_image_external_essl3 : require\n"
|
||||
"precision mediump float;\n"
|
||||
"uniform samplerExternalOES tex;\n"
|
||||
"in vec2 uv;\n"
|
||||
"out vec4 color;\n"
|
||||
"void main() { color = texture(tex, uv); }\n";
|
||||
g_program = glCreateProgram();
|
||||
glAttachShader(g_program, Compile(GL_VERTEX_SHADER, vs));
|
||||
glAttachShader(g_program, Compile(GL_FRAGMENT_SHADER, fs));
|
||||
glLinkProgram(g_program);
|
||||
GLint linked = 0;
|
||||
glGetProgramiv(g_program, GL_LINK_STATUS, &linked);
|
||||
CHECK(linked, "the samplerExternalOES program did not link");
|
||||
GLint type = 0, size = 0;
|
||||
char name[64];
|
||||
glGetActiveUniform(g_program, 0, sizeof(name), NULL, &size, (GLenum*)&type, name);
|
||||
printf("uniform 0: %s type 0x%04x\n", name, type);
|
||||
CHECK(type == GL_SAMPLER_EXTERNAL_OES, "the sampler reports type 0x%04x, not GL_SAMPLER_EXTERNAL_OES", type);
|
||||
glGenTextures(1, &g_rgba);
|
||||
glBindTexture(GL_TEXTURE_2D, g_rgba);
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, W, H);
|
||||
glGenFramebuffers(1, &g_fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, g_fbo);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, g_rgba, 0);
|
||||
CHECK(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, "the readback FBO is incomplete");
|
||||
GLuint vao;
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
}
|
||||
|
||||
// Draws the external texture `texture` and checks each bar's center (row `y` of the image).
|
||||
static void DrawAndCheck(GLuint texture, const char* what, int reverse, int tolerance) {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, g_fbo);
|
||||
glViewport(0, 0, W, H);
|
||||
glUseProgram(g_program);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture);
|
||||
glUniform1i(glGetUniformLocation(g_program, "tex"), 0);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
static unsigned char pixels[W * H * 4];
|
||||
glReadPixels(0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||
GLenum error = glGetError();
|
||||
CHECK(error == GL_NO_ERROR, "%s: GL error 0x%04x", what, error);
|
||||
if (tolerance < 0) {
|
||||
printf("%s: center pixel %d %d %d %d (content undefined)\n", what, pixels[(H / 2 * W + W / 2) * 4],
|
||||
pixels[(H / 2 * W + W / 2) * 4 + 1], pixels[(H / 2 * W + W / 2) * 4 + 2],
|
||||
pixels[(H / 2 * W + W / 2) * 4 + 3]);
|
||||
return;
|
||||
}
|
||||
// Rows: GL row 0 is the image's first row (the uv mapping above), and the bars are vertical,
|
||||
// so any row does; take two to catch a broken chroma row pitch.
|
||||
const int rows[2] = {H / 4, (3 * H) / 4};
|
||||
for (int r = 0; r < 2; ++r) {
|
||||
for (int bar = 0; bar < BARS; ++bar) {
|
||||
const int x = bar * (W / BARS) + W / (2 * BARS);
|
||||
const unsigned char* p = &pixels[(rows[r] * W + x) * 4];
|
||||
const unsigned char* want = kBars[reverse ? BARS - 1 - bar : bar];
|
||||
int worst = 0;
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
int d = abs((int)p[c] - (int)want[c]);
|
||||
if (d > worst) worst = d;
|
||||
}
|
||||
printf("%s: row %d bar %d -> %3d %3d %3d %3d (want %3d %3d %3d)%s\n", what, rows[r], bar, p[0], p[1], p[2],
|
||||
p[3], want[0], want[1], want[2], worst > tolerance ? " <-- off" : "");
|
||||
CHECK(worst <= tolerance, "%s: bar %d is off by %d", what, bar, worst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int AllocateDmaHeap(size_t size) {
|
||||
int heap = open("/dev/dma_heap/system", O_RDWR | O_CLOEXEC);
|
||||
if (heap < 0) {
|
||||
perror("open /dev/dma_heap/system");
|
||||
return -1;
|
||||
}
|
||||
struct dma_heap_allocation_data data = {.len = size, .fd_flags = O_RDWR | O_CLOEXEC};
|
||||
if (ioctl(heap, DMA_HEAP_IOCTL_ALLOC, &data) != 0) {
|
||||
perror("DMA_HEAP_IOCTL_ALLOC");
|
||||
close(heap);
|
||||
return -1;
|
||||
}
|
||||
close(heap);
|
||||
return (int)data.fd;
|
||||
}
|
||||
|
||||
static void Sync(int fd, int start) {
|
||||
struct dma_buf_sync sync = {.flags = DMA_BUF_SYNC_WRITE | (start ? DMA_BUF_SYNC_START : DMA_BUF_SYNC_END)};
|
||||
ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync);
|
||||
}
|
||||
|
||||
static EGLImageKHR Import(int fd, uint32_t fourcc, uint32_t pitchY, uint32_t offsetC, uint32_t pitchC, int modifier) {
|
||||
EGLint attribs[64];
|
||||
int n = 0;
|
||||
#define A(k, v) (attribs[n++] = (k), attribs[n++] = (EGLint)(v))
|
||||
A(EGL_WIDTH, W);
|
||||
A(EGL_HEIGHT, H);
|
||||
A(EGL_LINUX_DRM_FOURCC_EXT, fourcc);
|
||||
A(EGL_DMA_BUF_PLANE0_FD_EXT, fd);
|
||||
A(EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0);
|
||||
A(EGL_DMA_BUF_PLANE0_PITCH_EXT, pitchY);
|
||||
A(EGL_DMA_BUF_PLANE1_FD_EXT, fd);
|
||||
A(EGL_DMA_BUF_PLANE1_OFFSET_EXT, offsetC);
|
||||
A(EGL_DMA_BUF_PLANE1_PITCH_EXT, pitchC);
|
||||
if (modifier) {
|
||||
A(EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, 0);
|
||||
A(EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, 0);
|
||||
A(EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, 0);
|
||||
A(EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, 0);
|
||||
}
|
||||
A(EGL_YUV_COLOR_SPACE_HINT_EXT, EGL_ITU_REC709_EXT);
|
||||
A(EGL_SAMPLE_RANGE_HINT_EXT, EGL_YUV_NARROW_RANGE_EXT);
|
||||
attribs[n] = EGL_NONE;
|
||||
#undef A
|
||||
EGLImageKHR image = pCreateImage(g_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
|
||||
if (image == EGL_NO_IMAGE_KHR) printf("eglCreateImage failed: 0x%04x\n", eglGetError());
|
||||
return image;
|
||||
}
|
||||
|
||||
static GLuint ExternalTexture(EGLImageKHR image) {
|
||||
GLuint texture;
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture);
|
||||
pTargetTexture(GL_TEXTURE_EXTERNAL_OES, image);
|
||||
GLenum error = glGetError();
|
||||
CHECK(error == GL_NO_ERROR, "glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES) -> 0x%04x", error);
|
||||
GLint bound = 0;
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &bound);
|
||||
CHECK((GLuint)bound == texture, "GL_TEXTURE_BINDING_EXTERNAL_OES reads %d", bound);
|
||||
return texture;
|
||||
}
|
||||
|
||||
// A frame boundary as an offscreen producer makes one: a native fence.
|
||||
static void EndFrame(void) {
|
||||
EGLint attribs[] = {EGL_NONE};
|
||||
EGLSyncKHR sync = pCreateSync(g_display, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
|
||||
CHECK(sync != EGL_NO_SYNC_KHR, "eglCreateSync(native fence) failed");
|
||||
if (sync != EGL_NO_SYNC_KHR) pDestroySync(g_display, sync);
|
||||
}
|
||||
|
||||
static void ForeignTest(uint32_t fourcc, int bytes, int tolerance) {
|
||||
const char* what = fourcc == DRM_FORMAT_NV12 ? "foreign NV12" : "foreign P010";
|
||||
// Padded pitches and a gap before the chroma plane, as decoders lay buffers out.
|
||||
const uint32_t pitchY = W * bytes + 64, pitchC = W * bytes + 128;
|
||||
const uint32_t offsetC = pitchY * (H + 16);
|
||||
const size_t size = offsetC + (size_t)pitchC * (H / 2) + 4096;
|
||||
int fd = AllocateDmaHeap(size);
|
||||
if (fd < 0) {
|
||||
CHECK(0, "%s: no dma-buf to test with", what);
|
||||
return;
|
||||
}
|
||||
uint8_t* map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
CHECK(map != MAP_FAILED, "%s: mmap failed", what);
|
||||
if (map == MAP_FAILED) return;
|
||||
Sync(fd, 1);
|
||||
FillBars(map, pitchY, offsetC, pitchC, bytes, 0);
|
||||
Sync(fd, 0);
|
||||
EGLImageKHR image = Import(fd, fourcc, pitchY, offsetC, pitchC, fourcc == DRM_FORMAT_NV12);
|
||||
CHECK(image != EGL_NO_IMAGE_KHR, "%s: import refused", what);
|
||||
if (image == EGL_NO_IMAGE_KHR) return;
|
||||
GLuint texture = ExternalTexture(image);
|
||||
DrawAndCheck(texture, what, 0, tolerance);
|
||||
EndFrame();
|
||||
// New content, same buffer: shows after the frame boundary.
|
||||
Sync(fd, 1);
|
||||
FillBars(map, pitchY, offsetC, pitchC, bytes, 1);
|
||||
Sync(fd, 0);
|
||||
EndFrame();
|
||||
DrawAndCheck(texture, what, 1, tolerance);
|
||||
glDeleteTextures(1, &texture);
|
||||
pDestroyImage(g_display, image);
|
||||
munmap(map, size);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void GbmTest(void) {
|
||||
int node = open("/dev/dri/renderD128", O_RDWR | O_CLOEXEC);
|
||||
if (node < 0) node = open("/dev/null", O_RDWR | O_CLOEXEC);
|
||||
struct gbm_device* gbm = gbm_create_device(node);
|
||||
if (gbm == NULL) {
|
||||
CHECK(0, "gbm_create_device failed");
|
||||
return;
|
||||
}
|
||||
printf("gbm backend: %s\n", gbm_device_get_backend_name(gbm));
|
||||
const int supported = gbm_device_is_format_supported(gbm, DRM_FORMAT_NV12, 0);
|
||||
printf("gbm NV12 supported: %d, P010: %d\n", supported, gbm_device_is_format_supported(gbm, DRM_FORMAT_P010, 0));
|
||||
CHECK(supported == 1, "gbm does not support NV12");
|
||||
struct gbm_bo* bo = gbm_bo_create(gbm, W, H, DRM_FORMAT_NV12, 0);
|
||||
CHECK(bo != NULL, "gbm_bo_create(NV12) failed");
|
||||
if (bo != NULL) {
|
||||
const int planes = gbm_bo_get_plane_count(bo);
|
||||
printf("gbm NV12 bo: planes %d, stride %u/%u, offset %u/%u, modifier 0x%llx\n", planes,
|
||||
gbm_bo_get_stride_for_plane(bo, 0), gbm_bo_get_stride_for_plane(bo, 1), gbm_bo_get_offset(bo, 0),
|
||||
gbm_bo_get_offset(bo, 1), (unsigned long long)gbm_bo_get_modifier(bo));
|
||||
CHECK(planes == 2, "the NV12 bo has %d planes", planes);
|
||||
int fd = gbm_bo_get_fd(bo);
|
||||
EGLImageKHR image = Import(fd, DRM_FORMAT_NV12, gbm_bo_get_stride_for_plane(bo, 0), gbm_bo_get_offset(bo, 1),
|
||||
gbm_bo_get_stride_for_plane(bo, 1), 0);
|
||||
CHECK(image != EGL_NO_IMAGE_KHR, "the server's own NV12 buffer was not imported");
|
||||
if (image != EGL_NO_IMAGE_KHR) {
|
||||
GLuint texture = ExternalTexture(image);
|
||||
DrawAndCheck(texture, "gbm NV12", 0, -1);
|
||||
glDeleteTextures(1, &texture);
|
||||
pDestroyImage(g_display, image);
|
||||
}
|
||||
close(fd);
|
||||
gbm_bo_destroy(bo);
|
||||
}
|
||||
gbm_device_destroy(gbm);
|
||||
close(node);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
g_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
EGLint major, minor;
|
||||
if (!eglInitialize(g_display, &major, &minor)) {
|
||||
printf("FAIL: eglInitialize\n");
|
||||
return 2;
|
||||
}
|
||||
const char* eglExtensions = eglQueryString(g_display, EGL_EXTENSIONS);
|
||||
printf("EGL %d.%d dma_buf_import: %s\n", major, minor,
|
||||
strstr(eglExtensions, "EGL_EXT_image_dma_buf_import") ? "yes" : "no");
|
||||
eglBindAPI(EGL_OPENGL_ES_API);
|
||||
const EGLint configAttribs[] = {EGL_SURFACE_TYPE,
|
||||
EGL_PBUFFER_BIT,
|
||||
EGL_RENDERABLE_TYPE,
|
||||
EGL_OPENGL_ES3_BIT,
|
||||
EGL_RED_SIZE,
|
||||
8,
|
||||
EGL_GREEN_SIZE,
|
||||
8,
|
||||
EGL_BLUE_SIZE,
|
||||
8,
|
||||
EGL_NONE};
|
||||
EGLConfig config;
|
||||
EGLint count = 0;
|
||||
eglChooseConfig(g_display, configAttribs, &config, 1, &count);
|
||||
if (count < 1) {
|
||||
printf("FAIL: no config\n");
|
||||
return 2;
|
||||
}
|
||||
const EGLint pbufferAttribs[] = {EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE};
|
||||
EGLSurface surface = eglCreatePbufferSurface(g_display, config, pbufferAttribs);
|
||||
const EGLint contextAttribs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE};
|
||||
EGLContext context = eglCreateContext(g_display, config, EGL_NO_CONTEXT, contextAttribs);
|
||||
if (context == EGL_NO_CONTEXT || !eglMakeCurrent(g_display, surface, surface, context)) {
|
||||
printf("FAIL: no ES 3 context\n");
|
||||
return 2;
|
||||
}
|
||||
pCreateImage = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
|
||||
pDestroyImage = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
|
||||
pTargetTexture = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
|
||||
pQueryFormats = (PFNEGLQUERYDMABUFFORMATSEXTPROC)eglGetProcAddress("eglQueryDmaBufFormatsEXT");
|
||||
pQueryModifiers = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT");
|
||||
pCreateSync = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR");
|
||||
pDestroySync = (PFNEGLDESTROYSYNCKHRPROC)eglGetProcAddress("eglDestroySyncKHR");
|
||||
printf("GL_RENDERER: %s\n", glGetString(GL_RENDERER));
|
||||
const char* glExtensions = (const char*)glGetString(GL_EXTENSIONS);
|
||||
const int external = strstr(glExtensions, "GL_OES_EGL_image_external_essl3") != NULL;
|
||||
printf("GL_OES_EGL_image_external_essl3: %s\n", external ? "yes" : "no");
|
||||
CHECK(external, "GL_OES_EGL_image_external_essl3 is not advertised");
|
||||
|
||||
EGLint formats[32], formatCount = 0;
|
||||
pQueryFormats(g_display, 32, formats, &formatCount);
|
||||
int nv12 = 0;
|
||||
for (int i = 0; i < formatCount; ++i) {
|
||||
printf("dma-buf format %.4s", (const char*)&formats[i]);
|
||||
EGLuint64KHR modifiers[4];
|
||||
EGLBoolean externalOnly[4];
|
||||
EGLint modifierCount = 0;
|
||||
pQueryModifiers(g_display, formats[i], 4, modifiers, externalOnly, &modifierCount);
|
||||
for (int m = 0; m < modifierCount; ++m)
|
||||
printf(" [modifier 0x%llx%s]", (unsigned long long)modifiers[m], externalOnly[m] ? " external-only" : "");
|
||||
printf("\n");
|
||||
nv12 |= formats[i] == (EGLint)DRM_FORMAT_NV12;
|
||||
}
|
||||
CHECK(nv12, "NV12 is not a listed dma-buf format");
|
||||
|
||||
SetUpDraw();
|
||||
ForeignTest(DRM_FORMAT_NV12, 1, 12);
|
||||
ForeignTest(DRM_FORMAT_P010, 2, 16);
|
||||
GbmTest();
|
||||
|
||||
printf("%s (%d failure(s))\n", g_failures ? "FAILED" : "PASSED", g_failures);
|
||||
eglMakeCurrent(g_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
eglTerminate(g_display);
|
||||
return g_failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/push-tools.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# push-tools.sh - install this skill's device- and container-side helpers on the phone.
|
||||
# device/*.sh -> $DEV_TOOLS (run as root: adb shell 'su -c "sh $DEV_TOOLS/<x>.sh"')
|
||||
# container/mgrun -> <container>/usr/local/bin/mgrun
|
||||
# container/kwprep.sh -> <container>/root/kwprep.sh
|
||||
# Needs the container running (its rootfs is mounted at $CT_ROOT only then).
|
||||
# Overwrites same-named files other sessions may be using in $DEV_TOOLS; check first on a shared phone.
|
||||
set -euo pipefail
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/env.sh"
|
||||
adb_ shell "mkdir -p $DEV_TOOLS"
|
||||
for f in "$here"/device/*.sh; do
|
||||
tr -d '\r' < "$f" > "$ANL_DIR/push.tmp"
|
||||
adb_ push "$ANL_DIR/push.tmp" "$DEV_TOOLS/$(basename "$f")" >/dev/null
|
||||
echo "pushed $DEV_TOOLS/$(basename "$f")"
|
||||
done
|
||||
for f in mgrun kwprep.sh; do
|
||||
tr -d '\r' < "$here/container/$f" > "$ANL_DIR/push.tmp"
|
||||
adb_ push "$ANL_DIR/push.tmp" "$DEV_TOOLS/$f" >/dev/null
|
||||
done
|
||||
rm -f "$ANL_DIR/push.tmp"
|
||||
adb_ shell "su -c 'cp $DEV_TOOLS/mgrun $CT_ROOT/usr/local/bin/mgrun && chmod 755 $CT_ROOT/usr/local/bin/mgrun && cp $DEV_TOOLS/kwprep.sh $CT_ROOT/root/kwprep.sh && echo installed mgrun kwprep.sh'"
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/rec.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# rec.sh <name> - record 3 s of the screen while opening Kickoff, extract frames inside the container
|
||||
# with ffmpeg, pull them to $ANL_DIR/af_<name>/ and build a 3x4 contact sheet $ANL_DIR/grid_<name>.png
|
||||
# (needs Python + Pillow on Windows). Use it to catch flicker/black frames a single screenshot misses.
|
||||
# Env: TAP="x y" Kickoff button position (default "69 1349" = bottom-left on the 2560x1600 tablet;
|
||||
# device specific). Note: anland grabs the touchscreen (EVIOCGRAB); if `input tap` does nothing, use
|
||||
# `input mouse tap x y`. Env CHANNEL (default rec).
|
||||
set -euo pipefail
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/env.sh"
|
||||
N=${1:?usage: rec.sh <name>}; TAP=${TAP:-69 1349}
|
||||
adb_ shell "su -c 'input keyevent 111; sleep 1.5; (screenrecord --time-limit 3 --bit-rate 20000000 $DEV_SHARE/$N.mp4 &); sleep 0.7; input tap $TAP; sleep 3; cp $DEV_SHARE/$N.mp4 $CT_ROOT/root/$N.mp4'"
|
||||
bash "$here/ct.sh" "${CHANNEL:-rec}" <<IN
|
||||
rm -rf /root/af_$N; mkdir -p /root/af_$N; ffmpeg -loglevel quiet -i /root/$N.mp4 -vf scale=1000:-1 -fps_mode passthrough /root/af_$N/f%03d.png; ls /root/af_$N | wc -l
|
||||
IN
|
||||
adb_ shell "su -c 'rm -rf $DEV_SHARE/af_$N; cp -r $CT_ROOT/root/af_$N $DEV_SHARE/ && chmod -R 777 $DEV_SHARE/af_$N'"
|
||||
rm -rf "$ANL_DIR/af_$N"; adb_ pull "$DEV_SHARE/af_$N" "$ANL_DIR/" >/dev/null
|
||||
python - "$ANL_DIR/af_$N" "$ANL_DIR/grid_$N.png" <<'P'
|
||||
import sys, os
|
||||
from PIL import Image
|
||||
d, out = sys.argv[1], sys.argv[2]
|
||||
fs = sorted(os.listdir(d)); n = len(fs)
|
||||
pick = [fs[min(n-1, int(i * n / 12))] for i in range(12)]
|
||||
ims = [Image.open(os.path.join(d, f)) for f in pick]
|
||||
w, h = ims[0].size; tw, th = w // 2, h // 2
|
||||
g = Image.new('RGB', (tw * 3, th * 4))
|
||||
for i, im in enumerate(ims): g.paste(im.resize((tw, th)), ((i % 3) * tw, (i // 3) * th))
|
||||
g.save(out); print(n, pick)
|
||||
P
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/shot.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# shot.sh <name> [count] [interval-s] - take <count> (default 2) phone screenshots <interval> s apart
|
||||
# (default 1), pull them to $ANL_DIR/<name>-<i>.png and print their md5. Identical hashes while an
|
||||
# animated client (glmark2, a video) runs mean the frames are NOT reaching the screen.
|
||||
set -euo pipefail
|
||||
. "$(dirname "$0")/env.sh"
|
||||
name=${1:?usage: shot.sh <name> [count] [interval]}; n=${2:-2}; dt=${3:-1}
|
||||
cmd=""; i=1
|
||||
while [ "$i" -le "$n" ]; do cmd="$cmd screencap -p $DEV_SHARE/$name-$i.png; sleep $dt;"; i=$((i+1)); done
|
||||
adb_ shell "su -c '$cmd'"
|
||||
i=1
|
||||
while [ "$i" -le "$n" ]; do
|
||||
adb_ pull "$DEV_SHARE/$name-$i.png" "$ANL_DIR/$name-$i.png" >/dev/null
|
||||
md5sum "$ANL_DIR/$name-$i.png"; i=$((i+1))
|
||||
done
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/sync-src.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# sync-src.sh - FALLBACK path only (the WSL cross build, xbuild.sh, replaced it). Pushes the files the
|
||||
# worktree changed since the container's source stamp into the container's MobileGL source tree
|
||||
# (/root/mobilegl-unified-src), keeping mtimes so the in-container ninja build stays incremental;
|
||||
# then build/install inside the container with anland's anland-build-client.sh
|
||||
# (producers/kde/Arch_v5/mobilegl-tools/, gcc, -j4, slow on the phone).
|
||||
# A full re-sync of the tree is anland's anland-sync-source.sh (MOBILEGL_REPO=<worktree>).
|
||||
set -euo pipefail
|
||||
. "$(dirname "$0")/env.sh"
|
||||
SRC=$CT_ROOT/root/mobilegl-unified-src
|
||||
STAMP=$(adb_ shell "su -c 'cat $SRC/.anland-build-stamp'" | tr -d '\r\n')
|
||||
cd "$MGL_WORKTREE"
|
||||
FILES=$( (git diff --name-only --diff-filter=d "$STAMP"; git ls-files --others --exclude-standard) \
|
||||
| grep -v -E '^(3rdparty/|build-android|\.anland-build-stamp|\.claude/)' | sort -u)
|
||||
echo "$(echo "$FILES" | wc -l) files since $STAMP"
|
||||
tar --force-local -czf "$ANL_DIR/src-delta.tgz" $FILES
|
||||
adb_ push "$ANL_DIR/src-delta.tgz" /data/local/tmp/src-delta.tgz >/dev/null
|
||||
HEAD=$(git rev-parse HEAD)
|
||||
adb_ shell "su -c 'tar xzf /data/local/tmp/src-delta.tgz -C $SRC && echo $HEAD > $SRC/.anland-build-stamp && echo synced'"
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/wsl-all.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# wsl-all.sh - run INSIDE WSL archlinux after wsl-test.sh configured $B: build every *Test target except
|
||||
# DirectVulkan*/Benchmark* and run the unit label. NEVER run the DirectVulkan integration tests
|
||||
# (they need a live device + manual intervention); never run two ctest suites at once.
|
||||
B=${B:-$HOME/mgl-anl-bld}
|
||||
cd $B
|
||||
T=$(ninja -t targets all 2>/dev/null | grep -oE "^[A-Za-z]+Test:" | tr -d : | sort -u | grep -v -E "^(DirectVulkan|Benchmark)" | tr "\n" " ")
|
||||
ninja -j14 MobileGLServer $T 2>&1 | grep -E "error:|FAILED" | head -10
|
||||
ctest -L unit -j10 --timeout 300 -E "DirectVulkan" 2>&1 | tail -25
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/wsl-test.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# wsl-test.sh <targets...> - run INSIDE WSL archlinux: configure (once) and build MobileGL unit-test
|
||||
# targets for the host (clang, disaggregated + in-process server ON). From Git Bash:
|
||||
# MSYS2_ARG_CONV_EXCL='*' wsl -d archlinux -- bash /mnt/c/<repo>/tools/anland-mobilegl-plasma/scripts/wsl-test.sh MultiSessionTest
|
||||
# then ctest in $B (see wsl-all.sh). Env: W (worktree, WSL path; default: derived from this file),
|
||||
# B (build dir, default ~/mgl-anl-bld). The Windows MSVC build of feat/disaggregated is broken; use this.
|
||||
set -e
|
||||
W=${W:-$(cd "$(dirname "$0")/../../../.." && pwd)}
|
||||
B=${B:-$HOME/mgl-anl-bld}
|
||||
if [ ! -f $B/build.ninja ]; then
|
||||
cmake -S $W -B $B -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DMOBILEGL_BUILD_TEST=ON -DMOBILEGL_BUILD_DISAGGREGATED=ON -DMOBILEGL_BUILD_DISAGGREGATED_INPROC=ON -DMOBILEGL_BUILD_STAMP=wsl
|
||||
fi
|
||||
cmake --build $B --target "$@" -j16
|
||||
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/x11-verify.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# x11-verify.sh <step> [...] - device verification of X11 on the GPU (reference/plan-x11-gpu.md):
|
||||
# Xwayland drawing with glamor on MobileGL, and GLX windows presented through
|
||||
# DRI3 + Present from shared images. Steps, in the order a full run takes them:
|
||||
#
|
||||
# preflight read-only: battery, stack status, Xwayland package, render node, xcb libs,
|
||||
# test apps, Xwayland's extensions and its GLX vendor
|
||||
# measure <label> [s] glxgears 1280x720 (GEARS_ARGS; vblank_mode=0) for s seconds (10): its fps, then CPU/GPU/dma-buf
|
||||
# counts (device/x11-measure.sh); also with MOBILEGL_GLX_PRESENT=readback
|
||||
# deploy-client xbuild.sh + xdeploy.sh of this worktree's client (MGL_XBUILD_DIR)
|
||||
# deploy-kwin kwin-stage.sh build from ANLAND_WORKTREE; waits for "== ALL DONE =="
|
||||
# deploy-server build-android.sh + cyc.sh x11-server (swaps the APK lib, RESTARTS the session)
|
||||
# check the evidence: Xwayland took glamor (journal), its MobileGL log, GLX's path
|
||||
# line, dma-buf counts in Xwayland/KWin, the server's import lines
|
||||
# apps X11 clients on screen: konsole and kate on Qt's xcb platform (glamor-drawn
|
||||
# core X), glxgears (GLX), xterm/xeyes when installed; two screenshots
|
||||
# stop kill the test apps
|
||||
#
|
||||
# Typical run: preflight; measure before; deploy-client; deploy-kwin; deploy-server; check; measure
|
||||
# after; apps; stop. Battery: every load step refuses below MIN_BATTERY (40) %.
|
||||
# Env: CHANNEL (ct.sh channel, default x11), ANLAND_WORKTREE (anland x11-gpu worktree, Windows
|
||||
# form), MGL_XBUILD_DIR (default /home/swung/mgl-xbuild-x11), MIN_BATTERY.
|
||||
set -euo pipefail
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/env.sh"
|
||||
CH=${CHANNEL:-x11}
|
||||
: "${MGL_XBUILD_DIR:=/home/swung/mgl-xbuild-x11}"
|
||||
: "${ANLAND_WORKTREE:=$(cygpath -m "$USERPROFILE" 2>/dev/null)/AndroidStudioProjects/anland/.claude/worktrees/x11-gpu}"
|
||||
export MGL_XBUILD_DIR ANLAND_WORKTREE
|
||||
|
||||
battery_ok() {
|
||||
local level
|
||||
level=$(adb_ shell "dumpsys battery" | sed -n 's/^ *level: *//p' | tr -d '\r')
|
||||
echo "battery: ${level}%"
|
||||
if [ "${level:-0}" -lt "${MIN_BATTERY:-40}" ]; then
|
||||
echo "below ${MIN_BATTERY:-40}%: stopping GPU-heavy work; charge the phone first" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
push_measure() {
|
||||
tr -d '\r' < "$here/device/x11-measure.sh" > "$ANL_DIR/x11-measure.sh"
|
||||
adb_ push "$ANL_DIR/x11-measure.sh" "$DEV_TOOLS/x11-measure.sh" >/dev/null
|
||||
}
|
||||
|
||||
step=${1:?usage: x11-verify.sh <preflight|measure|deploy-client|deploy-kwin|deploy-server|check|apps|stop>}
|
||||
shift
|
||||
case "$step" in
|
||||
preflight)
|
||||
battery_ok || true
|
||||
adb_ shell "su -c 'sh $DEV_TOOLS/status.sh'" || true
|
||||
bash "$here/ct.sh" "$CH" <<'IN'
|
||||
echo "== packages"; pacman -Q xorg-xwayland libxcb mesa-utils xorg-xdpyinfo qt6-base 2>&1 | sed 's/^/ /'
|
||||
echo "== render nodes"; ls -l /dev/dri/ 2>&1 | sed 's/^/ /'
|
||||
U=swung0x48; for n in /dev/dri/renderD*; do sudo -u $U test -r "$n" -a -w "$n" && echo " $U can open $n"; done
|
||||
echo "== KWin's thin node"; tr '\0' '\n' < /proc/$(pgrep -x kwin_wayland)/environ 2>/dev/null | grep -E "MOBILEGL_GBM_NODE|ANLAND_XWAYLAND_GLAMOR|GBM_BACKEND" | sed 's/^/ /'
|
||||
echo "== xcb libraries"; for l in libxcb-dri3.so.0 libxcb-present.so.0 libxcb-xfixes.so.0 libxcb-shm.so.0; do ls /usr/lib/$l >/dev/null 2>&1 && echo " $l ok" || echo " $l MISSING"; done
|
||||
echo "== Xwayland"; pgrep -a -x Xwayland | sed 's/^/ /'
|
||||
mgrun offscreen xdpyinfo 2>/dev/null | grep -E "^ +(DRI3|Present|MIT-SHM|XFIXES|GLX)$" | sed 's/^/ ext/'
|
||||
mgrun offscreen glxinfo -B 2>&1 | grep -E "vendor|renderer|direct rendering" | sed 's/^/ /'
|
||||
IN
|
||||
;;
|
||||
measure)
|
||||
label=${1:?usage: x11-verify.sh measure <label> [seconds]}
|
||||
secs=${2:-10}
|
||||
battery_ok
|
||||
push_measure
|
||||
for mode in auto readback; do
|
||||
printf '%s\n' \
|
||||
"pkill -x glxgears; rm -f /tmp/mgl-x11-gears.client.log /tmp/x11-gears.out" \
|
||||
"MGLOG=/tmp/mgl-x11-gears.log timeout $((secs + 8)) mgrun offscreen env vblank_mode=${VBLANK_MODE:-0} MOBILEGL_GLX_PRESENT=$mode glxgears ${GEARS_ARGS:--geometry 1280x720} > /tmp/x11-gears.out 2>&1 &" \
|
||||
"sleep 3" | bash "$here/ct.sh" "$CH" >/dev/null
|
||||
adb_ shell "su -c 'sh $DEV_TOOLS/x11-measure.sh $label-$mode glxgears $secs'" || true
|
||||
sleep 6
|
||||
printf '%s\n' "grep -a 'frames in' /tmp/x11-gears.out | tail -3; grep -a 'presented through' /tmp/mgl-x11-gears.client.log | head -1; pkill -x glxgears; true" \
|
||||
| bash "$here/ct.sh" "$CH"
|
||||
done
|
||||
;;
|
||||
deploy-client)
|
||||
bash "$here/xbuild.sh"
|
||||
CHANNEL=$CH bash "$here/xdeploy.sh"
|
||||
;;
|
||||
deploy-kwin)
|
||||
CHANNEL=$CH bash "$here/kwin-stage.sh" build
|
||||
for _ in $(seq 1 120); do
|
||||
out=$(printf 'tail -3 /root/kwin-build.log\n' | bash "$here/ct.sh" "$CH" 2>/dev/null || true)
|
||||
echo "$out" | tail -1
|
||||
echo "$out" | grep -q "== ALL DONE ==" && exit 0
|
||||
echo "$out" | grep -qiE "error|failed" && { echo "KWin build failed" >&2; exit 1; }
|
||||
sleep 30
|
||||
done
|
||||
echo "KWin build did not finish in an hour" >&2; exit 1
|
||||
;;
|
||||
deploy-server)
|
||||
battery_ok
|
||||
bash "$here/build-android.sh"
|
||||
CHANNEL=$CH bash "$here/cyc.sh" x11-server
|
||||
;;
|
||||
check)
|
||||
bash "$here/ct.sh" "$CH" <<'IN'
|
||||
echo "== Xwayland's command line (no -shm with glamor)"; pgrep -a -x Xwayland | sed 's/^/ /'
|
||||
echo "== Xwayland's glamor (journal)"
|
||||
journalctl --no-pager --since -30min 2>/dev/null | grep -aiE "glamor|Xwayland.*(EGL|gbm|dri3|falling back)" | tail -10 | sed 's/^/ /'
|
||||
echo "== Xwayland's MobileGL client log"; grep -aE "presented|shared image|declin|Fatal|ERROR" /tmp/mgl-xwayland.client.log /tmp/mgl-xwayland.client 2>/dev/null | tail -8 | sed 's/^/ /' || echo " (none)"
|
||||
X=$(pgrep -x Xwayland); K=$(pgrep -x kwin_wayland)
|
||||
echo "== dma-bufs held: Xwayland $(ls -l /proc/$X/fd 2>/dev/null | grep -c dmabuf), kwin $(ls -l /proc/$K/fd 2>/dev/null | grep -c dmabuf)"
|
||||
echo "== Xwayland's GLX vendor and extensions"
|
||||
mgrun offscreen xdpyinfo 2>/dev/null | grep -E "^ +(DRI3|Present|MIT-SHM)$" | sed 's/^/ ext/'
|
||||
echo "== a GLX window's path"
|
||||
rm -f /tmp/mgl-x11-check.client.log
|
||||
MGLOG=/tmp/mgl-x11-check.log timeout 6 mgrun offscreen glxgears >/dev/null 2>&1
|
||||
grep -a "presented through\|leaves DRI3" /tmp/mgl-x11-check.client.log | sed 's/^/ /'
|
||||
IN
|
||||
adb_ logcat -d | grep -aE "MobileGL.*(shared image|dma-buf import|refused)" | tail -8 || true
|
||||
adb_ logcat -b crash -d | tail -5 || true
|
||||
;;
|
||||
apps)
|
||||
battery_ok
|
||||
printf '%s\n' \
|
||||
"pkill -x xterm; pkill -x xeyes; pkill -x glxgears; pkill -x konsole; pkill -x kate" \
|
||||
"MGLOG=/tmp/mgl-x11-konsole.log mgrun offscreen env QT_QPA_PLATFORM=xcb konsole >/dev/null 2>&1 &" \
|
||||
"MGLOG=/tmp/mgl-x11-kate.log mgrun offscreen env QT_QPA_PLATFORM=xcb kate >/dev/null 2>&1 &" \
|
||||
"command -v xterm >/dev/null && MGLOG=/tmp/mgl-x11-xterm.log mgrun offscreen xterm >/dev/null 2>&1 &" \
|
||||
"command -v xeyes >/dev/null && MGLOG=/tmp/mgl-x11-xeyes.log mgrun offscreen xeyes >/dev/null 2>&1 &" \
|
||||
"MGLOG=/tmp/mgl-x11-gears.log mgrun offscreen glxgears >/dev/null 2>&1 &" \
|
||||
"sleep 6; pgrep -a -x konsole; pgrep -a -x kate; pgrep -a -x xterm; pgrep -a -x xeyes; pgrep -a -x glxgears; true" | bash "$here/ct.sh" "$CH"
|
||||
bash "$here/shot.sh" x11-apps 2 1
|
||||
;;
|
||||
stop)
|
||||
printf '%s\n' "pkill -x xterm; pkill -x xeyes; pkill -x glxgears; pkill -x konsole; pkill -x kate; true" | bash "$here/ct.sh" "$CH"
|
||||
;;
|
||||
*)
|
||||
echo "unknown step $step" >&2; exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/xbuild.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# xbuild.sh [--reconfigure] [extra ninja targets...] - cross-build the container CLIENT: libMobileGL.so
|
||||
# (glibc aarch64, glvnd EGL/GLX vendor) + mobilegl_gbm.so (GBM backend), in WSL archlinux with
|
||||
# clang + lld against a sysroot exported from the container (xsysroot/refresh-sysroot.sh).
|
||||
# Full build ~150 s, incremental seconds. Deploy with xdeploy.sh.
|
||||
# Env: MGL_WORKTREE (source, Windows form), MGL_XBUILD_DIR (WSL build dir, default ~/mgl-xbuild-a64),
|
||||
# MGL_JOBS (16), WSL_DISTRO (archlinux).
|
||||
# Prereq in WSL: clang, lld, llvm (lld major MUST equal clang/llvm-libs major - lld 23 on llvm 22
|
||||
# broke ld.lld), cmake, ninja; ~/sysroots/aarch64-arch.cmake + ~/sysroots/arch-kde-mgl.
|
||||
set -euo pipefail
|
||||
. "$(dirname "$0")/env.sh"
|
||||
STAMP=$(git -C "$MGL_WORKTREE" rev-parse HEAD)
|
||||
RECONF=0
|
||||
if [ "${1:-}" = "--reconfigure" ]; then RECONF=1; shift; fi
|
||||
EXTRA="$*"
|
||||
SRC_WSL=$(win2wsl "$MGL_WORKTREE")
|
||||
wsl -d "${WSL_DISTRO:-archlinux}" -- env SRC="$SRC_WSL" STAMP="$STAMP" RECONF="$RECONF" EXTRA="$EXTRA" \
|
||||
OUT="${MGL_XBUILD_DIR:-}" JOBS="${MGL_JOBS:-16}" bash -s <<'IN' 2>&1 | tr -d '\0' | grep -av '^wsl:'
|
||||
set -euo pipefail
|
||||
OUT=${OUT:-$HOME/mgl-xbuild-a64}
|
||||
TC=$HOME/sysroots/aarch64-arch.cmake
|
||||
[ -f "$TC" ] || { echo "missing toolchain $TC (run xsysroot/refresh-sysroot.sh)"; exit 1; }
|
||||
ld.lld --version >/dev/null || { echo "ld.lld is not runnable in WSL (lld/llvm version mismatch?)"; exit 1; }
|
||||
t0=$(date +%s)
|
||||
if [ "$RECONF" = 1 ] || [ ! -f "$OUT/build.ninja" ]; then
|
||||
rm -rf "$OUT/CMakeCache.txt" "$OUT/CMakeFiles"
|
||||
cmake -S "$SRC" -B "$OUT" -G Ninja --toolchain "$TC" -DMOBILEGL_BUILD_STAMP="$STAMP" \
|
||||
-DCMAKE_BUILD_TYPE=Release -DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF \
|
||||
-DMOBILEGL_BUILD_DISAGGREGATED=ON -DMOBILEGL_ENABLE_LTO=OFF
|
||||
elif ! grep -qx "MOBILEGL_BUILD_STAMP:STRING=$STAMP" "$OUT/CMakeCache.txt"; then
|
||||
cmake -S "$SRC" -B "$OUT" -DMOBILEGL_BUILD_STAMP="$STAMP" >/dev/null
|
||||
fi
|
||||
t1=$(date +%s)
|
||||
cmake --build "$OUT" --target MobileGL mobilegl_gbm $EXTRA -j"$JOBS"
|
||||
t2=$(date +%s)
|
||||
echo "stamp: $STAMP"
|
||||
echo "configure: $((t1-t0))s build: $((t2-t1))s"
|
||||
for f in "$OUT/libMobileGL.so" "$OUT/MobileGL/MG_Gbm/mobilegl_gbm.so"; do
|
||||
ls -la "$f"; echo " windows: $(wslpath -w "$f")"
|
||||
done
|
||||
IN
|
||||
exit "${PIPESTATUS[0]}"
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/xdeploy.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# xdeploy.sh - install the WSL cross-built libMobileGL.so + mobilegl_gbm.so into the container, exactly
|
||||
# like anland's anland-build-client.sh: copy to a NEW inode and rename into place (overwriting a mapped
|
||||
# .so in place crashes every running client, KWin included), refresh the libEGL/libGL aliases, the
|
||||
# glvnd GLX vendor link, the EGL vendor JSON (system-wide 10_mobilegl.json + the /opt copy), the client
|
||||
# config /etc/mobilegl/client.conf and the GLX/GBM selection drop-ins. Does not restart anything: already-running clients
|
||||
# keep the old inode; restart the client (or the session) to pick up the new library.
|
||||
# Env: MGL_XBUILD_DIR (WSL build dir), CHANNEL (ct.sh channel, default xd), WSL_DISTRO.
|
||||
set -euo pipefail
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/env.sh"
|
||||
mkdir -p "$ANL_DIR/xdist"
|
||||
# 1. Copy the build outputs out of WSL.
|
||||
wsl -d "${WSL_DISTRO:-archlinux}" -- env OUT="${MGL_XBUILD_DIR:-}" D="$(win2wsl "$ANL_DIR")/xdist" bash -s <<'IN' 2>&1 | tr -d '\0' | grep -av '^wsl:'
|
||||
set -e; OUT=${OUT:-$HOME/mgl-xbuild-a64}
|
||||
cp -f "$OUT/libMobileGL.so" "$D/libMobileGL.so"; cp -f "$OUT/MobileGL/MG_Gbm/mobilegl_gbm.so" "$D/mobilegl_gbm.so"
|
||||
sha256sum "$D/libMobileGL.so" "$D/mobilegl_gbm.so"
|
||||
IN
|
||||
# 2. Push to the phone and stage them in the container's /root/xdeploy (the container's /tmp and the
|
||||
# phone's /data/local/tmp are different places; the rootfs is reachable at $CT_ROOT).
|
||||
adb_ shell "mkdir -p $DEV_TOOLS/xdist"
|
||||
adb_ push "$ANL_DIR/xdist/libMobileGL.so" "$DEV_TOOLS/xdist/libMobileGL.so"
|
||||
adb_ push "$ANL_DIR/xdist/mobilegl_gbm.so" "$DEV_TOOLS/xdist/mobilegl_gbm.so"
|
||||
adb_ shell "su -c 'mkdir -p $CT_ROOT/root/xdeploy && cp $DEV_TOOLS/xdist/libMobileGL.so $DEV_TOOLS/xdist/mobilegl_gbm.so $CT_ROOT/root/xdeploy/ && rm -f $DEV_TOOLS/xdist/*.so'"
|
||||
# 3. Install inside the container.
|
||||
bash "$here/ct.sh" "${CHANNEL:-xd}" <<'IN'
|
||||
set -euo pipefail
|
||||
in=/root/xdeploy
|
||||
mkdir -p /opt/mobilegl/lib /opt/mobilegl/share/glvnd/egl_vendor.d
|
||||
cp "$in/libMobileGL.so" /opt/mobilegl/lib/libMobileGL.so.new
|
||||
mv -f /opt/mobilegl/lib/libMobileGL.so.new /opt/mobilegl/lib/libMobileGL.so
|
||||
for lib in libEGL.so libEGL.so.1 libGL.so libGL.so.1; do ln -sf libMobileGL.so "/opt/mobilegl/lib/$lib"; done
|
||||
ln -sf /opt/mobilegl/lib/libMobileGL.so /usr/lib/libGLX_mobilegl.so.0
|
||||
vendor='{"file_format_version":"1.0.0","ICD":{"library_path":"/opt/mobilegl/lib/libMobileGL.so"}}'
|
||||
# Per-process forcing (__EGL_VENDOR_LIBRARY_FILENAMES=<this file>) keeps working off the /opt copy.
|
||||
printf '%s\n' "$vendor" >/opt/mobilegl/share/glvnd/egl_vendor.d/50_mobilegl.json
|
||||
# System-wide vendor: 10_ sorts ahead of the system's 50_mesa.json, so stock libglvnd asks MobileGL
|
||||
# first and moves on to the other vendor whenever MobileGL declines (no server reachable).
|
||||
mkdir -p /usr/share/glvnd/egl_vendor.d
|
||||
printf '%s\n' "$vendor" >/usr/share/glvnd/egl_vendor.d/10_mobilegl.json.new
|
||||
mv -f /usr/share/glvnd/egl_vendor.d/10_mobilegl.json.new /usr/share/glvnd/egl_vendor.d/10_mobilegl.json
|
||||
# Client settings every process reads when its environment does not say otherwise (the backend
|
||||
# comes from /etc/mobilegl/backend, which switch.sh rewrites).
|
||||
mkdir -p /etc/mobilegl
|
||||
cat >/etc/mobilegl/client.conf <<'CONF'
|
||||
# MobileGL client configuration, read by every process that loads libMobileGL.so.
|
||||
# KEY=value lines; a MOBILEGL_* variable in a process's environment overrides the line here.
|
||||
# The backend is /etc/mobilegl/backend. Written by xdeploy.sh / anland-build-client.sh.
|
||||
MOBILEGL_TRANSPORT=spawn
|
||||
MOBILEGL_IPC_DATA=shm
|
||||
MOBILEGL_IPC_CONTROL=unix:@anland-mobilegl
|
||||
CONF
|
||||
# What glvnd's libGLX and libgbm cannot infer: environment.d reaches the systemd user manager (every
|
||||
# Plasma service and what it launches, terminals included), profile.d reaches login shells.
|
||||
mkdir -p /etc/environment.d /etc/profile.d
|
||||
printf '%s\n' '__GLX_VENDOR_LIBRARY_NAME=mobilegl' 'GBM_BACKEND=mobilegl' >/etc/environment.d/10-mobilegl.conf
|
||||
printf '%s\n' 'export __GLX_VENDOR_LIBRARY_NAME=mobilegl' 'export GBM_BACKEND=mobilegl' >/etc/profile.d/mobilegl.sh
|
||||
mkdir -p /usr/lib/gbm
|
||||
cp "$in/mobilegl_gbm.so" /usr/lib/gbm/mobilegl_gbm.so.new
|
||||
mv -f /usr/lib/gbm/mobilegl_gbm.so.new /usr/lib/gbm/mobilegl_gbm.so
|
||||
rm -f "$in/libMobileGL.so" "$in/mobilegl_gbm.so"
|
||||
sha256sum /opt/mobilegl/lib/libMobileGL.so /usr/lib/gbm/mobilegl_gbm.so
|
||||
ls /usr/share/glvnd/egl_vendor.d/ /etc/mobilegl/ /etc/environment.d/
|
||||
echo DEPLOYED
|
||||
IN
|
||||
@@ -0,0 +1,55 @@
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/xsysroot/aarch64-arch.cmake
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# Cross toolchain: WSL clang -> aarch64 Arch Linux ARM (glibc) container "arch-kde-mgl".
|
||||
# Sysroot exported from the container by refresh-sysroot.sh (default $HOME/sysroots/arch-kde-mgl).
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
|
||||
set(_triple aarch64-unknown-linux-gnu)
|
||||
if (DEFINED ENV{MGL_SYSROOT})
|
||||
set(_sysroot "$ENV{MGL_SYSROOT}")
|
||||
else()
|
||||
set(_sysroot "$ENV{HOME}/sysroots/arch-kde-mgl")
|
||||
endif()
|
||||
# The container's GCC install dir (crtbegin/crtend, libgcc, libstdc++ via /usr/include/c++/<ver>).
|
||||
file(GLOB _gcc_dirs LIST_DIRECTORIES true "${_sysroot}/usr/lib/gcc/${_triple}/*")
|
||||
list(SORT _gcc_dirs COMPARE NATURAL ORDER DESCENDING)
|
||||
list(GET _gcc_dirs 0 _gcc_install_dir)
|
||||
|
||||
set(CMAKE_SYSROOT "${_sysroot}")
|
||||
set(CMAKE_C_COMPILER clang)
|
||||
set(CMAKE_CXX_COMPILER clang++)
|
||||
set(CMAKE_ASM_COMPILER clang)
|
||||
set(CMAKE_C_COMPILER_TARGET ${_triple})
|
||||
set(CMAKE_CXX_COMPILER_TARGET ${_triple})
|
||||
set(CMAKE_ASM_COMPILER_TARGET ${_triple})
|
||||
set(CMAKE_AR llvm-ar)
|
||||
set(CMAKE_RANLIB llvm-ranlib)
|
||||
set(CMAKE_NM llvm-nm)
|
||||
set(CMAKE_OBJCOPY llvm-objcopy)
|
||||
set(CMAKE_STRIP llvm-strip)
|
||||
|
||||
set(_xflags "--gcc-install-dir=${_gcc_install_dir}")
|
||||
set(CMAKE_C_FLAGS_INIT "${_xflags}")
|
||||
set(CMAKE_CXX_FLAGS_INIT "${_xflags}")
|
||||
set(CMAKE_ASM_FLAGS_INIT "${_xflags}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld")
|
||||
set(CMAKE_LINKER_TYPE LLD)
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH "${_sysroot}")
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
|
||||
set(ENV{PKG_CONFIG_SYSROOT_DIR} "${_sysroot}")
|
||||
set(ENV{PKG_CONFIG_LIBDIR} "${_sysroot}/usr/lib/pkgconfig:${_sysroot}/usr/share/pkgconfig")
|
||||
set(ENV{PKG_CONFIG_PATH} "")
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# MobileGL - tools/anland-mobilegl-plasma/scripts/xsysroot/refresh-sysroot.sh
|
||||
# Copyright (c) 2025-2026 MobileGL-Dev
|
||||
# Licensed under the GNU Lesser General Public License v3.0:
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
# End of Source File Header
|
||||
|
||||
# xsysroot/refresh-sysroot.sh - (re)export the cross sysroot from the container into WSL
|
||||
# ~/sysroots/arch-kde-mgl and install the toolchain file as ~/sysroots/aarch64-arch.cmake.
|
||||
# Read-only on the container side (it only tars headers/libs into /root and moves the tarball out).
|
||||
# Run once on a new machine, and again after the container upgrades glibc/gcc/vulkan/libdrm/gbm;
|
||||
# then `bash xbuild.sh --reconfigure`. Env: CHANNEL (default xr), WSL_DISTRO.
|
||||
set -euo pipefail
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
. "$here/../env.sh"
|
||||
cp -f "$here/aarch64-arch.cmake" "$ANL_DIR/aarch64-arch.cmake"
|
||||
bash "$here/../ct.sh" "${CHANNEL:-xr}" <<'IN'
|
||||
set -e; cd /
|
||||
L=$(ls -d usr/lib/*crt*.o usr/lib/lib{c,m,mvec,pthread,dl,rt,util,resolv,anl,stdc++,stdc++fs,stdc++exp,supc++,gcc_s,atomic,vulkan,drm,drm_*,gbm,z}.* usr/lib/lib{c,m}_nonshared* usr/lib/ld-linux* usr/lib/pkgconfig usr/share/pkgconfig usr/share/cmake/VulkanHeaders usr/lib/cmake/Vulkan* 2>/dev/null)
|
||||
tar czf /root/xsysroot.tar.gz --exclude='usr/lib/gcc/*/*/cc1' --exclude='usr/lib/gcc/*/*/cc1plus' --exclude='usr/lib/gcc/*/*/lto1' --exclude='usr/lib/gcc/*/*/lto-wrapper' --exclude='usr/lib/gcc/*/*/collect2' --exclude='usr/lib/gcc/*/*/plugin' --exclude='usr/lib/gcc/*/*/install-tools' --exclude='usr/lib/gcc/*/*/liblto_plugin.so' lib usr/include usr/lib/gcc $L
|
||||
echo SYSROOT_TAR_DONE
|
||||
IN
|
||||
adb_ shell "su -c 'mv $CT_ROOT/root/xsysroot.tar.gz $DEV_TOOLS/xsysroot.tar.gz && chmod 644 $DEV_TOOLS/xsysroot.tar.gz'"
|
||||
adb_ pull "$DEV_TOOLS/xsysroot.tar.gz" "$ANL_DIR/xsysroot.tar.gz"
|
||||
adb_ shell "su -c 'rm -f $DEV_TOOLS/xsysroot.tar.gz'"
|
||||
wsl -d "${WSL_DISTRO:-archlinux}" -- env A="$(win2wsl "$ANL_DIR")" bash -s <<'IN' 2>&1 | tr -d '\0' | grep -av '^wsl:'
|
||||
set -e; S=$HOME/sysroots/arch-kde-mgl; rm -rf "$S.new"; mkdir -p "$S.new"
|
||||
tar xzf "$A/xsysroot.tar.gz" -C "$S.new"
|
||||
rm -rf "$S"; mv "$S.new" "$S"; cp -f "$A/aarch64-arch.cmake" "$HOME/sysroots/aarch64-arch.cmake"
|
||||
du -sh "$S"; ls "$S/usr/lib/gcc/aarch64-unknown-linux-gnu/"
|
||||
IN
|
||||
rm -f "$ANL_DIR/xsysroot.tar.gz"
|
||||
@@ -1,23 +0,0 @@
|
||||
# GL CTS multi-suite conformance report
|
||||
|
||||
| Backend | Suite | Expected | Result | Pass | Accepted | Crash | Hang | Unrun | Duplicate | Coverage | Strict Pass-only | Conformance-accepted | Validation |
|
||||
|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|:---:|
|
||||
| DirectVulkan | shader-image | 125 | 125 | 63 | 119 | 0 | 0 | 0 | 0 | 100.00% (125/125) | 50.40% (63/125) | 95.20% (119/125) | OK |
|
||||
| DirectVulkan | ssbo | 124 | 124 | 124 | 124 | 0 | 0 | 0 | 0 | 100.00% (124/124) | 100.00% (124/124) | 100.00% (124/124) | OK |
|
||||
| DirectVulkan | dsa | 371 | 371 | 368 | 369 | 0 | 0 | 0 | 0 | 100.00% (371/371) | 99.19% (368/371) | 99.46% (369/371) | OK |
|
||||
| DirectVulkan | texture | 1055 | 1054 | 815 | 1036 | 0 | 0 | 1 | 0 | 99.91% (1054/1055) | 77.25% (815/1055) | 98.20% (1036/1055) | INCOMPLETE |
|
||||
| DirectVulkan | packed-pixels | 4732 | 4732 | 4732 | 4732 | 0 | 0 | 0 | 0 | 100.00% (4732/4732) | 100.00% (4732/4732) | 100.00% (4732/4732) | OK |
|
||||
| **DirectVulkan** | **DirectVulkan weighted subtotal** | **6407** | **6406** | **6102** | **6380** | **0** | **0** | **1** | **0** | **99.98% (6406/6407)** | **95.24% (6102/6407)** | **99.58% (6380/6407)** | **INCOMPLETE** |
|
||||
| **All backends** | **Overall weighted** | **6407** | **6406** | **6102** | **6380** | **0** | **0** | **1** | **0** | **99.98% (6406/6407)** | **95.24% (6102/6407)** | **99.58% (6380/6407)** | **INCOMPLETE** |
|
||||
|
||||
Rates use unique **Expected** caselist cases as the denominator. Unrun cases remain in the denominator and are not accepted.
|
||||
Accepted statuses: `Pass`, `NotSupported`, `QualityWarning`, `CompatibilityWarning`, `Waiver`.
|
||||
Duplicate is the number of extra QPA observations; the final observation wins.
|
||||
|
||||
## Validation details
|
||||
|
||||
- **DirectVulkan/shader-image OK**: result directory has no run_state.json; backend provenance is unverified
|
||||
- **DirectVulkan/ssbo OK**: result directory has no run_state.json; backend provenance is unverified
|
||||
- **DirectVulkan/dsa OK**: result directory has no run_state.json; backend provenance is unverified
|
||||
- **DirectVulkan/texture INCOMPLETE**: result directory has no run_state.json; backend provenance is unverified
|
||||
- **DirectVulkan/packed-pixels OK**: result directory has no run_state.json; backend provenance is unverified
|
||||
@@ -82,7 +82,7 @@ FCL's Minecraft renders into the Render Server's on-screen window on the same ph
|
||||
`fcl_accept.sh <backend> kill` kills the server and checks the client latches DEVICE LOST and FCL
|
||||
survives. Needs the FCL fork's version setting "keepRunningInBackground" ON in `config.json`
|
||||
(FCL pauses the game the moment its Activity leaves the foreground). The server is started through
|
||||
the Render Server screen's documented extras. Results: `docs/archive/notes/p12/FCL-ACCEPTANCE.md`.
|
||||
the Render Server screen's documented extras. Results: the phase notes.
|
||||
|
||||
## crosshost_accept.sh + crosshost_report.py — P12 exit gate (b)
|
||||
|
||||
@@ -90,7 +90,7 @@ A WSL client replays the openra and rd12 traces against the phone's render serve
|
||||
TCP + stream (ssim gate, then `PRESENT_CREDIT` 1/2/3 benchmarks). Adds a temporary /32 route
|
||||
around the host's TUN proxy, checks the path with `tcp_path_check.sh`, and removes the route and
|
||||
stops the server on exit. `crosshost_report.py` reduces the output to a table. Results and the
|
||||
P6.5 link numbers: `docs/archive/notes/p12/CROSSHOST-ACCEPTANCE.md`.
|
||||
P6.5 link numbers: the phase notes.
|
||||
|
||||
## netbench.sh — Wi-Fi TCP throughput baseline
|
||||
|
||||
@@ -133,7 +133,7 @@ Reducers, all reading a probe directory:
|
||||
- `fcl_stacks.py`: top stacks.
|
||||
|
||||
The simpleperf reducers need `SIMPLEPERF_DIR` and a `binary_cache` from `binary_cache_builder.py`.
|
||||
Findings: `docs/archive/notes/perf-p15/PLAN-P15.md` section 2.
|
||||
Findings: the phase notes §2.
|
||||
|
||||
## Clocks, bench sessions and run validity (P15)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ Its first use was `CONTRACT-P6.md` section 9 item 7 (the paired device gate) and
|
||||
doorbell ledger), and it is not specific to those: the thing it computes is "N transports, one
|
||||
thermal window, identical protocol" for any transport/backend/case triple the runner accepts.
|
||||
Everything it does is a step of the protocol in docs/Disaggregated/MEASUREMENTS.md sections 4.4 / 9
|
||||
/ 11 plus docs/archive/notes/p5f/device-report.md.
|
||||
/ 11 plus the phase notes.
|
||||
|
||||
Why it is a script and not a shell transcript: a run that wants to be quotable needs the CPU
|
||||
samples, the pin evidence and the library's own wait ledger to come from the SAME window, and the
|
||||
@@ -26,7 +26,7 @@ WHAT IT DELIBERATELY DOES NOT DO.
|
||||
- It does not decide anything about correctness. Benchmark mode takes no snapshot and compares no
|
||||
golden (--benchmark replays the whole trace for timing), so an SSIM column here would be a
|
||||
fabrication. Correctness on this device is section 3 of the P6 closing snapshot
|
||||
(docs/archive/notes/p6/README.md) and the retrace lanes.
|
||||
and the retrace lanes.
|
||||
- It does not read `MOBILEGL_TRANSPORT` out of `--transport` on the runner. That flag is applied by
|
||||
run_android_retrace_local.py's run_case() path only; run_benchmark_case() passes `args.env`
|
||||
alone, so `--transport spawn` alongside `--benchmark` is silently ignored and the arm runs
|
||||
@@ -821,7 +821,7 @@ def main():
|
||||
|
||||
# The fan is a CONSTANT of the protocol, not a treatment: both arms run under the same airflow,
|
||||
# and on this device it is the difference between a 60 s cool-down between arms and a 20-minute
|
||||
# one (docs/archive/notes/p4a/INTEGRATOR-DECISIONS.md). `real_speed` is read back because target_level alone
|
||||
# one. `real_speed` is read back because target_level alone
|
||||
# is a request, not a fact.
|
||||
fan = "/sys/class/xm_power/hw_monitor/pwm_fan"
|
||||
adb.su(f"echo {args.fan_level} > {fan}/target_level")
|
||||
|
||||
Reference in New Issue
Block a user