6 Commits
Author SHA1 Message Date
lbgos 8009f79310 Arch packages: release r31 2026-10-18 21:33:32 +03:00
lbgos d157d0ab51 roomsetup: seed universe 2 for a first Room Setup
Room Setup starts in the session universe 3 and the driver switches to
universe 2 once tracking reports setup. On a first setup the chaperone
file has no universe 2, so SteamVR logs "Failed to update shared data"
on every switch and has no calibration for the frame being drawn in:
the floor step shows no floor and the draw step registers no strokes.
Reported logs show this on all five switches; VM 124 always had a
universe 2 and never logged it.

When the file has no universe 2, write it with the same plain origin as
universe 3, so the switch keeps the frame. Accept replaces it with the
drawn room. A seeded entry has no play area or bounds, so the world map
treats it like a missing boundary. An existing universe 2 is untouched.
2026-10-18 21:21:47 +03:00
lbgos 1fb075c174 basalt: stop growing execution stats in live trackers
Basalt's VIO estimators append every optimize() timing sample to
stats_all_ and stats_sums_. Only debug_finalize() reads them, and VIT
never calls it, so the history grows for as long as tracking runs
(about 1.6 MB/min in a 30 Hz stereo + 500 Hz IMU harness). Patch 0004
disables the stats for VIT trackers and skips the per-iteration
/proc/self/statm read that only fed them.
2026-10-18 21:21:47 +03:00
lbgos e16f571e57 steamvr_drv: name the RLIMIT_RTTIME compositor kill in vrserver.txt
vrcompositor inherits vrserver's zero RLIMIT_RTTIME. With CAP_SYS_NICE on
vrcompositor-launcher its realtime threads are SIGKILLed by the kernel and
SteamVR only reports a compositor crash. Log the cause and the setcap -r
fix once at driver start.
2026-10-18 21:21:47 +03:00
lbgos 5fb003aaab u_linux: keep normal priority under a zero RLIMIT_RTTIME
With a zero RLIMIT_RTTIME the kernel SIGKILLs the whole process at the
first scheduler tick that lands while a realtime thread runs, even if the
thread sleeps every iteration. SteamVR's vrserver runs drivers with this
limit, so the Rift S USB thread's realtime request would take vrserver
down wherever it succeeds (CAP_SYS_NICE or RLIMIT_RTPRIO 99).
2026-10-18 21:21:47 +03:00
lbgos 2d0c513209 README: Flatpak and Snap Steam are not supported 2026-10-18 19:23:06 +03:00
14 changed files with 417 additions and 28 deletions

No files matched your search

+2 -2
View File
@@ -22,7 +22,7 @@ Free and open source, built on Monado and Basalt. Steam and SteamVR are installe
- Oculus Rift S, its Touch controllers, USB 3 and DisplayPort.
- An x86-64 Linux desktop with a systemd user session and local USB seat access.
- Native Steam with SteamVR installed from the Steam library.
- Native Steam with SteamVR installed from the Steam library. Flatpak and Snap Steam are not supported: Valve does not support SteamVR in them because they break DRM leasing and asynchronous reprojection.
- A Vulkan-capable GPU and drivers supported by SteamVR's direct display mode. Current testing uses an NVIDIA RTX 3090.
- The patched Basalt build. The Arch packages include it; an unpatched Basalt lacks the required reconnect support.
- KDE Plasma Wayland for the Desktop overlay.
@@ -54,7 +54,7 @@ tar -xf rift-s-linux-r30-x86_64.tar.xz
Install a C/C++ toolchain, CMake, Ninja, Eigen, TBB, fmt, OpenCV, Vulkan headers and loader, glslang, libusb, SDL2, cJSON, libcap, systemd and the X11, Wayland and DRM development libraries. The Desktop overlay also needs PipeWire development files, wayland-scanner, wayland-protocols and plasma-wayland-protocols. Settings needs Rust/Cargo, Node.js, pnpm, GTK3, WebKitGTK 4.1, shaderc's `glslc` and desktop-file-utils.
Build Basalt at the supported revision, applying all three patches in order:
Build Basalt at the supported revision, applying all four patches in order:
```sh
git clone https://github.com/lbgos/rift-s-linux.git
+3 -1
View File
@@ -1,7 +1,7 @@
pkgbase = basalt-monado-rifts
pkgdesc = Basalt tracking library with Rift S lifecycle and worker defaults
pkgver = 0.1.r544.gdf6e970
pkgrel = 1
pkgrel = 2
url = https://gitlab.freedesktop.org/mateosss/basalt
arch = x86_64
license = BSD-2-Clause
@@ -35,6 +35,7 @@ pkgbase = basalt-monado-rifts
source = 0001-keep-imu-frequency-per-tracker.patch
source = 0002-limit-workers-with-driver-calibration.patch
source = 0003-safe-recreation-and-realtime-worker-budget.patch
source = 0004-skip-unread-execution-stats.patch
sha256sums = SKIP
sha256sums = SKIP
sha256sums = SKIP
@@ -45,5 +46,6 @@ pkgbase = basalt-monado-rifts
sha256sums = a0bc2a766ad6ae5fdfa33cd80d45d12798d43efa4c34605ee6cf20bae157d18d
sha256sums = 607d53dadbd8ca5b0978d10378e631539bc8047ca0fce317b90d68a52f785a52
sha256sums = a434e76c66c129aa3a900ed151dd2dd9bc98b671451dda26855a438cff08b2ac
sha256sums = 1f8848384b376addb471add3b9efea8c6effbfe7fe615c2beb3ad5ebdf432290
pkgname = basalt-monado-rifts
@@ -0,0 +1,135 @@
--- a/include/basalt/utils/time_utils.hpp
+++ b/include/basalt/utils/time_utils.hpp
@@ -124,6 +124,9 @@ class ExecutionStats {
std::string format_;
};
+ //! Live trackers never read the history, so a disabled instance keeps no values.
+ bool enabled = true;
+
Meta& add(const std::string& name, double value);
Meta& add(const std::string& name, const Eigen::VectorXd& value);
Meta& add(const std::string& name, const Eigen::VectorXf& value);
@@ -139,6 +142,7 @@ class ExecutionStats {
private:
std::unordered_map<std::string, Meta> stats_;
std::vector<std::string> order_;
+ Meta discarded_; // returned by add() while disabled
};
} // namespace basalt
--- a/include/basalt/vi_estimator/sqrt_keypoint_vio.h
+++ b/include/basalt/vi_estimator/sqrt_keypoint_vio.h
@@ -130,6 +130,7 @@ class SqrtKeypointVioEstimator : public VioEstimatorBase, public SqrtBundleAdjus
bool show_uimat(UIMAT m) const;
void debug_finalize() override;
+ void disable_stats() override { stats_all_.enabled = stats_sums_.enabled = false; }
void logMargNullspace();
Eigen::VectorXd checkMargNullspace() const;
--- a/include/basalt/vi_estimator/sqrt_keypoint_vo.h
+++ b/include/basalt/vi_estimator/sqrt_keypoint_vo.h
@@ -176,6 +176,7 @@ class SqrtKeypointVoEstimator : public VioEstimatorBase, public SqrtBundleAdjust
Sophus::SE3d getT_w_i_init() override { return T_w_i_init.template cast<double>(); }
void debug_finalize() override;
+ void disable_stats() override { stats_all_.enabled = stats_sums_.enabled = false; }
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
--- a/include/basalt/vi_estimator/vio_estimator.h
+++ b/include/basalt/vi_estimator/vio_estimator.h
@@ -136,6 +136,9 @@ class VioEstimatorBase {
virtual inline void debug_finalize() {}
+ //! Stop keeping the per-frame timing stats that only debug_finalize() reads.
+ virtual void disable_stats() {}
+
virtual Sophus::SE3d getT_w_i_init() = 0;
// Legacy functions. Should not be used in the new code.
--- a/src/utils/time_utils.cpp
+++ b/src/utils/time_utils.cpp
@@ -31,6 +31,7 @@ Scalar variance(const Eigen::Matrix<Scalar, N, 1>& vec) {
}
ExecutionStats::Meta& ExecutionStats::add(const std::string& name, double value) {
+ if (!enabled) return discarded_;
auto [it, new_item] = stats_.try_emplace(name);
if (new_item) {
order_.push_back(name);
@@ -41,6 +42,7 @@ ExecutionStats::Meta& ExecutionStats::add(const std::string& name, double value)
}
ExecutionStats::Meta& ExecutionStats::add(const std::string& name, const Eigen::VectorXd& value) {
+ if (!enabled) return discarded_;
auto [it, new_item] = stats_.try_emplace(name);
if (new_item) {
order_.push_back(name);
@@ -56,6 +58,7 @@ ExecutionStats::Meta& ExecutionStats::add(const std::string& name, const Eigen::
}
void ExecutionStats::merge_all(const ExecutionStats& other) {
+ if (!enabled) return;
for (const auto& name : other.order_) {
const auto& meta = other.stats_.at(name);
std::visit(
@@ -80,6 +83,7 @@ overload(Ts...) -> overload<Ts...>;
} // namespace
void ExecutionStats::merge_sums(const ExecutionStats& other) {
+ if (!enabled) return;
for (const auto& name : other.order_) {
const auto& meta = other.stats_.at(name);
std::visit(overload{[&](const std::vector<double>& data) {
--- a/src/vi_estimator/sqrt_keypoint_vio.cpp
+++ b/src/vi_estimator/sqrt_keypoint_vio.cpp
@@ -1224,6 +1224,7 @@ bool SqrtKeypointVioEstimator<Scalar_>::optimize() {
// timing
ExecutionStats stats;
+ stats.enabled = stats_all_.enabled;
Timer timer_total;
Timer timer_iteration;
@@ -1544,7 +1545,7 @@ bool SqrtKeypointVioEstimator<Scalar_>::optimize() {
double cumulative_time = timer_total.elapsed();
stats.add("iteration", iteration_time).format("ms");
- {
+ if (stats.enabled) {
basalt::MemoryInfo mi;
if (get_memory_info(mi)) {
stats.add("resident_memory", mi.resident_memory);
--- a/src/vi_estimator/sqrt_keypoint_vo.cpp
+++ b/src/vi_estimator/sqrt_keypoint_vo.cpp
@@ -1108,6 +1108,7 @@ bool SqrtKeypointVoEstimator<Scalar_>::optimize() {
// timing
ExecutionStats stats;
+ stats.enabled = stats_all_.enabled;
Timer timer_total;
Timer timer_iteration;
@@ -1395,7 +1396,7 @@ bool SqrtKeypointVoEstimator<Scalar_>::optimize() {
double cumulative_time = timer_total.elapsed();
stats.add("iteration", iteration_time).format("ms");
- {
+ if (stats.enabled) {
basalt::MemoryInfo mi;
if (get_memory_info(mi)) {
stats.add("resident_memory", mi.resident_memory);
--- a/src/vit/vit_tracker.cpp
+++ b/src/vit/vit_tracker.cpp
@@ -398,6 +398,8 @@ struct Tracker::Implementation {
ASSERT_(image_data_queue != nullptr);
vio = VioEstimatorFactory::getVioEstimator(vio_config, calib, constants::g, true, use_double);
+ // Nothing reads these stats in a live session; they would grow for as long as it runs.
+ vio->disable_stats();
imu_data_queue = &vio->imu_data_queue;
ASSERT_(imu_data_queue != nullptr);
+4 -3
View File
@@ -1,7 +1,7 @@
# Maintainer: lbgos <285507185+lbgos@users.noreply.github.com>
pkgname=basalt-monado-rifts
pkgver=0.1.r544.gdf6e970
pkgrel=1
pkgrel=2
pkgdesc='Basalt tracking library with Rift S lifecycle and worker defaults'
arch=('x86_64')
url='https://gitlab.freedesktop.org/mateosss/basalt'
@@ -20,9 +20,10 @@ source=('basalt::git+https://gitlab.freedesktop.org/mateosss/basalt.git#commit=d
'cereal::git+https://github.com/USCiLab/cereal.git#commit=ebef1e929807629befafbb2918ea1a08c7194554'
'0001-keep-imu-frequency-per-tracker.patch'
'0002-limit-workers-with-driver-calibration.patch'
'0003-safe-recreation-and-realtime-worker-budget.patch')
'0003-safe-recreation-and-realtime-worker-budget.patch'
'0004-skip-unread-execution-stats.patch')
sha256sums=('SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP'
'a0bc2a766ad6ae5fdfa33cd80d45d12798d43efa4c34605ee6cf20bae157d18d' '607d53dadbd8ca5b0978d10378e631539bc8047ca0fce317b90d68a52f785a52' 'a434e76c66c129aa3a900ed151dd2dd9bc98b671451dda26855a438cff08b2ac')
'a0bc2a766ad6ae5fdfa33cd80d45d12798d43efa4c34605ee6cf20bae157d18d' '607d53dadbd8ca5b0978d10378e631539bc8047ca0fce317b90d68a52f785a52' 'a434e76c66c129aa3a900ed151dd2dd9bc98b671451dda26855a438cff08b2ac' '1f8848384b376addb471add3b9efea8c6effbfe7fe615c2beb3ad5ebdf432290')
prepare() {
cd basalt
+3 -3
View File
@@ -1,6 +1,6 @@
pkgbase = rift-s-linux-bin
pkgdesc = Oculus Rift S on Linux SteamVR: driver, Room Setup, Settings app and in-headset desktop (prebuilt)
pkgver = 30
pkgver = 31
pkgrel = 1
url = https://github.com/lbgos/rift-s-linux
install = rift-s-linux.install
@@ -72,7 +72,7 @@ pkgbase = rift-s-linux-bin
conflicts = monado-rifts-git
options = !strip
options = !debug
source = https://github.com/lbgos/rift-s-linux/releases/download/r30/rift-s-linux-r30-x86_64.tar.zst
sha256sums = 6a4ff30493b8ee42acd4187fae2ea27bd01063b6c7e106e0c1494609302ef501
source = https://github.com/lbgos/rift-s-linux/releases/download/r31/rift-s-linux-r31-x86_64.tar.zst
sha256sums = b466138ac5f2bfa38d78335e4fdbee3d1b4c8efd3891cb0639d1eb28df86f47b
pkgname = rift-s-linux-bin
+2 -2
View File
@@ -1,6 +1,6 @@
# Maintainer: lbgos <285507185+lbgos@users.noreply.github.com>
pkgname=rift-s-linux-bin
pkgver=30
pkgver=31
pkgrel=1
pkgdesc='Oculus Rift S on Linux SteamVR: driver, Room Setup, Settings app and in-headset desktop (prebuilt)'
arch=('x86_64')
@@ -24,7 +24,7 @@ conflicts=('rift-s-linux' 'monado' 'monado-git' 'rifts-settings' 'basalt-monado-
install=rift-s-linux.install
options=('!strip' '!debug')
source=("https://github.com/lbgos/rift-s-linux/releases/download/r$pkgver/rift-s-linux-r$pkgver-x86_64.tar.zst")
sha256sums=('6a4ff30493b8ee42acd4187fae2ea27bd01063b6c7e106e0c1494609302ef501')
sha256sums=('b466138ac5f2bfa38d78335e4fdbee3d1b4c8efd3891cb0639d1eb28df86f47b')
package() {
cp -a usr "$pkgdir/"
@@ -0,0 +1,135 @@
--- a/include/basalt/utils/time_utils.hpp
+++ b/include/basalt/utils/time_utils.hpp
@@ -124,6 +124,9 @@ class ExecutionStats {
std::string format_;
};
+ //! Live trackers never read the history, so a disabled instance keeps no values.
+ bool enabled = true;
+
Meta& add(const std::string& name, double value);
Meta& add(const std::string& name, const Eigen::VectorXd& value);
Meta& add(const std::string& name, const Eigen::VectorXf& value);
@@ -139,6 +142,7 @@ class ExecutionStats {
private:
std::unordered_map<std::string, Meta> stats_;
std::vector<std::string> order_;
+ Meta discarded_; // returned by add() while disabled
};
} // namespace basalt
--- a/include/basalt/vi_estimator/sqrt_keypoint_vio.h
+++ b/include/basalt/vi_estimator/sqrt_keypoint_vio.h
@@ -130,6 +130,7 @@ class SqrtKeypointVioEstimator : public VioEstimatorBase, public SqrtBundleAdjus
bool show_uimat(UIMAT m) const;
void debug_finalize() override;
+ void disable_stats() override { stats_all_.enabled = stats_sums_.enabled = false; }
void logMargNullspace();
Eigen::VectorXd checkMargNullspace() const;
--- a/include/basalt/vi_estimator/sqrt_keypoint_vo.h
+++ b/include/basalt/vi_estimator/sqrt_keypoint_vo.h
@@ -176,6 +176,7 @@ class SqrtKeypointVoEstimator : public VioEstimatorBase, public SqrtBundleAdjust
Sophus::SE3d getT_w_i_init() override { return T_w_i_init.template cast<double>(); }
void debug_finalize() override;
+ void disable_stats() override { stats_all_.enabled = stats_sums_.enabled = false; }
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
--- a/include/basalt/vi_estimator/vio_estimator.h
+++ b/include/basalt/vi_estimator/vio_estimator.h
@@ -136,6 +136,9 @@ class VioEstimatorBase {
virtual inline void debug_finalize() {}
+ //! Stop keeping the per-frame timing stats that only debug_finalize() reads.
+ virtual void disable_stats() {}
+
virtual Sophus::SE3d getT_w_i_init() = 0;
// Legacy functions. Should not be used in the new code.
--- a/src/utils/time_utils.cpp
+++ b/src/utils/time_utils.cpp
@@ -31,6 +31,7 @@ Scalar variance(const Eigen::Matrix<Scalar, N, 1>& vec) {
}
ExecutionStats::Meta& ExecutionStats::add(const std::string& name, double value) {
+ if (!enabled) return discarded_;
auto [it, new_item] = stats_.try_emplace(name);
if (new_item) {
order_.push_back(name);
@@ -41,6 +42,7 @@ ExecutionStats::Meta& ExecutionStats::add(const std::string& name, double value)
}
ExecutionStats::Meta& ExecutionStats::add(const std::string& name, const Eigen::VectorXd& value) {
+ if (!enabled) return discarded_;
auto [it, new_item] = stats_.try_emplace(name);
if (new_item) {
order_.push_back(name);
@@ -56,6 +58,7 @@ ExecutionStats::Meta& ExecutionStats::add(const std::string& name, const Eigen::
}
void ExecutionStats::merge_all(const ExecutionStats& other) {
+ if (!enabled) return;
for (const auto& name : other.order_) {
const auto& meta = other.stats_.at(name);
std::visit(
@@ -80,6 +83,7 @@ overload(Ts...) -> overload<Ts...>;
} // namespace
void ExecutionStats::merge_sums(const ExecutionStats& other) {
+ if (!enabled) return;
for (const auto& name : other.order_) {
const auto& meta = other.stats_.at(name);
std::visit(overload{[&](const std::vector<double>& data) {
--- a/src/vi_estimator/sqrt_keypoint_vio.cpp
+++ b/src/vi_estimator/sqrt_keypoint_vio.cpp
@@ -1224,6 +1224,7 @@ bool SqrtKeypointVioEstimator<Scalar_>::optimize() {
// timing
ExecutionStats stats;
+ stats.enabled = stats_all_.enabled;
Timer timer_total;
Timer timer_iteration;
@@ -1544,7 +1545,7 @@ bool SqrtKeypointVioEstimator<Scalar_>::optimize() {
double cumulative_time = timer_total.elapsed();
stats.add("iteration", iteration_time).format("ms");
- {
+ if (stats.enabled) {
basalt::MemoryInfo mi;
if (get_memory_info(mi)) {
stats.add("resident_memory", mi.resident_memory);
--- a/src/vi_estimator/sqrt_keypoint_vo.cpp
+++ b/src/vi_estimator/sqrt_keypoint_vo.cpp
@@ -1108,6 +1108,7 @@ bool SqrtKeypointVoEstimator<Scalar_>::optimize() {
// timing
ExecutionStats stats;
+ stats.enabled = stats_all_.enabled;
Timer timer_total;
Timer timer_iteration;
@@ -1395,7 +1396,7 @@ bool SqrtKeypointVoEstimator<Scalar_>::optimize() {
double cumulative_time = timer_total.elapsed();
stats.add("iteration", iteration_time).format("ms");
- {
+ if (stats.enabled) {
basalt::MemoryInfo mi;
if (get_memory_info(mi)) {
stats.add("resident_memory", mi.resident_memory);
--- a/src/vit/vit_tracker.cpp
+++ b/src/vit/vit_tracker.cpp
@@ -398,6 +398,8 @@ struct Tracker::Implementation {
ASSERT_(image_data_queue != nullptr);
vio = VioEstimatorFactory::getVioEstimator(vio_config, calib, constants::g, true, use_double);
+ // Nothing reads these stats in a live session; they would grow for as long as it runs.
+ vio->disable_stats();
imu_data_queue = &vio->imu_data_queue;
ASSERT_(imu_data_queue != nullptr);
+12
View File
@@ -12,6 +12,7 @@
#include "util/u_pretty_print.h"
#include <pthread.h>
#include <sys/resource.h>
#include <assert.h>
#include <unistd.h>
#include <sched.h>
@@ -104,6 +105,17 @@ u_linux_try_to_set_realtime_priority_on_thread(enum u_logging_level log_level, c
name = str;
}
/*
* With a zero RLIMIT_RTTIME the kernel kills the whole process at the first
* scheduler tick that lands while a realtime thread runs, even one that sleeps
* every iteration. SteamVR's vrserver runs drivers with this limit.
*/
struct rlimit rttime;
if (getrlimit(RLIMIT_RTTIME, &rttime) == 0 && (rttime.rlim_cur == 0 || rttime.rlim_max == 0)) {
LOG_W("Keeping normal priority for thread '%s': RLIMIT_RTTIME is 0", name);
return;
}
if (log_level <= U_LOGGING_DEBUG) {
u_pp(dg, "Trying to raise priority on thread '%s'\n\t", name);
u_pp(dg, "before: ");
@@ -42,23 +42,42 @@ bool rift_s_prepare_session_origin(const std::string &path)
auto universes = cJSON_GetObjectItemCaseSensitive(document.get(), "universes");
if (!cJSON_IsString(id) || std::strcmp(id->valuestring, "chaperone_info") || !cJSON_IsArray(universes))
return false;
Json origin(cJSON_Parse("{\"universeID\":\"3\",\"seated\":{\"translation\":[0,0,0],\"yaw\":0},\"standing\":{\"translation\":[0,0,0],\"yaw\":0}}"), cJSON_Delete);
if (!origin) return false;
// Each session re-bases tracking, so offsets saved in universe 3 (for example by
// Quick Calibrate) would misplace the floor next time. Reset it to the plain origin.
int index = 0;
cJSON *entry;
auto plain = [](const char *id) {
return Json(cJSON_Parse((std::string("{\"universeID\":\"") + id +
"\",\"seated\":{\"translation\":[0,0,0],\"yaw\":0},"
"\"standing\":{\"translation\":[0,0,0],\"yaw\":0}}").c_str()),
cJSON_Delete);
};
cJSON *session = nullptr, *room = nullptr, *entry;
cJSON_ArrayForEach(entry, universes) {
auto universe = cJSON_GetObjectItemCaseSensitive(entry, "universeID");
if (cJSON_IsString(universe) && std::strcmp(universe->valuestring, "3") == 0) {
if (cJSON_Compare(entry, origin.get(), true)) return true;
if (!cJSON_ReplaceItemInArray(universes, index, origin.get())) return false;
break;
}
++index;
if (!cJSON_IsString(universe)) continue;
if (std::strcmp(universe->valuestring, "3") == 0) session = entry;
if (std::strcmp(universe->valuestring, "2") == 0) room = entry;
}
if (!entry && !cJSON_AddItemToArray(universes, origin.get())) return false;
origin.release();
bool changed = false;
// Each session re-bases tracking, so offsets saved in universe 3 (for example by
// Quick Calibrate) would misplace the floor next time. Reset it to the plain origin.
auto origin = plain("3");
if (!origin) return false;
if (!session || !cJSON_Compare(session, origin.get(), true)) {
if (session ? !cJSON_ReplaceItemViaPointer(universes, session, origin.get())
: !cJSON_AddItemToArray(universes, origin.get()))
return false;
origin.release();
changed = true;
}
// Room Setup moves to universe 2 once tracking is in setup. Without an entry SteamVR has no
// calibration for the frame being set up ("Failed to update shared data"), and a first setup
// shows no floor and takes no strokes. Seed the same plain origin so the switch keeps the
// frame; Accept replaces it with the drawn room. A saved room is never touched.
if (!room) {
auto seed = plain("2");
if (!seed || !cJSON_AddItemToArray(universes, seed.get())) return false;
seed.release();
changed = true;
}
if (!changed) return true;
std::unique_ptr<char, decltype(&cJSON_free)> bytes(cJSON_PrintUnformatted(document.get()), cJSON_free);
if (!bytes) return false;
std::string temporary = path + ".monado-XXXXXX";
@@ -4,6 +4,7 @@
#include <string>
// Write a parseable, unbounded session origin before publishing universe 3, replacing any
// offsets left in universe 3 by an earlier session. Other universes remain unchanged.
// offsets left in universe 3 by an earlier session. A missing universe 2 gets the same origin
// so a first Room Setup has a calibrated frame; an existing universe 2 remains unchanged.
bool rift_s_prepare_session_origin(const std::string &path);
bool rift_s_prepare_session_origin();
@@ -21,6 +21,8 @@
#include <cmath>
#include <algorithm>
#include <fstream>
#include <filesystem>
#include <unistd.h>
#include "math/m_api.h"
#include "ovrd_log.hpp"
@@ -28,6 +30,7 @@
#include "ovrd_display.hpp"
#include "ovrd_camera.hpp"
#include "ovrd_roomsetup_guard_process.hpp"
#include "ovrd_realtime.hpp"
#include "ovrd_world_universe.hpp"
#include "ovrd_chaperone_origin.hpp"
#include "../../drivers/rift_s/rift_s_world_control.h"
@@ -1948,6 +1951,23 @@ CServerDriver_Monado g_serverDriverMonado;
#define NUM_XDEVS 16
#ifdef __linux__
static void
warn_realtime_compositor()
{
char server[4096];
ssize_t length = readlink("/proc/self/exe", server, sizeof(server) - 1);
if (length <= 0)
return;
server[length] = 0;
std::string launcher = (std::filesystem::path(server).parent_path() / "vrcompositor-launcher").string();
if (ovrd_realtime_compositor_killed(launcher.c_str()))
ovrd_log("WARNING: %s has CAP_SYS_NICE while vrserver's RLIMIT_RTTIME is 0. The kernel kills the "
"compositor as soon as its realtime threads run. Remove it: sudo setcap -r %s\n",
launcher.c_str(), launcher.c_str());
}
#endif
vr::EVRInitError
CServerDriver_Monado::Init(vr::IVRDriverContext *pDriverContext)
{
@@ -1956,6 +1976,9 @@ CServerDriver_Monado::Init(vr::IVRDriverContext *pDriverContext)
ovrd_log_init(vr::VRDriverLog());
ovrd_log("Initializing Monado driver\n");
#ifdef __linux__
warn_realtime_compositor();
#endif
if (debug_get_bool_option_steamvr_bench()) {
vr::EVRSettingsError error = vr::VRSettingsError_None;
m_savedBenchPause = vr::VRSettings()->GetBool("power", "pauseCompositorOnStandby", &error);
@@ -0,0 +1,28 @@
// Copyright 2026, lbgos
// SPDX-License-Identifier: BSL-1.0
#pragma once
#ifdef __linux__
#include <endian.h>
#include <linux/capability.h>
#include <sys/resource.h>
#include <sys/xattr.h>
#include <sys/types.h>
/*!
* vrcompositor inherits vrserver's RLIMIT_RTTIME. When this process has a zero limit and the
* launcher at @p launcher carries CAP_SYS_NICE, the compositor's realtime threads are SIGKILLed
* by the kernel at the first scheduler tick. SteamVR then only reports a compositor crash.
*/
static inline bool
ovrd_realtime_compositor_killed(const char *launcher)
{
struct rlimit rttime;
if (getrlimit(RLIMIT_RTTIME, &rttime) != 0 || (rttime.rlim_cur != 0 && rttime.rlim_max != 0))
return false;
struct vfs_ns_cap_data caps = {};
ssize_t size = getxattr(launcher, "security.capability", &caps, sizeof(caps));
if (size < (ssize_t)(sizeof(caps.magic_etc) + sizeof(caps.data[0])))
return false;
return (le32toh(caps.data[0].permitted) & (1u << CAP_SYS_NICE)) != 0;
}
#endif
@@ -92,8 +92,9 @@ public:
if (!transforms(standing, setup))
return false;
// An untrusted saved plane belongs to another tracking session. Offer this
// session's nominal floor instead (raw Y=0, where the unanchored universe
// puts it): without any result the native step shows no floor to adjust.
// session's nominal floor instead (raw Y=0, the plain origin of universe 3 and
// of a first-time universe 2): without any result the native step shows no
// floor to adjust.
const vr::HmdMatrix34_t session_origin{{{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}}};
auto y = saved_floor(saved_plane_trusted ? standing : session_origin, setup);
if (!y)
+32
View File
@@ -240,6 +240,38 @@ TEST_CASE("Native session origin has no geometry and preserves the saved room")
CHECK(read_file(fixture.path) == "broken json");
}
TEST_CASE("First Room Setup switches into a universe 2 with the session origin")
{
LogFixture fixture;
// An earlier release left only the session universe; a fresh install has no file at all.
const std::string session = R"({"universeID":"3","seated":{"translation":[0,0,0],"yaw":0},"standing":{"translation":[0,0,0],"yaw":0}})";
for (bool existing : {true, false}) {
if (existing) fixture.write(R"({"jsonid":"chaperone_info","version":5,"universes":[)" + session + "]}");
else fs::remove(fixture.path);
REQUIRE(rift_s_prepare_session_origin(fixture.path.string()));
auto bytes = read_file(fixture.path);
std::unique_ptr<cJSON, decltype(&cJSON_Delete)> document(cJSON_Parse(bytes.c_str()), cJSON_Delete);
auto universes = cJSON_GetObjectItemCaseSensitive(document.get(), "universes");
REQUIRE(cJSON_GetArraySize(universes) == 2);
cJSON *room = nullptr, *origin = nullptr, *entry;
cJSON_ArrayForEach(entry, universes) {
auto id = cJSON_GetObjectItemCaseSensitive(entry, "universeID")->valuestring;
(std::string(id) == "2" ? room : origin) = entry;
}
REQUIRE(room);
REQUIRE(origin);
// Same frame on both sides of the switch, and no room geometry to bind a world map to.
CHECK(cJSON_Compare(cJSON_GetObjectItemCaseSensitive(room, "standing"),
cJSON_GetObjectItemCaseSensitive(origin, "standing"), true));
CHECK(cJSON_Compare(cJSON_GetObjectItemCaseSensitive(room, "seated"),
cJSON_GetObjectItemCaseSensitive(origin, "seated"), true));
CHECK(cJSON_GetObjectItemCaseSensitive(room, "play_area") == nullptr);
CHECK(cJSON_GetObjectItemCaseSensitive(room, "collision_bounds") == nullptr);
REQUIRE(rift_s_prepare_session_origin(fixture.path.string()));
CHECK(read_file(fixture.path) == bytes);
}
}
TEST_CASE("Display recovery requires one exact runtime process and the expected server PID")
{
auto executable = fs::read_symlink("/proc/self/exe");