LÖVE 11.5 runtime for the Miyoo Mini / Mini+ (OnionOS) — a device with no GPU.
The whole OpenGL pipeline is served in software by glsoft (glsoft/core.c), a
purpose-built rasterizer loaded through SDL_VIDEO_GL_DRIVER into a custom SDL2
whose dummy video driver gained GL hooks (patches/sdl2-dummy-gl.patch). Drawing
goes straight to /dev/fb0; input comes from /dev/input/event0 (the Miyoo's
gpio_keys) via SDL's evdev path, delivered to games as keyboard events.
Born inside the Balatro-on-Miyoo port effort and extracted here as the reusable part: any LÖVE 11.x game enters the device through this same door. It has shipped two games so far — Balatro and gen1recomp.
Grab love-mmiyoo-runtime-<version>.tar.gz from
Releases. Contents:
love # launcher stub
lib/liblove-11.5.so # LÖVE 11.5 (patched: see patches/love-keep-context.patch)
lib/libSDL2-2.0.so.0 # SDL 2.28.5 (patched: dummy-GL hooks, evdev w/o udev)
lib/libglsoft.so # the software GL renderer
lib/... # LuaJIT, OpenAL, ALSA, ogg/vorbis, theora, freetype, ...
share/alsa/ # ALSA config (the Onion rootfs has no /usr/share/alsa)
19 shared libraries, ~4.4 MB compressed. glibc & friends come from the device (Onion ships glibc 2.28; the toolchain matches it by construction).
See example/launch.sh (annotated) and example/Game.port. The short version:
DIR=/mnt/SDCARD/Roms/PORTS/Games/YourGame export LD_LIBRARY_PATH=$DIR/runtime/lib:${LD_LIBRARY_PATH:-/lib:/config/lib:/mnt/SDCARD/miyoo/lib} unset LD_PRELOAD # libpadsp hooks fb0 AND audio; we use neither export SDL_VIDEODRIVER=evdev # the patched dummy driver's polling name export SDL_EVDEV_DEVICES="2:/dev/input/event0" export SDL_VIDEO_GL_DRIVER=$DIR/runtime/lib/libglsoft.so export SDL_DUMMY_MODE=640x480 # the fake desktop the dummy driver reports export GLSOFT_FB=/dev/fb0 GLSOFT_FILTER=nearest GLSOFT_KEEP_CONTEXT=1 export ALSA_CONFIG_PATH=$DIR/runtime/share/alsa/alsa.conf export ALSA_CONFIG_DIR=$DIR/runtime/share/alsa export ALSOFT_CONF=$DIR/alsoft.conf # 48 kHz — the only rate the hw accepts . /mnt/SDCARD/.tmp_update/script/stop_audioserver.sh # NEVER killall audioserver $DIR/runtime/love $DIR/game
Hard-won rules encoded there (each one cost a debugging round on the device):
- Prefix
LD_LIBRARY_PATH, never replace it — Onion launches ports withLD_PRELOAD=libpadsp.soand/config/libin the path; replacing the variable kills the dynamic loader before LÖVE even starts. SDL_VIDEODRIVER=evdev, notdummy— same driver, but only this name polls input.- Stop the audioserver with Onion's own script — it preserves volume through
/proc/mi_modules; a barekillallleaves the MI_AO device unusable until reboot. - OpenAL at 48 kHz (
example/alsoft.conf) — the hardware accepts only 8/16/32/48 kHz S16; OpenAL's default 44.1 kHz fails context creation. .portfiles needGameDir=andGameDataFile=— Onion's "Import ports" validates against them and renames the shortcut to.notfoundotherwise.- Test scripts that inject input need
SDL_EVDEV_GRAB=1, or the frozen MainUI replays your keys when it wakes.
| Var | Meaning |
|---|---|
GLSOFT_FB |
framebuffer device to present into (/dev/fb0) |
GLSOFT_W / GLSOFT_H |
surface size override |
GLSOFT_FILTER |
nearest (default; bilinear costs 4.5x) |
GLSOFT_TEX16=<px> |
store textures ≥ px in RGBA4444 (halves texture RAM) |
GLSOFT_KEEP_CONTEXT=1 |
context never dies; frees LÖVE's CPU-side image copies |
GLSOFT_FB_NOPAN=1 |
present at fb offset 0 (for screenshot harnesses) |
GLSOFT_LOG |
diagnostics file |
GLSOFT_DRAWLOG_FROM=<n> |
per-primitive draw journal starting at frame n |
Custom shaders become passthrough. glsoft implements the fixed pipeline LÖVE
uses (Texel(tex, uv) * VertexColor * ConstantColor), parses GLSL only to extract
uniforms, and runs every custom shader as that same passthrough. Games whose
correctness depends on shaders need those effects disabled or reimplemented;
games that only decorate with them run fine, flat. Canvas/FBO render-to-texture
is supported; stencil is accepted and ignored.
Measured throughput on the Mini+ (Cortex-A7 ×ばつ1.2 GHz): solid fill 137 Mpx/s, opaque solid color 54.7, translucent 19.4, nearest-texture 10.5 — plus ~2.7 μs per triangle and ~0.57 μs per raster line of fixed overhead. Budget accordingly: one full-screen 640x480 pass ≈ 20 ms on the texture path.
Requires Docker (the image is arm32v7/debian:buster, run emulated — matching
the device's glibc 2.28 exactly):
make toolchain # build the armhf image make sdl2 # SDL 2.28.5 + dummy-GL patch make love-build # LÖVE 11.5 against that SDL2 (+ keep-context patch) make glsoft # the rasterizer (regenerates glsoft_gen.c and verifies enums) make love-package # -> third_party/runtime.tar.gz make love-verify # ldd in a CLEAN container; fails on any missing lib
glsoft, the build scripts and everything original here: MIT (see LICENSE).
The runtime tarball redistributes third-party libraries — versions, sources and
licenses in THIRD_PARTY.md. LÖVE and SDL are zlib; the exact
patches applied to each are in patches/.