Steam Frame build, packaging and deployment scripts; render test
- build_scripts/steamframe/: Debian trixie (Qt 6.8) containers for the aarch64 cross build, native build and tests; toolchain; scripts to build, test (Vulkan validation layers), package (system Qt, ~3 MB) and deploy over SSH. Adapted from the vrdashboard template. - test/render/: render test for the OpenGL and Vulkan backends, driven like OverlayController (option OVRAS_BUILD_TESTS). - RPATH only $ORIGIN, without the build machine's link directories. - Ignore dist/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b22c35ae29
commit
bde675f1b8
@@ -17,3 +17,5 @@ build_scripts/win/current_build.bat
|
|||||||
AdvancedSettings_resource.rc
|
AdvancedSettings_resource.rc
|
||||||
.qtc_clangd/
|
.qtc_clangd/
|
||||||
out/
|
out/
|
||||||
|
# Steam Frame release packages (build_scripts/steamframe/package-release.sh)
|
||||||
|
dist/
|
||||||
|
|||||||
+12
-1
@@ -283,8 +283,10 @@ if(NOT WIN32 AND NOT APPLE)
|
|||||||
set_property(TARGET AdvancedSettings PROPERTY OUTPUT_NAME AdvancedSettings_app)
|
set_property(TARGET AdvancedSettings PROPERTY OUTPUT_NAME AdvancedSettings_app)
|
||||||
# libopenvr_api.so is copied next to the executable (see "openvrApiCopy");
|
# libopenvr_api.so is copied next to the executable (see "openvrApiCopy");
|
||||||
# $ORIGIN makes the loader look there, in the build tree and in packages.
|
# $ORIGIN makes the loader look there, in the build tree and in packages.
|
||||||
|
# BUILD_WITH_INSTALL_RPATH: only $ORIGIN, without the build machine's
|
||||||
|
# link directories (the build tree is what gets packaged).
|
||||||
set_target_properties(AdvancedSettings PROPERTIES
|
set_target_properties(AdvancedSettings PROPERTIES
|
||||||
BUILD_RPATH "$ORIGIN"
|
BUILD_WITH_INSTALL_RPATH ON
|
||||||
INSTALL_RPATH "$ORIGIN"
|
INSTALL_RPATH "$ORIGIN"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
@@ -594,3 +596,12 @@ install(SCRIPT ${deploy_script})
|
|||||||
# "${COPY_DEST_DIR}/AdvancedSettings.exe"
|
# "${COPY_DEST_DIR}/AdvancedSettings.exe"
|
||||||
#)
|
#)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
# Tests (off by default). test/render checks the render backends
|
||||||
|
# (docs/steamframe/DESIGN.md, "Tests"); build_scripts/steamframe/
|
||||||
|
# container-test.sh runs them with the Vulkan validation layers.
|
||||||
|
option(OVRAS_BUILD_TESTS "Build the tests" OFF)
|
||||||
|
if (OVRAS_BUILD_TESTS)
|
||||||
|
enable_testing()
|
||||||
|
add_subdirectory(test/render)
|
||||||
|
endif ()
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# CMake toolchain: cross-compile for aarch64 Linux (Steam Frame) inside
|
||||||
|
# cross-arm64.Dockerfile (Debian multiarch). Used by container-build.sh.
|
||||||
|
# Adapted from the vrdashboard template.
|
||||||
|
|
||||||
|
set(CMAKE_SYSTEM_NAME Linux)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||||
|
|
||||||
|
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
|
||||||
|
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
|
||||||
|
|
||||||
|
# Debian multiarch: target libraries live in /usr/lib/aarch64-linux-gnu;
|
||||||
|
# headers are shared.
|
||||||
|
set(CMAKE_LIBRARY_ARCHITECTURE aarch64-linux-gnu)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
|
||||||
|
set(ENV{PKG_CONFIG_LIBDIR} /usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig)
|
||||||
|
|
||||||
|
# Qt 6 cross builds need a host Qt for its code generators (moc, rcc,
|
||||||
|
# qmlcachegen, ...).
|
||||||
|
set(QT_HOST_PATH /usr CACHE PATH "Host Qt prefix")
|
||||||
|
set(QT_HOST_PATH_CMAKE_DIR /usr/lib/x86_64-linux-gnu/cmake CACHE PATH "Host Qt CMake dir")
|
||||||
|
set(CMAKE_PREFIX_PATH /usr/lib/aarch64-linux-gnu/cmake)
|
||||||
|
|
||||||
|
set(CMAKE_OBJDUMP aarch64-linux-gnu-objdump)
|
||||||
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Builds OVRAS for the Steam Frame inside a Debian container (Qt 6.8, the
|
||||||
|
# series of the Frame's system Qt). Result: build/steamframe-<mode>/Release/,
|
||||||
|
# packaged by package-release.sh. Adapted from the vrdashboard template.
|
||||||
|
#
|
||||||
|
# build_scripts/steamframe/container-build.sh cross x86_64 host -> aarch64
|
||||||
|
# build_scripts/steamframe/container-build.sh native for the host's own
|
||||||
|
# architecture (on the Frame: native arm64)
|
||||||
|
# build_scripts/steamframe/container-build.sh <mode> shell
|
||||||
|
# interactive shell in that container
|
||||||
|
#
|
||||||
|
# Uses podman if available, else docker (override with CONTAINER_ENGINE).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
root="$(cd "$here/../.." && pwd)"
|
||||||
|
mode="${1:-}"
|
||||||
|
case "$mode" in
|
||||||
|
cross) dockerfile=cross-arm64.Dockerfile
|
||||||
|
toolchain="-DCMAKE_TOOLCHAIN_FILE=build_scripts/steamframe/aarch64-linux-gnu.cmake" ;;
|
||||||
|
native) dockerfile=native.Dockerfile; toolchain="" ;;
|
||||||
|
*) echo "usage: $0 cross|native [shell]" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
image="ovras-steamframe-${dockerfile%.Dockerfile}"
|
||||||
|
engine="${CONTAINER_ENGINE:-$(command -v podman || command -v docker)}"
|
||||||
|
build="build/steamframe-$mode"
|
||||||
|
|
||||||
|
"$engine" build -q -t "$image" -f "$here/$dockerfile" "$here" >/dev/null
|
||||||
|
|
||||||
|
# Run as the calling user, so the build tree isn't owned by root.
|
||||||
|
run=("$engine" run --rm -v "$root:$root:z" -w "$root")
|
||||||
|
if [[ "$(basename "$engine")" == docker ]]; then
|
||||||
|
run+=(--user "$(id -u):$(id -g)")
|
||||||
|
else
|
||||||
|
run+=(--userns=keep-id)
|
||||||
|
fi
|
||||||
|
[[ -t 0 ]] && run+=(-it)
|
||||||
|
|
||||||
|
if [[ "${2:-}" == shell ]]; then
|
||||||
|
exec "${run[@]}" "$image" bash
|
||||||
|
fi
|
||||||
|
|
||||||
|
# OVRAS_STEAM_FRAME: Frame capabilities and defaults
|
||||||
|
# (src/utils/platform_capabilities.h).
|
||||||
|
"${run[@]}" "$image" bash -c "
|
||||||
|
set -e
|
||||||
|
cmake -S . -B $build -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DOVRAS_STEAM_FRAME=ON $toolchain
|
||||||
|
cmake --build $build
|
||||||
|
"
|
||||||
|
echo "Built: $root/$build/Release"
|
||||||
Executable
+50
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Builds and runs the tests (OVRAS_BUILD_TESTS) in a Debian container with
|
||||||
|
# both render backends, Vulkan with the Khronos validation layers. Fails on any
|
||||||
|
# validation message. Adapted from the vrdashboard template.
|
||||||
|
#
|
||||||
|
# build_scripts/steamframe/container-test.sh GPU via /dev/dri if
|
||||||
|
# available, else software
|
||||||
|
# VK_DRIVER=lvp build_scripts/steamframe/container-test.sh
|
||||||
|
# force lavapipe (software)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
root="$(cd "$here/../.." && pwd)"
|
||||||
|
engine="${CONTAINER_ENGINE:-$(command -v podman || command -v docker)}"
|
||||||
|
"$engine" build -q -t ovras-steamframe-native -f "$here/native.Dockerfile" "$here" >/dev/null
|
||||||
|
"$engine" build -q -t ovras-steamframe-test -f "$here/test.Dockerfile" "$here" >/dev/null
|
||||||
|
|
||||||
|
run=("$engine" run --rm -v "$root:$root:z" -w "$root" -e HOME=/tmp -e "VK_DRIVER=${VK_DRIVER:-}")
|
||||||
|
if [[ "$(basename "$engine")" == docker ]]; then
|
||||||
|
run+=(--user "$(id -u):$(id -g)")
|
||||||
|
else
|
||||||
|
run+=(--userns=keep-id)
|
||||||
|
fi
|
||||||
|
[[ -e /dev/dri/renderD128 ]] && run+=(--device /dev/dri/renderD128)
|
||||||
|
|
||||||
|
"${run[@]}" ovras-steamframe-test bash -c '
|
||||||
|
set -e
|
||||||
|
build=build/steamframe-test
|
||||||
|
cmake -S . -B $build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DOVRAS_BUILD_TESTS=ON >/dev/null
|
||||||
|
cmake --build $build --target tst_render
|
||||||
|
if [[ -n "$VK_DRIVER" ]]; then export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/${VK_DRIVER}_icd.json; fi
|
||||||
|
vulkaninfo --summary 2>/dev/null | grep -m1 deviceName || true
|
||||||
|
Xvfb :99 >/dev/null 2>&1 &
|
||||||
|
sleep 1
|
||||||
|
export DISPLAY=:99 OVRAS_VULKAN_VALIDATION=1
|
||||||
|
ctest --test-dir $build --output-on-failure -V > $build/ctest.log 2>&1 \
|
||||||
|
|| { tail -40 $build/ctest.log; exit 1; }
|
||||||
|
grep -E "tests passed|Test +#|Totals" $build/ctest.log
|
||||||
|
# A skipped test (no GPU context) must not count as passed.
|
||||||
|
if grep -q "SKIP" $build/ctest.log; then
|
||||||
|
echo "Tests were skipped (see $build/ctest.log)"; exit 1
|
||||||
|
fi
|
||||||
|
messages=$(grep -oE "(VUID|UNASSIGNED)-[A-Za-z0-9_.-]+" $build/ctest.log | sort | uniq -c || true)
|
||||||
|
if [[ -n "$messages" ]]; then
|
||||||
|
echo "Vulkan validation messages (see $build/ctest.log):"
|
||||||
|
echo "$messages"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "No Vulkan validation messages."
|
||||||
|
'
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Cross-compilation environment for the Steam Frame (aarch64), from an x86_64
|
||||||
|
# host via Debian multiarch: the compiler and Qt's code generators (moc, rcc,
|
||||||
|
# qmlcachegen) run natively as amd64 builds, the target libraries are the
|
||||||
|
# arm64 builds. Debian trixie ships Qt 6.8, the series of the Frame's system
|
||||||
|
# Qt (SteamOS holo 0.4.3: 6.8.0). Adapted from the vrdashboard template.
|
||||||
|
FROM debian:trixie
|
||||||
|
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
|
||||||
|
RUN dpkg --add-architecture arm64 \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates cmake ninja-build pkgconf file \
|
||||||
|
g++-aarch64-linux-gnu binutils-aarch64-linux-gnu \
|
||||||
|
qt6-base-dev qt6-base-dev-tools qt6-declarative-dev qt6-declarative-dev-tools \
|
||||||
|
qt6-base-dev:arm64 qt6-declarative-dev:arm64 qt6-multimedia-dev:arm64 \
|
||||||
|
qt6-websockets-dev:arm64 qt6-svg-dev:arm64 \
|
||||||
|
libgl-dev:arm64 libegl-dev:arm64 libvulkan-dev:arm64 libxkbcommon-dev:arm64 \
|
||||||
|
libx11-dev:arm64 libxt-dev:arm64 libxtst-dev:arm64 libpulse-dev:arm64 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copies the newest package (package-release.sh) to the Steam Frame over SSH
|
||||||
|
# and runs its install.sh there (registers with SteamVR and starts it; an
|
||||||
|
# existing installation in the same place is updated). SteamVR must be running
|
||||||
|
# on the Frame. Adapted from the vrdashboard template.
|
||||||
|
#
|
||||||
|
# build_scripts/steamframe/deploy-to-frame.sh <ssh-target> [package-dir] [remote-dir]
|
||||||
|
# e.g. build_scripts/steamframe/deploy-to-frame.sh steamos@frame
|
||||||
|
#
|
||||||
|
# remote-dir defaults to "AdvancedSettings" in the remote home directory.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
target="${1:?usage: $0 <ssh-target> [package-dir] [remote-dir]}"
|
||||||
|
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
package="${2:-$(ls -dt "$root"/dist/AdvancedSettings-*-linux-arm64 2>/dev/null | head -1)}"
|
||||||
|
[[ -n "$package" && -x "$package/install.sh" ]] || {
|
||||||
|
echo "No package found (run build_scripts/steamframe/package-release.sh first)" >&2; exit 1; }
|
||||||
|
# Without the version: updates go to the same place and replace the old one.
|
||||||
|
remote="${3:-AdvancedSettings}"
|
||||||
|
|
||||||
|
# Keep the log of earlier runs on the Frame.
|
||||||
|
rsync -a --delete --exclude AdvancedSettings.log "$package/" "$target:$remote/"
|
||||||
|
ssh "$target" "\$HOME/$remote/install.sh"
|
||||||
|
echo "Installed $(basename "$package") to $target:~/$remote"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Native build environment (any architecture). On x86_64 it is the base of the
|
||||||
|
# test container (test.Dockerfile); on the Steam Frame itself (aarch64, e.g.
|
||||||
|
# with podman) it builds the same package as the cross build. Debian trixie
|
||||||
|
# ships Qt 6.8, the series of the Frame's system Qt. Adapted from the
|
||||||
|
# vrdashboard template.
|
||||||
|
FROM debian:trixie
|
||||||
|
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates cmake ninja-build pkgconf file g++ \
|
||||||
|
qt6-base-dev qt6-base-dev-tools qt6-declarative-dev qt6-declarative-dev-tools \
|
||||||
|
qt6-multimedia-dev qt6-websockets-dev qt6-svg-dev \
|
||||||
|
libgl-dev libegl-dev libvulkan-dev libxkbcommon-dev \
|
||||||
|
libx11-dev libxt-dev libxtst-dev libpulse-dev \
|
||||||
|
qml6-module-qtquick qml6-module-qtqml-workerscript qml6-module-qtquick-window \
|
||||||
|
qt6-qpa-plugins \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
Executable
+93
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Builds OVRAS for the Steam Frame (aarch64) and packages it:
|
||||||
|
# dist/AdvancedSettings-<version>-linux-arm64/ (+ .tar.gz)
|
||||||
|
# The package uses SteamOS's own Qt (no Qt bundled). It contains the program
|
||||||
|
# folder as OVRAS expects it (binary, libopenvr_api.so, manifests, res/) plus
|
||||||
|
# install.sh, uninstall.sh, run.sh and README.txt. Adapted from the
|
||||||
|
# vrdashboard template.
|
||||||
|
#
|
||||||
|
# build_scripts/steamframe/package-release.sh [--no-build]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
root="$(cd "$here/../.." && pwd)"
|
||||||
|
build=1
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--no-build) build=0 ;;
|
||||||
|
*) echo "usage: $0 [--no-build]" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
# On the Frame itself build natively, elsewhere cross-compile.
|
||||||
|
if [[ "$(uname -m)" == aarch64 ]]; then mode=native; else mode=cross; fi
|
||||||
|
|
||||||
|
(( build )) && "$here/container-build.sh" "$mode"
|
||||||
|
|
||||||
|
exe=AdvancedSettings_app
|
||||||
|
bin="$root/build/steamframe-$mode/Release"
|
||||||
|
version="$(sed -n 's/^CMAKE_PROJECT_VERSION:[A-Z]*=//p' "$root/build/steamframe-$mode/CMakeCache.txt")"
|
||||||
|
[[ "$(file -b "$bin/$exe")" == *aarch64* ]] || { echo "$bin/$exe is not an aarch64 build" >&2; exit 1; }
|
||||||
|
|
||||||
|
package="AdvancedSettings-$version-linux-arm64"
|
||||||
|
out="$root/dist/$package"
|
||||||
|
rm -rf "$out" "$out.tar.gz"
|
||||||
|
mkdir -p "$out"
|
||||||
|
cp -a "$bin/." "$out/"
|
||||||
|
|
||||||
|
# The scripts start the program from the package folder: OVRAS writes its log
|
||||||
|
# (AdvancedSettings.log) to the working directory. Process names are cut to
|
||||||
|
# 15 characters, so running instances are found by command line.
|
||||||
|
cat > "$out/install.sh" <<EOF
|
||||||
|
#!/bin/sh
|
||||||
|
# Installs or updates OVR Advanced Settings: registers it with SteamVR (starts
|
||||||
|
# automatically with SteamVR from now on) and starts it now. SteamVR must be
|
||||||
|
# running. Works over SSH.
|
||||||
|
cd "\$(dirname "\$0")" || exit 1
|
||||||
|
# An update replaces the files of a running instance: stop it first.
|
||||||
|
pkill -f "/$exe( |\\\$)" 2>/dev/null && sleep 1
|
||||||
|
./$exe --force-install-manifest || exit 1
|
||||||
|
# Start now. Over SSH there is no display; Qt needs one even for off-screen
|
||||||
|
# rendering, so use the Frame's (:0).
|
||||||
|
DISPLAY="\${DISPLAY:-:0}" nohup ./$exe >/dev/null 2>&1 &
|
||||||
|
echo "Installed and started."
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$out/uninstall.sh" <<EOF
|
||||||
|
#!/bin/sh
|
||||||
|
# Unregisters OVR Advanced Settings from SteamVR and stops it. The folder can
|
||||||
|
# be deleted afterwards.
|
||||||
|
cd "\$(dirname "\$0")" || exit 1
|
||||||
|
pkill -f "/$exe( |\\\$)" 2>/dev/null
|
||||||
|
exec ./$exe --force-remove-manifest
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$out/run.sh" <<EOF
|
||||||
|
#!/bin/sh
|
||||||
|
# Restarts OVR Advanced Settings with the given graphics API, for testing:
|
||||||
|
# ./run.sh [opengl|vulkan] (default: opengl)
|
||||||
|
cd "\$(dirname "\$0")" || exit 1
|
||||||
|
pkill -f "/$exe( |\\\$)" 2>/dev/null && sleep 1
|
||||||
|
DISPLAY="\${DISPLAY:-:0}" nohup ./$exe --graphics-api "\${1:-opengl}" >/dev/null 2>&1 &
|
||||||
|
echo "Started with \${1:-opengl}; log: \$(pwd)/AdvancedSettings.log"
|
||||||
|
EOF
|
||||||
|
chmod +x "$out/install.sh" "$out/uninstall.sh" "$out/run.sh"
|
||||||
|
|
||||||
|
cat > "$out/README.txt" <<EOF
|
||||||
|
OVR Advanced Settings $version for the Steam Frame (SteamOS, aarch64)
|
||||||
|
|
||||||
|
Uses the system's Qt (SteamOS: Qt 6.8 or newer).
|
||||||
|
|
||||||
|
Install / update: ./install.sh (SteamVR must be running)
|
||||||
|
SteamVR remembers this folder's location: after moving it,
|
||||||
|
run ./install.sh again.
|
||||||
|
Uninstall: ./uninstall.sh
|
||||||
|
Test a graphics API: ./run.sh opengl | ./run.sh vulkan
|
||||||
|
Log: AdvancedSettings.log in the working directory (this folder
|
||||||
|
when started by the scripts)
|
||||||
|
|
||||||
|
License: GPL-3.0 (LICENSE); OpenVR: LICENSE-VALVE.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
tar -C "$root/dist" -czf "$out.tar.gz" "$package"
|
||||||
|
echo "Release folder: $out"
|
||||||
|
echo "Archive: $out.tar.gz ($(du -h "$out.tar.gz" | cut -f1))"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Test environment on top of native.Dockerfile: Mesa Vulkan/GL drivers (incl.
|
||||||
|
# the lavapipe software rasterizer), the Khronos validation layers and Xvfb.
|
||||||
|
# See container-test.sh. Adapted from the vrdashboard template.
|
||||||
|
FROM ovras-steamframe-native
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
mesa-vulkan-drivers vulkan-validationlayers vulkan-tools libgl1-mesa-dri \
|
||||||
|
xvfb xauth \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
+25
-13
@@ -211,27 +211,39 @@ existing code:
|
|||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `cross-arm64.Dockerfile` | Debian container with Qt 6.8 (aarch64 multiarch), CMake, Vulkan headers, X11/Xtst, libpulse |
|
| `cross-arm64.Dockerfile` | Debian trixie (Qt 6.8) with arm64 multiarch libraries, cross compiler |
|
||||||
|
| `native.Dockerfile` | The same for the host's own architecture (on the Frame: native build); base of the test container |
|
||||||
|
| `test.Dockerfile` | Mesa (incl. lavapipe), Vulkan validation layers, Xvfb |
|
||||||
| `aarch64-linux-gnu.cmake` | Cross-compile toolchain |
|
| `aarch64-linux-gnu.cmake` | Cross-compile toolchain |
|
||||||
| `container-build.sh` | Configures the build (`OVRAS_STEAM_FRAME=ON`) and builds it in the container |
|
| `container-build.sh cross\|native` | Builds with `OVRAS_STEAM_FRAME=ON` → `build/steamframe-<mode>/Release/` |
|
||||||
| `package-release.sh` | Folder with the binary, `libopenvr_api.so`, `res/`, manifests, `install.sh` / `uninstall.sh` → `.tar.gz` |
|
| `container-test.sh` | Render tests with both APIs, Vulkan with validation layers |
|
||||||
| `deploy-to-frame.sh` | Copies the package to the Frame over SSH and runs `install.sh` |
|
| `package-release.sh` | Build output + `install.sh` / `uninstall.sh` / `run.sh` / `README.txt` → `dist/AdvancedSettings-<version>-linux-arm64(.tar.gz)` |
|
||||||
| `install.sh` / `uninstall.sh` | `--force-install-manifest` / `--force-remove-manifest` (section 2) and start / stop |
|
| `deploy-to-frame.sh` | Copies the package to the Frame over SSH (`rsync`) and runs `install.sh` there |
|
||||||
|
|
||||||
|
- **Scripts in the package:**
|
||||||
|
- `install.sh` stops a running instance, registers it (`--force-install-manifest`, auto-start)
|
||||||
|
and starts it with `DISPLAY=:0`. Over SSH there is no display, and Qt needs one.
|
||||||
|
- `run.sh [opengl|vulkan]` restarts it with a given graphics API.
|
||||||
|
- The scripts start the program in the package folder, because OVRAS writes
|
||||||
|
`AdvancedSettings.log` into the working directory.
|
||||||
- Packages use the **system Qt**: no Qt is bundled, because of section 3 the binary uses no
|
- Packages use the **system Qt**: no Qt is bundled, because of section 3 the binary uses no
|
||||||
private API.
|
private API.
|
||||||
- The template's Qt-bundling fallback is not adopted for now.
|
- RPATH is only `$ORIGIN` (`BUILD_WITH_INSTALL_RPATH`): the build tree gets packaged.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
- **x86_64 locally:** the build succeeds; `objdump` check (R1).
|
- **x86_64 locally:** the build succeeds; `objdump` check (R1).
|
||||||
- **Vulkan:** `container-test.sh` with validation layers. It reports no messages, and the
|
- **Render test `test/render/tst_render`** (`OVRAS_BUILD_TESTS=ON`):
|
||||||
render path is checked by a snapshot test (`tst_render` from the template, reduced to the
|
- Drives the backends and `QQuickRenderControl` in the same order as `OverlayController`.
|
||||||
backends).
|
- Checks the texture description for SteamVR (type, Vulkan format) and the content and
|
||||||
- **OVRAS:** the keyboard parser test `test/keyboard_input` is included in CMake if that is
|
orientation via readback.
|
||||||
easy; otherwise it is noted as not run.
|
- One CTest entry per API.
|
||||||
- **On the Frame:** a checklist per acceptance criterion R1–R4, R6, R7, with both graphics
|
- `container-test.sh` runs it with the Vulkan validation layers. It fails on validation
|
||||||
APIs. Results go to `docs/steamframe/PROGRESS.md`.
|
messages and on skipped tests.
|
||||||
|
- **OVRAS's keyboard parser test** (`test/keyboard_input`, qmake) is not included in CMake
|
||||||
|
in iteration 1 (not run).
|
||||||
|
- **On the Frame:** the checklist in `PROGRESS.md` per acceptance criterion R1–R4, R6, R7,
|
||||||
|
with both graphics APIs.
|
||||||
|
|
||||||
## Implementation order
|
## Implementation order
|
||||||
|
|
||||||
|
|||||||
@@ -122,3 +122,55 @@
|
|||||||
- The build tree contains the binary, `libopenvr_api.so`, both manifests,
|
- The build tree contains the binary, `libopenvr_api.so`, both manifests,
|
||||||
`default_action_manifests/` and `res/`.
|
`default_action_manifests/` and `res/`.
|
||||||
- Not tested yet: `Background` on the Frame (step 5).
|
- Not tested yet: `Background` on the Frame (step 5).
|
||||||
|
|
||||||
|
### Step 5: Frame build, package, tests (2026-10-07)
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- **`build_scripts/steamframe/`** (new, see DESIGN.md section 9): containers, toolchain, build,
|
||||||
|
test, package and deploy scripts.
|
||||||
|
- **`test/render/`** (new): render test for both backends (`OVRAS_BUILD_TESTS=ON`).
|
||||||
|
- **`CMakeLists.txt`:**
|
||||||
|
- option `OVRAS_BUILD_TESTS`
|
||||||
|
- RPATH only `$ORIGIN` (`BUILD_WITH_INSTALL_RPATH`). Before, the cross binary also had the
|
||||||
|
host's absolute link directory in its RUNPATH.
|
||||||
|
- **`.gitignore`:** `dist/`.
|
||||||
|
|
||||||
|
**Results:**
|
||||||
|
- **Render tests:**
|
||||||
|
- locally (Arch, Qt 6.11, Intel Iris Xe): OpenGL and Vulkan, 4/4 passed each
|
||||||
|
- in the container (Debian trixie, Qt 6.8, same GPU via `/dev/dri`): 2/2 passed,
|
||||||
|
**no Vulkan validation messages**
|
||||||
|
- Proof that the layer was active: `VK_LOADER_DEBUG=layer` shows
|
||||||
|
"Inserted device layer VK_LAYER_KHRONOS_validation".
|
||||||
|
- **Cross build (aarch64, Qt 6.8 Debian):** succeeds once the container has `libxt-dev`
|
||||||
|
(`input_sender_X11.cpp` includes `X11/Intrinsic.h`).
|
||||||
|
- `Qt_6_PRIVATE_API` without `QVulkan*`: 0 → **R1 symbols met**.
|
||||||
|
- Qt version tag: `Qt_6.8` (fits the Frame's 6.8.0).
|
||||||
|
- GLIBC ≤ 2.38 (the Frame has 2.39); GLIBCXX ≤ 3.4.32 (not checked against the Frame).
|
||||||
|
- NEEDED: Qt6 Core/Gui/Qml/Quick/Widgets/Network/DBus/Multimedia/WebSockets, libX11,
|
||||||
|
libXtst, libpulse, libopenvr_api, libstdc++/libc.
|
||||||
|
- **Package** `dist/AdvancedSettings-5.7.4-linux-arm64.tar.gz`: 3.0 MB (9.1 MB unpacked).
|
||||||
|
The version 5.7.4 comes from `qt6`'s `project()` (master is at 5.8.17).
|
||||||
|
|
||||||
|
**Open (needs the Frame):**
|
||||||
|
- Are the QML modules OVRAS imports present in the Frame's system Qt? QtQuick.Controls
|
||||||
|
(Material), Layouts, Dialogs, Multimedia.
|
||||||
|
- Do libXt/libXtst exist on SteamOS? Is libstdc++ new enough?
|
||||||
|
|
||||||
|
### Test checklist on the Frame (step 5)
|
||||||
|
|
||||||
|
Deploy: `build_scripts/steamframe/deploy-to-frame.sh <user>@<frame>` (SteamVR running).
|
||||||
|
|
||||||
|
| # | Check | Criterion | Result |
|
||||||
|
|---|---|---|---|
|
||||||
|
| T1 | `install.sh`: registration works (log: "Manifest reinstalled", via Background) | R2 | open |
|
||||||
|
| T2 | Tab appears in the dashboard, UI correct (OpenGL) | R2, R3 | open |
|
||||||
|
| T3 | `./run.sh vulkan`: UI correct | R3 | open |
|
||||||
|
| T4 | Laser right way up, click, scrolling on several pages (both APIs) | R4 | open |
|
||||||
|
| T5 | Text field (e.g. new profile name): SteamVR keyboard opens, input arrives | R4 | open |
|
||||||
|
| T6 | An OVRAS action arrives (e.g. push-to-talk, log/notification) | R4 | open |
|
||||||
|
| T7 | Play area before/after start unchanged; log: "CommitWorkingCopy skipped" | R7 | open |
|
||||||
|
| T8 | Quit app (OVRAS "Exit" and Quit in the SteamVR dashboard): no crash (both APIs) | R2 | open |
|
||||||
|
| T9 | Auto-start after a SteamVR restart / reboot | R2 | open |
|
||||||
|
| T10 | `pidstat -p $(pgrep -f AdvancedSettings_app) 10 6`: dashboard closed / OVRAS visible / other tab visible | R6 | open |
|
||||||
|
| T11 | `uninstall.sh` unregisters it | R2 | open |
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Render test for src/render/ (see tst_render.cpp). Runs without SteamVR or a
|
||||||
|
# headset; skips itself without a GPU context. One CTest entry per graphics
|
||||||
|
# API, because the API is process-wide.
|
||||||
|
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS Test)
|
||||||
|
|
||||||
|
qt_add_executable(tst_render
|
||||||
|
tst_render.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/render/RenderBackend.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/render/OpenGLRenderBackend.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(tst_render PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/src
|
||||||
|
${CMAKE_SOURCE_DIR}/third-party/openvr/headers
|
||||||
|
)
|
||||||
|
# Only openvr.h's types are used: nothing links against libopenvr_api.
|
||||||
|
target_link_libraries(tst_render PRIVATE Qt::Gui Qt::Quick Qt::Qml Qt6::Test)
|
||||||
|
if (QT_FEATURE_vulkan)
|
||||||
|
target_sources(tst_render PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/src/render/VulkanRenderBackend.cpp
|
||||||
|
)
|
||||||
|
target_compile_definitions(tst_render PRIVATE OVRAS_HAS_VULKAN=1)
|
||||||
|
else ()
|
||||||
|
target_compile_definitions(tst_render PRIVATE OVRAS_HAS_VULKAN=0)
|
||||||
|
endif ()
|
||||||
|
qt_add_qml_module(tst_render
|
||||||
|
URI RenderTest
|
||||||
|
VERSION 1.0
|
||||||
|
QML_FILES TwoTone.qml
|
||||||
|
)
|
||||||
|
|
||||||
|
add_test(NAME render_opengl COMMAND tst_render)
|
||||||
|
set_tests_properties(render_opengl PROPERTIES ENVIRONMENT "OVRAS_TEST_API=opengl")
|
||||||
|
add_test(NAME render_vulkan COMMAND tst_render)
|
||||||
|
set_tests_properties(render_vulkan PROPERTIES ENVIRONMENT "OVRAS_TEST_API=vulkan")
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
// Top half red, bottom half blue: lets the render test check content and orientation.
|
||||||
|
Item {
|
||||||
|
Rectangle { width: parent.width; height: parent.height / 2; color: "#ff0000" }
|
||||||
|
Rectangle { y: parent.height / 2; width: parent.width; height: parent.height / 2; color: "#0000ff" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
// Render test for the render backends (src/render/), adapted from the
|
||||||
|
// vrdashboard template's tst_render. It drives a backend and
|
||||||
|
// QQuickRenderControl in the same order as OverlayController::SetWidget() and
|
||||||
|
// renderOverlay(), then checks the texture handed to SteamVR and its content
|
||||||
|
// (via readback).
|
||||||
|
//
|
||||||
|
// The backend comes from OVRAS_TEST_API (opengl / vulkan): the graphics API is
|
||||||
|
// process-wide, so CTest runs one process per API. Without a GPU context the
|
||||||
|
// test skips itself. With OVRAS_VULKAN_VALIDATION=1 the Vulkan validation
|
||||||
|
// layers are enabled (build_scripts/steamframe/container-test.sh).
|
||||||
|
|
||||||
|
#include <QQmlComponent>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QQuickItem>
|
||||||
|
#include <QQuickRenderControl>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
#include <QtTest>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "render/RenderBackend.h"
|
||||||
|
|
||||||
|
class RenderTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void initTestCase()
|
||||||
|
{
|
||||||
|
GraphicsApi api = GraphicsApi::OpenGL;
|
||||||
|
const QString apiName
|
||||||
|
= qEnvironmentVariable( "OVRAS_TEST_API", "opengl" );
|
||||||
|
QVERIFY2( parseGraphicsApi( apiName, &api ), qPrintable( apiName ) );
|
||||||
|
m_backend = createRenderBackend( api );
|
||||||
|
if ( !m_backend )
|
||||||
|
{
|
||||||
|
QSKIP( qPrintable( apiName + " not compiled in" ) );
|
||||||
|
}
|
||||||
|
// Before any QQuickWindow exists, as in main().
|
||||||
|
QQuickWindow::setGraphicsApi( m_backend->graphicsApi() );
|
||||||
|
QString error;
|
||||||
|
// No SteamVR here: empty interop, the backend picks defaults.
|
||||||
|
if ( !m_backend->initialize( {}, &error ) )
|
||||||
|
{
|
||||||
|
QSKIP( qPrintable( QString( "No %1 context: %2" )
|
||||||
|
.arg( m_backend->name(), error ) ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// One frame of a known scene (top red, bottom blue): texture type and
|
||||||
|
// description for SteamVR, content and orientation.
|
||||||
|
void rendersSceneUpright()
|
||||||
|
{
|
||||||
|
const QSize size( 64, 32 );
|
||||||
|
Scene scene( *m_backend, size );
|
||||||
|
QVERIFY2( scene.error.isEmpty(), qPrintable( scene.error ) );
|
||||||
|
|
||||||
|
const vr::Texture_t texture = scene.renderFrame();
|
||||||
|
QVERIFY( texture.handle != nullptr );
|
||||||
|
const auto expectedType
|
||||||
|
= m_backend->graphicsApi() == QSGRendererInterface::Vulkan
|
||||||
|
? vr::TextureType_Vulkan
|
||||||
|
: vr::TextureType_OpenGL;
|
||||||
|
QCOMPARE( texture.eType, expectedType );
|
||||||
|
if ( texture.eType == vr::TextureType_Vulkan )
|
||||||
|
{
|
||||||
|
const auto* vk = static_cast<const vr::VRVulkanTextureData_t*>(
|
||||||
|
texture.handle );
|
||||||
|
QVERIFY( vk->m_nImage != 0 && vk->m_pDevice && vk->m_pQueue
|
||||||
|
&& vk->m_pInstance );
|
||||||
|
QCOMPARE( vk->m_nWidth, static_cast<uint32_t>( size.width() ) );
|
||||||
|
// VK_FORMAT_R8G8B8A8_UNORM, the format SteamVR gets told.
|
||||||
|
QCOMPARE( vk->m_nFormat, 37u );
|
||||||
|
}
|
||||||
|
QVERIFY( !m_backend->deviceName().isEmpty() );
|
||||||
|
|
||||||
|
const QImage image = m_backend->readback();
|
||||||
|
QCOMPARE( image.size(), size );
|
||||||
|
QCOMPARE( image.pixelColor( size.width() / 2, 4 ),
|
||||||
|
QColor( 255, 0, 0 ) );
|
||||||
|
QCOMPARE( image.pixelColor( size.width() / 2, size.height() - 4 ),
|
||||||
|
QColor( 0, 0, 255 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Several frames in a row exercise the per-frame state handling (e.g.
|
||||||
|
// the Vulkan layout transitions between Qt's frames and the SteamVR
|
||||||
|
// hand-off).
|
||||||
|
void rendersRepeatedly()
|
||||||
|
{
|
||||||
|
Scene scene( *m_backend, { 32, 32 } );
|
||||||
|
QVERIFY2( scene.error.isEmpty(), qPrintable( scene.error ) );
|
||||||
|
for ( int i = 0; i < 5; ++i )
|
||||||
|
{
|
||||||
|
scene.renderFrame();
|
||||||
|
}
|
||||||
|
QCOMPARE( m_backend->readback().pixelColor( 16, 2 ),
|
||||||
|
QColor( 255, 0, 0 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanupTestCase()
|
||||||
|
{
|
||||||
|
m_backend.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// The TwoTone scene in an offscreen window, set up like
|
||||||
|
// OverlayController::SetWidget(). Tears down like
|
||||||
|
// OverlayController::Shutdown() (minus VR_Shutdown).
|
||||||
|
struct Scene
|
||||||
|
{
|
||||||
|
Scene( RenderBackend& backend, QSize size ) : backend( backend )
|
||||||
|
{
|
||||||
|
window = std::make_unique<QQuickWindow>( &renderControl );
|
||||||
|
window->setGeometry( 0, 0, size.width(), size.height() );
|
||||||
|
QQmlComponent component( &engine );
|
||||||
|
component.loadFromModule( "RenderTest", "TwoTone" );
|
||||||
|
root.reset( qobject_cast<QQuickItem*>( component.create() ) );
|
||||||
|
if ( !root )
|
||||||
|
{
|
||||||
|
error = component.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root->setSize( size );
|
||||||
|
root->setParentItem( window->contentItem() );
|
||||||
|
|
||||||
|
backend.configureWindow( window.get() );
|
||||||
|
if ( !backend.makeCurrent() || !renderControl.initialize() )
|
||||||
|
{
|
||||||
|
error = "QQuickRenderControl::initialize failed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( !backend.attachWindow( window.get(), &error ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QQuickRenderTarget target
|
||||||
|
= backend.createRenderTarget( size );
|
||||||
|
if ( target.isNull() )
|
||||||
|
{
|
||||||
|
error = "createRenderTarget failed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window->setRenderTarget( target );
|
||||||
|
}
|
||||||
|
|
||||||
|
~Scene()
|
||||||
|
{
|
||||||
|
backend.makeCurrent();
|
||||||
|
root.reset();
|
||||||
|
backend.releaseRenderTarget();
|
||||||
|
backend.detachWindow();
|
||||||
|
window.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// One frame as in OverlayController::renderOverlay(); returns what
|
||||||
|
// would be handed to IVROverlay::SetOverlayTexture.
|
||||||
|
vr::Texture_t renderFrame()
|
||||||
|
{
|
||||||
|
backend.makeCurrent();
|
||||||
|
backend.beginFrame();
|
||||||
|
renderControl.polishItems();
|
||||||
|
renderControl.beginFrame();
|
||||||
|
renderControl.sync();
|
||||||
|
renderControl.render();
|
||||||
|
renderControl.endFrame();
|
||||||
|
backend.finishFrame();
|
||||||
|
return backend.vrTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderBackend& backend;
|
||||||
|
QQmlEngine engine;
|
||||||
|
// Destroyed after the window (declared before it): the window
|
||||||
|
// belongs to this render control.
|
||||||
|
QQuickRenderControl renderControl;
|
||||||
|
std::unique_ptr<QQuickWindow> window;
|
||||||
|
std::unique_ptr<QQuickItem> root;
|
||||||
|
QString error;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<RenderBackend> m_backend;
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_MAIN( RenderTest )
|
||||||
|
#include "tst_render.moc"
|
||||||
Reference in New Issue
Block a user