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:
Matthias Janetschek
2026-10-07 13:50:57 +02:00
co-authored by Claude Opus 5.5
parent b22c35ae29
commit bde675f1b8
15 changed files with 610 additions and 14 deletions
+2
View File
@@ -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
View File
@@ -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)
+51
View File
@@ -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"
+50
View File
@@ -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/*
+24
View File
@@ -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/*
+93
View File
@@ -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))"
+10
View File
@@ -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
View File
@@ -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
+52
View File
@@ -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 |
+35
View File
@@ -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")
+7
View File
@@ -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" }
}
+183
View File
@@ -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"