# 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-.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-.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 --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:`. **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=`. 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://:40613` and `MOBILEGL_IPC_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='' CLASSPATH= app_process / top.mobilegl.plugin.ExternalClientHelper -- [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:,` and `MOBILEGL_IPC_DATA=shm` already set. Helper options: `--server ` (default: the APK it was loaded from), `--timeout-ms ` (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("")` 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:@` (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 @ --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`.