Files
Pixelplanet--Pixel-VR-Player/README.md
T
Pixelplanet ee95d79efe feat(input): trigger selects in the browser, grip toggles the laser, downloads window replaces the browser
- Restore trigger-to-select in the file browser (it is the intuitive gesture);
  A remains an alternative for thumbstick/d-pad navigation. Both skip when the
  laser is resting on a control-bar button, which owns that click.
- Grip now toggles that hand's laser pointer, SteamVR-style: turn one off to
  drive a panel with the stick without the beam fighting it, and to pick the
  pointing hand. The raycast and the beam both honour it per hand.
- Recenter moves from Grip to Bumper (saved grip bindings migrate), since Grip
  is now the laser toggle.
- The downloads window replaces the browser panel instead of stacking over it,
  and browser input is suspended while it (or the confirm dialog) is open.
2026-10-15 20:52:38 +02:00

195 lines
8.0 KiB
Markdown

# Pixel VR Player
A native VR video player for the **Valve Steam Frame**, playing flat 2D, 180°, and
360° video (mono and stereoscopic 3D) from local storage and the local network.
- **Platform:** SteamOS (Arch Linux, ARM64) on Snapdragon 8 Gen 3
- **Stack:** C++20 · OpenXR (SteamVR runtime) · Vulkan · FFmpeg (V4L2 M2M HW decode)
- **Targets:** native Linux `x86_64` (for SteamVR-streamed desktop dev) and
`aarch64` (on-device, run directly on the SteamOS host)
> Status: **Feature-complete player.** OpenXR + Vulkan per-eye rendering with a
> shared depth buffer; hardware video decode (`h264_v4l2m2m` / `hevc_v4l2m2m` →
> NV12) with zero-copy dma-buf import; flat / 180° / 360° projection and mono /
> SBS (including half-SBS) / TB stereo, auto-detected from the filename; spatial
> audio with a master clock. In-headset UI: file browser (with file type and
> size), transport bar, settings menu, and a downloads manager. Native Valve
> Steam Frame controller support (`XR_VALVE_frame_controller_interaction`) using
> the runtime's own render model, with per-button animation on the model and
> press-to-bind remapping.
## Install (prebuilt release)
Download `pixelvr-linux-arm64.tar.gz` from the
[Releases](../../releases) page and copy it to your Steam Frame, then:
```bash
tar -xzf pixelvr-linux-arm64.tar.gz -C ~
chmod +x ~/PixelVRPlayer/run.sh
~/PixelVRPlayer/run.sh # opens the in-headset file browser
~/PixelVRPlayer/run.sh /path/clip.mp4 # …or auto-play a specific clip
```
To launch it from the headset library, add `~/PixelVRPlayer/run.sh` as a
**Non-Steam Game** (Steam ▸ Add a Non-Steam Game). SteamVR must be running; the
app logs to `~/pixelvr-run.log`.
Requirements: the SteamVR OpenXR runtime (SteamOS), Vulkan, and FFmpeg with the
V4L2 M2M decoders. For zero-copy dma-buf decoding, build the patched FFmpeg into
`~/ffbuild` — `run.sh` picks it up automatically when present, otherwise it falls
back to the system FFmpeg with CPU colour conversion.
## Controls
The laser pointer and the control bar are **separate** input paths, so the bar
stays usable while a panel is open.
| Input | Action |
| --- | --- |
| **Trigger** | Point and click: open the entry under the laser, or press a control-bar button |
| **A** | Open the highlighted entry — folder navigates, video plays / streams. Outside the browser it is play / pause |
| **X** / **Y** | Download the highlighted entry (folders confirm first, showing their size) |
| **B** | Open or close the file browser; also closes a panel and cancels the download dialog |
| **Grip** | Toggle **that hand's** laser pointer on/off. Turn one off to navigate with the thumbstick or d-pad without the beam interfering, and to choose which controller points |
| Thumbstick | Move the highlight in a panel; left/right seeks while playing |
| **Bumper** | Recenter the view in front of you |
The control bar has a **Download** button (arrow) that downloads the entry
highlighted in the browser, and a **Downloads** button (list icon) that opens the
downloads manager — where you can pause, resume, stop and clear jobs, then play a
finished file or open a finished folder. Opening the downloads manager replaces
the file browser rather than stacking on top of it.
All button bindings can be changed in **Settings ▸ Controls** (press-to-bind).
## Repository layout
```
src/
util/ logging, math, small helpers
xr/ OpenXR instance/system/session, swapchains, frame loop, input
gfx/ Vulkan device (from OpenXR), render pass, pipelines, per-eye render
scene/ projection meshes (flat / 180 hemisphere / 360 sphere), stereo material
media/ demux + decode, frame queue, A/V clock, audio out (Phase 2+)
ui/ in-VR panel: file/library browser, transport, settings (Phase 2+)
sources/ local FS + SMB/DLNA/HTTP media sources (Phase 4)
app/ app lifecycle, config, wiring
shaders/ GLSL sources compiled to SPIR-V at build time
cmake/ toolchain + shader-compile helpers
tools/ build & deploy scripts
```
## Prerequisites (Linux build environment)
On Windows, use **WSL2 (Ubuntu 22.04+)** or a Linux container. Install:
```bash
sudo apt update
sudo apt install -y build-essential cmake ninja-build git pkg-config \
libvulkan-dev vulkan-tools glslang-tools spirv-tools \
libx11-dev libxrandr-dev libxxf86vm-dev mesa-common-dev libgl1-mesa-dev \
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev
```
The OpenXR loader is fetched and built from source via CMake `FetchContent`, so no
system OpenXR package is required. It is built **statically** into the executable
(no `libopenxr_loader.so` to ship) and configured for the **xlib** presentation
backend only (XCB/Wayland disabled), so the `libx11`/`mesa` headers above are the
only windowing dependencies needed to build it.
> On WSL, VS Code cannot answer `sudo`'s password prompt; run apt as root instead:
> `wsl -u root -- apt-get install -y ...`.
### aarch64 cross-compilation
Two options, in order of runtime reliability:
1. **Device sysroot (recommended).** Capture the headset's own libraries so the
binary links against the exact FFmpeg / Vulkan / X11 SONAMEs it will load:
```bash
./tools/fetch-sysroot.sh steamos@frame # rsyncs into ./sysroot
PIXELVR_SYSROOT="$PWD/sysroot" ./tools/build.sh --aarch64
```
2. **Debian/Ubuntu arm64 multiarch (quick validation).** Enable the arm64 ports
archive and install the `:arm64` `-dev` packages, then build without a sysroot:
```bash
sudo dpkg --add-architecture arm64
# add http://ports.ubuntu.com/ubuntu-ports/ as an arm64 source, then:
sudo apt-get update
sudo apt-get install -y g++-aarch64-linux-gnu \
libavcodec-dev:arm64 libavformat-dev:arm64 libavutil-dev:arm64 \
libswscale-dev:arm64 libswresample-dev:arm64 \
libvulkan-dev:arm64 libx11-dev:arm64 libxrandr-dev:arm64 libxxf86vm-dev:arm64
./tools/build.sh --aarch64
```
Ubuntu's arm64 FFmpeg may differ in SONAME from the headset's; if the app
fails to load a library on-device, use the sysroot path above. Audio
(`libpulse-dev`) is omitted here because its glib dev chain is not
co-installable across architectures — the sysroot build restores it.
## Build
Native (desktop, for SteamVR-streamed iteration):
```bash
./tools/build.sh # configures + builds build/ for host arch
```
aarch64 cross (for the Steam Frame):
```bash
./tools/build.sh --aarch64 # configures + builds build-aarch64/
```
## Deploy to Steam Frame
See the full walkthrough in
[docs/deploy-to-steam-frame.md](docs/deploy-to-steam-frame.md) (options, runtime
choice, and troubleshooting). In short, after a one-time Developer-Mode pairing:
- **GUI (easiest to start):** `./tools/stage.sh` to assemble `dist/PixelVRPlayer/`,
then upload it in the **SteamOS Devkit Client** (Runtime: *Steam Linux Runtime
3.0 ARM64 (Sniper)*, Start Command: `pixelvr`).
- **CLI (easiest to iterate):** one command copies the binary + shaders and
launches it in the headset:
```bash
./tools/deploy.sh --launch -- /home/steamos/Videos/clip_360_tb.mp4
# override the target with --host steamos@<ip> or FRAME_HOST
```
Either way the app appears in **Steam ▸ Library ▸ Non-Steam** as *Devkit Game:
PixelVRPlayer*. To run it manually over SSH for debugging:
```bash
ssh steamos@frame '~/devkit-game/PixelVRPlayer/launch.sh /path/to/video.mp4'
```
## Test on device
Projection and stereo layout are inferred from the filename, so name test clips
accordingly (or pass a plain 2D clip for flat playback):
| Filename contains | Projection | Stereo |
|--------------------------|------------|---------------|
| `...360...`, `_sbs`/`_lr`| 360° | side-by-side |
| `...180...`/`vr180`, `_tb`/`_ou` | 180° | top-bottom |
| (none) | flat 2D | mono |
Requirements and tips:
- **8-bit H.264 or HEVC** with even width/height (4:2:0). 10-bit / Main10 is not
supported by the V4L2 M2M decoder path.
- `PIXELVR_FORCE_SW=1` forces software decode (useful for debugging).
- `PIXELVR_LOG=debug` raises log verbosity.
## License
TBD.