mirror of
https://github.com/spoopyghosty0/frameport.git
synced 2026-10-06 08:00:35 +02:00
- Sound: the default output's pulse monitor (what the headset plays) as AAC in the same fMP4; the player starts muted (browser autoplay rules) with a Sound on button. Video timestamps go to the wall clock (-ts mono2abs) instead of forcing it on the pulse input, which gave bursts of AAC packets one shared timestamp; aresample=async=1 keeps the sound continuous. - Late viewers start at a keyframe of the video track (audio samples are all sync). - Qualities 360p/480p/720p/1080p scale down only; 'full' sends SteamVR's headset view at its own size (1920x1080 today; v4l2cam has no size option, so 2K/4K would only upscale). - Player keeps a 0.3 s cushion and catches up at 1.1x; it seeks only when >2 s behind (seeking to the very edge starved playback).
13 KiB
13 KiB
Steam Frame runtime reference (SteamOS 0.3.0, build 20260922)
Lepton (Android container)
- Steam app 3029110 "Lepton" (runtime) and 3056000 "Lepton Development" (needs Developer Mode). The binary
is
<Steam library>/steamapps/common/Lepton/lepton; FramePort finds it via the appmanifests. - Started per game with
lepton startand these env vars:SteamAppId,STEAM_COMPAT_INSTALL_PATH(dir withgame.apk+obb/),STEAM_COMPAT_DATA_PATH(container data/saves),STEAM_COMPAT_SHADER_PATH,STEAM_COMPAT_LIBRARY_PATHS,IS_PARENT=true, plusLEPTON_ENV_<NAME>to pass<NAME>into the container (FramePort passesFRAMEBRIDGE_CONFIG). - Runs as podman container
lepton-steamlaunch-<appid>; logcat is mirrored to stdout (→launch.log) and to~/.local/share/Steam/logs/lepton-logcats/steamlaunch-<appid>. "Exited!" / "Early-exit" mark the end. - Rootless podman creates a kernel session keyring per container start and never frees it; the per-user quota is
200 keys, so ~200 launches after boot every container fails ("create keyring … Disk quota exceeded"). FramePort sets
keyring = falsein~/.config/containers/containers.conf; a reboot clears already-leaked keys. - Picks the activity with category LAUNCHER (
APP_ACTIVITY). Game data is visible at/sdcard/Android/{obb,data}/<pkg>. - 64-bit only (no AArch32).
<install>/lepton-shaders/holds Mesa's shader cache (Lepton setsmesa.shader.cache.dir=/data/shaders):foz_cache.foz+foz_cache_idx.fozper graphics driver (Turnip Vulkan, zink GL), typically 4–6 files and 7–19 MB per game, 2D apps included. It speeds up later starts; deleting it only costs one slower start.- 2D apps (verified 2026-10-02 with an open-source 2048 game): Lepton runs every app headless
(
lepton.headless=true; only OpenXR output reaches the headset) unless the app folder (STEAM_COMPAT_INSTALL_PATH) contains a filelepton-show-flatscreen(liblepton/app_metadata.sh): then Android (11, Waydroid) gets a Wayland window shown as a flat panel in the headset. FramePort creates the file for Android apps without VR. Android's back/home/recents bar is drawn over the app's own controls;qemu.hw.mainkeys=1removes it, but it's only read at boot and Lepton has no setting for extra properties, so patchdevice.hide_navbar(default on for apps without VR) passes it as a second line of aLEPTON_GFXRECON_*value (Lepton copies those into the boot properties unescaped). Runtime alternatives didn't work on Android 11:policy_controlis gone,cmd statusbar send-disable-flag home recentshas nobackand moves the back button onto the app's controls, thesysui_nav_barlayout and disabling SystemUI had no effect.
OpenXR runtime (as seen by games through overport's loader)
- Instance extensions present include KHR_android_create_instance (must be enabled; the adapter adds it), KHR_vulkan_enable(2), KHR_opengl_es_enable, KHR_composition_layer_depth, FB_display_refresh_rate, EXT_hand_tracking (synthesized from controllers), VALVE_frame_controller_interaction, META_recommended_layer_resolution, FB_swapchain_update_state, FB_space_warp, EXT_frame_synthesis, … (full list in any launch.log, tag overportOXR).
- Missing (emulated by the adapter): XR_FB_passthrough, XR_FB_scene / spatial_entity(_query/_storage/_container) /
scene_capture, XR_FB_composition_layer_image_layout (flip), XR_KHR_convert_timespec_time (returns
FUNCTION_UNSUPPORTED). XR_FB_render_model is presumably missing too (not checked on the device yet); the adapter
emulates it only with
controller_models=1and uses a native one if the runtime ever has it. Missing (dropped): XR_KHR_composition_layer_equirect2, XR_KHR_composition_layer_cylinder (the VrApi bridge converts cylinders to quads). - Layer types (checked 2026-09-30 in
/opt/steamvr/bin/androidarm64/vrclient.so, the Android-side runtime Lepton mounts at/data/steamvr/runtime): its compositors (CSxrCompositorPrism,CSxrCompositorOpenVR) only haveComposeLayerQuadandComposeLayerProjection; cube/cylinder/equirect/equirect2 exist only as enum names, so there is nothing to switch on. It has a layer limit ("Exceeded the layer limit"; the value is logged bylayer_debugfrommaxLayerCount). The adapter emulates cylinders (strips) and, per game, equirect layers (equirect_emul, an adapter projection layer). Quad layers are drawn above every projection layer regardless of submission order (seen 2026-10-01: quads placed before 4XVR's projection layer covered it). - Swapchain formats: GLES
GL_SRGB8_ALPHA8(35907) /GL_SRGB8(35905) only, samples = 1. Vulkan: 43 (R8G8B8A8_SRGB) and 50, not 37/44 (UNORM). - Environment blend: ALPHA_BLEND available (greyscale passthrough cameras).
- Reference spaces: STAGE bounds are reported as 1×1 m.
- Display 72 Hz by default in tests. Head pose is only tracked while the headset is worn; otherwise flags 0x3 and the session stays below FOCUSED.
Graphics stack
- Vulkan: freedreno (Mesa Turnip); Valve injects
VK_LAYER_VALVE_rpoandVALVE_fdm_injectionvia VK_INSTANCE_LAYERS (setVK_INSTANCE_LAYERS=""throughdevice.lepton_envto test without them). - GL ES: Zink (Mesa GL on Vulkan). Strict GLSL (see PLAYBOOK) and occasional
DEVICE LOSTwith MSAA render-to-texture.
Proton / Windows games (surveyed 2026-09-29; running a Rift game under it not yet verified)
- Steam on the Frame registers ARM64 compat tools from the app "Steam Frame ARM64 Compat List" (appinfo
extended.compat_tools, found dynamically by the agent):proton_11-arm64(4628740, needssteamlinuxruntime_steamrt4-arm644185400),proton-experimental-arm64(4427310),fex(3127680, FEX-Emu for Linux x86 binaries;/usr/bin/FEXBashshows the chain steam-launch-wrapper → reaper → fex-compat-tool → SteamLinuxRuntime_4). None are installed by default. steam -ifrunning steam://install/<appid>only opens a confirmation dialog in the headset. The agent's unattended mode writes an appmanifest stub (StateFlags 1026, installdir from appinfo) and restarts Steam, which then downloads it.- The command Steam runs is built from each tool's
toolmanifest.vdf(commandline,require_tool_appid):<SLR4-arm64>/_v2-entry-point --verb=waitforexitandrun -- <Proton>/proton waitforexitandrun <exe>. - Proton sets up VR (vrclient/wineopenxr registry) only when
SteamGameIdis set (steam_helpersetup_vr_registry). - Host OpenXR runtime for Linux processes:
~/.config/openxr/1/active_runtime.json→/opt/steamvr/steamxr_linuxarm64.json("SteamVR",bin/linuxarm64/vrclient.so); implicit API layerXrApiLayer_VALVE_fdm_injection. - The "Steam" VR games in the Frame's library (Pistol Whip, Job Simulator, SUPERHOT VR) are APKs run by Lepton, not Proton.
- Verified on the device (agent
proton_selftest,xr_layer_test): FramePort installs Proton 11 (ARM64) + SLR4 by itself (~90 s); Proton runs x86cmd.exein ~3 s; withSteamGameIdset it registers wineopenxr (HKLM\Software\Khronos\OpenXR\1→C:\openxr\wineopenxr64.json). Headless launches need the display session (DISPLAY=:0,GAMESCOPE_WAYLAND_DISPLAY=gamescope-0), else Wine can't create windows and the process hangs. - The Linux SteamVR runtime (Proton's loader
files/lib/aarch64-linux-gnu/libopenxr_loader.so.1) does supportXR_KHR_convert_timespec_time(unlike the Android runtime Quest games see), so wineopenxr'sXR_KHR_win32_convert_performance_counter_timeworks. - The Linux SteamVR runtime only accepts OpenXR 1.0 apps:
xrCreateInstancewithapiVersion1.1 returnsXR_ERROR_API_VERSION_UNSUPPORTED(-4), with any extensions; 1.0 works (checked 2026-09-29, SteamOS 0.4.2). Proton 11's VR helper (proton_vrhelper) requests 1.1, so without help VR never starts for Proton games: the log showsLoaderInstance::CreateInstance chained CreateInstance call failed, games run as a flat window (Rick and Morty) and Revive's OVRPlugin reportsovr_Initialize failed: Unable to load LibOVRRT DLL(Lies Beneath, then a crash). FramePort's OpenXR layer (native/xrlayer, patchpcvr.xr_timefix, default on for PC VR games) retries such a create as 1.0; enabled byXR_ENABLE_API_LAYERSfrom launch.sh. Proton's Steam Linux Runtime container dropsXR_API_LAYER_PATH(it keepsXR_ENABLE_API_LAYERS), so the agent registers the layer as an explicit layer in~/.local/share/openxr/1/api_layers/explicit.d/(home is shared into the container) with an absolute library path. - Oculus Store builds that delay-load
LibOVRPlatform64_1.dll(Platform SDK, e.g. Lies Beneath) crash with0xc06d007e(delay-load module not found): that DLL comes with the Oculus app, which the Frame doesn't have. (No Oculus runtime DLLs or registry keys are needed: Revive's LibOVRRT hook works once OpenXR does.) - Unreal's Oculus plugin (and LibOVR's
ovr_Detect) first checks for the Windows eventOculusHMDConnected(created by the Oculus service on a PC) and silently skips VR without it. Revive hooksOpenEventWfor that — Lies Beneath got as far as OVRPlugin, so the hook evidently works there — but it depends on Detours patching Wine's kernelbase, which is ARM64EC code under Proton arm64. Patchpcvr.oculus_unreal(default for Unreal Rift games) makes it independent of that: launch.sh starts the injector throughhelpers/fp_oculushmd.exe(native/oculushmd), which creates the real event and keeps it until the game (tracked by a job object) has exited.
Steam integration
- Non-Steam shortcut: binary
userdata/<id>/config/shortcuts.vdf; appid = crc32(exe+title) | 0x80000000; artwork inconfig/grid/<appid>p.*(portrait),<appid>.*(landscape),<appid>_hero.*,<appid>_logo.*. Steam reads the file only at start → stop steam.service, edit, start (FramePort does it once per batch). - Processes started from Steam (Konsole, SSH sessions?) share steam.service's cgroup: use
systemd-run --user. - SSH:
sshdmust be enabled (sudo systemctl enable --now sshd), which needs a user password (passwd). - mDNS: avahi-daemon runs by default; hostname
frame→frame.local.
Video of the headset view (surveyed 2026-10-05; used by the Live view tab)
steamvr-v4l2cam.service(user unit, part of gamescope-session.target,Restart=always) runs SteamVR's/opt/steamvr/bin/linuxarm64/v4l2cam --output=99: it reads the compositor's "Headset View" (IVRHeadsetView) and writes it to a v4l2loopback webcam named "SteamVR" (/dev/video99, 1920x1080 RGB24, advertised 30 fps, frames arrive at the display rate). Nothing on the Frame reads it by default; idle it costs nothing, read ~0.2 core. It shows what the wearer sees (SteamVR home, Steam's panels; a game's layers are expected but not yet seen in it). Black and ~1 fps (one frame per ~1.0 s) while the headset sleeps (standby): a 30 fps stream then repeats each frame in bursts, which looks like a stall in a player. Its size follows SteamVR's headset view (v4l2cam has no size option), so the live view only scales down (360p/480p/720p/1080p) or sends it as is ("full").- Sound:
pactl get-default-sink(alsa_loopback_device.stereo.alsa_output.platform-sound.HiFi__Speaker__sink) and its.monitorsource carry what the headset plays; the Frame's ffmpeg has thepulseinput andaac. Timestamps: pulse uses the wall clock, v4l2 CLOCK_MONOTONIC →-ts mono2abson the v4l2 input. Don't force-use_wallclock_as_timestampson the pulse input: it stamped bursts of AAC packets with one time. - Steam's own game recording / Remote Play / broadcast capture the gamescope PipeWire node (
CDesktopCapturePipeWire: ... node path: gamescope): with gamescope's--backend openvrthat's only the flat Steam UI, not VR. Steam's arm64libvideo.soencodes with x264 (vaapi/nvenc paths don't apply). No cast/spectator feature for the Frame's own VR view exists in Steam's UI; the SteamVR web server (27062) has no mirror route. - Encoding: the Qualcomm encoder (
/dev/video23qcom-iris-encoder, V4L2 M2M) doesn't work with the stock tools (ffmpegh264_v4l2m2mhangs, gstv4l2h264encnot-negotiated). ffmpeg + libx264 works: FramePort's live view (install/livestream.py:fps=30before the scale, ultrafast/zerolatency, 3 threads, nice 10, fragmented MP4 on stdout, + AAC 128k) measured 0.34 core at 720p / 0.56 at 1080p with a quiet picture (video only); expect ~1-1.4 cores with a busy scene.
Text input
- Lepton's Android has no on-screen keyboard (IME) and VR apps run headless, so no Android window has keyboard focus
and key presses (Steam's keyboard, USB/Bluetooth keyboards,
input text) don't reach VR apps. The app folder'slepton-show-flatscreenmarker shows the window (VR keeps working) and gives it focus; Steam's on-screen keyboard then opens for its text fields (FramePort patchdevice.text_input_window). /dev/uinputis writable by the steamos user (ACL for Steam Input): a uinput virtual keyboard works like a real one everywhere (FramePort's "Type on Frame", agent_keyboard).- Unity text fields close without a system keyboard; see
frame.unity_text_inputin PLAYBOOK.md.