diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index 5f0de322c..01cc460a9 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -538,6 +538,12 @@ void AndroidHostInterface::SetControllerAxisState(u32 index, s32 button_code, fl false); } +void AndroidHostInterface::SetFastForwardEnabled(bool enabled) +{ + m_fast_forward_enabled = enabled; + UpdateSpeedLimiterState(); +} + void AndroidHostInterface::RefreshGameList(bool invalidate_cache, bool invalidate_database, ProgressCallback* progress_callback) { @@ -895,6 +901,20 @@ DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_hasAnyBIOSImages, jobject return hi->HasAnyBIOSImages(); } +DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_isFastForwardEnabled, jobject obj) +{ + return AndroidHelpers::GetNativeClass(env, obj)->IsFastForwardEnabled(); +} + +DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setFastForwardEnabled, jobject obj, jboolean enabled) +{ + if (!System::IsValid()) + return; + + AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); + hi->RunOnEmulationThread([enabled, hi]() { hi->SetFastForwardEnabled(enabled); }); +} + DEFINE_JNI_ARGS_METHOD(jstring, AndroidHostInterface_importBIOSImage, jobject obj, jbyteArray data) { AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); diff --git a/android/app/src/cpp/android_host_interface.h b/android/app/src/cpp/android_host_interface.h index ff1871d52..ef445ecfb 100644 --- a/android/app/src/cpp/android_host_interface.h +++ b/android/app/src/cpp/android_host_interface.h @@ -49,6 +49,7 @@ public: void SetControllerType(u32 index, std::string_view type_name); void SetControllerButtonState(u32 index, s32 button_code, bool pressed); void SetControllerAxisState(u32 index, s32 button_code, float value); + void SetFastForwardEnabled(bool enabled); void RefreshGameList(bool invalidate_cache, bool invalidate_database, ProgressCallback* progress_callback); void ApplySettings(bool display_osd_messages); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java index 6b32db583..7033c3181 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java @@ -79,6 +79,9 @@ public class AndroidHostInterface { public native String importBIOSImage(byte[] data); + public native boolean isFastForwardEnabled(); + public native void setFastForwardEnabled(boolean enabled); + static { System.loadLibrary("duckstation-native"); } diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java index 217e71174..26c4a0379 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java @@ -284,11 +284,9 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde return; } - case 3: // Toggle Speed Limiter + case 3: // Toggle Fast Forward { - boolean newSetting = !getBooleanSetting("Main/SpeedLimiterEnabled", true); - setBooleanSetting("Main/SpeedLimiterEnabled", newSetting); - applySettings(); + AndroidHostInterface.getInstance().setFastForwardEnabled(!AndroidHostInterface.getInstance().isFastForwardEnabled()); return; } diff --git a/android/app/src/main/res/values/arrays.xml b/android/app/src/main/res/values/arrays.xml index 1d17b8e7f..c7b91ec45 100644 --- a/android/app/src/main/res/values/arrays.xml +++ b/android/app/src/main/res/values/arrays.xml @@ -131,7 +131,7 @@ Load State Save State Save State Slot - Toggle Speed Limiter + Toggle Fast Forward More Options Quit @@ -283,4 +283,60 @@ 75 90 + + Unlimited + 10% [6 FPS (NTSC) / 5 FPS (PAL)] + 20% [12 FPS (NTSC) / 10 FPS (PAL)] + 30% [18 FPS (NTSC) / 15 FPS (PAL)] + 40% [24 FPS (NTSC) / 20 FPS (PAL)] + 50% [30 FPS (NTSC) / 25 FPS (PAL)] + 60% [36 FPS (NTSC) / 30 FPS (PAL)] + 70% [42 FPS (NTSC) / 35 FPS (PAL)] + 80% [48 FPS (NTSC) / 40 FPS (PAL)] + 90% [54 FPS (NTSC) / 45 FPS (PAL)] + 100% [60 FPS (NTSC) / 50 FPS (PAL), Default] + 125% [75 FPS (NTSC) / 62 FPS (PAL)] + 150% [90 FPS (NTSC) / 75 FPS (PAL)] + 175% [105 FPS (NTSC) / 87 FPS (PAL)] + 200% [120 FPS (NTSC) / 100 FPS (PAL)] + 250% [150 FPS (NTSC) / 125 FPS (PAL)] + 300% [180 FPS (NTSC) / 150 FPS (PAL)] + 350% [210 FPS (NTSC) / 175 FPS (PAL)] + 400% [240 FPS (NTSC) / 200 FPS (PAL)] + 450% [270 FPS (NTSC) / 225 FPS (PAL)] + 500% [300 FPS (NTSC) / 250 FPS (PAL)] + 600% [360 FPS (NTSC) / 300 FPS (PAL)] + 700% [420 FPS (NTSC) / 350 FPS (PAL)] + 800% [480 FPS (NTSC) / 400 FPS (PAL)] + 900% [540 FPS (NTSC) / 450 FPS (PAL)] + 1000% [600 FPS (NTSC) / 500 FPS (PAL)] + + + 0.0 + 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 0.6 + 0.7 + 0.8 + 0.9 + 1.0 + 1.25 + 1.5 + 1.75 + 2.0 + 2.5 + 3.0 + 3.5 + 4.0 + 4.5 + 5.0 + 6.0 + 7.0 + 8.0 + 9.0 + 10.0 + diff --git a/android/app/src/main/res/xml/general_preferences.xml b/android/app/src/main/res/xml/general_preferences.xml index 59fcfa0cd..ed4dd570a 100644 --- a/android/app/src/main/res/xml/general_preferences.xml +++ b/android/app/src/main/res/xml/general_preferences.xml @@ -17,6 +17,22 @@ + + ResetGraphicsAPIState(); } +void SetTargetSpeed(float speed) +{ + s_target_speed = speed; + UpdateThrottlePeriod(); +} + void SetThrottleFrequency(float frequency) { s_throttle_frequency = frequency; @@ -1178,8 +1185,8 @@ void SetThrottleFrequency(float frequency) void UpdateThrottlePeriod() { - s_throttle_period = static_cast(1000000000.0 / static_cast(s_throttle_frequency) / - static_cast(g_settings.emulation_speed)); + s_throttle_period = + static_cast(1000000000.0 / static_cast(s_throttle_frequency) / static_cast(s_target_speed)); } void Throttle() diff --git a/src/core/system.h b/src/core/system.h index 1971936a2..45aedf46f 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -146,6 +146,9 @@ bool RecreateGPU(GPURenderer renderer); void RunFrame(); +/// Sets target emulation speed. +void SetTargetSpeed(float speed); + /// Adjusts the throttle frequency, i.e. how many times we should sleep per second. void SetThrottleFrequency(float frequency); diff --git a/src/duckstation-qt/advancedsettingswidget.cpp b/src/duckstation-qt/advancedsettingswidget.cpp index 0b1865a7a..bef59261f 100644 --- a/src/duckstation-qt/advancedsettingswidget.cpp +++ b/src/duckstation-qt/advancedsettingswidget.cpp @@ -107,6 +107,8 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface, "UseDebugDevice", false); addIntRangeTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Display FPS Limit"), "Display", "MaxFPS", 0, 1000, 0); + addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Increase Timer Resolution"), "Main", + "IncreaseTimerResolution", true); } AdvancedSettingsWidget::~AdvancedSettingsWidget() = default; @@ -125,4 +127,5 @@ void AdvancedSettingsWidget::onResetToDefaultClicked() setIntRangeTweakOption(m_ui.tweakOptionTable, 9, static_cast(Settings::DEFAULT_GPU_MAX_RUN_AHEAD)); setBooleanTweakOption(m_ui.tweakOptionTable, 10, false); setIntRangeTweakOption(m_ui.tweakOptionTable, 11, 0); + setBooleanTweakOption(m_ui.tweakOptionTable, 12, true); } diff --git a/src/duckstation-qt/generalsettingswidget.cpp b/src/duckstation-qt/generalsettingswidget.cpp index cf165f7b9..36e8c2cac 100644 --- a/src/duckstation-qt/generalsettingswidget.cpp +++ b/src/duckstation-qt/generalsettingswidget.cpp @@ -1,6 +1,7 @@ #include "generalsettingswidget.h" #include "autoupdaterdialog.h" #include "frontend-common/controller_interface.h" +#include "qtutils.h" #include "settingsdialog.h" #include "settingwidgetbinder.h" @@ -27,22 +28,25 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW true); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.autoLoadCheats, "Main", "AutoLoadCheats", false); - SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableSpeedLimiter, "Main", "SpeedLimiterEnabled", - true); - SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.increaseTimerResolution, "Main", - "IncreaseTimerResolution", true); - SettingWidgetBinder::BindWidgetToNormalizedSetting(m_host_interface, m_ui.emulationSpeed, "Main", "EmulationSpeed", - 100.0f, 1.0f); SettingWidgetBinder::BindWidgetToEnumSetting( m_host_interface, m_ui.controllerBackend, "Main", "ControllerBackend", &ControllerInterface::ParseBackendName, &ControllerInterface::GetBackendName, ControllerInterface::GetDefaultBackend()); - connect(m_ui.enableSpeedLimiter, &QCheckBox::stateChanged, this, - &GeneralSettingsWidget::onEnableSpeedLimiterStateChanged); - connect(m_ui.emulationSpeed, &QSlider::valueChanged, this, &GeneralSettingsWidget::onEmulationSpeedValueChanged); + QtUtils::FillComboBoxWithEmulationSpeeds(m_ui.emulationSpeed); + const int emulation_speed_index = + m_ui.emulationSpeed->findData(QVariant(m_host_interface->GetFloatSettingValue("Main", "EmulationSpeed"))); + if (emulation_speed_index >= 0) + m_ui.emulationSpeed->setCurrentIndex(emulation_speed_index); + connect(m_ui.emulationSpeed, QOverload::of(&QComboBox::currentIndexChanged), this, + &GeneralSettingsWidget::onEmulationSpeedIndexChanged); - onEnableSpeedLimiterStateChanged(); - onEmulationSpeedValueChanged(m_ui.emulationSpeed->value()); + QtUtils::FillComboBoxWithEmulationSpeeds(m_ui.fastForwardSpeed); + const int fast_forward_speed_index = + m_ui.emulationSpeed->findData(QVariant(m_host_interface->GetFloatSettingValue("Main", "FastForwardSpeed"))); + if (fast_forward_speed_index >= 0) + m_ui.fastForwardSpeed->setCurrentIndex(fast_forward_speed_index); + connect(m_ui.fastForwardSpeed, QOverload::of(&QComboBox::currentIndexChanged), this, + &GeneralSettingsWidget::onFastForwardSpeedIndexChanged); dialog->registerWidgetHelp( m_ui.confirmPowerOff, tr("Confirm Power Off"), tr("Checked"), @@ -68,18 +72,14 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW m_ui.applyGameSettings, tr("Apply Per-Game Settings"), tr("Checked"), tr("When enabled, per-game settings will be applied, and incompatible enhancements will be disabled. You should " "leave this option enabled except when testing enhancements with incompatible games.")); - dialog->registerWidgetHelp( - m_ui.enableSpeedLimiter, tr("Enable Speed Limiter"), tr("Checked"), - tr("Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will " - "run as fast as possible, which may not be playable.")); - dialog->registerWidgetHelp( - m_ui.increaseTimerResolution, tr("Increase Timer Resolution"), tr("Checked"), - tr("Increases the system timer resolution when emulation is started to provide more accurate " - "frame pacing. May increase battery usage on laptops.")); dialog->registerWidgetHelp( m_ui.emulationSpeed, tr("Emulation Speed"), "100%", tr("Sets the target emulation speed. It is not guaranteed that this speed will be reached, " "and if not, the emulator will run as fast as it can manage.")); + dialog->registerWidgetHelp( + m_ui.fastForwardSpeed, tr("Fast Forward Speed"), "100%", + tr( + "Sets the fast forward (turbo) speed. This speed will be used when the fast forward hotkey is pressed/toggled.")); dialog->registerWidgetHelp(m_ui.controllerBackend, tr("Controller Backend"), qApp->translate("ControllerInterface", ControllerInterface::GetBackendName( ControllerInterface::GetDefaultBackend())), @@ -119,12 +119,18 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW GeneralSettingsWidget::~GeneralSettingsWidget() = default; -void GeneralSettingsWidget::onEnableSpeedLimiterStateChanged() +void GeneralSettingsWidget::onEmulationSpeedIndexChanged(int index) { - m_ui.emulationSpeed->setDisabled(!m_ui.enableSpeedLimiter->isChecked()); + bool okay; + const float value = m_ui.emulationSpeed->currentData().toFloat(&okay); + m_host_interface->SetFloatSettingValue("Main", "EmulationSpeed", okay ? value : 1.0f); + m_host_interface->applySettings(); } -void GeneralSettingsWidget::onEmulationSpeedValueChanged(int value) +void GeneralSettingsWidget::onFastForwardSpeedIndexChanged(int index) { - m_ui.emulationSpeedLabel->setText(tr("%1%").arg(value)); + bool okay; + const float value = m_ui.fastForwardSpeed->currentData().toFloat(&okay); + m_host_interface->SetFloatSettingValue("Main", "FastForwardSpeed", okay ? value : 0.0f); + m_host_interface->applySettings(); } diff --git a/src/duckstation-qt/generalsettingswidget.h b/src/duckstation-qt/generalsettingswidget.h index da85ac79f..c7b3eb886 100644 --- a/src/duckstation-qt/generalsettingswidget.h +++ b/src/duckstation-qt/generalsettingswidget.h @@ -16,8 +16,8 @@ public: ~GeneralSettingsWidget(); private Q_SLOTS: - void onEnableSpeedLimiterStateChanged(); - void onEmulationSpeedValueChanged(int value); + void onEmulationSpeedIndexChanged(int index); + void onFastForwardSpeedIndexChanged(int index); private: Ui::GeneralSettingsWidget m_ui; diff --git a/src/duckstation-qt/generalsettingswidget.ui b/src/duckstation-qt/generalsettingswidget.ui index 8601bae93..b1a0b3637 100644 --- a/src/duckstation-qt/generalsettingswidget.ui +++ b/src/duckstation-qt/generalsettingswidget.ui @@ -94,61 +94,28 @@ - Emulation Speed + Speed Control - - - - - - - 25 - - - 500 - - - 25 - - - 25 - - - 100 - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 25 - - - - - - - 100% - - - - + + + + + Emulation Speed: + + + + + - + - Enable Speed Limiter + Fast Forward Speed: - - - Increase Timer Resolution - - + @@ -192,7 +159,7 @@ - + diff --git a/src/duckstation-qt/qtutils.cpp b/src/duckstation-qt/qtutils.cpp index a16a49e6b..ad48dc602 100644 --- a/src/duckstation-qt/qtutils.cpp +++ b/src/duckstation-qt/qtutils.cpp @@ -1,5 +1,6 @@ #include "qtutils.h" #include "common/byte_stream.h" +#include "common/make_array.h" #include #include #include @@ -719,4 +720,20 @@ void FillComboBoxWithMSAAModes(QComboBox* cb) cb->addItem(qApp->translate("GPUSettingsWidget", "%1x SSAA").arg(i), GetMSAAModeValue(i, true)); } +void FillComboBoxWithEmulationSpeeds(QComboBox* cb) +{ + cb->addItem(qApp->translate("GeneralSettingsWidget", "Unlimited"), QVariant(0.0f)); + + static constexpr auto speeds = make_array(10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, 250, 300, 350, + 400, 450, 500, 600, 700, 800, 900, 1000); + for (const int speed : speeds) + { + cb->addItem(qApp->translate("GeneralSettingsWidget", "%1% [%2 FPS (NTSC) / %3 FPS (PAL)]") + .arg(speed) + .arg((60 * speed) / 100) + .arg((50 * speed) / 100), + QVariant(static_cast(speed) / 100.0f)); + } +} + } // namespace QtUtils \ No newline at end of file diff --git a/src/duckstation-qt/qtutils.h b/src/duckstation-qt/qtutils.h index 4e883dd60..9bcf4c3cf 100644 --- a/src/duckstation-qt/qtutils.h +++ b/src/duckstation-qt/qtutils.h @@ -68,4 +68,7 @@ QVariant GetMSAAModeValue(uint multisamples, bool ssaa); void DecodeMSAAModeValue(const QVariant& userdata, uint* multisamples, bool* ssaa); void FillComboBoxWithMSAAModes(QComboBox* cb); +/// Fills a combo box with emulation speed options. +void FillComboBoxWithEmulationSpeeds(QComboBox* cb); + } // namespace QtUtils \ No newline at end of file diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index 35e5da966..d06c08caa 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -843,9 +843,6 @@ void SDLHostInterface::DrawMainMenuBar() void SDLHostInterface::DrawQuickSettingsMenu() { bool settings_changed = false; - settings_changed |= ImGui::MenuItem("Enable Speed Limiter", nullptr, &m_settings_copy.speed_limiter_enabled); - - ImGui::Separator(); if (ImGui::BeginMenu("CPU Execution Mode")) { @@ -1288,7 +1285,6 @@ void SDLHostInterface::DrawSettingsWindow() ImGui::SameLine(indent); settings_changed |= ImGui::SliderFloat("##speed", &m_settings_copy.emulation_speed, 0.25f, 5.0f); - settings_changed |= ImGui::Checkbox("Enable Speed Limiter", &m_settings_copy.speed_limiter_enabled); settings_changed |= ImGui::Checkbox("Increase Timer Resolution", &m_settings_copy.increase_timer_resolution); settings_changed |= ImGui::Checkbox("Pause On Start", &m_settings_copy.start_paused); settings_changed |= ImGui::Checkbox("Start Fullscreen", &m_settings_copy.start_fullscreen); diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index 91e15746c..2288060a7 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -598,9 +598,10 @@ bool CommonHostInterface::ResumeSystemFromMostRecentState() void CommonHostInterface::UpdateSpeedLimiterState() { - m_speed_limiter_enabled = g_settings.speed_limiter_enabled && !m_speed_limiter_temp_disabled; + const float target_speed = m_fast_forward_enabled ? g_settings.fast_forward_speed : g_settings.emulation_speed; + m_speed_limiter_enabled = (target_speed != 0.0f); - const bool is_non_standard_speed = (std::abs(g_settings.emulation_speed - 1.0f) > 0.05f); + const bool is_non_standard_speed = (std::abs(target_speed - 1.0f) > 0.05f); const bool audio_sync_enabled = !System::IsRunning() || (m_speed_limiter_enabled && g_settings.audio_sync_enabled && !is_non_standard_speed); const bool video_sync_enabled = @@ -627,7 +628,10 @@ void CommonHostInterface::UpdateSpeedLimiterState() SetTimerResolutionIncreased(m_speed_limiter_enabled); if (System::IsValid()) + { + System::SetTargetSpeed(m_speed_limiter_enabled ? target_speed : 1.0f); System::ResetPerformanceCounters(); + } } void CommonHostInterface::RecreateSystem() @@ -1367,7 +1371,7 @@ void CommonHostInterface::RegisterGeneralHotkeys() { RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("FastForward"), TRANSLATABLE("Hotkeys", "Fast Forward"), [this](bool pressed) { - m_speed_limiter_temp_disabled = pressed; + m_fast_forward_enabled = pressed; UpdateSpeedLimiterState(); }); @@ -1375,11 +1379,11 @@ void CommonHostInterface::RegisterGeneralHotkeys() StaticString(TRANSLATABLE("Hotkeys", "Toggle Fast Forward")), [this](bool pressed) { if (pressed) { - m_speed_limiter_temp_disabled = !m_speed_limiter_temp_disabled; + m_fast_forward_enabled = !m_fast_forward_enabled; UpdateSpeedLimiterState(); - AddOSDMessage(m_speed_limiter_enabled ? - TranslateStdString("OSDMessage", "Speed limiter enabled.") : - TranslateStdString("OSDMessage", "Speed limiter disabled."), + AddOSDMessage(m_fast_forward_enabled ? + TranslateStdString("OSDMessage", "Fast forwarding...") : + TranslateStdString("OSDMessage", "Stopped fast forwarding."), 2.0f); } }); @@ -2080,9 +2084,9 @@ void CommonHostInterface::CheckForSettingsChanges(const Settings& old_settings) g_settings.audio_buffer_size != old_settings.audio_buffer_size || g_settings.video_sync_enabled != old_settings.video_sync_enabled || g_settings.audio_sync_enabled != old_settings.audio_sync_enabled || - g_settings.speed_limiter_enabled != old_settings.speed_limiter_enabled || g_settings.increase_timer_resolution != old_settings.increase_timer_resolution || g_settings.emulation_speed != old_settings.emulation_speed || + g_settings.fast_forward_speed != old_settings.fast_forward_speed || g_settings.display_max_fps != old_settings.display_max_fps) { UpdateSpeedLimiterState(); diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index 3396aa689..4d291dc57 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -180,6 +180,9 @@ public: /// Parses a fullscreen mode into its components (width * height @ refresh hz) static bool ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate); + /// Returns true if fast forwarding is currently active. + bool IsFastForwardEnabled() const { return m_fast_forward_enabled; } + protected: enum : u32 { @@ -325,9 +328,9 @@ protected: std::mutex m_osd_messages_lock; bool m_frame_step_request = false; - bool m_speed_limiter_temp_disabled = false; - bool m_speed_limiter_enabled = false; + bool m_fast_forward_enabled = false; bool m_timer_resolution_increased = false; + bool m_speed_limiter_enabled = true; private: void InitializeUserDirectory();