From 750c5f20f69babc615004c4e47de7ebd184467ec Mon Sep 17 00:00:00 2001
From: HifiExperiments <thingsandstuffblog@gmail.com>
Date: Fri, 29 Mar 2024 21:31:19 -0700
Subject: [PATCH] cleanup, fixes, update QML

---
 .../dialogs/graphics/GraphicsSettings.qml     | 270 ++++++++++--------
 interface/src/RefreshRateManager.cpp          |  44 +--
 interface/src/RefreshRateManager.h            |  17 +-
 3 files changed, 191 insertions(+), 140 deletions(-)

diff --git a/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml b/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml
index feb42f59a4..5bac374fb5 100644
--- a/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml
+++ b/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml
@@ -366,15 +366,7 @@ Flickable {
                         currentIndex: -1
 
                         function refreshRefreshRateDropdownDisplay() {
-                            if (Performance.getRefreshRateProfile() === 0) {
-                                refreshRateDropdown.currentIndex = 0;
-                            } else if (Performance.getRefreshRateProfile() === 1) {
-                                refreshRateDropdown.currentIndex = 1;
-                            } else if (Performance.getRefreshRateProfile() === 2) {
-                                refreshRateDropdown.currentIndex = 2;
-                            } else {
-                                refreshRateDropdown.currentIndex = 3;
-                            }
+                            refreshRateDropdown.currentIndex = Performance.getRefreshRateProfile();
                         }
 
                         Component.onCompleted: {
@@ -386,130 +378,178 @@ Flickable {
                             refreshRateDropdown.displayText = model.get(currentIndex).text;
                         }
                     }
+                }
 
-                    HifiControlsUit.SpinBox {
-                        id: refreshRateCustomFocusActive
-                        decimals: 0
-                        width: 160
-                        height: parent.height
-                        suffix: " FPS"
-                        label: "Focus Active"
-                        minimumValue: 1.0
-                        realStepSize: 1.0
-                        realValue: 60.0
-                        colorScheme: hifi.colorSchemes.dark
+                ColumnLayout {
+                    width: parent.width
+                    Layout.topMargin: 32
+                    visible: refreshRateDropdown.currentIndex == 3
 
-                        Component.onCompleted: {
-                            realValue = Performance.getCustomRefreshRate(0)
+                    RowLayout {
+                        Layout.margins: 8
+
+                        HifiControlsUit.SpinBox {
+                            id: refreshRateCustomFocusActive
+                            decimals: 0
+                            width: 160
+                            height: 32
+                            suffix: " FPS"
+                            label: "Focus Active"
+                            realFrom: 1
+                            realTo: 1000
+                            realStepSize: 15
+                            realValue: 60
+                            colorScheme: hifi.colorSchemes.dark
+                            property var loaded: false
+
+                            Component.onCompleted: {
+                                realValue = Performance.getCustomRefreshRate(0)
+                                loaded = true
+                            }
+
+                            onRealValueChanged: {
+                                if (loaded) {
+                                    Performance.setCustomRefreshRate(0, realValue)
+                                }
+                            }
                         }
 
-                        onRealValueChanged: {
-                            Performance.setCustomRefreshRate(0, realValue);
+                        HifiControlsUit.SpinBox {
+                            id: refreshRateCustomFocusInactive
+                            decimals: 0
+                            width: 160
+                            height: 32
+                            suffix: " FPS"
+                            label: "Focus Inactive"
+                            realFrom: 1
+                            realTo: 1000
+                            realStepSize: 15
+                            realValue: 60
+                            colorScheme: hifi.colorSchemes.dark
+                            property var loaded: false
+
+                            Component.onCompleted: {
+                                realValue = Performance.getCustomRefreshRate(1)
+                                loaded = true
+                            }
+
+                            onRealValueChanged: {
+                                if (loaded) {
+                                    Performance.setCustomRefreshRate(1, realValue)
+                                }
+                            }
                         }
                     }
 
-                    HifiControlsUit.SpinBox {
-                        id: refreshRateCustomFocusInactive
-                        decimals: 0
-                        width: 160
-                        height: parent.height
-                        suffix: " FPS"
-                        label: "Focus Inactive"
-                        minimumValue: 1.0
-                        realStepSize: 1.0
-                        realValue: 60.0
-                        colorScheme: hifi.colorSchemes.dark
+                    RowLayout {
+                        Layout.margins: 8
 
-                        Component.onCompleted: {
-                            realValue = Performance.getCustomRefreshRate(1)
+                        HifiControlsUit.SpinBox {
+                            id: refreshRateCustomUnfocus
+                            decimals: 0
+                            width: 160
+                            height: 32
+                            suffix: " FPS"
+                            label: "Unfocus"
+                            realFrom: 1
+                            realTo: 1000
+                            realStepSize: 15
+                            realValue: 60
+                            colorScheme: hifi.colorSchemes.dark
+                            property var loaded: false
+
+                            Component.onCompleted: {
+                                realValue = Performance.getCustomRefreshRate(2)
+                                loaded = true
+                            }
+
+                            onRealValueChanged: {
+                                if (loaded) {
+                                    Performance.setCustomRefreshRate(2, realValue);
+                                }
+                            }
                         }
 
-                        onRealValueChanged: {
-                            Performance.setCustomRefreshRate(1, realValue);
+                        HifiControlsUit.SpinBox {
+                            id: refreshRateCustomMinimized
+                            decimals: 0
+                            width: 160
+                            height: 32
+                            suffix: " FPS"
+                            label: "Minimized"
+                            realFrom: 1
+                            realTo: 1000
+                            realStepSize: 1
+                            realValue: 60
+                            colorScheme: hifi.colorSchemes.dark
+                            property var loaded: false
+
+                            Component.onCompleted: {
+                                realValue = Performance.getCustomRefreshRate(3)
+                                loaded = true
+                            }
+
+                            onRealValueChanged: {
+                                if (loaded) {
+                                    Performance.setCustomRefreshRate(3, realValue)
+                                }
+                            }
                         }
                     }
 
-                    HifiControlsUit.SpinBox {
-                        id: refreshRateCustomUnfocus
-                        decimals: 0
-                        width: 160
-                        height: parent.height
-                        suffix: " FPS"
-                        label: "Unfocus"
-                        minimumValue: 1.0
-                        realStepSize: 1.0
-                        realValue: 60.0
-                        colorScheme: hifi.colorSchemes.dark
+                    RowLayout {
+                        Layout.margins: 8
 
-                        Component.onCompleted: {
-                            realValue = Performance.getCustomRefreshRate(2)
+                        HifiControlsUit.SpinBox {
+                            id: refreshRateCustomStartup
+                            decimals: 0
+                            width: 160
+                            height: 32
+                            suffix: " FPS"
+                            label: "Startup"
+                            realFrom: 1
+                            realTo: 1000
+                            realStepSize: 15
+                            realValue: 60
+                            colorScheme: hifi.colorSchemes.dark
+                            property var loaded: false
+
+                            Component.onCompleted: {
+                                realValue = Performance.getCustomRefreshRate(4)
+                                loaded = true
+                            }
+
+                            onRealValueChanged: {
+                                if (loaded) {
+                                    Performance.setCustomRefreshRate(4, realValue)
+                                }
+                            }
                         }
 
-                        onRealValueChanged: {
-                            Performance.setCustomRefreshRate(2, realValue);
-                        }
-                    }
+                        HifiControlsUit.SpinBox {
+                            id: refreshRateCustomShutdown
+                            decimals: 0
+                            width: 160
+                            height: 32
+                            suffix: " FPS"
+                            label: "Shutdown"
+                            realFrom: 1
+                            realTo: 1000
+                            realStepSize: 15
+                            realValue: 60
+                            colorScheme: hifi.colorSchemes.dark
+                            property var loaded: false
 
-                    HifiControlsUit.SpinBox {
-                        id: refreshRateCustomMinimized
-                        decimals: 0
-                        width: 160
-                        height: parent.height
-                        suffix: " FPS"
-                        label: "Minimized"
-                        minimumValue: 1.0
-                        realStepSize: 1.0
-                        realValue: 60.0
-                        colorScheme: hifi.colorSchemes.dark
+                            Component.onCompleted: {
+                                realValue = Performance.getCustomRefreshRate(5)
+                                loaded = true
+                            }
 
-                        Component.onCompleted: {
-                            realValue = Performance.getCustomRefreshRate(3)
-                        }
-
-                        onRealValueChanged: {
-                            Performance.setCustomRefreshRate(3, realValue);
-                        }
-                    }
-
-                    HifiControlsUit.SpinBox {
-                        id: refreshRateCustomStartup
-                        decimals: 0
-                        width: 160
-                        height: parent.height
-                        suffix: " FPS"
-                        label: "Startup"
-                        minimumValue: 1.0
-                        realStepSize: 1.0
-                        realValue: 60.0
-                        colorScheme: hifi.colorSchemes.dark
-
-                        Component.onCompleted: {
-                            realValue = Performance.getCustomRefreshRate(4)
-                        }
-
-                        onRealValueChanged: {
-                            Performance.setCustomRefreshRate(4, realValue);
-                        }
-                    }
-
-                    HifiControlsUit.SpinBox {
-                        id: refreshRateCustomShutdown
-                        decimals: 0
-                        width: 160
-                        height: parent.height
-                        suffix: " FPS"
-                        label: "Shutdown"
-                        minimumValue: 1.0
-                        realStepSize: 1.0
-                        realValue: 60.0
-                        colorScheme: hifi.colorSchemes.dark
-
-                        Component.onCompleted: {
-                            realValue = Performance.getCustomRefreshRate(5)
-                        }
-
-                        onRealValueChanged: {
-                            Performance.setCustomRefreshRate(5, realValue);
+                            onRealValueChanged: {
+                                if (loaded) {
+                                    Performance.setCustomRefreshRate(5, realValue)
+                                }
+                            }
                         }
                     }
                 }
diff --git a/interface/src/RefreshRateManager.cpp b/interface/src/RefreshRateManager.cpp
index 0238267180..9ba446750b 100644
--- a/interface/src/RefreshRateManager.cpp
+++ b/interface/src/RefreshRateManager.cpp
@@ -48,12 +48,13 @@ static const int VR_TARGET_RATE = 90;
  *     <tr><td><code>"Interactive"</code></td><td>Medium refresh rate, which is reduced when Interface doesn't have focus or is 
  *         minimized.</td></tr>
  *     <tr><td><code>"Realtime"</code></td><td>High refresh rate, even when Interface doesn't have focus or is minimized.
+ *     <tr><td><code>"Custom"</code></td><td>Custom refresh rate for full control over the refresh rate in all states.
  *   </tbody>
  * </table>
  * @typedef {string} RefreshRateProfileName
  */
 static const std::array<std::string, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> REFRESH_RATE_PROFILE_TO_STRING =
-    { { "Eco", "Interactive", "Realtime" } };
+    { { "Eco", "Interactive", "Realtime", "Custom" } };
 
 /*@jsdoc
  * <p>Interface states that affect the refresh rate.</p>
@@ -108,10 +109,12 @@ static const std::array<int, RefreshRateManager::RefreshRateRegime::REGIME_NUM>
     { { 30, 20, 10, 2, 30, 30 } };
 
 static const std::array<int, RefreshRateManager::RefreshRateRegime::REGIME_NUM> REALTIME_PROFILE =
-    { { 60, 60, 60, 2, 30, 30} };
+    { { 60, 60, 60, 2, 30, 30 } };
 
-static const std::array<std::array<int, RefreshRateManager::RefreshRateRegime::REGIME_NUM>, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> REFRESH_RATE_PROFILES =
-    { { ECO_PROFILE, INTERACTIVE_PROFILE, REALTIME_PROFILE } };
+static const std::array<int, RefreshRateManager::RefreshRateRegime::REGIME_NUM> CUSTOM_PROFILE = REALTIME_PROFILE; // derived from settings and modified by scripts below
+
+static std::array<std::array<int, RefreshRateManager::RefreshRateRegime::REGIME_NUM>, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> REFRESH_RATE_PROFILES =
+    { { ECO_PROFILE, INTERACTIVE_PROFILE, REALTIME_PROFILE, CUSTOM_PROFILE } };
 
 
 static const int INACTIVE_TIMER_LIMIT = 3000;
@@ -135,6 +138,10 @@ std::string RefreshRateManager::uxModeToString(RefreshRateManager::RefreshRateMa
 
 RefreshRateManager::RefreshRateManager() {
     _refreshRateProfile = (RefreshRateManager::RefreshRateProfile) _refreshRateProfileSetting.get();
+    for (size_t i = 0; i < _customRefreshRateSettings.size(); i++) {
+        REFRESH_RATE_PROFILES[CUSTOM][i] = _customRefreshRateSettings[i].get();
+    }
+
     _inactiveTimer->setInterval(INACTIVE_TIMER_LIMIT);
     _inactiveTimer->setSingleShot(true);
     QObject::connect(_inactiveTimer.get(), &QTimer::timeout, [&] {
@@ -169,23 +176,23 @@ void RefreshRateManager::setRefreshRateProfile(RefreshRateManager::RefreshRatePr
     }
 }
 
-int RefreshRateManager::getCustomRefreshRate(RefreshRateRegime regime)
-{
-    Q_ASSERT(regime >= 0 && regime < RefreshRateRegime::REGIME_NUM);
-    if (regime < 0 && regime >= RefreshRateRegime::REGIME_NUM)
-        return -1;
+int RefreshRateManager::getCustomRefreshRate(RefreshRateRegime regime) {
+    if (isValidRefreshRateRegime(regime)) {
+        return REFRESH_RATE_PROFILES[RefreshRateProfile::CUSTOM][regime];
+    }
 
-    return _customProfile[regime];
+    return 0;
 }
 
-int RefreshRateManager::setCustomRefreshRate(RefreshRateRegime regime, int value)
-{
-    Q_ASSERT(regime >= 0 && regime < RefreshRateRegime::REGIME_NUM);
-    if (regime < 0 && regime >= RefreshRateRegime::REGIME_NUM)
-        return -1;
-
-    _customProfile[regime] = value;
-    return 0;
+void RefreshRateManager::setCustomRefreshRate(RefreshRateRegime regime, int value) {
+    value = std::max(value, 1);
+    if (isValidRefreshRateRegime(regime)) {
+        _refreshRateProfileSettingLock.withWriteLock([&] {
+            REFRESH_RATE_PROFILES[RefreshRateProfile::CUSTOM][regime] = value;
+            _customRefreshRateSettings[regime].set(value);
+        });
+        updateRefreshRateController();
+    }
 }
 
 RefreshRateManager::RefreshRateProfile RefreshRateManager::getRefreshRateProfile() const {
@@ -211,7 +218,6 @@ void RefreshRateManager::setRefreshRateRegime(RefreshRateManager::RefreshRateReg
         _refreshRateRegime = refreshRateRegime;
         updateRefreshRateController();
     }
-
 }
 
 void RefreshRateManager::setUXMode(RefreshRateManager::UXMode uxMode) {
diff --git a/interface/src/RefreshRateManager.h b/interface/src/RefreshRateManager.h
index 27c615d2e0..6b94a94a4a 100644
--- a/interface/src/RefreshRateManager.h
+++ b/interface/src/RefreshRateManager.h
@@ -36,7 +36,7 @@ public:
         PROFILE_NUM
     };
     Q_ENUM(RefreshRateProfile)
-    static bool isValidRefreshRateProfile(RefreshRateProfile value) { return (value >= RefreshRateProfile::ECO && value <= RefreshRateProfile::REALTIME); }
+    static bool isValidRefreshRateProfile(RefreshRateProfile value) { return (value >= 0 && value < RefreshRateProfile::PROFILE_NUM); }
 
     /*@jsdoc
      * <p>Interface states that affect the refresh rate.</p>
@@ -108,7 +108,7 @@ public:
     int queryRefreshRateTarget(RefreshRateProfile profile, RefreshRateRegime regime, UXMode uxMode) const;
 
     int getCustomRefreshRate(RefreshRateRegime regime);
-    int setCustomRefreshRate(RefreshRateRegime regime, int value);
+    void setCustomRefreshRate(RefreshRateRegime regime, int value);
 
     void resetInactiveTimer();
     void toggleInactive();
@@ -119,16 +119,21 @@ public:
     static std::string refreshRateRegimeToString(RefreshRateRegime refreshRateRegime);
 
 private:
-    std::array<int, RefreshRateRegime::REGIME_NUM> _customProfile =
-        { { 0, 0, 0, 0, 0, 0 } };
-
     mutable int _activeRefreshRate { 20 };
     RefreshRateProfile _refreshRateProfile { RefreshRateProfile::INTERACTIVE};
     RefreshRateRegime _refreshRateRegime { RefreshRateRegime::STARTUP };
     UXMode _uxMode { UXMode::DESKTOP };
 
     mutable ReadWriteLockable _refreshRateProfileSettingLock;
-    Setting::Handle<int> _refreshRateProfileSetting { "refreshRateProfile", RefreshRateProfile::INTERACTIVE };
+    Setting::Handle<int> _refreshRateProfileSetting{ "refreshRateProfile", RefreshRateProfile::INTERACTIVE };
+    std::array<Setting::Handle<int>, REGIME_NUM> _customRefreshRateSettings { {
+        { "customRefreshRateFocusActive", 60 },
+        { "customRefreshRateFocusInactive", 60 },
+        { "customRefreshRateUnfocus", 60 },
+        { "customRefreshRateMinimized", 2 },
+        { "customRefreshRateStartup", 30 },
+        { "customRefreshRateShutdown", 30 }
+    } };
 
     std::function<void(int)> _refreshRateOperator { nullptr };