duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

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 }