Files
MobileGL/userguide/android-plugin.md
T

238 lines
13 KiB
Markdown

# MobileGL renderer plugin APK (Android)
The plugin is a standalone app (`top.mobilegl.plugin`, built from `android-plugin/`). It does three
jobs:
1. **Renderer plugin.** FCL and other launchers that read FCL renderer plugins find it and load its
`libMobileGL.so` into the game process.
2. **Render server.** It can run a MobileGL server, either offscreen (a foreground service) or
on-screen (a full-screen Activity that shows a client's frames).
3. **Driver self-test.** Its launcher screen (**POST** tab) tests the device's GLES and Vulkan
drivers, and can run a short benchmark.
## When to use it
- You want MobileGL in a launcher without rebuilding the launcher.
- You want `inproc`, out-of-process (`spawn`) or TCP rendering on Android. Only split builds can do
that, and the plugin is a split build by default.
- You want a phone to render for a client on another machine.
When not to use it: if your FCL build already embeds MobileGL and you only need monolith rendering,
the embedded copy is simpler ([android-fcl.md](android-fcl.md)).
## Build and install
Build instructions are in [building.md](building.md#android-plugin-apk). In short:
```sh
gradle -p android-plugin :app:assemblePluginRelease -Pmobilegl.buildDisaggregated=ON -Pmobilegl.buildDisaggregatedInproc=ON
```
The APK is
`android-plugin/app/build/outputs/apk/plugin/release/MobileGL-plugin-release-<git-hash>.apk`. A
release APK is signed only when the signing environment variables are set (see
[building.md](building.md#signing)); otherwise sign it yourself before installing.
```sh
adb install -r MobileGL-plugin-release-<git-hash>.apk
```
Android refuses to replace an installed plugin that was signed with a different key. Either
uninstall the old one first (the launcher then forgets its plugin settings) or build with
`-Pmobilegl.applicationIdSuffix=.dev` to install a second copy next to it.
Requirements: Android 8.0 (API 26) or newer. The default ABI is arm64-v8a
(`-Pmobilegl.abis=all` adds x86_64).
## Using the plugin in a launcher
The plugin advertises itself through manifest metadata. The launcher lists it as a renderer named
**MobileGL** and loads `libMobileGL.so` from the plugin's install directory.
| Metadata | Read by | Contents |
|---|---|---|
| `fclPlugin`, `renderer`, `boatEnv`, `pojavEnv` | launchers using the original FCL plugin format | Fixed environment: `LIBGL_ES=3`, `POJAV_RENDERER=opengles3`, `MOBILEGL_BACKEND_TYPE=DirectGLES`, plus `MOBILEGL_TRANSPORT=inproc` in an `inproc` build (see below) |
| `fclPlugin_V2` | launchers that read the v2 renderer-plugin format | The same environment plus user options: backend (`DirectGLES`/`DirectVulkan`), frames in flight, and toggles for `MOBILEGL_DISABLE_TIMERQUERY`, `MOBILEGL_SUBGROUP_EMULATION`, `MOBILEGL_MAGMA_R11G11B10F_FALLBACK`, `MOBILEGL_ESPRYT_AVOID_SAMPLER_MIPMAP_MIN_FILTER`, `MOBILEGL_COHERENT_AS_FLUSH`, `MOBILEGL_RELAXED_SEMANTICS`, `MOBILEGL_ESPRYT_USE_ANGLE` |
The FCL build this guide was checked against reads only the original format, so it shows no plugin
options and always starts the plugin with Espryt. To change anything there, use FCL's
**Environment Variables** editor (applied after the plugin's variables), for example:
```text
MOBILEGL_BACKEND_TYPE=DirectVulkan
```
The `debug.mobilegl.*` properties described in [android-fcl.md](android-fcl.md#other-settings) also
work for the plugin. One more exists for split builds: `debug.mobilegl.transport` overrides
`MOBILEGL_TRANSPORT`.
## In-process split (`inproc`)
With `MOBILEGL_TRANSPORT=inproc`, the GL frontend runs on the game's thread and the backend runs on
a second thread. On a CPU-bound game this can raise the frame rate; it costs up to one extra frame of
latency.
The plugin's launcher environment includes `MOBILEGL_TRANSPORT=inproc` **only if** the APK was built
with `-Pmobilegl.buildDisaggregated=ON` or `-Pmobilegl.buildDisaggregatedInproc=ON` passed explicitly
(or the matching `MOBILEGL_BUILD_DISAGGREGATED` / `MOBILEGL_BUILD_DISAGGREGATED_INPROC` environment
variable set to `ON`; the CI build of `feat/disaggregated` does this). A plain `gradle :app:assemblePluginRelease`
still builds the split layer into the library, but the launcher environment then omits the transport
and the plugin runs monolith. Either rebuild with the property, or add
`MOBILEGL_TRANSPORT=inproc` in the launcher's environment editor.
Check the log for:
```text
Config: MOBILEGL_TRANSPORT=inproc - the MGPipe record stream crosses a real ring to an apply thread
```
## The render server
Open the MobileGL app and switch to the **Render Server** tab. It starts one of two servers:
| Mode | Component | Process | What it is for |
|---|---|---|---|
| Offscreen (default) | `MobileGLServerService` (foreground service) running `libMobileGLServer.so <endpoint> --serve` | `:mglsrv` | Remote clients over TCP, offscreen (pbuffer) programs, same-device programs through the broker |
| On-screen (tick **On-screen window**) | `MobileGLDisplayActivity`, a full-screen Activity with an in-process server | `:mglwin` | Clients that set `MOBILEGL_IPC_SURFACE=server`: their window surface is drawn into this Activity |
Fields:
| Field | Default | Notes |
|---|---|---|
| Listen endpoint | `tcp://0.0.0.0:40613` | `tcp://host:port`, `@name` (abstract Unix socket) or a file path (relative paths resolve against the app's files directory) |
| Auth token | empty | Clients present it as `MOBILEGL_IPC_TOKEN`. A TCP listen on a non-loopback address (including `0.0.0.0`) **requires** a token of at least 16 bytes; without a token, use `tcp://127.0.0.1:<port>`. **Generate token** makes a random one. |
| Extra env | empty | `KEY=VALUE;KEY` list for the server process (`KEY` alone unsets). For example `MOBILEGL_BACKEND_TYPE=DirectVulkan` pins the server's backend. |
Only one server runs at a time: starting one stops the other. The plugin flavor does not export these
components, so other apps and `adb` cannot start them by component name; use the screen.
Backend selection: the client's `MOBILEGL_BACKEND_TYPE` decides the backend for its session. A
server started with `MOBILEGL_BACKEND_TYPE` in its extra env refuses clients that ask for the other
backend. The on-screen server keeps the first session's backend for its whole process lifetime; to
switch, stop the server and start it again.
Server logs go to logcat (tags `MobileGL`, `MobileGLServer`, `MobileGLDisplay`) and to
`mgl.server.log` (offscreen) or `mglwin.server.log` (on-screen) in the app's private files directory.
## Out-of-process rendering with a window
A spawned server process has no display, and an Android client's window cannot be passed to another
process. So a windowed app (a launcher's Minecraft, for example) can render out of process only into
a window the **server** owns: the plugin's on-screen display server.
Without that, a `spawn` client that creates a window surface fails. With the default
`MOBILEGL_IPC_SURFACE=offscreen`, the server rejects the window with
`Fatal{UnmigratedSurface, "AndroidNativeWindow:use-MOBILEGL_IPC_SURFACE=server"}`. With `MOBILEGL_IPC_SURFACE=server` and an
offscreen server, the client logs `Refuse ServerOwned (NoServerDisplay)`.
Steps (client in FCL, server in the plugin, same device):
1. In the MobileGL app, **Render Server** tab: set **Listen endpoint** to
`tcp://127.0.0.1:40613`, leave the token empty, tick **On-screen window**, tap **Start server**.
A black full-screen window appears; this is the display server.
2. In FCL, select the **MobileGL** plugin renderer. The FCL-embedded MobileGL entries cannot do
this, because they are built without the split layer.
3. In FCL's **Environment Variables** editor, add:
```text
MOBILEGL_TRANSPORT=spawn
MOBILEGL_IPC_CONTROL=tcp://127.0.0.1:40613
MOBILEGL_IPC_SURFACE=server
```
If the server has a token, also add `MOBILEGL_IPC_TOKEN=<token>`.
4. Launch the game. Its frames are drawn into the display server's window, at the size the game
asks for, letterboxed to the screen.
Limits:
- The display window only shows frames. It has no input handling: touch and keys go to whichever
window has focus.
- The display window must be visible when the game creates its window surface. Otherwise the client
logs `Refuse ServerOwned (NoServerWindow)`. If the display window goes away while a game renders
into it, that session ends with a device loss. The server keeps listening, and the next session
renders when the window is back.
- Use this setup to test out-of-process rendering. For normal play, monolith or `inproc` is faster
and simpler.
- A known open issue: each run logs `MG_Remote server: shared image 1 could not be sent on the aux
socket (rc=3)`. Rendering continues.
How to tell it works: the client log has
`MG_Remote client: control=tcp data=stream server=127.0.0.1:40613 ... dial=connect`, and the
server's logcat has `listening on tcp://127.0.0.1:40613 (in-process display server, display installed)`.
## TCP server for remote clients
The phone renders; a client on another machine sends GL commands over TCP. The client must be a
split build of the **same commit** (see [linux.md](linux.md#tcp-client)). Only window surfaces the
client presents itself (Linux X11/Wayland windows) or offscreen surfaces work this way.
1. Find the phone's address:
```sh
adb shell ip -f inet addr show wlan0
```
2. In the **Render Server** tab: endpoint `tcp://0.0.0.0:40613`, tap **Generate token**, keep
**On-screen window** unticked, tap **Start server**.
3. On the client, set `MOBILEGL_TRANSPORT=spawn`, `MOBILEGL_IPC_CONTROL=tcp://<phone-ip>:40613` and
`MOBILEGL_IPC_TOKEN=<token>`.
If the network isolates devices, tunnel over adb instead: start the server on
`tcp://127.0.0.1:40613` (no token needed), then on the client machine run the command below and
point the client at `tcp://127.0.0.1:40613`:
```sh
adb forward tcp:40613 tcp:40613
```
## Same-device programs outside the app (Termux, `adb shell`)
Android does not let another app connect to the plugin's sockets directly. The plugin ships a broker
for this case: a helper class, run with `app_process` under the program's own user, asks the plugin
for a connected socket pair and then starts the program with it.
1. Start the **offscreen** server with a token of at least 16 bytes (**Generate token**).
2. Tap **Copy launch command**. It produces a line of this form:
```sh
MOBILEGL_IPC_TOKEN='<token>' CLASSPATH=<plugin APK path> app_process / top.mobilegl.plugin.ExternalClientHelper -- <program> [args...]
```
3. Run it with your program. The program must use a split build of MobileGL built for Android
(bionic). The helper starts it with `MOBILEGL_TRANSPORT=spawn`,
`MOBILEGL_IPC_CONTROL=fd:<control>,<aux>` and `MOBILEGL_IPC_DATA=shm` already set.
Helper options: `--server <package>` (default: the APK it was loaded from), `--timeout-ms <n>`
(default 10000), `--keep-affinity` (do not keep the program off the server's reserved CPU core). The
token can also come from a file named by `MOBILEGL_IPC_TOKEN_FILE`; never put it on the command line.
The program renders offscreen: the offscreen server has no display.
## Embedding the server in your own APK
An app can host the server itself instead of using the plugin. The C interface is
`MobileGL/MG_Remote/Server/EmbeddedServer.h` (plain C types, also reachable through `dlsym`); the
library must be a split build.
1. Before loading `libMobileGL.so`, set `MOBILEGL_IPC_ROLE=server` and `MOBILEGL_IPC_DIAL=no`, and
unset `MOBILEGL_TRANSPORT`, `MOBILEGL_IPC_SERVER_PATH`, `MOBILEGL_IPC_RING_MB` and
`MOBILEGL_IPC_STAGE_MB`. Running the server in its own process keeps a GPU fault away from your UI.
2. For a windowed server, call `mobilegl_server_display_install_android(callback, user)`, then
`mobilegl_server_display_attach_android(window, width, height)` with the `ANativeWindow` of your
`Surface`, and `mobilegl_server_display_detach(timeout_ms)` when the surface is destroyed.
3. Call `mobilegl_server_serve_inprocess("<endpoint>")` on a thread of its own (it blocks). Stop it
with `mobilegl_server_stop_inprocess()`, then `mobilegl_server_display_uninstall(timeout_ms)`.
Clients connect with `MOBILEGL_TRANSPORT=spawn`, `MOBILEGL_IPC_CONTROL=unix:@<name>` (or `tcp://...`)
and, for the windowed server, `MOBILEGL_IPC_SURFACE=server`. The anland Linux-container setup uses
exactly this interface ([linux.md](linux.md#the-anland-setup)). For more offscreen clients at the same
time, package `libMobileGLServer.so` and run it as
`libMobileGLServer.so @<name> --serve --max-sessions <1-64>` from your own service.
`MobileGLServerService.java` and `ServerEnvironment.java` in `android-plugin/` are working examples.
## The trace flavor
`assembleTraceRelease` builds `top.mobilegl.plugin.trace`, a development APK that replays recorded GL
traces and exports the server components so that `adb` can start them by name. It is not a renderer
plugin. See `android-plugin/TRACE_REPLAY.md`.