Add a Steam Frame build option; no chaperone commit at start; timer-based tick

- CMake option OVRAS_STEAM_FRAME. It is evaluated only in
  src/utils/platform_capabilities.h, which turns it into named capability
  constants and platform defaults.
- Where the chaperone can't be committed (Steam Frame: it shifts the play
  area), MoveCenterTabController no longer commits the working copy at start-up
  or when applying reset data, and doesn't create the init autosave profile.
  Skips are logged once.
- In "vsync disabled" mode the pump timer now runs directly at the custom tick
  rate instead of firing every 1 ms and counting. The Frame defaults to this
  mode at 11 ms (~90 Hz).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Matthias Janetschek
2026-10-07 13:35:46 +02:00
co-authored by Claude Opus 5.5
parent 266a2f9ce9
commit 423ea57db7
7 changed files with 141 additions and 19 deletions
+22 -14
View File
@@ -480,9 +480,7 @@ void OverlayController::SetWidget( QQuickItem* quickItem,
this,
SLOT( OnTimeoutPumpEvents() ) );
// Every 1ms we check if the current frame has advanced (for vsync)
m_pumpEventsTimer.setInterval( 1 );
applyTickInterval();
m_pumpEventsTimer.start();
m_steamVRTabController.initStage2( this );
@@ -966,6 +964,8 @@ void OverlayController::setVsyncDisabled( bool value, bool notify )
{
settings::setSetting( settings::BoolSetting::APPLICATION_vsyncDisabled,
value );
// The timer interval depends on the mode, see applyTickInterval().
applyTickInterval();
if ( notify )
{
emit vsyncDisabledChanged( value );
@@ -1076,6 +1076,8 @@ void OverlayController::setCustomTickRateMs( int value, bool notify )
settings::setSetting( settings::IntSetting::APPLICATION_customTickRateMs,
verifiedTickRate );
m_verifiedCustomTickRateMs = verifiedTickRate;
// In "vsync disabled" mode the timer runs at this interval.
applyTickInterval();
if ( notify )
{
@@ -1083,6 +1085,19 @@ void OverlayController::setCustomTickRateMs( int value, bool notify )
}
}
// Sets the interval of m_pumpEventsTimer for the current tick mode:
// - vsync: every 1 ms, OnTimeoutPumpEvents() checks whether the compositor
// started a new frame.
// - vsync disabled: the timer fires directly at the custom tick rate, so the
// process wakes up once per tick instead of every 1 ms.
// QTimer::setInterval() also restarts a running timer with the new interval.
void OverlayController::applyTickInterval()
{
m_pumpEventsTimer.setTimerType( Qt::PreciseTimer );
m_pumpEventsTimer.setInterval( vsyncDisabled() ? customTickRateMs() : 1 );
}
// Called by m_pumpEventsTimer, see applyTickInterval().
// vsync implementation:
// this function triggers every 1ms
// this function should remain lightweight and only check if it's time to
@@ -1091,17 +1106,10 @@ void OverlayController::OnTimeoutPumpEvents()
{
if ( vsyncDisabled() )
{
// check if it's time for a custom tick rate tick
if ( m_customTickRateCounter > customTickRateMs() )
{
mainEventLoop();
m_customTickRateCounter = 0;
updateRate.incrementCounter();
}
else
{
m_customTickRateCounter++;
}
// The timer already runs at the custom tick rate: every call is a
// tick.
mainEventLoop();
updateRate.incrementCounter();
}
// vsync is enabled
+2 -1
View File
@@ -158,7 +158,6 @@ private:
uint64_t m_currentFrame = 0;
uint64_t m_lastFrame = 0;
int m_vsyncTooLateCounter = 0;
int m_customTickRateCounter = 0;
int m_verifiedCustomTickRateMs = 0;
input::SteamIVRInput m_actions;
@@ -200,6 +199,8 @@ private:
return m_renderControl.rhi();
}
void applyTickInterval();
bool m_exclusiveState = false;
bool m_keyPressOneState = false;
bool m_keyPressTwoState = false;
+5 -2
View File
@@ -9,6 +9,7 @@
#include "../../utils/setup.h"
#include "specific_setting_value.h"
#include "../../tabcontrollers/MoveCenterTabController.h"
#include "../../utils/platform_capabilities.h"
namespace settings
{
@@ -303,10 +304,11 @@ private:
SettingCategory::Application,
QtInfo{ "previousShutdownSafe" },
true },
// Default depends on the platform (fixed-interval tick on the Frame).
BoolSettingValue{ BoolSetting::APPLICATION_vsyncDisabled,
SettingCategory::Application,
QtInfo{ "vsyncDisabled" },
false },
platform::k_defaultVsyncDisabled },
BoolSettingValue{ BoolSetting::APPLICATION_crashRecoveryDisabled,
SettingCategory::Application,
QtInfo{ "crashRecoveryDisabled" },
@@ -610,10 +612,11 @@ private:
SettingCategory::Application,
QtInfo{ "debugState" },
0 },
// Default depends on the platform (about 90 Hz on the Frame).
IntSettingValue{ IntSetting::APPLICATION_customTickRateMs,
SettingCategory::Application,
QtInfo{ "customTickRateMs" },
20 },
platform::k_defaultCustomTickRateMs },
IntSettingValue{ IntSetting::UTILITY_alarmHour,
SettingCategory::Utility,
+44 -2
View File
@@ -1,10 +1,13 @@
#include "MoveCenterTabController.h"
#include <QQuickWindow>
#include <set>
#include <string>
#include <QtLogging>
#include <QtDebug>
#include "../overlaycontroller.h"
#include "../quaternion/quaternion.h"
#include "../settings/settings.h"
#include "../utils/platform_capabilities.h"
void rotateCoordinates( double coordinates[3], double angle )
{
@@ -1106,7 +1109,13 @@ void MoveCenterTabController::zeroOffsets()
if ( calState != 200 )
{
// all init complete, safe to autosave chaperone profile
parent->m_chaperoneTabController.createNewAutosaveProfile();
// Not where the chaperone can't be committed: the autosave
// exists to restore a committed play area later.
if ( platform::k_chaperoneCommitSupported )
{
parent->m_chaperoneTabController
.createNewAutosaveProfile();
}
m_initComplete = true;
m_chaperoneBasisAcquired = true;
qInfo() << "Chaperone Initilization Complete, Space Drag "
@@ -1172,6 +1181,19 @@ void MoveCenterTabController::clampVelocity( double* velocity )
}
}
// Logs once per caller that a chaperone commit was skipped, so the log shows
// it without repeating it every tick.
static void logSkippedChaperoneCommit( const char* caller )
{
static std::set<std::string> logged;
if ( logged.insert( caller ).second )
{
qInfo() << caller
<< ": CommitWorkingCopy skipped, not supported on this "
"platform (logged once)";
}
}
void MoveCenterTabController::updateChaperoneResetData()
{
//auto cstate = vr::VRChaperone()->GetCalibrationState();
@@ -1183,9 +1205,20 @@ void MoveCenterTabController::updateChaperoneResetData()
// }
// else
//{
// Committing writes the working copy as the live play area. Where that
// isn't supported (it shifts the play area on the Steam Frame), only the
// working copy is read below.
if ( platform::k_chaperoneCommitSupported )
{
vr::VRChaperoneSetup()->CommitWorkingCopy(
vr::EChaperoneConfigFile_Live );
vr::VRChaperoneSetup()->RevertWorkingCopy();
}
else
{
logSkippedChaperoneCommit( "updateChaperoneResetData" );
}
//}
unsigned currentQuadCount = 0;
vr::VRChaperoneSetup()->GetWorkingCollisionBoundsInfo( nullptr,
@@ -1228,7 +1261,16 @@ void MoveCenterTabController::applyChaperoneResetData()
// vr::VRChaperoneSetup()->SetWorkingSeatedZeroPoseToRawTrackingPose(
// &m_seatedCenterForReset );
vr::VRChaperoneSetup()->CommitWorkingCopy( vr::EChaperoneConfigFile_Live );
// See updateChaperoneResetData(): no commit where it isn't supported.
if ( platform::k_chaperoneCommitSupported )
{
vr::VRChaperoneSetup()->CommitWorkingCopy(
vr::EChaperoneConfigFile_Live );
}
else
{
logSkippedChaperoneCommit( "applyChaperoneResetData" );
}
unsigned checkQuadCount = 0;
vr::VRChaperoneSetup()->GetWorkingCollisionBoundsInfo( nullptr,
+28
View File
@@ -0,0 +1,28 @@
#pragma once
// What the target platform supports, and platform-specific defaults.
//
// This is the only place that evaluates the build option OVRAS_STEAM_FRAME
// (CMake option of the same name). All other code asks for the named constants
// below, never for the platform, so it stays clear *why* something differs.
// See docs/steamframe/DESIGN.md, section 7.
#ifndef OVRAS_STEAM_FRAME
# define OVRAS_STEAM_FRAME 0
#endif
namespace platform
{
// IVRChaperoneSetup::CommitWorkingCopy() writes the working copy as the live
// chaperone. OVRAS does this at start-up and when applying offsets. On the
// Steam Frame (inside-out tracking) it shifts the play area, so it must not
// happen there.
constexpr bool k_chaperoneCommitSupported = !OVRAS_STEAM_FRAME;
// Default tick: by default OVRAS polls every 1 ms for a new compositor frame
// ("vsync"). The Frame defaults to a fixed-interval tick instead, which wakes
// the process about 11 times less often. 11 ms is about 90 Hz, the frame rate
// OVRAS's tick-counted intervals were designed for.
constexpr bool k_defaultVsyncDisabled = OVRAS_STEAM_FRAME;
constexpr int k_defaultCustomTickRateMs = OVRAS_STEAM_FRAME ? 11 : 20;
} // namespace platform