From 17544a04d80e18fc62f9dd60c574f4a3600ee5cf Mon Sep 17 00:00:00 2001 From: Sad Moments Date: Sun, 26 Jul 2026 16:36:05 +0200 Subject: [PATCH 1/7] Feature: Add performance manager This commits adds a power controller for the cpu that allows you to change frequencies and disable cpu cluster change cpu governor and set fan speed on supported devices, any device the ships with PServerBinder/mostly android handhelds or a rooted device should work. --- app/build.gradle | 2 + app/src/main/app/shell/UnifiedActivity.kt | 2 + app/src/main/feature/library/GameSettings.kt | 139 ++++++++ .../main/feature/power/PerformanceManager.kt | 327 ++++++++++++++++++ .../ContainerSettingsComposeDialog.kt | 23 ++ .../ShortcutSettingsComposeDialog.kt | 57 +++ app/src/main/res/values/strings.xml | 10 + .../display/XServerDisplayActivity.java | 47 +++ app/src/main/shared/util/RootManager.kt | 71 ++++ 9 files changed, 678 insertions(+) create mode 100644 app/src/main/feature/power/PerformanceManager.kt create mode 100644 app/src/main/shared/util/RootManager.kt diff --git a/app/build.gradle b/app/build.gradle index 62637d031..5049e6d52 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -285,6 +285,8 @@ dependencies { implementation libs.playServicesGamesV2 implementation libs.workRuntimeKtx + + implementation "com.github.topjohnwu.libsu:core:6.0.0" } spotless { diff --git a/app/src/main/app/shell/UnifiedActivity.kt b/app/src/main/app/shell/UnifiedActivity.kt index ab661f60d..cb1339e3b 100644 --- a/app/src/main/app/shell/UnifiedActivity.kt +++ b/app/src/main/app/shell/UnifiedActivity.kt @@ -215,6 +215,7 @@ import com.winlator.cmod.shared.ui.CarouselView import com.winlator.cmod.shared.ui.dialog.PopupDialog import com.winlator.cmod.shared.ui.dialog.PopupTextAction import androidx.compose.foundation.focusGroup +import com.winlator.cmod.feature.power.PerformanceManager import com.winlator.cmod.shared.ui.focus.controllerFocusGlow import com.winlator.cmod.shared.ui.focus.controllerMenuInput import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape @@ -931,6 +932,7 @@ class UnifiedActivity : com.winlator.cmod.runtime.display.GlassesManager.init(this) bootstrapStartupState() maybeAutoSignInGoogleOnLaunch() + PerformanceManager.checkForPossibleCrash() // Surface store-session events as toasts. lifecycleScope.launch { diff --git a/app/src/main/feature/library/GameSettings.kt b/app/src/main/feature/library/GameSettings.kt index c79f8eca3..071dd83d1 100644 --- a/app/src/main/feature/library/GameSettings.kt +++ b/app/src/main/feature/library/GameSettings.kt @@ -142,6 +142,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.json.JSONObject import androidx.compose.foundation.focusable +import androidx.compose.material.icons.outlined.Power import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key @@ -150,6 +151,8 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import com.winlator.cmod.shared.ui.focus.controllerFocusBorder import com.winlator.cmod.shared.ui.focus.controllerFocusGlow +import com.winlator.cmod.feature.power.CpuPolicy +import com.winlator.cmod.feature.power.PerformanceManager import com.winlator.cmod.shared.ui.focus.controllerSliderEscape import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape import com.winlator.cmod.shared.ui.outlinedSwitchColors @@ -575,6 +578,11 @@ class GameSettingsStateHolder { val drives = mutableStateOf("") val isLoaded = mutableStateOf(false) + val cpuFanMode = mutableStateOf("") + val cpuGovernor = mutableStateOf("") + val cpuEditingMode = mutableStateOf(false) + val cpuBoostState = mutableStateOf(true) + val cpuPolicies = mutableStateOf>(emptyList()) } interface GameSettingsCallbacks { @@ -654,6 +662,7 @@ private const val SEC_INPUT = 7 private const val SEC_ADVANCED = 8 private const val SEC_DRIVES = 9 private const val SEC_SAVES = 10 +private const val SEC_POWER = 11 private fun buildSections(isSteam: Boolean, isContainer: Boolean): List> { val list = mutableListOf>() @@ -672,6 +681,9 @@ private fun buildSections(isSteam: Boolean, isContainer: Boolean): List AdvancedSection(state, callbacks) SEC_DRIVES -> DrivesSection(state, callbacks) SEC_SAVES -> SavesSection(state, callbacks) + SEC_POWER -> PerformanceControlSection(state, callbacks) } Spacer(Modifier.height(SettingSectionGap)) } @@ -1052,6 +1065,132 @@ private fun SidebarItem( } } +@Composable +private fun PerformanceControlSection( + state: GameSettingsStateHolder, + callbacks: GameSettingsCallbacks +) { + val governors = PerformanceManager.allCpuGovernors + val fanModes = PerformanceManager.getSupportedFanModes() + var fanModeIndex by remember { mutableStateOf(fanModes?.indexOf(state.cpuFanMode.value) ?: 0) } + var governorIndex by remember { mutableStateOf(governors?.indexOf(state.cpuGovernor.value) ?: 0) } + + if (!governors.isNullOrEmpty()) { + SettingGroup { + Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { + Box(Modifier.weight(1f)) { + SettingDropdown( + label = stringResource(R.string.performance_cpu_governor_label), + entries = governors, + selectedIndex = governorIndex, + onSelected = { + governorIndex = it + state.cpuGovernor.value = governors[it] + } + ) + } + if (PerformanceManager.isFanSupported && !fanModes.isNullOrEmpty()) { + Box(Modifier.weight(1f)) { + SettingDropdown( + label = stringResource(R.string.performance_cpu_fan_label), + entries = fanModes, + selectedIndex = fanModeIndex, + onSelected = { + fanModeIndex = it + state.cpuFanMode.value = fanModes[it] + } + ) + } + } + } + } + Spacer(Modifier.height(SettingSectionGap)) + } + + if (state.cpuPolicies.value.isNotEmpty()) { + SettingGroup { + Column { + Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { + Box(Modifier.weight(1f)) { + SettingCheckbox(stringResource(R.string.performance_cpu_editing_enabled), state.cpuEditingMode.value, onCheckedChange = {state.cpuEditingMode.value = it}) + } + Box(Modifier.weight(1f)) { + SettingCheckbox(stringResource(R.string.performance_cpu_enable_boost), state.cpuBoostState.value, onCheckedChange = { + state.cpuBoostState.value = it + if (state.cpuPolicies.value.isNotEmpty()) + state.cpuPolicies.value.forEach { policy -> policy.boostState = it } + }) + } + } + + Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { + for (index in state.cpuPolicies.value.indices) { + var checked by remember { mutableStateOf(state.cpuPolicies.value[index].enabled) } + Box(Modifier.weight(1f)) { + SettingCheckbox( + stringResource(R.string.performance_cpu_enabled_cluster, state.cpuPolicies.value[index].policyId.toString()), + checked, + onCheckedChange = { it -> + val disabledClusterCount = state.cpuPolicies.value.count { !it.enabled } + if (!it && disabledClusterCount >= state.cpuPolicies.value.size - 1) + return@SettingCheckbox + checked = it + state.cpuPolicies.value[index].enabled = it + } + ) + } + } + } + + for (index in state.cpuPolicies.value.indices) { + if (!state.cpuPolicies.value[index].enabled) continue + val frequencies = state.cpuPolicies.value[index].availableFrequencies ?: return@SettingGroup + var minIndex by remember { mutableStateOf(frequencies.indexOf(state.cpuPolicies.value[index].minFrequency)) } + var maxIndex by remember { mutableStateOf(frequencies.indexOf(state.cpuPolicies.value[index].maxFrequency)) } + + + Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { + Box(Modifier.weight(1f)) { + SettingSlider( + label = stringResource(R.string.performance_cpu_cluster_min_label, state.cpuPolicies.value[index].policyId.toString()), + value = minIndex, + range = frequencies.indices, + valueText = PerformanceManager.formatFrequency(frequencies[minIndex].toInt()), + steps = (frequencies.size-2).coerceAtLeast(0), + enabled = true, + onValueChange = { + if (minIndex >= maxIndex) { + maxIndex = if ((minIndex + 1) >= frequencies.size) frequencies.size - 1 else minIndex + 1 + } + minIndex = it + } + ) + } + Box(Modifier.weight(1f)) { + SettingSlider( + label = stringResource(R.string.performance_cpu_cluster_max_label, state.cpuPolicies.value[index].policyId.toString()), + value = maxIndex, + range = frequencies.indices, + valueText = PerformanceManager.formatFrequency(frequencies[maxIndex].toInt(), state.cpuPolicies.value[index].boostState), + steps = (frequencies.size-2).coerceAtLeast(0), + enabled = true, + onValueChange = { + if (maxIndex <= minIndex) { + minIndex = if ((maxIndex - 1) < 0) 0 else maxIndex - 1 + } + maxIndex = it + } + ) + } + } + state.cpuPolicies.value[index].minFrequency = frequencies[minIndex] + state.cpuPolicies.value[index].maxFrequency = frequencies[maxIndex] + } + } + } + } +} + @Composable private fun GeneralSection( state: GameSettingsStateHolder, diff --git a/app/src/main/feature/power/PerformanceManager.kt b/app/src/main/feature/power/PerformanceManager.kt new file mode 100644 index 000000000..a57cdeb4a --- /dev/null +++ b/app/src/main/feature/power/PerformanceManager.kt @@ -0,0 +1,327 @@ +package com.winlator.cmod.feature.power + +import java.io.File +import com.winlator.cmod.shared.util.RootManager +import org.apache.commons.lang3.mutable.Mutable + +data class CpuPolicy( + var enabled: Boolean, + val policyId: Int, + val policyName: String, + var boostState: Boolean, + var minFrequency: Long, + var maxFrequency: Long, + val boostFrequency: Long?, + val cpuCores: List, + val availableFrequencies: MutableList? +) + +object PerformanceManager { + private const val CPU_PATH = "/sys/devices/system/cpu" + private const val CPU_BOOST_PATH = "/sys/devices/system/cpu/cpufreq/boost" + private const val CPU_POLICY_BASE_PATH = "/sys/devices/system/cpu/cpufreq/" + + private var defaultSystemPolicies: List? = null + private val defaultSystemGovernor = getCpuGovernor() + + private var defautlSystemFanMode: Int? = null + + private val FAN_MODES = mapOf("QUIET" to 1, "SMART" to 4, "SPORT" to 5, "CUSTOM" to 6) + + val allCpuGovernors = getCpuGovernors() + + val disabledCores = mutableListOf() + + val isDeviceSupported + get() = RootManager.isRooted + + val isFanSupported = checkForFanSupport() + + val isGpuSupported = false + + private var running = false + + fun checkForFanSupport(): Boolean { + if (!isDeviceSupported) return false + val response = RootManager.executeAsRoot("settings get system fan_mode") ?: return false + val fanMode = response.toIntOrNull() + defautlSystemFanMode = fanMode + return fanMode != null + } + + fun checkForPossibleCrash() { + if (!isDeviceSupported) return + val numberOfCpuCores = getNumberOfCpuCores() ?: return + var resetDefaultSystemPolicies = false + getCpuBoostState()?.let { + if (!it) + setCpuBoostState(true) + } + for (cpu in 0..? { + if (!isDeviceSupported || !isFanSupported) return null + return FAN_MODES.map { it.key } + } + + fun getDefaultSystemPolicies(): List? { + if (!isDeviceSupported) return null + return defaultSystemPolicies + } + + fun getCpuBoostState(): Boolean? { + if (!isDeviceSupported) return null + return RootManager.readSysfsFile(CPU_BOOST_PATH) == "1" + } + + fun getCpuGovernors(): List? { + if (!isDeviceSupported) return null + val cpuPath = "${CPU_PATH}/cpu0/cpufreq/scaling_available_governors" + return runCatching { RootManager.readSysfsFile(cpuPath)?.split(" ")?.map{it} }.getOrNull() + } + + fun getCpuGovernor(cpu: Int=0): String? { + if (!isDeviceSupported) return null + return RootManager.readSysfsFile("${CPU_PATH}/cpu${cpu}/cpufreq/scaling_governor") + } + + fun getNumberOfCpuCores(): Int? { + if (!isDeviceSupported) return null + val content = RootManager.readSysfsFile("${CPU_PATH}/present") ?: return null + val parts = content.split("-") + if (parts.size == 2) + return runCatching { parts[1].toInt()+1 }.getOrNull() + return null + } + + fun getCpuPolicies(): List? { + if (!isDeviceSupported) return null + val numberOfCpuCores = getNumberOfCpuCores() ?: return null + val policies = mutableMapOf>() + for (cpu in 0.. + val frequencies: MutableList? = RootManager.readSysfsFile("${policyDirectory}/scaling_available_frequencies")?.split(" ")?.map { it -> runCatching { it.toLong()}.getOrDefault(0) } as MutableList? + val minFrequency = runCatching { frequencies!!.first() }.getOrDefault(0) + val maxFrequency = runCatching { frequencies!!.last() }.getOrDefault(0) + val boostFrequency = runCatching { RootManager.readSysfsFile("${policyDirectory}/cpuinfo_max_freq")!!.toLong() }.getOrDefault(0) + //if (boostState && frequencies != null && maxFrequency > frequencies.last()) + // frequencies.add(maxFrequency) + //frequencies = frequencies + listOf(maxFrequency) + CpuPolicy( + enabled = true, + policyId = index, + policyName = File(policyDirectory).name, + boostState = boostState, + minFrequency = minFrequency, + maxFrequency = maxFrequency, + cpuCores = cpuCoreList, + availableFrequencies = frequencies, + boostFrequency = boostFrequency + ) + } + } + + fun getCpuCoreAvailableFrequencies(cpu: Int): List? { + if (!isDeviceSupported) return null + return RootManager.readSysfsFile("${CPU_PATH}/cpu${cpu}/scaling_available_frequencies")?.split(" ")?.map { it -> runCatching { it.toLong()}.getOrDefault(0) } + } + + fun getCpuPolicyAvailableFrequencies(policyName: String): List? { + if (!isDeviceSupported) return null + return RootManager.readSysfsFile("${CPU_POLICY_BASE_PATH}/${policyName}/scaling_available_frequencies")?.split(" ")?.map { it -> runCatching { it.toLong()}.getOrDefault(0) } + } + + fun getCpuCoreOnlineState(cpuCore: Int): Boolean? { + if (!isDeviceSupported) return null + val onlineState = RootManager.readSysfsFile("${CPU_PATH}/cpu${cpuCore}/online") + return onlineState == "1" + } + + fun setCpuCoreGovernor(cpuCore: Int, governor: String): Boolean? { + if (!isDeviceSupported) return null + val governorPath = "${CPU_PATH}/cpu${cpuCore}/cpufreq/scaling_governor" + return RootManager.writeSysfsFile(governorPath, governor) + } + + fun setAllCpuCoreGovernor(governor: String): Boolean? { + if (!isDeviceSupported) return null + val numberOfCpuCores = getNumberOfCpuCores() ?: return null + for (cpu in 0..numberOfCpuCores) + setCpuCoreGovernor(cpu, governor) + return true + } + + fun setCpuBoostState(state: Boolean): Boolean? { + if (!isDeviceSupported) return null + return RootManager.writeSysfsFile(CPU_BOOST_PATH, if(state) "1" else "0") + } + + fun setCpuCoreOnlineState(cpuCore: Int, state: Boolean): Boolean? { + if (!isDeviceSupported) return null + val response = RootManager.writeSysfsFile("${CPU_PATH}/cpu${cpuCore}/online", if(state) "1" else "0") + if (response) { + if (state) + disabledCores.remove(cpuCore) + else + disabledCores.add(cpuCore) + } + return true + } + + fun setPolicyOnlineState(policy: CpuPolicy, state: Boolean): Boolean? { + if (!isDeviceSupported) return null + if (!policy.cpuCores.isNotEmpty()) return null + policy.cpuCores.forEach { setCpuCoreOnlineState(it, state) } + return true + } + + fun setCpuCoreFrequency(cpuCore: Int, minFrequency: Long, maxFrequency: Long): Boolean? { + if (!isDeviceSupported) return null + val cpuFrequencyPath = "${CPU_PATH}/cpu${cpuCore}/cpufreq" + if (!RootManager.writeSysfsFile("${cpuFrequencyPath}/scaling_min_freq", minFrequency.toString())) return false + if (!RootManager.writeSysfsFile("${cpuFrequencyPath}/scaling_max_freq", maxFrequency.toString())) return false + return true + } + + fun setCpuCorePolicies(cpuCorePolicies: List, setBoostFrequency: Boolean=false): Boolean? { + if (!isDeviceSupported) return null + for (cpuPolicy in cpuCorePolicies) { + setCpuBoostState(cpuPolicy.boostState) + for (cpuCore in cpuPolicy.cpuCores) { + if (cpuPolicy.enabled) + if (setBoostFrequency && cpuPolicy.boostState && cpuPolicy.boostFrequency != null && cpuPolicy.boostFrequency.toInt() != 0) + setCpuCoreFrequency(cpuCore, cpuPolicy.minFrequency, cpuPolicy.boostFrequency) + else + setCpuCoreFrequency(cpuCore, cpuPolicy.minFrequency, cpuPolicy.maxFrequency) + else + setCpuCoreOnlineState(cpuCore, false) + } + } + return true + } + + + fun setCpuCorePolicies(rawCpuPolicy: String, setBoostFrequency: Boolean=false): Boolean? { + if (!isDeviceSupported) return null + val cpuCorePolicies = parseRawPoliciesFromString(rawCpuPolicy) + if (cpuCorePolicies.isNullOrEmpty()) return null + setCpuCorePolicies(cpuCorePolicies, setBoostFrequency) + return true + } + + fun setFanMode(fanMode: String): Boolean? { + if (!isDeviceSupported || !isFanSupported) return null + val mode = FAN_MODES[fanMode] ?: return null + return RootManager.executeAsRoot("settings put system fan_mode ${mode}") != null + } + + fun setFanMode(fanMode: Int): Boolean? { + if (!isDeviceSupported || !isFanSupported) return null + if (!FAN_MODES.containsValue(fanMode)) return null + return RootManager.executeAsRoot("settings put system fan_mode ${fanMode}") != null + } + + fun parseRawPoliciesFromString(rawPolicies: String): List? { + // Example policies string: "policyID-policyName:minFrequency-maxFrequency!start_core-end_core?policyOnlineState-boostState-boostfrequency," "0-policy0:1000-1111!3-7?1-1-9999," + if (!isDeviceSupported) return null + val policies = mutableListOf() + for (policy in rawPolicies.split(",")) { + try { + val policyId = policy.split(":")[0].split("-")[0].toInt() + val policyName = policy.split(":")[0].split("-")[1] + val minFrequency = policy.split(":")[1].split("!")[0].split("-")[0].toLong() + val maxFrequency = policy.split(":")[1].split("!")[0].split("-")[1].toLong() + val cpuCoresRange = policy.split(":")[1].split("!")[1].split("?")[0].split("-") + val cpuCoreState = policy.split(":")[1].split("!")[1].split("?")[1].split('-')[0].toBoolean() + val boostState = policy.split(":")[1].split("!")[1].split("?")[1].split('-')[1].toBoolean() + val boostFrequency = policy.split(":")[1].split("!")[1].split("?")[1].split('-')[2] + + val cpuCores = mutableListOf() + (cpuCoresRange[0].toInt()..cpuCoresRange[1].toInt()) + .forEach { cpu -> + cpuCores.add(cpu) + } + + policies.add(CpuPolicy( + enabled = cpuCoreState, + policyId = policyId, + policyName = policyName, + boostState = boostState, + minFrequency = minFrequency, + maxFrequency = maxFrequency, + boostFrequency = if (boostFrequency == "0") null else boostFrequency.toLong(), + cpuCores = cpuCores, + availableFrequencies = getCpuPolicyAvailableFrequencies(policyName) as MutableList? + )) + } catch (e: Exception) {} + } + return policies + } + + fun policiesToString(policies: List): String? { + if (!isDeviceSupported) return null + var stringPolicies = "" + for (policy in policies) { + stringPolicies += "${policy.policyId}-${policy.policyName}:${policy.minFrequency}-${policy.maxFrequency}!${policy.cpuCores.first()}-${policy.cpuCores.last()}?${policy.enabled}-${policy.boostState}-${policy.boostFrequency}," + } + return stringPolicies + } + + fun formatFrequency(valueKhz: Int, boosted: Boolean = false): String { + val base = when { + valueKhz >= 1_000_000 -> String.format("%.2f GHz", valueKhz / 1_000_000f) + valueKhz >= 1_000 -> String.format("%.0f MHz", valueKhz / 1_000f) + else -> "$valueKhz kHz" + } + return if (boosted) "$base+" else base + } + + fun resetSystemToDefault(): Boolean? { + if (!isDeviceSupported) return null + if (!defaultSystemGovernor.isNullOrEmpty()) + setAllCpuCoreGovernor(defaultSystemGovernor) + if (isFanSupported && defautlSystemFanMode != null) + setFanMode(defautlSystemFanMode!!) + if (defaultSystemPolicies.isNullOrEmpty()) return null + setCpuCorePolicies(defaultSystemPolicies!!, true) + return true + } +} \ No newline at end of file diff --git a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt index 8704af95c..f8d10861c 100644 --- a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt +++ b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt @@ -75,6 +75,7 @@ import com.winlator.cmod.shared.ui.dialog.ContainerProgressPopup import com.winlator.cmod.shared.ui.dialog.PopupDialog import com.winlator.cmod.shared.ui.dialog.WinNativeComposeDialogs import android.os.Environment +import com.winlator.cmod.feature.power.PerformanceManager /** * Compose replacement for the legacy `ContainerDetailFragment`. Reuses @@ -455,6 +456,19 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( state.directXComponents.value = directX state.generalComponents.value = general + if (PerformanceManager.isDeviceSupported && c != null) { + state.cpuGovernor.value = c.getExtra("cpuGovernor") + state.cpuBoostState.value = c.getExtra("cpuBoostState").toBoolean() + state.cpuEditingMode.value = c.getExtra("cpuEditingMode").toBoolean() + state.cpuFanMode.value = c.getExtra("cpuFanMode") + val cpuPolicies = c.getExtra("cpuPolicies") + if (cpuPolicies.isNotEmpty()) { + val policies = PerformanceManager.parseRawPoliciesFromString(cpuPolicies) + if (policies != null) + state.cpuPolicies.value = policies + } + } + val envVarsStr = c?.getEnvVars() ?: Container.DEFAULT_ENV_VARS val items = parseEnvVarItems(envVarsStr) state.sdl2Compatibility.value = EnvVars(envVarsStr).get("SDL_XINPUT_ENABLED") == "1" @@ -774,6 +788,15 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( state.emulator64Entries.value, state.selectedEmulator64.intValue ) + if (PerformanceManager.isDeviceSupported && c != null) { + c.putExtra("cpuBoostState", state.cpuBoostState.value.toString()) + if (state.cpuGovernor.value.isNotEmpty()) + c.putExtra("cpuGovernor", state.cpuGovernor.value) + c.putExtra("cpuEditingMode", state.cpuEditingMode.value.toString()) + c.putExtra("cpuPolicies", PerformanceManager.policiesToString(state.cpuPolicies.value)) + c.putExtra("cpuFanMode", state.cpuFanMode.value) + } + val midiSoundFontEntries = state.midiSoundFontEntries.value val midiIdx = state.selectedMidiSoundFont.intValue val midiSoundFont = diff --git a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt index 75fc5471b..1feae826d 100644 --- a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt +++ b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt @@ -83,6 +83,7 @@ import com.winlator.cmod.runtime.input.controls.InputControlsManager import com.winlator.cmod.runtime.audio.midi.MidiManager import com.winlator.cmod.runtime.display.winhandler.WinHandler import com.winlator.cmod.feature.artwork.SteamArtworkScraper +import com.winlator.cmod.feature.power.PerformanceManager import java.io.File import java.lang.reflect.Field import java.util.Arrays @@ -455,6 +456,53 @@ class ShortcutSettingsComposeDialog private constructor( } state.cpuCheckedWoW64.value = checkedWoW64 + CoroutineScope(Dispatchers.IO).launch { + if (PerformanceManager.isDeviceSupported) { + state.cpuGovernor.value = shortcut.getExtra("cpuGovernor") + state.cpuBoostState.value = shortcut.getExtra("cpuBoostState").toBoolean() + state.cpuEditingMode.value = shortcut.getExtra("cpuEditingMode").toBoolean() + state.cpuFanMode.value = shortcut.getExtra("cpuFanMode") + + val cpuPolicies = shortcut.getExtra("cpuPolicies") + if (cpuPolicies.isNotEmpty()) { + var policies = PerformanceManager.parseRawPoliciesFromString(cpuPolicies) + if (!policies.isNullOrEmpty()) + state.cpuPolicies.value = policies + else { + policies = PerformanceManager.getDefaultSystemPolicies() + if (policies != null) + state.cpuPolicies.value = policies + } + } else { + val containerPolicies = shortcut.container.getExtra("cpuPolicies") + state.cpuGovernor.value = shortcut.container.getExtra("cpuGovernor") + state.cpuBoostState.value = shortcut.container.getExtra("cpuBoostState").toBoolean() + state.cpuEditingMode.value = shortcut.container.getExtra("cpuEditingMode").toBoolean() + state.cpuFanMode.value = shortcut.container.getExtra("cpuFanMode") + + if (containerPolicies.isNotEmpty()) { + var policies = PerformanceManager.parseRawPoliciesFromString(containerPolicies) + if (!policies.isNullOrEmpty()) { + state.cpuPolicies.value = policies + shortcut.putExtra("cpuPolicies", containerPolicies) + shortcut.putExtra("cpuGovernor", state.cpuGovernor.value) + shortcut.putExtra("cpuBoostState", state.cpuBoostState.value.toString()) + shortcut.putExtra("cpuEditingMode", state.cpuEditingMode.value.toString()) + shortcut.putExtra("cpuFanMode", state.cpuFanMode.value) + } + else { + policies = PerformanceManager.getDefaultSystemPolicies() + if (!policies.isNullOrEmpty()) + state.cpuPolicies.value = policies + } + } else { + val policies = PerformanceManager.getDefaultSystemPolicies() + if (policies != null) + state.cpuPolicies.value = policies + } + } + } + } // Win Components loadWinComponents() @@ -1364,6 +1412,15 @@ class ShortcutSettingsComposeDialog private constructor( ) } + if (PerformanceManager.isDeviceSupported) { + shortcut.putExtra("cpuBoostState", state.cpuBoostState.value.toString()) + if (state.cpuGovernor.value.isNotEmpty()) + shortcut.putExtra("cpuGovernor", state.cpuGovernor.value) + shortcut.putExtra("cpuEditingMode", state.cpuEditingMode.value.toString()) + shortcut.putExtra("cpuPolicies", PerformanceManager.policiesToString(state.cpuPolicies.value)) + shortcut.putExtra("cpuFanMode", state.cpuFanMode.value) + } + // Container defaults flag shortcut.putExtra( EXTRA_USE_CONTAINER_DEFAULTS, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 364f9c208..ad8b99e37 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -319,6 +319,16 @@ Re-added existing shortcut Failed to add shortcut. + + Enabled CPU Frequencies + Enabled CPU Boost + Enabled Cluster %s + Cluster %s Min + Cluster %s Max + CPU Governor + Fan Control + Power Controls + Exec Arguments Target Path diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index b5429ae71..6a81ec826 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -50,6 +50,7 @@ import androidx.core.view.WindowInsetsCompat; import com.winlator.cmod.BuildConfig; import com.winlator.cmod.feature.leaderboard.SessionRecordingController; +import com.winlator.cmod.feature.power.PerformanceManager; import com.winlator.cmod.feature.stores.steam.enums.Marker; import com.winlator.cmod.feature.stores.steam.utils.MarkerUtils; import com.winlator.cmod.feature.stores.steam.utils.PrefManager; @@ -1877,6 +1878,7 @@ public void onFailed(Exception e) { String controlsProfile = shortcut != null ? shortcut.getExtra("controlsProfile", "") : ""; Runnable runnable = () -> { + startPerformanceManager(); setupUI(); if (controlsProfile.isEmpty()) { simulateConfirmInputControlsDialog(); @@ -1984,6 +1986,47 @@ this, shortcut, isCloudSyncEnabledForShortcut(), } } + private void startPerformanceManager() { + //All wintoast are just for debug will remove later + var performanceManager = PerformanceManager.INSTANCE; + if (performanceManager.isDeviceSupported()) { + if (shortcut.getExtra("cpuPolicies").isEmpty() && !container.getExtra("cpuPolicies").isEmpty()) { + WinToast.show(this, "settings empty using container default"); + String[] fields = {"cpuPolicies", "cpuEditingMode", "cpuBoostState", "cpuFanMode", "cpuGovernor"}; + for (String field : fields) shortcut.putExtra(field, container.getExtra(field)); + } + var cpuPolicies = shortcut.getExtra("cpuPolicies"); + var cpuEditingMode = Boolean.parseBoolean(shortcut.getExtra("cpuEditingMode")); + var cpuBoostState = Boolean.parseBoolean(shortcut.getExtra("cpuBoostState")); + var cpuFanMode = shortcut.getExtra("cpuFanMode"); + if (!cpuFanMode.isEmpty()) { + WinToast.show(this, String.format("CPU fan mode set: %s", cpuFanMode)); + performanceManager.setFanMode(cpuFanMode); + } + if (cpuEditingMode && !cpuPolicies.isEmpty()) { + WinToast.show(this, "CPU policies set"); + performanceManager.setCpuCorePolicies(cpuPolicies, false); + } else if (!cpuBoostState) { + performanceManager.setCpuBoostState(false); + } + if (!cpuEditingMode && !cpuPolicies.isEmpty()) { + var policies = performanceManager.parseRawPoliciesFromString(cpuPolicies); + if (policies != null && !policies.isEmpty()) { + policies.forEach(it -> { + if (!it.getEnabled()) + performanceManager.setPolicyOnlineState(it, false); + }); + } + } + var cpuGovernor = shortcut.getExtra("cpuGovernor"); + if (!cpuGovernor.isEmpty()) { + WinToast.show(this, String.format("CPU governor set: %s", cpuGovernor)); + performanceManager.setAllCpuCoreGovernor(cpuGovernor); + } + WinToast.show(this, String.format("disabled cores %s: ", performanceManager.getDisabledCores())); + } + } + private String resolveDesktopPathFromUri(android.net.Uri uri) { if (uri == null) return null; try { @@ -3292,6 +3335,10 @@ private void exit() { }, "XServerExitCleanup").start(); }, 1000); }); + var performanceManager = PerformanceManager.INSTANCE; + if (performanceManager.isDeviceSupported()) { + performanceManager.resetSystemToDefault(); + } } private void closeAfterSessionExit() { diff --git a/app/src/main/shared/util/RootManager.kt b/app/src/main/shared/util/RootManager.kt new file mode 100644 index 000000000..70335a2b0 --- /dev/null +++ b/app/src/main/shared/util/RootManager.kt @@ -0,0 +1,71 @@ +package com.winlator.cmod.shared.util + +import android.os.IBinder +import android.os.Parcel +import com.topjohnwu.superuser.Shell +import java.nio.charset.Charset + + +object RootManager { + var isRooted: Boolean = false + get() = isDeviceRooted() + private set + + private val binder: IBinder? = runCatching { + val serviceManager = Class.forName("android.os.ServiceManager") + val getService = serviceManager.getDeclaredMethod("getService", String::class.java) + val rawBinder = getService.invoke(serviceManager, "PServerBinder") as IBinder + rawBinder + }.getOrNull() + + fun readSysfsFile(path: String): String? { + if (!isRooted) return null + val result = executeAsRoot("cat '$path'") ?: return null + return result + } + + fun writeSysfsFile(path: String, value: String, lockFile: Boolean=true): Boolean { + return try { + var command = "chmod 644 '$path'; echo '$value' > '$path';" + if (lockFile) + command+=" chmod 444 '$path'" + val result = executeAsRoot(command) + return result != null + } catch (e: Exception) { + false + } + } + + private fun decodeReply(reply: Parcel): String? { + return reply.createByteArray() + ?.toString(Charset.defaultCharset()) + ?.trim() + ?.let { value -> if (value == "null") null else value } + } + + fun executeAsRoot(cmd: String): String? { + if (binder != null) { + val data = Parcel.obtain() + val reply = Parcel.obtain() + return try { + data.writeStringArray(arrayOf(cmd, "1")) + binder.transact(0, data, reply, 0) + decodeReply(reply) + } catch (throwable: Throwable) { + return null + } finally { + data.recycle() + reply.recycle() + } + } else if (Shell.isAppGrantedRoot() == true) { + val result: Shell.Result = Shell.cmd(cmd).exec() + if (!result.isSuccess) return null + return result.out.toString() + } + return null + } + + fun isDeviceRooted(): Boolean { + return binder != null || Shell.isAppGrantedRoot() == true + } +} \ No newline at end of file From 39fc41a935ca0b0f598a3fb4764bbc6cf3dfa057 Mon Sep 17 00:00:00 2001 From: Sad Moments Date: Sun, 26 Jul 2026 16:36:39 +0200 Subject: [PATCH 2/7] Don't show disabled cores on mangohudview --- app/src/main/runtime/display/ui/MangoHudView.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/runtime/display/ui/MangoHudView.java b/app/src/main/runtime/display/ui/MangoHudView.java index 5cff40e1d..33cb8e3e7 100644 --- a/app/src/main/runtime/display/ui/MangoHudView.java +++ b/app/src/main/runtime/display/ui/MangoHudView.java @@ -19,6 +19,8 @@ import android.view.View; import android.view.ViewGroup; import androidx.preference.PreferenceManager; + +import com.winlator.cmod.feature.power.PerformanceManager; import com.winlator.cmod.runtime.system.CPUStatus; import java.io.BufferedReader; import java.io.File; @@ -1192,7 +1194,9 @@ protected void onDraw(Canvas canvas) { y += rowH; } if (elements[EL_CORES]) { + var disabledCores = PerformanceManager.INSTANCE.getDisabledCores(); for (int i = 0; i < coreCount; i++) { + if (disabledCores.contains(i)) continue; float x = drawLabel(canvas, coreLabels[i], C_CPU, y); x = drawStatCell(canvas, sbCorePct[i], "%", x, y, 3); drawStatCell(canvas, sbCoreMhz[i], "MHz", x, y, 4); From 84e457afb4d05dc6f0085492af78a5fa34543e9d Mon Sep 17 00:00:00 2001 From: Sad Moments Date: Sun, 26 Jul 2026 16:57:46 +0200 Subject: [PATCH 3/7] Fix empty controls --- app/src/main/feature/library/GameSettings.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/feature/library/GameSettings.kt b/app/src/main/feature/library/GameSettings.kt index 071dd83d1..7553135d3 100644 --- a/app/src/main/feature/library/GameSettings.kt +++ b/app/src/main/feature/library/GameSettings.kt @@ -1075,6 +1075,11 @@ private fun PerformanceControlSection( var fanModeIndex by remember { mutableStateOf(fanModes?.indexOf(state.cpuFanMode.value) ?: 0) } var governorIndex by remember { mutableStateOf(governors?.indexOf(state.cpuGovernor.value) ?: 0) } + if (state.cpuPolicies.value.isEmpty()) { + val policies = PerformanceManager.getCpuPolicies() + if (!policies.isNullOrEmpty()) + state.cpuPolicies.value = policies + } if (!governors.isNullOrEmpty()) { SettingGroup { Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { From 7f3d0b2584211f1fe5c8552cfcce6f06872fef47 Mon Sep 17 00:00:00 2001 From: Sad Moments Date: Mon, 27 Jul 2026 20:38:30 +0200 Subject: [PATCH 4/7] Add: automatic cpu tuning targeting a specific fps If you set a fps target this will raise or lower cpu frequencies to hit fps target. --- app/src/main/feature/library/GameSettings.kt | 80 +++++++++++-- .../main/feature/power/PerformanceManager.kt | 109 +++++++++++++++++- .../ContainerSettingsComposeDialog.kt | 6 + .../ShortcutSettingsComposeDialog.kt | 12 ++ app/src/main/res/values/strings.xml | 3 + .../display/XServerDisplayActivity.java | 31 ++++- 6 files changed, 220 insertions(+), 21 deletions(-) diff --git a/app/src/main/feature/library/GameSettings.kt b/app/src/main/feature/library/GameSettings.kt index 7553135d3..c6c4356d6 100644 --- a/app/src/main/feature/library/GameSettings.kt +++ b/app/src/main/feature/library/GameSettings.kt @@ -583,6 +583,9 @@ class GameSettingsStateHolder { val cpuEditingMode = mutableStateOf(false) val cpuBoostState = mutableStateOf(true) val cpuPolicies = mutableStateOf>(emptyList()) + val cpuTargetFPS = mutableStateOf(0) + val cpuAutoTargetFPS = mutableStateOf(false) + val cpuAvgFrameCount = mutableStateOf(3) } interface GameSettingsCallbacks { @@ -1112,22 +1115,79 @@ private fun PerformanceControlSection( Spacer(Modifier.height(SettingSectionGap)) } - if (state.cpuPolicies.value.isNotEmpty()) { - SettingGroup { - Column { - Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { + SettingGroup { + Column { + Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { + if (!state.cpuAutoTargetFPS.value) { Box(Modifier.weight(1f)) { - SettingCheckbox(stringResource(R.string.performance_cpu_editing_enabled), state.cpuEditingMode.value, onCheckedChange = {state.cpuEditingMode.value = it}) + SettingCheckbox( + stringResource(R.string.performance_cpu_editing_enabled), + state.cpuEditingMode.value, + onCheckedChange = { state.cpuEditingMode.value = it }) } Box(Modifier.weight(1f)) { - SettingCheckbox(stringResource(R.string.performance_cpu_enable_boost), state.cpuBoostState.value, onCheckedChange = { - state.cpuBoostState.value = it - if (state.cpuPolicies.value.isNotEmpty()) - state.cpuPolicies.value.forEach { policy -> policy.boostState = it } - }) + SettingCheckbox( + stringResource(R.string.performance_cpu_enable_boost), + state.cpuBoostState.value, + onCheckedChange = { + state.cpuBoostState.value = it + if (state.cpuPolicies.value.isNotEmpty()) + state.cpuPolicies.value.forEach { policy -> + policy.boostState = it + } + } + ) } } + Box(Modifier.weight(1f)) { + SettingCheckbox( + "AutoFPS", + state.cpuAutoTargetFPS.value, + onCheckedChange = { state.cpuAutoTargetFPS.value = it }) + } + } + if (state.cpuAutoTargetFPS.value) { + Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { + Box(Modifier.weight(1f)) { + val fpsMin = 30 + val supportedMax = state.refreshRateEntries.value + .mapNotNull { it.trim().substringBefore(" ").toIntOrNull() } + .maxOrNull() ?: 60 + val maxFps = supportedMax.coerceAtLeast(fpsMin) + var lastFps by remember(maxFps) { + mutableStateOf( + (if (state.cpuTargetFPS.value > 0) state.cpuTargetFPS.value else 60) + .coerceIn(fpsMin, maxFps) + ) + } + SettingSlider( + label = stringResource(R.string.performance_fps_target_label), + value = lastFps, + range = fpsMin..maxFps, + valueText = "$lastFps FPS", + steps = (maxFps - fpsMin - 1).coerceAtLeast(0), + onValueChange = { + val v = it.coerceIn(fpsMin, maxFps) + lastFps = v + state.cpuTargetFPS.value = v + } + ) + } + Box(Modifier.weight(1f)) { + SettingSlider( + label = stringResource(R.string.performance_frames_per_check_label), + value = state.cpuAvgFrameCount.value, + range = 1..5, + valueText = stringResource(R.string.performance_frames_label, state.cpuAvgFrameCount.value), + steps = (0 - 5 - 1).coerceAtLeast(0), + onValueChange = { + state.cpuAvgFrameCount.value = it + } + ) + } + } + } else if (state.cpuPolicies.value.isNotEmpty()) { Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { for (index in state.cpuPolicies.value.indices) { var checked by remember { mutableStateOf(state.cpuPolicies.value[index].enabled) } diff --git a/app/src/main/feature/power/PerformanceManager.kt b/app/src/main/feature/power/PerformanceManager.kt index a57cdeb4a..cd69b071f 100644 --- a/app/src/main/feature/power/PerformanceManager.kt +++ b/app/src/main/feature/power/PerformanceManager.kt @@ -2,7 +2,9 @@ package com.winlator.cmod.feature.power import java.io.File import com.winlator.cmod.shared.util.RootManager -import org.apache.commons.lang3.mutable.Mutable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch data class CpuPolicy( var enabled: Boolean, @@ -17,6 +19,7 @@ data class CpuPolicy( ) object PerformanceManager { + private const val IDLE_TIMEOUT_NS = 1500000000L private const val CPU_PATH = "/sys/devices/system/cpu" private const val CPU_BOOST_PATH = "/sys/devices/system/cpu/cpufreq/boost" private const val CPU_POLICY_BASE_PATH = "/sys/devices/system/cpu/cpufreq/" @@ -38,8 +41,14 @@ object PerformanceManager { val isFanSupported = checkForFanSupport() val isGpuSupported = false - - private var running = false + var targetFPS: Float = 0f + var fpsAvgFrameCount = 0 + var useAutoTargeting: Boolean = false + private var ticks: Long = 0L + private var fpsLastFrameNano = 0L + private var autoTargetSetup: Boolean = false + private var autoTargetPolicies: List? = null + private var fpsPastFrames: MutableList = mutableListOf() fun checkForFanSupport(): Boolean { if (!isDeviceSupported) return false @@ -159,6 +168,96 @@ object PerformanceManager { } } + private fun calculateFPS(): Float { + val nowNano = System.nanoTime() + if (fpsLastFrameNano == 0L || nowNano - fpsLastFrameNano > IDLE_TIMEOUT_NS) { + fpsLastFrameNano = nowNano + return 0f + } + val fps = 1000000000.0f / (nowNano - fpsLastFrameNano) + fpsLastFrameNano = nowNano + return fps + } + + fun onTick() { + if (!isDeviceSupported || targetFPS == 0f || !useAutoTargeting) return + if (!autoTargetSetup) { + val policies = getCpuPolicies() ?: return + for (policy in policies) { + policy.enabled = true + policy.boostState = true + if (policy.availableFrequencies.isNullOrEmpty()) continue + policy.maxFrequency = policy.availableFrequencies[policy.availableFrequencies.size-1] + policy.minFrequency = policy.availableFrequencies[policy.availableFrequencies.size-1] + } + autoTargetSetup = true + autoTargetPolicies = policies + return + } + ticks++ + if (ticks%targetFPS==0f && !autoTargetPolicies.isNullOrEmpty()) { + var fps = calculateFPS() * targetFPS + if (fps == 0f) return + fpsPastFrames.add(fps) + if (fpsAvgFrameCount >= fpsPastFrames.size) return + fps = fpsPastFrames.sum() / fpsPastFrames.size + var shouldUpdate = false + if (fps > targetFPS*1.1) { + for (policy in autoTargetPolicies) { + if (policy.availableFrequencies.isNullOrEmpty()) continue + val coreHasBoost = policy.availableFrequencies.last() != policy.boostFrequency + if (coreHasBoost && policy.maxFrequency == policy.boostFrequency) { + policy.maxFrequency = policy.availableFrequencies.last() + shouldUpdate = true + } + else if (policy.maxFrequency == policy.boostFrequency){ + policy.maxFrequency = policy.availableFrequencies.takeLast(2).first() + shouldUpdate = true + } else { + val maxIndex = policy.availableFrequencies.indexOf(policy.maxFrequency) + if (maxIndex > 1) { + policy.maxFrequency = policy.availableFrequencies[maxIndex - 1] + shouldUpdate = true + } + } + if (coreHasBoost && policy.maxFrequency == policy.boostFrequency) { + policy.minFrequency = policy.availableFrequencies.last() + shouldUpdate = true + } else { + val maxIndex = policy.availableFrequencies.indexOf(policy.maxFrequency) + if (maxIndex-1 <= 0) continue + policy.minFrequency = policy.availableFrequencies[maxIndex-1] + shouldUpdate = true + } + } + }else if (fps < targetFPS*0.98) { + for (policy in autoTargetPolicies) { + if (policy.availableFrequencies.isNullOrEmpty()) continue + val maxIndex = policy.availableFrequencies.indexOf(policy.maxFrequency) + if (maxIndex == -1) { + policy.minFrequency = policy.availableFrequencies.last() + shouldUpdate = true + } else { + if (maxIndex + 1 >= policy.availableFrequencies.size && policy.boostFrequency != null) + policy.maxFrequency = policy.boostFrequency + else + policy.maxFrequency = policy.availableFrequencies[maxIndex + 1] + policy.minFrequency = policy.availableFrequencies[maxIndex] + shouldUpdate = true + } + } + } + fpsPastFrames.clear() + if (shouldUpdate) { + CoroutineScope(Dispatchers.IO).launch { + autoTargetPolicies?.let { + setCpuCorePolicies(it) + } + } + } + } + } + fun getCpuCoreAvailableFrequencies(cpu: Int): List? { if (!isDeviceSupported) return null return RootManager.readSysfsFile("${CPU_PATH}/cpu${cpu}/scaling_available_frequencies")?.split(" ")?.map { it -> runCatching { it.toLong()}.getOrDefault(0) } @@ -319,9 +418,9 @@ object PerformanceManager { if (!defaultSystemGovernor.isNullOrEmpty()) setAllCpuCoreGovernor(defaultSystemGovernor) if (isFanSupported && defautlSystemFanMode != null) - setFanMode(defautlSystemFanMode!!) + defautlSystemFanMode?.let { setFanMode(it) } if (defaultSystemPolicies.isNullOrEmpty()) return null - setCpuCorePolicies(defaultSystemPolicies!!, true) + defaultSystemPolicies?.let { setCpuCorePolicies(it, true) } return true } } \ No newline at end of file diff --git a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt index f8d10861c..db42fbe6d 100644 --- a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt +++ b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt @@ -461,6 +461,9 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( state.cpuBoostState.value = c.getExtra("cpuBoostState").toBoolean() state.cpuEditingMode.value = c.getExtra("cpuEditingMode").toBoolean() state.cpuFanMode.value = c.getExtra("cpuFanMode") + state.cpuTargetFPS.value = c.getExtra("cpuTargetFPS").toIntOrNull() ?: 0 + state.cpuAvgFrameCount.value = c.getExtra("cpuAvgFrameCount").toIntOrNull() ?: 3 + state.cpuAutoTargetFPS.value = c.getExtra("cpuAutoTargetFPS").toBoolean() val cpuPolicies = c.getExtra("cpuPolicies") if (cpuPolicies.isNotEmpty()) { val policies = PerformanceManager.parseRawPoliciesFromString(cpuPolicies) @@ -795,6 +798,9 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( c.putExtra("cpuEditingMode", state.cpuEditingMode.value.toString()) c.putExtra("cpuPolicies", PerformanceManager.policiesToString(state.cpuPolicies.value)) c.putExtra("cpuFanMode", state.cpuFanMode.value) + c.putExtra("cpuTargetFPS", state.cpuTargetFPS.value.toString()) + c.putExtra("cpuAvgFrameCount", state.cpuAvgFrameCount.value.toString()) + c.putExtra("cpuAutoTargetFPS", state.cpuAutoTargetFPS.value.toString()) } val midiSoundFontEntries = state.midiSoundFontEntries.value diff --git a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt index 1feae826d..664146f80 100644 --- a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt +++ b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt @@ -462,6 +462,9 @@ class ShortcutSettingsComposeDialog private constructor( state.cpuBoostState.value = shortcut.getExtra("cpuBoostState").toBoolean() state.cpuEditingMode.value = shortcut.getExtra("cpuEditingMode").toBoolean() state.cpuFanMode.value = shortcut.getExtra("cpuFanMode") + state.cpuTargetFPS.value = shortcut.getExtra("cpuTargetFPS").toIntOrNull() ?: 0 + state.cpuAvgFrameCount.value = shortcut.getExtra("cpuAvgFrameCount").toIntOrNull() ?: 3 + state.cpuAutoTargetFPS.value = shortcut.getExtra("cpuAutoTargetFPS").toBoolean() val cpuPolicies = shortcut.getExtra("cpuPolicies") if (cpuPolicies.isNotEmpty()) { @@ -479,6 +482,9 @@ class ShortcutSettingsComposeDialog private constructor( state.cpuBoostState.value = shortcut.container.getExtra("cpuBoostState").toBoolean() state.cpuEditingMode.value = shortcut.container.getExtra("cpuEditingMode").toBoolean() state.cpuFanMode.value = shortcut.container.getExtra("cpuFanMode") + state.cpuTargetFPS.value = shortcut.container.getExtra("cpuTargetFPS").toIntOrNull() ?: 0 + state.cpuAvgFrameCount.value = shortcut.container.getExtra("cpuAvgFrameCount").toIntOrNull() ?: 3 + state.cpuAutoTargetFPS.value = shortcut.container.getExtra("cpuAutoTargetFPS").toBoolean() if (containerPolicies.isNotEmpty()) { var policies = PerformanceManager.parseRawPoliciesFromString(containerPolicies) @@ -489,6 +495,9 @@ class ShortcutSettingsComposeDialog private constructor( shortcut.putExtra("cpuBoostState", state.cpuBoostState.value.toString()) shortcut.putExtra("cpuEditingMode", state.cpuEditingMode.value.toString()) shortcut.putExtra("cpuFanMode", state.cpuFanMode.value) + shortcut.putExtra("cpuTargetFPS", state.cpuTargetFPS.toString()) + shortcut.putExtra("cpuAvgFrameCount", state.cpuAvgFrameCount.toString()) + shortcut.putExtra("cpuAutoTargetFPS", state.cpuAutoTargetFPS.toString()) } else { policies = PerformanceManager.getDefaultSystemPolicies() @@ -1419,6 +1428,9 @@ class ShortcutSettingsComposeDialog private constructor( shortcut.putExtra("cpuEditingMode", state.cpuEditingMode.value.toString()) shortcut.putExtra("cpuPolicies", PerformanceManager.policiesToString(state.cpuPolicies.value)) shortcut.putExtra("cpuFanMode", state.cpuFanMode.value) + shortcut.putExtra("cpuTargetFPS", state.cpuTargetFPS.value.toString()) + shortcut.putExtra("cpuAvgFrameCount", state.cpuAvgFrameCount.value.toString()) + shortcut.putExtra("cpuAutoTargetFPS", state.cpuAutoTargetFPS.value.toString()) } // Container defaults flag diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ad8b99e37..228701461 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -328,6 +328,9 @@ CPU Governor Fan Control Power Controls + FPS Target + Frames per update. + %s Frames Exec Arguments diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index 6a81ec826..3712f757a 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -1836,6 +1836,7 @@ public void onModifyWindowProperty(Window window, Property property) { @Override public void onFramePresented(Window window, WindowManager.FrameSource source, int serial) { if (window != null && window.id == rendererWindowId) rendererWindowPresented = true; + PerformanceManager.INSTANCE.onTick(); if (shouldRecordFpsFrame(window, source)) { frameRating.recordGameFrame(source == WindowManager.FrameSource.PRESENT, serial); if (mangoHud != null) mangoHud.recordGameFrame(source == WindowManager.FrameSource.PRESENT); @@ -1992,7 +1993,7 @@ private void startPerformanceManager() { if (performanceManager.isDeviceSupported()) { if (shortcut.getExtra("cpuPolicies").isEmpty() && !container.getExtra("cpuPolicies").isEmpty()) { WinToast.show(this, "settings empty using container default"); - String[] fields = {"cpuPolicies", "cpuEditingMode", "cpuBoostState", "cpuFanMode", "cpuGovernor"}; + String[] fields = {"cpuPolicies", "cpuEditingMode", "cpuBoostState", "cpuFanMode", "cpuGovernor", "cpuAutoTargetFPS", "cpuTargetFPS", "cpuAvgFrameCount"}; for (String field : fields) shortcut.putExtra(field, container.getExtra(field)); } var cpuPolicies = shortcut.getExtra("cpuPolicies"); @@ -2003,6 +2004,29 @@ private void startPerformanceManager() { WinToast.show(this, String.format("CPU fan mode set: %s", cpuFanMode)); performanceManager.setFanMode(cpuFanMode); } + var cpuGovernor = shortcut.getExtra("cpuGovernor"); + if (!cpuGovernor.isEmpty()) { + WinToast.show(this, String.format("CPU governor set: %s", cpuGovernor)); + performanceManager.setAllCpuCoreGovernor(cpuGovernor); + } + if (Boolean.parseBoolean(shortcut.getExtra("cpuAutoTargetFPS"))) { + var cpuTargetFPS = shortcut.getExtra("cpuTargetFPS"); + var cpuAvgFrameCount = shortcut.getExtra("cpuAvgFrameCount"); + if (!cpuTargetFPS.isEmpty() && !cpuAvgFrameCount.isEmpty()) { + try { + var targetFPS = Integer.parseInt(cpuTargetFPS); + var frameCountPerCheck = Integer.parseInt(cpuAvgFrameCount); + + if (targetFPS > 0) { + performanceManager.setUseAutoTargeting(true); + performanceManager.setFpsAvgFrameCount(frameCountPerCheck); + performanceManager.setTargetFPS(targetFPS); + } + }catch (Exception ignored) {} + WinToast.show(this, "Auto FPS enabled"); + return; + } + } if (cpuEditingMode && !cpuPolicies.isEmpty()) { WinToast.show(this, "CPU policies set"); performanceManager.setCpuCorePolicies(cpuPolicies, false); @@ -2018,11 +2042,6 @@ private void startPerformanceManager() { }); } } - var cpuGovernor = shortcut.getExtra("cpuGovernor"); - if (!cpuGovernor.isEmpty()) { - WinToast.show(this, String.format("CPU governor set: %s", cpuGovernor)); - performanceManager.setAllCpuCoreGovernor(cpuGovernor); - } WinToast.show(this, String.format("disabled cores %s: ", performanceManager.getDisabledCores())); } } From d3e21a62fbb755d7957b36b4f781e23d3d1c369d Mon Sep 17 00:00:00 2001 From: Sad Moments Date: Tue, 28 Jul 2026 00:21:13 +0200 Subject: [PATCH 5/7] Add: gpu support and add gpu to auto tunning --- app/src/main/feature/library/GameSettings.kt | 18 +++++ .../main/feature/power/PerformanceManager.kt | 67 +++++++++++++++++-- .../ContainerSettingsComposeDialog.kt | 2 + .../ShortcutSettingsComposeDialog.kt | 4 ++ app/src/main/res/values/strings.xml | 2 + .../display/XServerDisplayActivity.java | 12 ++++ 6 files changed, 99 insertions(+), 6 deletions(-) diff --git a/app/src/main/feature/library/GameSettings.kt b/app/src/main/feature/library/GameSettings.kt index c6c4356d6..a704fb76f 100644 --- a/app/src/main/feature/library/GameSettings.kt +++ b/app/src/main/feature/library/GameSettings.kt @@ -586,6 +586,7 @@ class GameSettingsStateHolder { val cpuTargetFPS = mutableStateOf(0) val cpuAutoTargetFPS = mutableStateOf(false) val cpuAvgFrameCount = mutableStateOf(3) + val gpuFrequency = mutableStateOf(0) } interface GameSettingsCallbacks { @@ -1075,6 +1076,7 @@ private fun PerformanceControlSection( ) { val governors = PerformanceManager.allCpuGovernors val fanModes = PerformanceManager.getSupportedFanModes() + val gpuFrequencies = PerformanceManager.gpuFrequencies var fanModeIndex by remember { mutableStateOf(fanModes?.indexOf(state.cpuFanMode.value) ?: 0) } var governorIndex by remember { mutableStateOf(governors?.indexOf(state.cpuGovernor.value) ?: 0) } @@ -1251,6 +1253,22 @@ private fun PerformanceControlSection( state.cpuPolicies.value[index].minFrequency = frequencies[minIndex] state.cpuPolicies.value[index].maxFrequency = frequencies[maxIndex] } + if (!PerformanceManager.isGpuSupported || gpuFrequencies.isNullOrEmpty()) return@SettingGroup + Spacer(Modifier.height(SettingSectionGap)) + Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { + Box(Modifier.weight(1f)) { + SettingSlider( + label = stringResource(R.string.performance_gpu_frequencies_label), + value = state.gpuFrequency.value, + range = 0..gpuFrequencies.size-1, + valueText = "${gpuFrequencies[state.gpuFrequency.value].toString()}MHz", + steps = (0 - gpuFrequencies.size - 1).coerceAtLeast(0), + onValueChange = { + state.gpuFrequency.value = it + } + ) + } + } } } } diff --git a/app/src/main/feature/power/PerformanceManager.kt b/app/src/main/feature/power/PerformanceManager.kt index cd69b071f..6a3e6ac74 100644 --- a/app/src/main/feature/power/PerformanceManager.kt +++ b/app/src/main/feature/power/PerformanceManager.kt @@ -5,6 +5,7 @@ import com.winlator.cmod.shared.util.RootManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlin.let data class CpuPolicy( var enabled: Boolean, @@ -21,6 +22,7 @@ data class CpuPolicy( object PerformanceManager { private const val IDLE_TIMEOUT_NS = 1500000000L private const val CPU_PATH = "/sys/devices/system/cpu" + private const val GPU_PATH = "/sys/class/kgsl/kgsl-3d0" private const val CPU_BOOST_PATH = "/sys/devices/system/cpu/cpufreq/boost" private const val CPU_POLICY_BASE_PATH = "/sys/devices/system/cpu/cpufreq/" @@ -40,7 +42,9 @@ object PerformanceManager { val isFanSupported = checkForFanSupport() - val isGpuSupported = false + val isGpuSupported = checkForGpuSupport() + var gpuFrequencyIndex: Int? = 0 + var gpuFrequencies: List? = null var targetFPS: Float = 0f var fpsAvgFrameCount = 0 var useAutoTargeting: Boolean = false @@ -50,6 +54,19 @@ object PerformanceManager { private var autoTargetPolicies: List? = null private var fpsPastFrames: MutableList = mutableListOf() + + fun checkForGpuSupport(): Boolean { + if (!isDeviceSupported) return false + val gpuDirectorySymlink = File(GPU_PATH) + if (!gpuDirectorySymlink.isDirectory) return false + val frequencies = RootManager.readSysfsFile("${gpuDirectorySymlink.canonicalPath}/gpu_available_frequencies") + if (frequencies.isNullOrEmpty()) return false + val availableFrequencies = frequencies.split(" ").reversed().map { it -> runCatching { (it.toLong()/1_000_000).toInt() }.getOrDefault(0) } + if (availableFrequencies.size < 2 || availableFrequencies.contains(0)) return false + gpuFrequencies = availableFrequencies + return availableFrequencies.size >= 2 + } + fun checkForFanSupport(): Boolean { if (!isDeviceSupported) return false val response = RootManager.executeAsRoot("settings get system fan_mode") ?: return false @@ -190,17 +207,22 @@ object PerformanceManager { policy.maxFrequency = policy.availableFrequencies[policy.availableFrequencies.size-1] policy.minFrequency = policy.availableFrequencies[policy.availableFrequencies.size-1] } + gpuFrequencies?.let { + if (it.size > 2) + gpuFrequencyIndex = it.size-1 + } autoTargetSetup = true autoTargetPolicies = policies return } ticks++ + var fps = calculateFPS() + if (fps == 0f) return + fpsPastFrames.add(fps) + if (fpsPastFrames.size < fpsAvgFrameCount) return + fps = fpsPastFrames.sum() / fpsPastFrames.size + if (ticks%targetFPS==0f && !autoTargetPolicies.isNullOrEmpty()) { - var fps = calculateFPS() * targetFPS - if (fps == 0f) return - fpsPastFrames.add(fps) - if (fpsAvgFrameCount >= fpsPastFrames.size) return - fps = fpsPastFrames.sum() / fpsPastFrames.size var shouldUpdate = false if (fps > targetFPS*1.1) { for (policy in autoTargetPolicies) { @@ -230,6 +252,10 @@ object PerformanceManager { shouldUpdate = true } } + gpuFrequencyIndex?.let { + if (it > 0) + gpuFrequencyIndex = it-1 + } }else if (fps < targetFPS*0.98) { for (policy in autoTargetPolicies) { if (policy.availableFrequencies.isNullOrEmpty()) continue @@ -246,10 +272,19 @@ object PerformanceManager { shouldUpdate = true } } + gpuFrequencyIndex?.let { it -> + gpuFrequencies?.let { it1 -> + if (it+1 < it1.size) + gpuFrequencyIndex = it+1 + } + } } fpsPastFrames.clear() if (shouldUpdate) { CoroutineScope(Dispatchers.IO).launch { + gpuFrequencyIndex?.let { + setGpuFrequency(it) + } autoTargetPolicies?.let { setCpuCorePolicies(it) } @@ -274,6 +309,20 @@ object PerformanceManager { return onlineState == "1" } + fun setGpuFrequency(gpuIndex: Int, lockfile: Boolean=true): Boolean? { + if (!isDeviceSupported || !isGpuSupported) return null + if (gpuFrequencies.isNullOrEmpty()) return null + gpuFrequencies?.let { + if (gpuIndex < 0 || gpuIndex > it.size) return false + if (lockfile) gpuFrequencyIndex = gpuIndex + RootManager.writeSysfsFile("${GPU_PATH}/max_pwrlevel", "0", lockfile) + RootManager.writeSysfsFile("${GPU_PATH}/max_clock_mhz", it[gpuIndex].toString(), lockfile) + RootManager.writeSysfsFile("${GPU_PATH}/min_clock_mhz", it[gpuIndex].toString(), lockfile) + return true + } + return false + } + fun setCpuCoreGovernor(cpuCore: Int, governor: String): Boolean? { if (!isDeviceSupported) return null val governorPath = "${CPU_PATH}/cpu${cpuCore}/cpufreq/scaling_governor" @@ -421,6 +470,12 @@ object PerformanceManager { defautlSystemFanMode?.let { setFanMode(it) } if (defaultSystemPolicies.isNullOrEmpty()) return null defaultSystemPolicies?.let { setCpuCorePolicies(it, true) } + gpuFrequencies?.let { + if (it.size > 1) { + setGpuFrequency(0, false) + gpuFrequencyIndex = 0 + } + } return true } } \ No newline at end of file diff --git a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt index db42fbe6d..05bc48f3e 100644 --- a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt +++ b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt @@ -464,6 +464,7 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( state.cpuTargetFPS.value = c.getExtra("cpuTargetFPS").toIntOrNull() ?: 0 state.cpuAvgFrameCount.value = c.getExtra("cpuAvgFrameCount").toIntOrNull() ?: 3 state.cpuAutoTargetFPS.value = c.getExtra("cpuAutoTargetFPS").toBoolean() + state.gpuFrequency.value = c.getExtra("gpuFrequency").toIntOrNull() ?: 0 val cpuPolicies = c.getExtra("cpuPolicies") if (cpuPolicies.isNotEmpty()) { val policies = PerformanceManager.parseRawPoliciesFromString(cpuPolicies) @@ -801,6 +802,7 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( c.putExtra("cpuTargetFPS", state.cpuTargetFPS.value.toString()) c.putExtra("cpuAvgFrameCount", state.cpuAvgFrameCount.value.toString()) c.putExtra("cpuAutoTargetFPS", state.cpuAutoTargetFPS.value.toString()) + c.putExtra("gpuFrequency", state.gpuFrequency.value.toString()) } val midiSoundFontEntries = state.midiSoundFontEntries.value diff --git a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt index 664146f80..4e5d224d1 100644 --- a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt +++ b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt @@ -465,6 +465,7 @@ class ShortcutSettingsComposeDialog private constructor( state.cpuTargetFPS.value = shortcut.getExtra("cpuTargetFPS").toIntOrNull() ?: 0 state.cpuAvgFrameCount.value = shortcut.getExtra("cpuAvgFrameCount").toIntOrNull() ?: 3 state.cpuAutoTargetFPS.value = shortcut.getExtra("cpuAutoTargetFPS").toBoolean() + state.gpuFrequency.value = shortcut.getExtra("gpuFrequency").toIntOrNull() ?: 0 val cpuPolicies = shortcut.getExtra("cpuPolicies") if (cpuPolicies.isNotEmpty()) { @@ -485,6 +486,7 @@ class ShortcutSettingsComposeDialog private constructor( state.cpuTargetFPS.value = shortcut.container.getExtra("cpuTargetFPS").toIntOrNull() ?: 0 state.cpuAvgFrameCount.value = shortcut.container.getExtra("cpuAvgFrameCount").toIntOrNull() ?: 3 state.cpuAutoTargetFPS.value = shortcut.container.getExtra("cpuAutoTargetFPS").toBoolean() + state.gpuFrequency.value = shortcut.container.getExtra("gpuFrequency").toIntOrNull() ?: 0 if (containerPolicies.isNotEmpty()) { var policies = PerformanceManager.parseRawPoliciesFromString(containerPolicies) @@ -498,6 +500,7 @@ class ShortcutSettingsComposeDialog private constructor( shortcut.putExtra("cpuTargetFPS", state.cpuTargetFPS.toString()) shortcut.putExtra("cpuAvgFrameCount", state.cpuAvgFrameCount.toString()) shortcut.putExtra("cpuAutoTargetFPS", state.cpuAutoTargetFPS.toString()) + shortcut.putExtra("gpuFrequency", state.gpuFrequency.toString()) } else { policies = PerformanceManager.getDefaultSystemPolicies() @@ -1431,6 +1434,7 @@ class ShortcutSettingsComposeDialog private constructor( shortcut.putExtra("cpuTargetFPS", state.cpuTargetFPS.value.toString()) shortcut.putExtra("cpuAvgFrameCount", state.cpuAvgFrameCount.value.toString()) shortcut.putExtra("cpuAutoTargetFPS", state.cpuAutoTargetFPS.value.toString()) + shortcut.putExtra("gpuFrequency", state.gpuFrequency.value.toString()) } // Container defaults flag diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 228701461..293f996c7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -331,6 +331,8 @@ FPS Target Frames per update. %s Frames + GPU Frequency + Exec Arguments diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index 3712f757a..6c58c10bc 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -2000,6 +2000,7 @@ private void startPerformanceManager() { var cpuEditingMode = Boolean.parseBoolean(shortcut.getExtra("cpuEditingMode")); var cpuBoostState = Boolean.parseBoolean(shortcut.getExtra("cpuBoostState")); var cpuFanMode = shortcut.getExtra("cpuFanMode"); + var gpuFrequency = shortcut.getExtra("gpuFrequency"); if (!cpuFanMode.isEmpty()) { WinToast.show(this, String.format("CPU fan mode set: %s", cpuFanMode)); performanceManager.setFanMode(cpuFanMode); @@ -2043,6 +2044,17 @@ private void startPerformanceManager() { } } WinToast.show(this, String.format("disabled cores %s: ", performanceManager.getDisabledCores())); + if (!gpuFrequency.isEmpty()) { + try { + var gpuIndex = Integer.parseInt(gpuFrequency); + performanceManager.setGpuFrequency(gpuIndex, true); + var freqs = performanceManager.getGpuFrequencies(); + if (freqs != null) { + WinToast.show(this, String.format("GPU SET: %s", freqs.get(gpuIndex))); + } + + } catch (Exception ignored) {} + } } } From 9b99fb8bf058b0510296b599b684f0ad03c7dcf8 Mon Sep 17 00:00:00 2001 From: Sad Moments Date: Wed, 29 Jul 2026 04:57:51 +0200 Subject: [PATCH 6/7] Add auto gpu tunning and clean up --- app/src/main/feature/library/GameSettings.kt | 11 +- .../main/feature/power/PerformanceManager.kt | 131 ++++++++++-------- .../display/XServerDisplayActivity.java | 6 +- 3 files changed, 82 insertions(+), 66 deletions(-) diff --git a/app/src/main/feature/library/GameSettings.kt b/app/src/main/feature/library/GameSettings.kt index a704fb76f..b1409c6fa 100644 --- a/app/src/main/feature/library/GameSettings.kt +++ b/app/src/main/feature/library/GameSettings.kt @@ -1253,18 +1253,17 @@ private fun PerformanceControlSection( state.cpuPolicies.value[index].minFrequency = frequencies[minIndex] state.cpuPolicies.value[index].maxFrequency = frequencies[maxIndex] } - if (!PerformanceManager.isGpuSupported || gpuFrequencies.isNullOrEmpty()) return@SettingGroup - Spacer(Modifier.height(SettingSectionGap)) + if (!PerformanceManager.isGpuSupported || gpuFrequencies.isEmpty()) return@SettingGroup Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { Box(Modifier.weight(1f)) { SettingSlider( label = stringResource(R.string.performance_gpu_frequencies_label), - value = state.gpuFrequency.value, - range = 0..gpuFrequencies.size-1, - valueText = "${gpuFrequencies[state.gpuFrequency.value].toString()}MHz", + value = gpuFrequencies.indexOf(state.gpuFrequency.value), + range = gpuFrequencies.indices, + valueText = "${state.gpuFrequency.value}MHz", steps = (0 - gpuFrequencies.size - 1).coerceAtLeast(0), onValueChange = { - state.gpuFrequency.value = it + state.gpuFrequency.value = gpuFrequencies[it] } ) } diff --git a/app/src/main/feature/power/PerformanceManager.kt b/app/src/main/feature/power/PerformanceManager.kt index 6a3e6ac74..76524e2b7 100644 --- a/app/src/main/feature/power/PerformanceManager.kt +++ b/app/src/main/feature/power/PerformanceManager.kt @@ -23,11 +23,15 @@ object PerformanceManager { private const val IDLE_TIMEOUT_NS = 1500000000L private const val CPU_PATH = "/sys/devices/system/cpu" private const val GPU_PATH = "/sys/class/kgsl/kgsl-3d0" + private const val POLICY_PATH = "/sys/devices/system/cpu/cpufreq" private const val CPU_BOOST_PATH = "/sys/devices/system/cpu/cpufreq/boost" private const val CPU_POLICY_BASE_PATH = "/sys/devices/system/cpu/cpufreq/" - private var defaultSystemPolicies: List? = null + val isDeviceSupported + get() = RootManager.isRooted + private val defaultSystemGovernor = getCpuGovernor() + private var defaultSystemPolicies: List? = getCpuPolicies() private var defautlSystemFanMode: Int? = null @@ -37,14 +41,11 @@ object PerformanceManager { val disabledCores = mutableListOf() - val isDeviceSupported - get() = RootManager.isRooted - val isFanSupported = checkForFanSupport() + var gpuFrequencyIndex: Int = 0 + var gpuFrequencies: MutableList = mutableListOf() val isGpuSupported = checkForGpuSupport() - var gpuFrequencyIndex: Int? = 0 - var gpuFrequencies: List? = null var targetFPS: Float = 0f var fpsAvgFrameCount = 0 var useAutoTargeting: Boolean = false @@ -61,10 +62,13 @@ object PerformanceManager { if (!gpuDirectorySymlink.isDirectory) return false val frequencies = RootManager.readSysfsFile("${gpuDirectorySymlink.canonicalPath}/gpu_available_frequencies") if (frequencies.isNullOrEmpty()) return false - val availableFrequencies = frequencies.split(" ").reversed().map { it -> runCatching { (it.toLong()/1_000_000).toInt() }.getOrDefault(0) } - if (availableFrequencies.size < 2 || availableFrequencies.contains(0)) return false - gpuFrequencies = availableFrequencies - return availableFrequencies.size >= 2 + frequencies.split(" ").reversed().forEach { it -> + runCatching { + val freq = (it.toLong() / 1_000_000).toInt() + gpuFrequencies.add(freq) + }.getOrDefault(0) + } + return gpuFrequencies.size >= 2 } fun checkForFanSupport(): Boolean { @@ -198,7 +202,8 @@ object PerformanceManager { fun onTick() { if (!isDeviceSupported || targetFPS == 0f || !useAutoTargeting) return - if (!autoTargetSetup) { + ticks++ + if (ticks == 1L) { val policies = getCpuPolicies() ?: return for (policy in policies) { policy.enabled = true @@ -207,22 +212,20 @@ object PerformanceManager { policy.maxFrequency = policy.availableFrequencies[policy.availableFrequencies.size-1] policy.minFrequency = policy.availableFrequencies[policy.availableFrequencies.size-1] } - gpuFrequencies?.let { - if (it.size > 2) - gpuFrequencyIndex = it.size-1 - } - autoTargetSetup = true + if (isGpuSupported && gpuFrequencies.isNotEmpty() && gpuFrequencies.size > 2) + setGpuFrequency(gpuFrequencies.last(), gpuFrequencies.last()) autoTargetPolicies = policies + setCpuCorePolicies(policies) return } - ticks++ + var fps = calculateFPS() if (fps == 0f) return - fpsPastFrames.add(fps) - if (fpsPastFrames.size < fpsAvgFrameCount) return - fps = fpsPastFrames.sum() / fpsPastFrames.size if (ticks%targetFPS==0f && !autoTargetPolicies.isNullOrEmpty()) { + fpsPastFrames.add(fps) + if (fpsPastFrames.size < fpsAvgFrameCount) return + fps = fpsPastFrames.sum() / fpsPastFrames.size var shouldUpdate = false if (fps > targetFPS*1.1) { for (policy in autoTargetPolicies) { @@ -252,10 +255,9 @@ object PerformanceManager { shouldUpdate = true } } - gpuFrequencyIndex?.let { - if (it > 0) - gpuFrequencyIndex = it-1 - } + if (isGpuSupported && gpuFrequencyIndex > 1) + gpuFrequencyIndex -= 1 + }else if (fps < targetFPS*0.98) { for (policy in autoTargetPolicies) { if (policy.availableFrequencies.isNullOrEmpty()) continue @@ -272,19 +274,14 @@ object PerformanceManager { shouldUpdate = true } } - gpuFrequencyIndex?.let { it -> - gpuFrequencies?.let { it1 -> - if (it+1 < it1.size) - gpuFrequencyIndex = it+1 - } - } + if (isGpuSupported && gpuFrequencyIndex+1 < gpuFrequencies.size) + gpuFrequencyIndex+=1 } fpsPastFrames.clear() if (shouldUpdate) { CoroutineScope(Dispatchers.IO).launch { - gpuFrequencyIndex?.let { - setGpuFrequency(it) - } + if (isGpuSupported && gpuFrequencies.isNotEmpty()) + setGpuFrequency(gpuFrequencies[gpuFrequencyIndex-1], gpuFrequencies[gpuFrequencyIndex]) autoTargetPolicies?.let { setCpuCorePolicies(it) } @@ -309,18 +306,15 @@ object PerformanceManager { return onlineState == "1" } - fun setGpuFrequency(gpuIndex: Int, lockfile: Boolean=true): Boolean? { + fun setGpuFrequency(gpuMin: Int, gpuMax: Int, lockfile: Boolean=true): Boolean? { if (!isDeviceSupported || !isGpuSupported) return null - if (gpuFrequencies.isNullOrEmpty()) return null - gpuFrequencies?.let { - if (gpuIndex < 0 || gpuIndex > it.size) return false - if (lockfile) gpuFrequencyIndex = gpuIndex - RootManager.writeSysfsFile("${GPU_PATH}/max_pwrlevel", "0", lockfile) - RootManager.writeSysfsFile("${GPU_PATH}/max_clock_mhz", it[gpuIndex].toString(), lockfile) - RootManager.writeSysfsFile("${GPU_PATH}/min_clock_mhz", it[gpuIndex].toString(), lockfile) - return true - } - return false + if (gpuFrequencies.isEmpty() || gpuFrequencies.size < 2) return null + if (!gpuFrequencies.contains(gpuMin) || !gpuFrequencies.contains(gpuMax)) return false + if (lockfile) gpuFrequencyIndex = gpuFrequencies.indexOf(gpuMax) + RootManager.writeSysfsFile("${GPU_PATH}/max_pwrlevel", "0", lockfile) + RootManager.writeSysfsFile("${GPU_PATH}/max_clock_mhz", gpuMax.toString(), lockfile) + RootManager.writeSysfsFile("${GPU_PATH}/min_clock_mhz", gpuMin.toString(), lockfile) + return true } fun setCpuCoreGovernor(cpuCore: Int, governor: String): Boolean? { @@ -354,6 +348,24 @@ object PerformanceManager { return true } + fun setCpuCoresOnlineState(cpuCores: List, state: Boolean): Boolean? { + if (!isDeviceSupported) return null + if (cpuCores.isEmpty()) return false + for (cpuCore in cpuCores) { + val response = RootManager.writeSysfsFile( + "${CPU_PATH}/cpu${cpuCore}/online", + if (state) "1" else "0" + ) + if (response) { + if (state) + disabledCores.remove(cpuCore) + else + disabledCores.add(cpuCore) + } + } + return true + } + fun setPolicyOnlineState(policy: CpuPolicy, state: Boolean): Boolean? { if (!isDeviceSupported) return null if (!policy.cpuCores.isNotEmpty()) return null @@ -369,19 +381,25 @@ object PerformanceManager { return true } + fun setCpuPolicyFrequency(policyPath: String, minFrequency: Long, maxFrequency: Long): Boolean? { + if (!isDeviceSupported) return null + if (!RootManager.writeSysfsFile("${policyPath}/scaling_min_freq", minFrequency.toString())) return false + if (!RootManager.writeSysfsFile("${policyPath}/scaling_max_freq", maxFrequency.toString())) return false + return true + } + fun setCpuCorePolicies(cpuCorePolicies: List, setBoostFrequency: Boolean=false): Boolean? { if (!isDeviceSupported) return null for (cpuPolicy in cpuCorePolicies) { setCpuBoostState(cpuPolicy.boostState) - for (cpuCore in cpuPolicy.cpuCores) { - if (cpuPolicy.enabled) - if (setBoostFrequency && cpuPolicy.boostState && cpuPolicy.boostFrequency != null && cpuPolicy.boostFrequency.toInt() != 0) - setCpuCoreFrequency(cpuCore, cpuPolicy.minFrequency, cpuPolicy.boostFrequency) - else - setCpuCoreFrequency(cpuCore, cpuPolicy.minFrequency, cpuPolicy.maxFrequency) + if (cpuPolicy.enabled) { + setCpuCoresOnlineState(cpuPolicy.cpuCores, true) + if (setBoostFrequency && cpuPolicy.boostState && cpuPolicy.boostFrequency != null && cpuPolicy.boostFrequency.toInt() != 0) + setCpuPolicyFrequency("${POLICY_PATH}/${cpuPolicy.policyName}", cpuPolicy.minFrequency, cpuPolicy.boostFrequency) else - setCpuCoreOnlineState(cpuCore, false) - } + setCpuPolicyFrequency("${POLICY_PATH}/${cpuPolicy.policyName}", cpuPolicy.minFrequency, cpuPolicy.maxFrequency) + } else + setCpuCoresOnlineState(cpuPolicy.cpuCores, false) } return true } @@ -470,11 +488,10 @@ object PerformanceManager { defautlSystemFanMode?.let { setFanMode(it) } if (defaultSystemPolicies.isNullOrEmpty()) return null defaultSystemPolicies?.let { setCpuCorePolicies(it, true) } - gpuFrequencies?.let { - if (it.size > 1) { - setGpuFrequency(0, false) - gpuFrequencyIndex = 0 - } + if (isGpuSupported && gpuFrequencies.isNotEmpty()) { + setGpuFrequency(gpuFrequencies.first(), gpuFrequencies.last(), false) + gpuFrequencyIndex = 0 + ticks = 0 } return true } diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index 6c58c10bc..a0b9efa07 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -2046,11 +2046,11 @@ private void startPerformanceManager() { WinToast.show(this, String.format("disabled cores %s: ", performanceManager.getDisabledCores())); if (!gpuFrequency.isEmpty()) { try { - var gpuIndex = Integer.parseInt(gpuFrequency); - performanceManager.setGpuFrequency(gpuIndex, true); + var gpuFreq = Integer.parseInt(gpuFrequency); + performanceManager.setGpuFrequency(gpuFreq, gpuFreq,true); var freqs = performanceManager.getGpuFrequencies(); if (freqs != null) { - WinToast.show(this, String.format("GPU SET: %s", freqs.get(gpuIndex))); + WinToast.show(this, String.format("GPU SET: %s", freqs.get(gpuFreq))); } } catch (Exception ignored) {} From 0707cb027d5b36834afef749cf5caad17bf1e0b0 Mon Sep 17 00:00:00 2001 From: Sad Moments Date: Thu, 30 Jul 2026 05:03:31 +0200 Subject: [PATCH 7/7] Add system performance dropdown --- app/src/main/feature/library/GameSettings.kt | 67 ++++++---- .../main/feature/power/PerformanceManager.kt | 122 ++++++++++++------ .../ContainerSettingsComposeDialog.kt | 3 + .../ShortcutSettingsComposeDialog.kt | 5 + app/src/main/res/values/strings.xml | 1 + .../display/XServerDisplayActivity.java | 7 +- 6 files changed, 144 insertions(+), 61 deletions(-) diff --git a/app/src/main/feature/library/GameSettings.kt b/app/src/main/feature/library/GameSettings.kt index b1409c6fa..0d76293a7 100644 --- a/app/src/main/feature/library/GameSettings.kt +++ b/app/src/main/feature/library/GameSettings.kt @@ -587,6 +587,7 @@ class GameSettingsStateHolder { val cpuAutoTargetFPS = mutableStateOf(false) val cpuAvgFrameCount = mutableStateOf(3) val gpuFrequency = mutableStateOf(0) + val performanceMode = mutableStateOf("") } interface GameSettingsCallbacks { @@ -1074,8 +1075,9 @@ private fun PerformanceControlSection( state: GameSettingsStateHolder, callbacks: GameSettingsCallbacks ) { - val governors = PerformanceManager.allCpuGovernors + val governors = PerformanceManager.getCpuGovernors() val fanModes = PerformanceManager.getSupportedFanModes() + val performanceModes = PerformanceManager.getSupportedPerformanceModes() val gpuFrequencies = PerformanceManager.gpuFrequencies var fanModeIndex by remember { mutableStateOf(fanModes?.indexOf(state.cpuFanMode.value) ?: 0) } var governorIndex by remember { mutableStateOf(governors?.indexOf(state.cpuGovernor.value) ?: 0) } @@ -1085,37 +1087,54 @@ private fun PerformanceControlSection( if (!policies.isNullOrEmpty()) state.cpuPolicies.value = policies } - if (!governors.isNullOrEmpty()) { - SettingGroup { - Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { + SettingGroup { + Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { + if (!governors.isNullOrEmpty()) { Box(Modifier.weight(1f)) { SettingDropdown( label = stringResource(R.string.performance_cpu_governor_label), entries = governors, - selectedIndex = governorIndex, + selectedIndex = governors.indexOf(state.cpuGovernor.value), onSelected = { - governorIndex = it state.cpuGovernor.value = governors[it] } ) } - if (PerformanceManager.isFanSupported && !fanModes.isNullOrEmpty()) { - Box(Modifier.weight(1f)) { - SettingDropdown( - label = stringResource(R.string.performance_cpu_fan_label), - entries = fanModes, - selectedIndex = fanModeIndex, - onSelected = { - fanModeIndex = it - state.cpuFanMode.value = fanModes[it] - } - ) - } + } + + if (!performanceModes.isNullOrEmpty()) { + Box(Modifier.weight(1f)) { + SettingDropdown( + label = stringResource(R.string.performance_system_performance_label), + entries = performanceModes, + selectedIndex = performanceModes.indexOf(state.performanceMode.value), + onSelected = { + state.performanceMode.value = performanceModes[it] + } + ) + } + } + } + } + Spacer(Modifier.height(SettingSectionGap)) + + SettingGroup { + Row(horizontalArrangement = Arrangement.spacedBy(SettingItemGap)) { + if (PerformanceManager.isFanSupported && !fanModes.isNullOrEmpty()) { + Box(Modifier.weight(1f)) { + SettingDropdown( + label = stringResource(R.string.performance_cpu_fan_label), + entries = fanModes, + selectedIndex = fanModes.indexOf(state.cpuFanMode.value), + onSelected = { + state.cpuFanMode.value = fanModes[it] + } + ) } } } - Spacer(Modifier.height(SettingSectionGap)) } + Spacer(Modifier.height(SettingSectionGap)) SettingGroup { Column { @@ -1145,7 +1164,11 @@ private fun PerformanceControlSection( SettingCheckbox( "AutoFPS", state.cpuAutoTargetFPS.value, - onCheckedChange = { state.cpuAutoTargetFPS.value = it }) + onCheckedChange = { + if (state.cpuTargetFPS.value == 0) + state.cpuTargetFPS.value = 60 + state.cpuAutoTargetFPS.value = it + }) } } if (state.cpuAutoTargetFPS.value) { @@ -1180,9 +1203,9 @@ private fun PerformanceControlSection( SettingSlider( label = stringResource(R.string.performance_frames_per_check_label), value = state.cpuAvgFrameCount.value, - range = 1..5, + range = 1..10, valueText = stringResource(R.string.performance_frames_label, state.cpuAvgFrameCount.value), - steps = (0 - 5 - 1).coerceAtLeast(0), + steps = (0 - 10 - 1).coerceAtLeast(0), onValueChange = { state.cpuAvgFrameCount.value = it } diff --git a/app/src/main/feature/power/PerformanceManager.kt b/app/src/main/feature/power/PerformanceManager.kt index 76524e2b7..5ce92f38f 100644 --- a/app/src/main/feature/power/PerformanceManager.kt +++ b/app/src/main/feature/power/PerformanceManager.kt @@ -19,6 +19,14 @@ data class CpuPolicy( val availableFrequencies: MutableList? ) +data class SystemInformation( + var fanMode: Int?, + var cpuGovernor: String?, + var gpuFrequency: Int?, + var policies: List?, + var performanceMode: Int? +) + object PerformanceManager { private const val IDLE_TIMEOUT_NS = 1500000000L private const val CPU_PATH = "/sys/devices/system/cpu" @@ -26,35 +34,27 @@ object PerformanceManager { private const val POLICY_PATH = "/sys/devices/system/cpu/cpufreq" private const val CPU_BOOST_PATH = "/sys/devices/system/cpu/cpufreq/boost" private const val CPU_POLICY_BASE_PATH = "/sys/devices/system/cpu/cpufreq/" + private val FAN_MODES = mapOf("QUIET" to 1, "SMART" to 4, "SPORT" to 5, "CUSTOM" to 6) + private val PERFORMANCE_MODES = mapOf("Standard" to 0, "Performance" to 1, "High Performance" to 2) val isDeviceSupported get() = RootManager.isRooted - private val defaultSystemGovernor = getCpuGovernor() - private var defaultSystemPolicies: List? = getCpuPolicies() - - private var defautlSystemFanMode: Int? = null - - private val FAN_MODES = mapOf("QUIET" to 1, "SMART" to 4, "SPORT" to 5, "CUSTOM" to 6) - - val allCpuGovernors = getCpuGovernors() - - val disabledCores = mutableListOf() - - val isFanSupported = checkForFanSupport() - - var gpuFrequencyIndex: Int = 0 - var gpuFrequencies: MutableList = mutableListOf() - val isGpuSupported = checkForGpuSupport() var targetFPS: Float = 0f var fpsAvgFrameCount = 0 + var gpuFrequencyIndex: Int = 0 + var gpuFrequencies: MutableList = mutableListOf() var useAutoTargeting: Boolean = false + private var ticks: Long = 0L private var fpsLastFrameNano = 0L - private var autoTargetSetup: Boolean = false - private var autoTargetPolicies: List? = null private var fpsPastFrames: MutableList = mutableListOf() + private var autoTargetPolicies: List? = null + val disabledCores = mutableListOf() + val isFanSupported = checkForFanSupport() + val isGpuSupported = checkForGpuSupport() + private val defaultSystemInfo: SystemInformation? = getSystemInformation() fun checkForGpuSupport(): Boolean { if (!isDeviceSupported) return false @@ -74,9 +74,7 @@ object PerformanceManager { fun checkForFanSupport(): Boolean { if (!isDeviceSupported) return false val response = RootManager.executeAsRoot("settings get system fan_mode") ?: return false - val fanMode = response.toIntOrNull() - defautlSystemFanMode = fanMode - return fanMode != null + return response.toIntOrNull() != null } fun checkForPossibleCrash() { @@ -110,7 +108,42 @@ object PerformanceManager { } } if (resetDefaultSystemPolicies) - defaultSystemPolicies = getCpuPolicies() + defaultSystemInfo?.policies = getCpuPolicies() + } + + fun getSystemInformation(): SystemInformation? { + if (!isDeviceSupported) return null + if (defaultSystemInfo != null) return defaultSystemInfo + return SystemInformation( + fanMode = getFanMode(), + cpuGovernor = getCpuGovernor(), + gpuFrequency = getGpuFrequency(), + policies = getCpuPolicies(), + performanceMode = getPerformanceMode() + ) + } + + fun getPerformanceMode(): Int? { + if (!isDeviceSupported) return null + val response = RootManager.executeAsRoot("settings get system performance_mode") ?: return null + return response.toIntOrNull() + } + + fun getGpuFrequency(): Int? { + if (!isDeviceSupported || !isGpuSupported) return null + val response = RootManager.readSysfsFile("${GPU_PATH}/max_clock_mhz") ?: return null + return response.toIntOrNull() + } + + fun getFanMode(): Int? { + if (!isDeviceSupported || !isFanSupported) return null + val response = RootManager.executeAsRoot("settings get system fan_mode") ?: return null + return response.toIntOrNull() + } + + fun getSupportedPerformanceModes(): List? { + if (!isDeviceSupported || !isGpuSupported) return null + return PERFORMANCE_MODES.map { it.key } } fun getSupportedFanModes(): List? { @@ -120,7 +153,7 @@ object PerformanceManager { fun getDefaultSystemPolicies(): List? { if (!isDeviceSupported) return null - return defaultSystemPolicies + return defaultSystemInfo?.policies } fun getCpuBoostState(): Boolean? { @@ -203,17 +236,17 @@ object PerformanceManager { fun onTick() { if (!isDeviceSupported || targetFPS == 0f || !useAutoTargeting) return ticks++ - if (ticks == 1L) { + if (ticks.toFloat() == targetFPS*60f) { val policies = getCpuPolicies() ?: return for (policy in policies) { policy.enabled = true policy.boostState = true if (policy.availableFrequencies.isNullOrEmpty()) continue - policy.maxFrequency = policy.availableFrequencies[policy.availableFrequencies.size-1] - policy.minFrequency = policy.availableFrequencies[policy.availableFrequencies.size-1] + policy.maxFrequency = policy.availableFrequencies.first() + policy.minFrequency = policy.availableFrequencies.first() } if (isGpuSupported && gpuFrequencies.isNotEmpty() && gpuFrequencies.size > 2) - setGpuFrequency(gpuFrequencies.last(), gpuFrequencies.last()) + setGpuFrequency(gpuFrequencies.first(), gpuFrequencies.first()) autoTargetPolicies = policies setCpuCorePolicies(policies) return @@ -419,6 +452,17 @@ object PerformanceManager { return RootManager.executeAsRoot("settings put system fan_mode ${mode}") != null } + fun setPerformanceMode(performanceMode: Int): Boolean? { + if (!isDeviceSupported || !isGpuSupported) return null + return RootManager.executeAsRoot("settings put system performance_mode ${performanceMode}") != null + } + + fun setPerformanceMode(performanceMode: String): Boolean? { + if (!isDeviceSupported || !isGpuSupported) return null + val mode = PERFORMANCE_MODES[performanceMode] ?: return null + return RootManager.executeAsRoot("settings put system performance_mode ${mode}") != null + } + fun setFanMode(fanMode: Int): Boolean? { if (!isDeviceSupported || !isFanSupported) return null if (!FAN_MODES.containsValue(fanMode)) return null @@ -471,6 +515,15 @@ object PerformanceManager { return stringPolicies } + fun setSystemInformation(systemInfo: SystemInformation, lockfile: Boolean=true, reset: Boolean=false) { + if (!isDeviceSupported) return + systemInfo.fanMode?.let { setFanMode(it) } + systemInfo.cpuGovernor?.let { setAllCpuCoreGovernor(it) } + systemInfo.gpuFrequency?.let { setGpuFrequency(gpuFrequencies.first(), it, lockfile) } + systemInfo.policies?.let { setCpuCorePolicies(it, reset) } + systemInfo.performanceMode?.let { setPerformanceMode(it) } + } + fun formatFrequency(valueKhz: Int, boosted: Boolean = false): String { val base = when { valueKhz >= 1_000_000 -> String.format("%.2f GHz", valueKhz / 1_000_000f) @@ -482,17 +535,10 @@ object PerformanceManager { fun resetSystemToDefault(): Boolean? { if (!isDeviceSupported) return null - if (!defaultSystemGovernor.isNullOrEmpty()) - setAllCpuCoreGovernor(defaultSystemGovernor) - if (isFanSupported && defautlSystemFanMode != null) - defautlSystemFanMode?.let { setFanMode(it) } - if (defaultSystemPolicies.isNullOrEmpty()) return null - defaultSystemPolicies?.let { setCpuCorePolicies(it, true) } - if (isGpuSupported && gpuFrequencies.isNotEmpty()) { - setGpuFrequency(gpuFrequencies.first(), gpuFrequencies.last(), false) - gpuFrequencyIndex = 0 - ticks = 0 - } + ticks = 0 + gpuFrequencyIndex = 0 + autoTargetPolicies = null + defaultSystemInfo?.let { setSystemInformation(it, false, true) } return true } } \ No newline at end of file diff --git a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt index 05bc48f3e..b95b49ace 100644 --- a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt +++ b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt @@ -465,6 +465,8 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( state.cpuAvgFrameCount.value = c.getExtra("cpuAvgFrameCount").toIntOrNull() ?: 3 state.cpuAutoTargetFPS.value = c.getExtra("cpuAutoTargetFPS").toBoolean() state.gpuFrequency.value = c.getExtra("gpuFrequency").toIntOrNull() ?: 0 + state.performanceMode.value = c.getExtra("performanceMode") + val cpuPolicies = c.getExtra("cpuPolicies") if (cpuPolicies.isNotEmpty()) { val policies = PerformanceManager.parseRawPoliciesFromString(cpuPolicies) @@ -803,6 +805,7 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( c.putExtra("cpuAvgFrameCount", state.cpuAvgFrameCount.value.toString()) c.putExtra("cpuAutoTargetFPS", state.cpuAutoTargetFPS.value.toString()) c.putExtra("gpuFrequency", state.gpuFrequency.value.toString()) + c.putExtra("performanceMode", state.performanceMode.value) } val midiSoundFontEntries = state.midiSoundFontEntries.value diff --git a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt index 4e5d224d1..b0cdaa8ef 100644 --- a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt +++ b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt @@ -466,6 +466,8 @@ class ShortcutSettingsComposeDialog private constructor( state.cpuAvgFrameCount.value = shortcut.getExtra("cpuAvgFrameCount").toIntOrNull() ?: 3 state.cpuAutoTargetFPS.value = shortcut.getExtra("cpuAutoTargetFPS").toBoolean() state.gpuFrequency.value = shortcut.getExtra("gpuFrequency").toIntOrNull() ?: 0 + state.gpuFrequency.value = shortcut.getExtra("gpuFrequency").toIntOrNull() ?: 0 + state.performanceMode.value = shortcut.getExtra("performanceMode") val cpuPolicies = shortcut.getExtra("cpuPolicies") if (cpuPolicies.isNotEmpty()) { @@ -487,6 +489,7 @@ class ShortcutSettingsComposeDialog private constructor( state.cpuAvgFrameCount.value = shortcut.container.getExtra("cpuAvgFrameCount").toIntOrNull() ?: 3 state.cpuAutoTargetFPS.value = shortcut.container.getExtra("cpuAutoTargetFPS").toBoolean() state.gpuFrequency.value = shortcut.container.getExtra("gpuFrequency").toIntOrNull() ?: 0 + state.performanceMode.value = shortcut.container.getExtra("performanceMode") if (containerPolicies.isNotEmpty()) { var policies = PerformanceManager.parseRawPoliciesFromString(containerPolicies) @@ -501,6 +504,7 @@ class ShortcutSettingsComposeDialog private constructor( shortcut.putExtra("cpuAvgFrameCount", state.cpuAvgFrameCount.toString()) shortcut.putExtra("cpuAutoTargetFPS", state.cpuAutoTargetFPS.toString()) shortcut.putExtra("gpuFrequency", state.gpuFrequency.toString()) + shortcut.putExtra("performanceMode", state.performanceMode.value) } else { policies = PerformanceManager.getDefaultSystemPolicies() @@ -1435,6 +1439,7 @@ class ShortcutSettingsComposeDialog private constructor( shortcut.putExtra("cpuAvgFrameCount", state.cpuAvgFrameCount.value.toString()) shortcut.putExtra("cpuAutoTargetFPS", state.cpuAutoTargetFPS.value.toString()) shortcut.putExtra("gpuFrequency", state.gpuFrequency.value.toString()) + shortcut.putExtra("performanceMode", state.performanceMode.value) } // Container defaults flag diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 293f996c7..89368dd2c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -326,6 +326,7 @@ Cluster %s Min Cluster %s Max CPU Governor + Performance Fan Control Power Controls FPS Target diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index a0b9efa07..64e09d5f0 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -2001,15 +2001,20 @@ private void startPerformanceManager() { var cpuBoostState = Boolean.parseBoolean(shortcut.getExtra("cpuBoostState")); var cpuFanMode = shortcut.getExtra("cpuFanMode"); var gpuFrequency = shortcut.getExtra("gpuFrequency"); + var performanceMode = shortcut.getExtra("performanceMode"); + var cpuGovernor = shortcut.getExtra("cpuGovernor"); if (!cpuFanMode.isEmpty()) { WinToast.show(this, String.format("CPU fan mode set: %s", cpuFanMode)); performanceManager.setFanMode(cpuFanMode); } - var cpuGovernor = shortcut.getExtra("cpuGovernor"); if (!cpuGovernor.isEmpty()) { WinToast.show(this, String.format("CPU governor set: %s", cpuGovernor)); performanceManager.setAllCpuCoreGovernor(cpuGovernor); } + if (!performanceMode.isEmpty()) { + WinToast.show(this, String.format("Performance mode set: %s", performanceMode)); + performanceManager.setPerformanceMode(performanceMode); + } if (Boolean.parseBoolean(shortcut.getExtra("cpuAutoTargetFPS"))) { var cpuTargetFPS = shortcut.getExtra("cpuTargetFPS"); var cpuAvgFrameCount = shortcut.getExtra("cpuAvgFrameCount");