emulationsettingswidget.cpp (13922B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "emulationsettingswidget.h" 5 #include "core/system.h" 6 #include "qtutils.h" 7 #include "settingswindow.h" 8 #include "settingwidgetbinder.h" 9 #include <QtWidgets/QMessageBox> 10 #include <limits> 11 12 EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget* parent) 13 : QWidget(parent), m_dialog(dialog) 14 { 15 SettingsInterface* sif = dialog->getSettingsInterface(); 16 17 m_ui.setupUi(this); 18 19 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vsync, "Display", "VSync", false); 20 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.syncToHostRefreshRate, "Main", "SyncToHostRefreshRate", false); 21 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.optimalFramePacing, "Display", "OptimalFramePacing", false); 22 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.preFrameSleep, "Display", "PreFrameSleep", false); 23 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.skipPresentingDuplicateFrames, "Display", 24 "SkipPresentingDuplicateFrames", false); 25 SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.preFrameSleepBuffer, "Display", "PreFrameSleepBuffer", 26 Settings::DEFAULT_DISPLAY_PRE_FRAME_SLEEP_BUFFER); 27 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.rewindEnable, "Main", "RewindEnable", false); 28 SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.rewindSaveFrequency, "Main", "RewindFrequency", 10.0f); 29 SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.rewindSaveSlots, "Main", "RewindSaveSlots", 10); 30 SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.runaheadFrames, "Main", "RunaheadFrameCount", 0); 31 32 const float effective_emulation_speed = m_dialog->getEffectiveFloatValue("Main", "EmulationSpeed", 1.0f); 33 fillComboBoxWithEmulationSpeeds(m_ui.emulationSpeed, effective_emulation_speed); 34 if (m_dialog->isPerGameSettings() && !m_dialog->getFloatValue("Main", "EmulationSpeed", std::nullopt).has_value()) 35 { 36 m_ui.emulationSpeed->setCurrentIndex(0); 37 } 38 else 39 { 40 const int emulation_speed_index = m_ui.emulationSpeed->findData(QVariant(effective_emulation_speed)); 41 if (emulation_speed_index >= 0) 42 m_ui.emulationSpeed->setCurrentIndex(emulation_speed_index); 43 } 44 connect(m_ui.emulationSpeed, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 45 &EmulationSettingsWidget::onEmulationSpeedIndexChanged); 46 47 const float effective_fast_forward_speed = m_dialog->getEffectiveFloatValue("Main", "FastForwardSpeed", 0.0f); 48 fillComboBoxWithEmulationSpeeds(m_ui.fastForwardSpeed, effective_fast_forward_speed); 49 if (m_dialog->isPerGameSettings() && !m_dialog->getFloatValue("Main", "FastForwardSpeed", std::nullopt).has_value()) 50 { 51 m_ui.emulationSpeed->setCurrentIndex(0); 52 } 53 else 54 { 55 const int fast_forward_speed_index = m_ui.fastForwardSpeed->findData(QVariant(effective_fast_forward_speed)); 56 if (fast_forward_speed_index >= 0) 57 m_ui.fastForwardSpeed->setCurrentIndex(fast_forward_speed_index); 58 } 59 connect(m_ui.fastForwardSpeed, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 60 &EmulationSettingsWidget::onFastForwardSpeedIndexChanged); 61 62 const float effective_turbo_speed = m_dialog->getEffectiveFloatValue("Main", "TurboSpeed", 0.0f); 63 fillComboBoxWithEmulationSpeeds(m_ui.turboSpeed, effective_turbo_speed); 64 if (m_dialog->isPerGameSettings() && !m_dialog->getFloatValue("Main", "TurboSpeed", std::nullopt).has_value()) 65 { 66 m_ui.emulationSpeed->setCurrentIndex(0); 67 } 68 else 69 { 70 const int turbo_speed_index = m_ui.turboSpeed->findData(QVariant(effective_turbo_speed)); 71 if (turbo_speed_index >= 0) 72 m_ui.turboSpeed->setCurrentIndex(turbo_speed_index); 73 } 74 connect(m_ui.turboSpeed, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 75 &EmulationSettingsWidget::onTurboSpeedIndexChanged); 76 connect(m_ui.vsync, &QCheckBox::checkStateChanged, this, &EmulationSettingsWidget::updateSkipDuplicateFramesEnabled); 77 connect(m_ui.syncToHostRefreshRate, &QCheckBox::checkStateChanged, this, 78 &EmulationSettingsWidget::updateSkipDuplicateFramesEnabled); 79 connect(m_ui.optimalFramePacing, &QCheckBox::checkStateChanged, this, 80 &EmulationSettingsWidget::onOptimalFramePacingChanged); 81 connect(m_ui.preFrameSleep, &QCheckBox::checkStateChanged, this, &EmulationSettingsWidget::onPreFrameSleepChanged); 82 83 connect(m_ui.rewindEnable, &QCheckBox::checkStateChanged, this, &EmulationSettingsWidget::updateRewind); 84 connect(m_ui.rewindSaveFrequency, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, 85 &EmulationSettingsWidget::updateRewind); 86 connect(m_ui.rewindSaveSlots, QOverload<int>::of(&QSpinBox::valueChanged), this, 87 &EmulationSettingsWidget::updateRewind); 88 connect(m_ui.runaheadFrames, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 89 &EmulationSettingsWidget::updateRewind); 90 91 dialog->registerWidgetHelp( 92 m_ui.emulationSpeed, tr("Emulation Speed"), "100%", 93 tr("Sets the target emulation speed. It is not guaranteed that this speed will be reached, " 94 "and if not, the emulator will run as fast as it can manage.")); 95 dialog->registerWidgetHelp( 96 m_ui.fastForwardSpeed, tr("Fast Forward Speed"), tr("User Preference"), 97 tr("Sets the fast forward speed. This speed will be used when the fast forward hotkey is pressed/toggled.")); 98 dialog->registerWidgetHelp( 99 m_ui.turboSpeed, tr("Turbo Speed"), tr("User Preference"), 100 tr("Sets the turbo speed. This speed will be used when the turbo hotkey is pressed/toggled. Turboing will take " 101 "priority over fast forwarding if both hotkeys are pressed/toggled.")); 102 dialog->registerWidgetHelp( 103 m_ui.vsync, tr("Vertical Sync (VSync)"), tr("Unchecked"), 104 tr("Synchronizes presentation of the console's frames to the host. Enabling may result in smoother animations, at " 105 "the cost of increased input lag. <strong>GSync/FreeSync users should enable Optimal Frame Pacing " 106 "instead.</strong>")); 107 dialog->registerWidgetHelp( 108 m_ui.syncToHostRefreshRate, tr("Sync To Host Refresh Rate"), tr("Unchecked"), 109 tr( 110 "Adjusts the emulation speed so the console's refresh rate matches the host's refresh rate when VSync is " 111 "enabled. This results in the smoothest animations possible, at the cost of potentially increasing the emulation " 112 "speed by less than 1%. Sync To Host Refresh Rate will not take effect if the console's refresh rate is too far " 113 "from the host's refresh rate. Users with variable refresh rate displays should disable this option.")); 114 dialog->registerWidgetHelp( 115 m_ui.optimalFramePacing, tr("Optimal Frame Pacing"), tr("Unchecked"), 116 tr("Enabling this option will ensure every frame the console renders is displayed to the screen, at a consistent " 117 "rate, for optimal frame pacing. If you have a GSync/FreeSync display, enable this option. If you are having " 118 "difficulties maintaining full speed, or are getting audio glitches, try disabling this option.")); 119 dialog->registerWidgetHelp( 120 m_ui.preFrameSleep, tr("Reduce Input Latency"), tr("Unchecked"), 121 tr("Reduces input latency by delaying the start of frame until closer to the presentation time. This may cause " 122 "dropped frames on slower systems with higher frame time variance, if the buffer size is not sufficient.")); 123 dialog->registerWidgetHelp(m_ui.preFrameSleepBuffer, tr("Frame Time Buffer"), 124 tr("%1 ms").arg(Settings::DEFAULT_DISPLAY_PRE_FRAME_SLEEP_BUFFER), 125 tr("Specifies the amount of buffer time added, which reduces the additional sleep time " 126 "introduced. Higher values increase input latency, but decrease the risk of overrun, " 127 "or missed frames. Lower values require faster hardware.")); 128 dialog->registerWidgetHelp( 129 m_ui.skipPresentingDuplicateFrames, tr("Skip Duplicate Frame Display"), tr("Unchecked"), 130 tr("Skips the presentation/display of frames that are not unique. Can be combined with driver-level frame " 131 "generation to increase perceptible frame rate. Can result in worse frame pacing, and is not compatible with " 132 "syncing to host refresh.")); 133 dialog->registerWidgetHelp( 134 m_ui.rewindEnable, tr("Rewinding"), tr("Unchecked"), 135 tr("<b>Enable Rewinding:</b> Saves state periodically so you can rewind any mistakes while playing.<br> " 136 "<b>Rewind Save Frequency:</b> How often a rewind state will be created. Higher frequencies have greater system " 137 "requirements.<br> " 138 "<b>Rewind Buffer Size:</b> How many saves will be kept for rewinding. Higher values have greater memory " 139 "requirements.")); 140 dialog->registerWidgetHelp( 141 m_ui.runaheadFrames, tr("Runahead"), tr("Disabled"), 142 tr( 143 "Simulates the system ahead of time and rolls back/replays to reduce input lag. Very high system requirements.")); 144 145 onOptimalFramePacingChanged(); 146 updateSkipDuplicateFramesEnabled(); 147 updateRewind(); 148 } 149 150 EmulationSettingsWidget::~EmulationSettingsWidget() = default; 151 152 void EmulationSettingsWidget::fillComboBoxWithEmulationSpeeds(QComboBox* cb, float global_value) 153 { 154 if (m_dialog->isPerGameSettings()) 155 { 156 if (global_value == 0.0f) 157 cb->addItem(tr("Use Global Setting [Unlimited]")); 158 else 159 cb->addItem(tr("Use Global Setting [%1%]").arg(static_cast<u32>(global_value * 100.0f))); 160 } 161 162 cb->addItem(tr("Unlimited"), QVariant(0.0f)); 163 164 static constexpr const std::array speeds = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 165 200, 250, 300, 350, 400, 450, 500, 600, 700, 800, 900, 1000}; 166 for (const int speed : speeds) 167 { 168 cb->addItem(tr("%1% [%2 FPS (NTSC) / %3 FPS (PAL)]").arg(speed).arg((60 * speed) / 100).arg((50 * speed) / 100), 169 QVariant(static_cast<float>(speed) / 100.0f)); 170 } 171 } 172 173 void EmulationSettingsWidget::onEmulationSpeedIndexChanged(int index) 174 { 175 if (m_dialog->isPerGameSettings() && index == 0) 176 { 177 m_dialog->removeSettingValue("Main", "EmulationSpeed"); 178 return; 179 } 180 181 bool okay; 182 const float value = m_ui.emulationSpeed->currentData().toFloat(&okay); 183 m_dialog->setFloatSettingValue("Main", "EmulationSpeed", okay ? value : 1.0f); 184 } 185 186 void EmulationSettingsWidget::onFastForwardSpeedIndexChanged(int index) 187 { 188 if (m_dialog->isPerGameSettings() && index == 0) 189 { 190 m_dialog->removeSettingValue("Main", "FastForwardSpeed"); 191 return; 192 } 193 194 bool okay; 195 const float value = m_ui.fastForwardSpeed->currentData().toFloat(&okay); 196 m_dialog->setFloatSettingValue("Main", "FastForwardSpeed", okay ? value : 0.0f); 197 } 198 199 void EmulationSettingsWidget::onTurboSpeedIndexChanged(int index) 200 { 201 if (m_dialog->isPerGameSettings() && index == 0) 202 { 203 m_dialog->removeSettingValue("Main", "TurboSpeed"); 204 return; 205 } 206 207 bool okay; 208 const float value = m_ui.turboSpeed->currentData().toFloat(&okay); 209 m_dialog->setFloatSettingValue("Main", "TurboSpeed", okay ? value : 0.0f); 210 } 211 212 void EmulationSettingsWidget::onOptimalFramePacingChanged() 213 { 214 const bool optimal_frame_pacing_enabled = m_dialog->getEffectiveBoolValue("Display", "OptimalFramePacing", false); 215 m_ui.preFrameSleep->setEnabled(optimal_frame_pacing_enabled); 216 onPreFrameSleepChanged(); 217 } 218 219 void EmulationSettingsWidget::onPreFrameSleepChanged() 220 { 221 const bool pre_frame_sleep_enabled = m_dialog->getEffectiveBoolValue("Display", "PreFrameSleep", false); 222 const bool show_buffer_size = (m_ui.preFrameSleep->isEnabled() && pre_frame_sleep_enabled); 223 m_ui.preFrameSleepBuffer->setVisible(show_buffer_size); 224 m_ui.preFrameSleepBufferLabel->setVisible(show_buffer_size); 225 } 226 227 void EmulationSettingsWidget::updateSkipDuplicateFramesEnabled() 228 { 229 const bool vsync = m_dialog->getEffectiveBoolValue("Display", "VSync", false); 230 const bool sync_to_host = m_dialog->getEffectiveBoolValue("Main", "SyncToHostRefreshRate", false) && vsync; 231 m_ui.skipPresentingDuplicateFrames->setEnabled(!sync_to_host); 232 } 233 234 void EmulationSettingsWidget::updateRewind() 235 { 236 const bool rewind_enabled = m_dialog->getEffectiveBoolValue("Main", "RewindEnable", false); 237 const bool runahead_enabled = m_dialog->getIntValue("Main", "RunaheadFrameCount", 0) > 0; 238 m_ui.rewindEnable->setEnabled(!runahead_enabled); 239 240 if (!runahead_enabled && rewind_enabled) 241 { 242 const u32 resolution_scale = static_cast<u32>(m_dialog->getEffectiveIntValue("GPU", "ResolutionScale", 1)); 243 const u32 frames = static_cast<u32>(m_ui.rewindSaveSlots->value()); 244 const float frequency = static_cast<float>(m_ui.rewindSaveFrequency->value()); 245 const float duration = 246 ((frequency <= std::numeric_limits<float>::epsilon()) ? (1.0f / 60.0f) : frequency) * static_cast<float>(frames); 247 248 u64 ram_usage, vram_usage; 249 System::CalculateRewindMemoryUsage(frames, resolution_scale, &ram_usage, &vram_usage); 250 251 m_ui.rewindSummary->setText( 252 tr("Rewind for %n frame(s), lasting %1 second(s) will require up to %2MB of RAM and %3MB of VRAM.", "", frames) 253 .arg(duration) 254 .arg(ram_usage / 1048576) 255 .arg(vram_usage / 1048576)); 256 m_ui.rewindSaveFrequency->setEnabled(true); 257 m_ui.rewindSaveSlots->setEnabled(true); 258 } 259 else 260 { 261 if (runahead_enabled) 262 { 263 m_ui.rewindSummary->setText(tr( 264 "Rewind is disabled because runahead is enabled. Runahead will significantly increase system requirements.")); 265 } 266 else 267 { 268 m_ui.rewindSummary->setText( 269 tr("Rewind is not enabled. Please note that enabling rewind may significantly increase system requirements.")); 270 } 271 m_ui.rewindSaveFrequency->setEnabled(false); 272 m_ui.rewindSaveSlots->setEnabled(false); 273 } 274 }