[frontend] Slow and Turbo modes (#3525)

Closes #3344

Adds slow and turbo modes with configurable speeds that can then be
toggled by the user. Behavior is:
- Standard/slow limit, toggle turbo = turbo
- Turbo limit, toggle turbo = standard
- Standard/turbo limit, toggle slow = slow
- Slow limit, toggle slow = standard

Enabling the turbo/slow mode enables the frame limiter unconditionally.

This has some conflicts with VSync. For example when I set my refresh
rate to 60hz and enable vsync, turbo mode does nothing. Not sure how to
go about fixing this, @MaranBr probably knows better the proper
solution.

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3525
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
This commit is contained in:
crueter 2026-02-12 01:31:55 +01:00
parent 5f676a6a55
commit 2b979024cb
No known key found for this signature in database
GPG key ID: 425ACD2D4830EBC6
18 changed files with 295 additions and 29 deletions

View file

@ -203,6 +203,24 @@ object NativeLibrary {
external fun getDebugKnobAt(index: Int): Boolean
/**
* Set the current speed limit to the configured turbo speed.
*/
external fun setTurboSpeedLimit(enabled: Boolean)
/**
* Set the current speed limit to the configured slow speed.
*/
external fun setSlowSpeedLimit(enabled: Boolean)
/**
* Set the current speed limit to the configured standard speed.
*/
external fun setStandardSpeedLimit(enabled: Boolean)
external fun isTurboMode(): Boolean
external fun isSlowMode(): Boolean
/**
* Returns Vulkan driver version / API version / GPU model
*/

View file

@ -11,6 +11,7 @@ import android.widget.RadioGroup
import android.widget.TextView
import androidx.drawerlayout.widget.DrawerLayout
import com.google.android.material.color.MaterialColors
import com.google.android.material.materialswitch.MaterialSwitch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
@ -135,6 +136,39 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
container.addView(itemView)
}
fun addCustomToggle(
name: Int,
isChecked: Boolean,
isEnabled: Boolean,
container: ViewGroup,
callback: (Boolean) -> Unit
): MaterialSwitch? {
val inflater = LayoutInflater.from(emulationFragment.requireContext())
val itemView = inflater.inflate(R.layout.item_quick_settings_menu, container, false)
val switchContainer = itemView.findViewById<ViewGroup>(R.id.switch_container)
val titleView = itemView.findViewById<TextView>(R.id.switch_title)
val switchView = itemView.findViewById<MaterialSwitch>(R.id.setting_switch)
titleView.text = YuzuApplication.appContext.getString(name)
switchContainer.visibility = View.VISIBLE
switchView.isChecked = isChecked
switchView.setOnCheckedChangeListener { _, checked ->
callback(checked)
saveSettings()
}
switchContainer.setOnClickListener {
switchView.toggle()
}
container.addView(itemView)
return switchView
}
fun addSliderSetting(
name: Int,
container: ViewGroup,

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -6,7 +9,10 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ShortSetting(override val key: String) : AbstractShortSetting {
RENDERER_SPEED_LIMIT("speed_limit");
RENDERER_SPEED_LIMIT("speed_limit"),
RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"),
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit"),
;
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)

View file

@ -180,6 +180,26 @@ abstract class SettingsItem(
units = "%"
)
)
put(
SliderSetting(
ShortSetting.RENDERER_TURBO_SPEED_LIMIT,
titleId = R.string.turbo_speed_limit,
descriptionId = R.string.turbo_speed_limit_description,
min = 1,
max = 400,
units = "%"
)
)
put(
SliderSetting(
ShortSetting.RENDERER_SLOW_SPEED_LIMIT,
titleId = R.string.slow_speed_limit,
descriptionId = R.string.slow_speed_limit_description,
min = 1,
max = 400,
units = "%"
)
)
put(
SingleChoiceSetting(
IntSetting.CPU_BACKEND,

View file

@ -227,6 +227,8 @@ class SettingsFragmentPresenter(
add(StringSetting.DEVICE_NAME.key)
add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
add(ShortSetting.RENDERER_SPEED_LIMIT.key)
add(ShortSetting.RENDERER_TURBO_SPEED_LIMIT.key)
add(ShortSetting.RENDERER_SLOW_SPEED_LIMIT.key)
add(BooleanSetting.USE_DOCKED_MODE.key)
add(IntSetting.REGION_INDEX.key)
add(IntSetting.LANGUAGE_INDEX.key)

View file

@ -55,6 +55,7 @@ import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.materialswitch.MaterialSwitch
import com.google.android.material.textview.MaterialTextView
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
@ -1055,11 +1056,47 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
quickSettings.addPerGameConfigStatusIndicator(container)
}
quickSettings.addBooleanSetting(
lateinit var slowSpeed: MaterialSwitch
lateinit var turboSpeed: MaterialSwitch
turboSpeed = quickSettings.addCustomToggle(
R.string.turbo_speed_limit,
NativeLibrary.isTurboMode(),
BooleanSetting.RENDERER_USE_SPEED_LIMIT.getBoolean(false),
container
) { enabled ->
if (enabled)
slowSpeed.isChecked = false
NativeLibrary.setTurboSpeedLimit(enabled)
}!!
slowSpeed = quickSettings.addCustomToggle(
R.string.slow_speed_limit,
NativeLibrary.isSlowMode(),
BooleanSetting.RENDERER_USE_SPEED_LIMIT.getBoolean(false),
container
) { enabled ->
if (enabled)
turboSpeed.isChecked = false
NativeLibrary.setSlowSpeedLimit(enabled)
}!!
quickSettings.addCustomToggle(
R.string.frame_limit_enable,
container,
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
)
BooleanSetting.RENDERER_USE_SPEED_LIMIT.getBoolean(false),
true,
container
) { enabled ->
if (!enabled) {
turboSpeed.isChecked = false
slowSpeed.isChecked = false
}
turboSpeed.isEnabled = enabled
slowSpeed.isEnabled = enabled
NativeLibrary.setStandardSpeedLimit(enabled)
}!!
quickSettings.addSliderSetting(
R.string.frame_limit_slider,

View file

@ -1233,6 +1233,39 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobje
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
if (enabled) {
Settings::values.use_speed_limit.SetValue(true);
Settings::SetSpeedMode(Settings::SpeedMode::Turbo);
} else {
Settings::SetSpeedMode(Settings::SpeedMode::Standard);
}
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setSlowSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
if (enabled) {
Settings::values.use_speed_limit.SetValue(true);
Settings::SetSpeedMode(Settings::SpeedMode::Slow);
} else {
Settings::SetSpeedMode(Settings::SpeedMode::Standard);
}
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setStandardSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
Settings::values.use_speed_limit.SetValue(enabled);
if (enabled) {
Settings::SetSpeedMode(Settings::SpeedMode::Standard);
}
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isTurboMode(JNIEnv *env, jobject jobj) {
return Settings::values.current_speed_mode.GetValue() == Settings::SpeedMode::Turbo;
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isSlowMode(JNIEnv *env, jobject jobj) {
return Settings::values.current_speed_mode.GetValue() == Settings::SpeedMode::Slow;
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run(JNIEnv* env, jobject jobj, jstring j_path,
jint j_program_index,
jboolean j_frontend_initiated) {

View file

@ -422,6 +422,10 @@
<string name="frame_limit_enable_description">Limits emulation speed to a specified percentage of normal speed.</string>
<string name="frame_limit_slider">Limit speed percent</string>
<string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string>
<string name="turbo_speed_limit">Turbo speed</string>
<string name="turbo_speed_limit_description">When Turbo Mode is enabled, emulation will run at this speed.</string>
<string name="slow_speed_limit">Slow speed</string>
<string name="slow_speed_limit_description">When Slow Mode is enabled, emulation will run at this speed.</string>
<string name="cpu_backend">CPU backend</string>
<string name="cpu_accuracy">CPU accuracy</string>
<string name="value_with_units">%1$s%2$s</string>