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
|
||||
.qtc_clangd/
|
||||
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)
|
||||
# libopenvr_api.so is copied next to the executable (see "openvrApiCopy");
|
||||
# $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
|
||||
BUILD_RPATH "$ORIGIN"
|
||||
BUILD_WITH_INSTALL_RPATH ON
|
||||
INSTALL_RPATH "$ORIGIN"
|
||||
)
|
||||
endif()
|
||||
@@ -594,3 +596,12 @@ install(SCRIPT ${deploy_script})
|
||||
# "${COPY_DEST_DIR}/AdvancedSettings.exe"
|
||||
#)
|
||||
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 |
|
||||
|---|---|
|
||||
| `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 |
|
||||
| `container-build.sh` | Configures the build (`OVRAS_STEAM_FRAME=ON`) and builds it in the container |
|
||||
| `package-release.sh` | Folder with the binary, `libopenvr_api.so`, `res/`, manifests, `install.sh` / `uninstall.sh` → `.tar.gz` |
|
||||
| `deploy-to-frame.sh` | Copies the package to the Frame over SSH and runs `install.sh` |
|
||||
| `install.sh` / `uninstall.sh` | `--force-install-manifest` / `--force-remove-manifest` (section 2) and start / stop |
|
||||
| `container-build.sh cross\|native` | Builds with `OVRAS_STEAM_FRAME=ON` → `build/steamframe-<mode>/Release/` |
|
||||
| `container-test.sh` | Render tests with both APIs, Vulkan with validation layers |
|
||||
| `package-release.sh` | Build output + `install.sh` / `uninstall.sh` / `run.sh` / `README.txt` → `dist/AdvancedSettings-<version>-linux-arm64(.tar.gz)` |
|
||||
| `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
|
||||
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
|
||||
|
||||
- **x86_64 locally:** the build succeeds; `objdump` check (R1).
|
||||
- **Vulkan:** `container-test.sh` with validation layers. It reports no messages, and the
|
||||
render path is checked by a snapshot test (`tst_render` from the template, reduced to the
|
||||
backends).
|
||||
- **OVRAS:** the keyboard parser test `test/keyboard_input` is included in CMake if that is
|
||||
easy; otherwise it is noted as not run.
|
||||
- **On the Frame:** a checklist per acceptance criterion R1–R4, R6, R7, with both graphics
|
||||
APIs. Results go to `docs/steamframe/PROGRESS.md`.
|
||||
- **Render test `test/render/tst_render`** (`OVRAS_BUILD_TESTS=ON`):
|
||||
- Drives the backends and `QQuickRenderControl` in the same order as `OverlayController`.
|
||||
- Checks the texture description for SteamVR (type, Vulkan format) and the content and
|
||||
orientation via readback.
|
||||
- One CTest entry per API.
|
||||
- `container-test.sh` runs it with the Vulkan validation layers. It fails on validation
|
||||
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
|
||||
|
||||
|
||||
@@ -122,3 +122,55 @@
|
||||
- The build tree contains the binary, `libopenvr_api.so`, both manifests,
|
||||
`default_action_manifests/` and `res/`.
|
||||
- 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