12 KiB
Deploying Pixel VR Player to the Steam Frame
This guide covers how to get the built app running on the headset, the available options, and which one to pick.
What you're shipping: a single self-contained ARM64 binary
(build-aarch64/bin/pixelvr, ~11 MB, static OpenXR loader) plus SPIR-V shaders
and a shell launcher. That's it — no libopenxr_loader.so or other bundled
libraries.
Just installing a release (no devkit, no build)
End users do not need to build anything or use devkits. Choose whichever option fits your setup:
- USB Drive / MicroSD Card (No typing & No Dev Mode needed):
Downloadpixelvr-linux-arm64.tar.gzfrom GitHub Releases onto a USB flash drive or MicroSD card, plug it into the headset, switch to Desktop Mode, and double-clickInstall Pixel VR Player.desktop. - From your PC over Wi-Fi (Requires SSH or Dev Mode):
Runtools/install-from-pc.bat(Windows) ortools/install-from-pc.sh(Mac/Linux) and enter the headset's IP address. - One command in terminal (Desktop Mode / SSH):
curl -fsSL https://raw.githubusercontent.com/Pixelplanet/Pixel-VR-Player/main/tools/install.sh | sh
The installer verifies checksums, installs to ~/PixelVRPlayer, and registers Pixel VR Player as a Non-Steam Game in your library. Re-running upgrades in-place while keeping all settings, thumbnails, and hidden vault files (~/.pixelvr_vault).
Uninstalling
To cleanly remove the application and its Steam shortcut:
# On headset:
~/PixelVRPlayer/install.sh --uninstall
# Or one-liner:
curl -fsSL https://raw.githubusercontent.com/Pixelplanet/Pixel-VR-Player/main/tools/install.sh | sh -s -- --uninstall
# Or from PC over Wi-Fi:
./tools/uninstall-from-pc.sh # Mac/Linux
tools/uninstall-from-pc.bat # Windows
To purge user settings, thumbnails, and vault files:
rm -rf ~/.config/pixelvr ~/.cache/pixelvr ~/.pixelvr_vault
The rest of this document covers building and deploying as a developer.
TL;DR
# 1. Build the ARM64 binary (in WSL / Linux)
./tools/build.sh --aarch64
# 2a. Fastest to iterate — one command (needs pairing done once):
./tools/deploy.sh --launch -- /home/steamos/Videos/clip_360_tb.mp4
# 2b. Or stage a clean folder for the GUI Devkit Client:
./tools/stage.sh # -> dist/PixelVRPlayer/
# 2c. Or assemble release tarball with desktop installer:
./tools/package-release.sh build-aarch64 pixelvr-linux-arm64.tar.gz v1.4.2
# 2d. Capture in-headset screenshots over OpenVR / SteamVR compositor:
python tools/take_headset_screenshot.py --delay 5
One-time setup
All three methods below share the same setup. Do this once.
- Developer Mode on the headset: Settings ▸ System ▸ Enable Developer Mode. This turns on SSH/ADB/RDP.
- Set a user password: Settings ▸ Developer ▸ Set User Password (needed for SSH).
- (Recommended) Beta channel: Settings ▸ System ▸ System Update Channel ▸ Beta, and opt the desktop Steam client + SteamVR into Beta too.
- Hostname defaults to
frame(Settings ▸ System ▸ Hostname). Wherever this guide saysframe, substitute your hostname or the headset's IP. - Install the SteamOS Devkit Client on your PC from Steam
(
steam://install/943760; it's under Library ▸ Software). - Pair: on the headset, Settings ▸ Developer ▸ Pair new host. In the Devkit Client's Devkits tab, click Register next to your frame, then confirm on the headset. This installs an SSH key and grants full control.
Using the SSH key from WSL. The Devkit Client (Windows) stores its key at
%LOCALAPPDATA%\steamos-devkit\steamos-devkit\devkit_rsa. Our CLI tools run in WSL, so copy it where they can find it:mkdir -p ~/.config/steamos-devkit cp "/mnt/c/Users/$USER/AppData/Local/steamos-devkit/steamos-devkit/devkit_rsa" \ ~/.config/steamos-devkit/devkit_rsa chmod 600 ~/.config/steamos-devkit/devkit_rsa(Adjust the Windows username if it differs from
$USER.)
Which option is easiest?
| Method | Per-deploy effort | Best for |
|---|---|---|
| A. Devkit Client (GUI) | click Upload → Start | first run; the official, guided path |
B. tools/deploy.sh (CLI) |
one command | fast iteration during development |
| C. Manual SSH + scp | a few commands | debugging, capturing logs live |
| — | APKs only — not this app |
- Easiest to start: Option A (the GUI does the runtime wiring for you).
- Easiest to iterate: Option B (one command builds nothing you didn't ask for and launches in the headset).
They all end up in the same place: Steam ▸ Library ▸ Non-Steam ▸ Pixel VR Player.
Option A — SteamOS Devkit Client (GUI)
The official, no-scripting path.
- Stage a clean upload folder so you don't upload the whole build tree:
./tools/stage.sh # creates dist/PixelVRPlayer/{pixelvr, shaders/, launch.sh} - In the Devkit Client, Connect to devkit by IP =
frame(or your IP), then open Title Upload and fill in:- Name:
PixelVRPlayer— required, and must match^[A-Za-z_][A-Za-z0-9_.]+$: letters/digits/_/.only, no spaces or hyphens, starting with a letter or underscore. Leaving it blank (or usingPixel VR Player) fails withTitle name '' must match pattern .... - Local Folder:
dist/PixelVRPlayer - Start Command:
pixelvr— required (the binary's path relative to the Local Folder). An empty value fails withstart command is empty / missing!. If launch later errors with "not found", use./pixelvr. - Runtime:
Steam Linux Runtime 3.0 ARM64 (Sniper)
- Name:
- Click Upload, then Start.
- To pass a video or environment variables, set them in the game's Properties ▸
Launch Options in Steam. Arguments are appended; env vars use
%command%:PIXELVR_LOG=debug %command% /home/steamos/Videos/clip.mp4
Option B — One command (tools/deploy.sh)
Best for the edit-build-run loop. Copies the binary, shaders and launch.sh into
~/devkit-game/PixelVRPlayer/, registers the Non-Steam shortcut, and (optionally)
launches it.
# Copy + register + launch, with a video argument:
./tools/deploy.sh --launch -- /home/steamos/Videos/clip_360_tb.mp4
# Just stage it (no launch):
./tools/deploy.sh
# Different headset:
./tools/deploy.sh --host steamos@192.168.1.50 --launch
Overrides: FRAME_HOST (default steamos@frame) and FRAME_SSH_KEY. If frame
doesn't resolve from WSL, pass the IP with --host.
This registers a shortcut that runs the binary directly on the SteamOS host (see Runtime choice below).
Option C — Manual SSH (debugging)
Most control, and you see stdout/stderr live.
# Copy the staged folder over:
scp -r dist/PixelVRPlayer steamos@frame:~/
# Run it with the SteamVR OpenXR runtime selected:
ssh steamos@frame '~/PixelVRPlayer/launch.sh /home/steamos/Videos/clip.mp4'
launch.sh sets XR_RUNTIME_JSON, LD_LIBRARY_PATH and PIXELVR_SHADER_DIR for
you. Note that OpenXR needs the SteamVR runtime/compositor to be up — if a plain
SSH run fails at XR init, launch it once from the Steam library instead (Option A
or B), which brings the runtime up.
Runtime choice: Sniper vs on-host
There are two ways a native ARM64 Linux binary can run on the Frame, and they differ in which system libraries it sees:
- Steam Linux Runtime 3.0 ARM64 (Sniper) — a containerized, stable library environment. This is what Valve's docs recommend and what the GUI (Option A) selects. Good default.
- Directly on the host — uses SteamOS's own FFmpeg/SteamVR libraries. This is
what
deploy.sh(Option B) registers, matching how a shipping native OpenXR player runs.
Both expose the SteamVR OpenXR runtime, so both can work. If the app fails to load a library under one, try the other.
The library-compatibility gotcha (important)
The quick --aarch64 build links against Ubuntu's arm64 FFmpeg
(libavcodec.so.60). SteamOS is Arch-based and may ship a different FFmpeg
version, so on-device you might see:
error while loading shared libraries: libavcodec.so.60: cannot open shared object file
Fix — build against the headset's own libraries:
./tools/fetch-sysroot.sh steamos@frame # rsyncs the device libs into ./sysroot
PIXELVR_SYSROOT="$PWD/sysroot" ./tools/build.sh --aarch64
This produces a binary linked against the exact SONAMEs the Frame provides, and also restores audio (the quick multiarch build omits PulseAudio because its glib dev chain isn't co-installable across architectures).
Preparing test clips
Projection and stereo layout are auto-detected from the filename:
| Filename contains | Projection | Stereo |
|---|---|---|
360, or _sbs/_lr |
360° | side-by-side |
180/vr180, or _tb/_ou |
180° | top/bottom |
| (none of the above) | flat 2D | mono |
Example: nature_360_tb.mp4 → 360° top/bottom stereo.
Requirements:
- 8-bit H.264 or HEVC, even width/height (4:2:0). 10-bit/Main10 is not supported by the hardware decoder path.
PIXELVR_FORCE_SW=1forces software decode (debugging).PIXELVR_LOG=debugraises log verbosity.
Taking in-headset screenshots
To capture real in-headset screenshots directly from the SteamVR compositor while the app is running:
# Capture with a 5-second countdown to put the headset on:
python tools/take_headset_screenshot.py --delay 5
# Point to a specific headset IP:
python tools/take_headset_screenshot.py --host steamos@192.168.178.61 --delay 3
# Continuous series (e.g. 5 captures spaced 2 seconds apart):
python tools/take_headset_screenshot.py --interval 2 --count 5
The script connects over SSH, triggers SteamVR's compositor screenshot API, fetches the resulting PNGs, and places them into docs/screenshots/ with both the raw stereo composite (_stereo.png) and a cropped single-eye preview (_preview.png).
Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
Title name '' must match pattern ^[A-Za-z_]... |
The Name field is empty or has spaces/hyphens. Use a bare token like PixelVRPlayer. |
start command is empty / missing! |
The Start Command field is blank. Set it to pixelvr (binary path relative to the Local Folder). |
| Headset not listed in Devkit Client | Same LAN + mDNS required; use Connect by IP = frame or the IP. |
ssh: Could not resolve hostname frame |
Use the IP: --host steamos@<ip>. |
Permission denied (publickey) |
Finish Pair new host + Register; point tools at devkit_rsa (see setup note). |
libavcodec.so.60: cannot open shared object file |
FFmpeg SONAME mismatch — do the sysroot build. |
XR init fails / RUNTIME_UNAVAILABLE over SSH |
Launch from the Steam library once so SteamVR is running. |
failed to open shader |
Ensure shaders/ sits next to the binary, or set PIXELVR_SHADER_DIR. |
| Black view, no video | Clip must be 8-bit H.264/HEVC, even dimensions; check PIXELVR_LOG=debug. |
| No audio | Expected on the quick multiarch build; use the sysroot build. |