diff --git a/CMakeLists.txt b/CMakeLists.txt index ed08717..e200346 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,6 +111,7 @@ set(HEADERS src/utils/paths.h src/utils/setup.h src/utils/update_rate.h + src/utils/platform_capabilities.h src/utils/Matrix.h src/quaternion/quaternion.h ) @@ -290,6 +291,14 @@ target_compile_definitions(AdvancedSettings PRIVATE APPLICATION_VERSION=\"${PROJECT_VERSION}\" ) +# Build for the Steam Frame (SteamOS, aarch64). Switches off what doesn't work +# there and changes some defaults; the details are named capability constants +# in src/utils/platform_capabilities.h, the only place reading this definition. +option(OVRAS_STEAM_FRAME "Build for the Steam Frame" OFF) +if (OVRAS_STEAM_FRAME) + target_compile_definitions(AdvancedSettings PRIVATE OVRAS_STEAM_FRAME=1) +endif () + #find_library(OPENVR_LIBRARIES # NAMES # openvr_api diff --git a/docs/steamframe/PROGRESS.md b/docs/steamframe/PROGRESS.md index 811e9f8..bbd1ded 100644 --- a/docs/steamframe/PROGRESS.md +++ b/docs/steamframe/PROGRESS.md @@ -26,3 +26,34 @@ - `libopenvr_api.so` is resolved from the binary's directory (`ldd`). - Not started: without a running SteamVR the start hangs in OpenVR init (expected; tested on the Frame in step 5). + +### Step 2: platform option, R7, tick (2026-10-07) + +**Changes:** +- **Platform option:** + - CMake option `OVRAS_STEAM_FRAME` (default OFF) sets the compile definition + `OVRAS_STEAM_FRAME=1`. + - New `src/utils/platform_capabilities.h` evaluates it as the only place. It holds + `k_chaperoneCommitSupported`, `k_defaultVsyncDisabled` and `k_defaultCustomTickRateMs`. +- **Tick (R6):** + - In "vsync disabled" mode, `m_pumpEventsTimer` runs directly at `customTickRateMs` + (`applyTickInterval()`, `Qt::PreciseTimer`); the 1 ms counting is gone. + - Changing the setting restarts the timer. + - Setting defaults in `settings_controller.h` come from the constants: Frame `true`/11 ms, + otherwise unchanged `false`/20 ms. +- **R7:** with `!k_chaperoneCommitSupported`: + - `updateChaperoneResetData()` and `applyChaperoneResetData()` skip + `CommitWorkingCopy`/`RevertWorkingCopy`. Each logs once per function. + - The init autosave profile is skipped. + +**Results:** +- Both variants (default and `-DOVRAS_STEAM_FRAME=ON`) build on x86_64. The definition is + only set in the Frame variant (`compile_commands.json`). +- Not tested at runtime yet (Frame, step 5). + +**Notes:** +- Another commit at start-up remains, but only on opt-in. `SetWidget()` applies a chaperone + profile and commits when "auto-apply chaperone" (`autoApplyChaperoneToggle`, default + `false`) is on. That's a chaperone-profile feature, so it belongs to R5 (iteration 2). +- clang-format reflows comment blocks next to new comments. Separate new comments from + existing commented-out code with a blank line, otherwise the diff grows. diff --git a/src/overlaycontroller.cpp b/src/overlaycontroller.cpp index f13e741..0aef604 100644 --- a/src/overlaycontroller.cpp +++ b/src/overlaycontroller.cpp @@ -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 diff --git a/src/overlaycontroller.h b/src/overlaycontroller.h index e2c6970..f9568e9 100644 --- a/src/overlaycontroller.h +++ b/src/overlaycontroller.h @@ -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; diff --git a/src/settings/internal/settings_controller.h b/src/settings/internal/settings_controller.h index e8efb81..cadaf0a 100644 --- a/src/settings/internal/settings_controller.h +++ b/src/settings/internal/settings_controller.h @@ -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, diff --git a/src/tabcontrollers/MoveCenterTabController.cpp b/src/tabcontrollers/MoveCenterTabController.cpp index d074978..f6ff08a 100644 --- a/src/tabcontrollers/MoveCenterTabController.cpp +++ b/src/tabcontrollers/MoveCenterTabController.cpp @@ -1,10 +1,13 @@ #include "MoveCenterTabController.h" #include +#include +#include #include #include #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 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, diff --git a/src/utils/platform_capabilities.h b/src/utils/platform_capabilities.h new file mode 100644 index 0000000..9870b68 --- /dev/null +++ b/src/utils/platform_capabilities.h @@ -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