Add turbo speed hotkey (#605)

* Add the turbo slider

* [WIP] Add fast forward toggle hotkey

* Make Increase/Decrease speed hotkeys change turbo key instead of `frame_limit`

* Allow non-runtime editable settings on `general` settings tab`

* `frame_limit` is now  non-runtime-editable

* Disable `toggle per game speed limit` if turbo mode is set

* Reset `frame_limit` back to initial value once the emulator closes

* Improve `AdjustSpeedLimit`

- Set frameskip value directly
- Bypass if turbo mode isn't active

* Some code cleanup

* Move `turbo_speed_slider` from UISettings to CommonSettings

Also rename to just `turbo_speed`

* android: Add turbo mode hotkey

* Fixed build failure + Applied clang-format

* configure_general.ui Make padding on right side of sliders consistent

Not sure why there's a difference here, so I just threw in a spacer

* android: Corrected build failures caused by bad merge

* Updated `turbo_speed_description` to be a little more descriptive

* android: Corrected turbo crash caused by bad JNI function names

* Updated license headers

* HotkeyFunctions.kt: Fixed minor fomatting irregularities

* Applied clang-format

---------

Co-authored-by: kleidis <167202775+kleidis@users.noreply.github.com>
# Conflicts:
#	src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt
This commit is contained in:
OpenSauce
2025-04-11 12:29:07 +01:00
committed by AzaharPlus
parent 3e128b5137
commit fe01009dca
23 changed files with 352 additions and 55 deletions

View File

@@ -188,6 +188,16 @@ object NativeLibrary {
external fun unlinkConsole()
/**
* Turbo speed.
*/
external fun toggleTurboSpeed(enabled: Boolean)
external fun getTurboSpeedSlider(): Int
external fun setTurboSpeedSlider(value: Int)
external fun downloadTitleFromNus(title: Long): InstallStatus
private var coreErrorAlertResult = false

View File

@@ -33,6 +33,7 @@ import org.citra.citra_emu.contracts.OpenFileResultContract
import org.citra.citra_emu.databinding.ActivityEmulationBinding
import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.features.hotkeys.HotkeyUtility
import org.citra.citra_emu.features.hotkeys.HotkeyFunctions
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.SettingsViewModel
@@ -55,6 +56,7 @@ class EmulationActivity : AppCompatActivity() {
private lateinit var binding: ActivityEmulationBinding
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
private lateinit var hotkeyFunctions: HotkeyFunctions
private lateinit var hotkeyUtility: HotkeyUtility
private val emulationFragment: EmulationFragment
@@ -75,7 +77,8 @@ class EmulationActivity : AppCompatActivity() {
binding = ActivityEmulationBinding.inflate(layoutInflater)
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
hotkeyFunctions = HotkeyFunctions(settingsViewModel.settings)
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, hotkeyFunctions, this)
setContentView(binding.root)
val navHostFragment =
@@ -138,6 +141,7 @@ class EmulationActivity : AppCompatActivity() {
}
override fun onDestroy() {
hotkeyFunctions.resetTurboSpeed()
EmulationLifecycleUtil.clear()
isEmulationRunning = false
instance = null

View File

@@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -10,5 +10,6 @@ enum class Hotkey(val button: Int) {
CLOSE_GAME(10003),
PAUSE_OR_RESUME(10004),
QUICKSAVE(10005),
QUICKLOAD(10006);
QUICKLOAD(10006),
TURBO_SPEED(10007);
}

View File

@@ -0,0 +1,57 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.features.hotkeys
import android.widget.Toast
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.utils.SettingsFile
class HotkeyFunctions (
private val settings: Settings
) {
private var normalSpeed = IntSetting.FRAME_LIMIT.int
var isTurboSpeedEnabled = false
// Turbo Speed
fun setTurboSpeed(enabled: Boolean) {
isTurboSpeedEnabled = enabled
toggleTurboSpeed()
}
fun toggleTurboSpeed() {
if (isTurboSpeedEnabled) {
normalSpeed = IntSetting.FRAME_LIMIT.int
NativeLibrary.toggleTurboSpeed(true)
NativeLibrary.setTurboSpeedSlider(IntSetting.TURBO_SPEED.int)
IntSetting.FRAME_LIMIT.int = IntSetting.TURBO_SPEED.int
} else {
NativeLibrary.toggleTurboSpeed(false)
NativeLibrary.setTurboSpeedSlider(normalSpeed)
IntSetting.FRAME_LIMIT.int = normalSpeed
}
settings.saveSetting(IntSetting.FRAME_LIMIT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
val context = CitraApplication.appContext
Toast.makeText(context,
"Changed Emulation Speed to: ${IntSetting.FRAME_LIMIT.int}%", Toast.LENGTH_SHORT).show()
}
fun resetTurboSpeed() {
if (isTurboSpeedEnabled) {
isTurboSpeedEnabled = false
NativeLibrary.toggleTurboSpeed(false)
IntSetting.FRAME_LIMIT.int = normalSpeed
settings.saveSetting(IntSetting.FRAME_LIMIT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -11,9 +11,12 @@ import org.citra.citra_emu.R
import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.display.ScreenAdjustmentUtil
class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil, private val context: Context) {
class HotkeyUtility(
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
private val hotkeyFunctions: HotkeyFunctions,
private val context: Context) {
val hotkeyButtons = Hotkey.entries.map { it.button }
private val hotkeyButtons = Hotkey.entries.map { it.button }
fun handleHotkey(bindedButton: Int): Boolean {
if(hotkeyButtons.contains(bindedButton)) {
@@ -22,6 +25,7 @@ class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil, priv
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
Hotkey.TURBO_SPEED.button -> hotkeyFunctions.setTurboSpeed(!hotkeyFunctions.isTurboSpeedEnabled)
Hotkey.QUICKSAVE.button -> {
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
Toast.makeText(context,

View File

@@ -65,7 +65,9 @@ enum class IntSetting(
DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0),
USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, 0),
ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2),
DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, 0);
DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, 0),
TURBO_SPEED("turbo_speed", Settings.SECTION_CORE, 200);
override var int: Int = defaultValue
override val valueAsString: String
@@ -95,6 +97,7 @@ enum class IntSetting(
AUDIO_INPUT_TYPE,
USE_ARTIC_BASE_CONTROLLER,
SHADERS_ACCURATE_MUL,
FRAME_LIMIT
)
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }

View File

@@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -139,6 +139,7 @@ class Settings {
const val HOTKEY_PAUSE_OR_RESUME = "hotkey_pause_or_resume_game"
const val HOTKEY_QUICKSAVE = "hotkey_quickload"
const val HOTKEY_QUICKlOAD = "hotkey_quickpause"
const val HOTKEY_TURBO_SPEED = "hotkey_turbo_speed"
val buttonKeys = listOf(
KEY_BUTTON_A,
@@ -204,7 +205,8 @@ class Settings {
HOTKEY_CLOSE_GAME,
HOTKEY_PAUSE_OR_RESUME,
HOTKEY_QUICKSAVE,
HOTKEY_QUICKlOAD
HOTKEY_QUICKlOAD,
HOTKEY_TURBO_SPEED
)
val hotkeyTitles = listOf(
R.string.emulation_swap_screens,
@@ -213,6 +215,7 @@ class Settings {
R.string.emulation_toggle_pause,
R.string.emulation_quicksave,
R.string.emulation_quickload,
R.string.emulation_toggle_turbo_speed
)
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"

View File

@@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -133,6 +133,7 @@ class InputBindingSetting(
Settings.HOTKEY_PAUSE_OR_RESUME -> Hotkey.PAUSE_OR_RESUME.button
Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button
Settings.HOTKEY_QUICKlOAD -> Hotkey.QUICKLOAD.button
Settings.HOTKEY_TURBO_SPEED -> Hotkey.TURBO_SPEED.button
else -> -1
}

View File

@@ -237,6 +237,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.FRAME_LIMIT.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.TURBO_SPEED,
R.string.turbo_speed,
R.string.turbo_speed_description,
100,
400,
"%",
IntSetting.TURBO_SPEED.key,
IntSetting.TURBO_SPEED.defaultValue.toFloat()
)
)
}
}

View File

@@ -149,8 +149,8 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.use_vsync_new);
ReadSetting("Renderer", Settings::values.texture_filter);
ReadSetting("Renderer", Settings::values.texture_sampling);
// Work around to map Android setting for enabling the frame limiter to the format Citra expects
ReadSetting("Renderer", Settings::values.turbo_speed);
// Workaround to map Android setting for enabling the frame limiter to the format Citra expects
if (sdl2_config->GetBoolean("Renderer", "use_frame_limit", true)) {
ReadSetting("Renderer", Settings::values.frame_limit);
} else {

View File

@@ -786,6 +786,22 @@ void Java_org_citra_citra_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIE
LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level());
}
void JNICALL Java_org_citra_citra_1emu_NativeLibrary_toggleTurboSpeed([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jobject obj,
jboolean enabled) {
Settings::values.turbo_speed = enabled ? true : false;
}
jint JNICALL Java_org_citra_citra_1emu_NativeLibrary_getTurboSpeedSlider(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
return static_cast<jint>(Settings::values.turbo_speed);
}
void JNICALL Java_org_citra_citra_1emu_NativeLibrary_setTurboSpeedSlider(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj, jint value) {
Settings::values.turbo_speed = value;
}
jboolean Java_org_citra_citra_1emu_NativeLibrary_isFullConsoleLinked(JNIEnv* env, jobject obj) {
return HW::UniqueData::IsFullConsoleLinked();
}

View File

@@ -262,11 +262,14 @@
<string name="asynchronous_gpu">Enable asynchronous GPU emulation</string>
<string name="asynchronous_gpu_description">Uses a separate thread to emulate the GPU asynchronously. When enabled, performance will be improved.</string>
<string name="frame_limit_enable">Limit Speed</string>
<string name="expand_to_cutout_area">Expand to Cutout Area</string>
<string name="expand_to_cutout_area_description">Expands the display area to include the cutout (or notch) area.</string>
<string name="frame_limit_enable_description">When enabled, emulation speed will be limited to a specified percentage of normal speed.</string>
<string name="frame_limit_enable_description">When enabled, emulation speed will be limited to a specified percentage of normal speed. If disabled, emulation speed will be uncapped and the turbo speed hotkey will not work.</string>
<string name="frame_limit_slider">Limit Speed Percent</string>
<string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. With the default of 100% emulation will be limited to normal speed. Values higher or lower will increase or decrease the speed limit.</string>
<string name="expand_to_cutout_area">Expand to Cutout Area</string>
<string name="expand_to_cutout_area_description">Expands the display area to include the cutout (or notch) area.</string>
<string name="emulation_toggle_turbo_speed">Turbo Speed</string>
<string name="turbo_speed">Turbo Speed</string>
<string name="turbo_speed_description">Emulation speed limit used while the turbo hotkey is active.</string>
<string name="internal_resolution">Internal Resolution</string>
<string name="internal_resolution_description">Specifies the resolution used to render at. A high resolution will improve visual quality a lot but is also quite heavy on performance and might cause glitches in certain applications.</string>
<string name="internal_resolution_setting_auto">Auto (Screen Size)</string>