mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-08 17:11:46 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d51395ec7 |
No files matched your search
@@ -347,6 +347,8 @@ dependencies {
|
||||
implementation 'com.github.luben:zstd-jni:1.5.7-4@aar'
|
||||
implementation 'org.apache.commons:commons-compress:1.27.1'
|
||||
implementation 'org.tukaani:xz:1.10'
|
||||
// dx (Apache-2.0) turns the Samsung performance SDK a user imports into dex on the phone (SamsungPerf).
|
||||
implementation 'com.jakewharton.android.repackaged:dalvik-dx:16.0.1'
|
||||
implementation platform('androidx.compose:compose-bom:2024.02.00')
|
||||
implementation 'androidx.compose.ui:ui'
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.droiddeck.launcher.session.GameSaves
|
||||
import com.droiddeck.launcher.session.SessionLogShare
|
||||
import com.droiddeck.launcher.session.SessionPaths
|
||||
import com.droiddeck.launcher.session.SessionPrefs
|
||||
import com.droiddeck.launcher.session.SamsungPerf
|
||||
import com.droiddeck.launcher.session.SteamRepair
|
||||
import com.droiddeck.launcher.core.CpuCores
|
||||
import com.droiddeck.launcher.core.DownloadSource
|
||||
@@ -171,6 +172,8 @@ class FrontEndActivity : ComponentActivity() {
|
||||
private var zramClient by mutableStateOf(true)
|
||||
private var vramCapMb by mutableStateOf(0)
|
||||
private var gpuClockPin by mutableStateOf(false)
|
||||
private var performanceBoost by mutableStateOf(false)
|
||||
private var samsungSdk by mutableStateOf<Boolean?>(null)
|
||||
private var prootNoSeccomp by mutableStateOf(false)
|
||||
private var prootFastPath by mutableStateOf(true)
|
||||
private var cursorComposite by mutableStateOf(false)
|
||||
@@ -254,6 +257,9 @@ class FrontEndActivity : ComponentActivity() {
|
||||
private val pickLossless = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { r ->
|
||||
if (r.resultCode == RESULT_OK) InAppFilePicker.pickedFile(r.data)?.let { importLossless(it) }
|
||||
}
|
||||
private val pickSamsungSdk = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { r ->
|
||||
if (r.resultCode == RESULT_OK) InAppFilePicker.pickedFile(r.data)?.let { importSamsungSdk(it) }
|
||||
}
|
||||
private val pickSaveZip = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { r ->
|
||||
val then = onSavePicked.also { onSavePicked = null } ?: return@registerForActivityResult
|
||||
if (r.resultCode == RESULT_OK) InAppFilePicker.pickedFile(r.data)?.let(then)
|
||||
@@ -395,6 +401,27 @@ class FrontEndActivity : ComponentActivity() {
|
||||
}, "lossless-import").start()
|
||||
}
|
||||
|
||||
private fun importSamsungSdk(source: File) {
|
||||
Thread({
|
||||
val message = getString(
|
||||
when (SamsungPerf.import(this, source)) {
|
||||
SamsungPerf.ImportResult.IMPORTED -> R.string.perf_samsung_sdk_imported
|
||||
SamsungPerf.ImportResult.INVALID -> R.string.perf_samsung_sdk_invalid
|
||||
SamsungPerf.ImportResult.FAILED -> R.string.perf_samsung_sdk_failed
|
||||
},
|
||||
)
|
||||
val imported = SamsungPerf.imported(this)
|
||||
ui.post {
|
||||
samsungSdk = imported
|
||||
android.widget.Toast.makeText(this, message, android.widget.Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}, "samsung-sdk-import").start()
|
||||
}
|
||||
|
||||
private fun pickSamsungSdk() {
|
||||
pickSamsungSdk.launch(InAppFilePicker.buildIntent(this, listOf("zip", "jar"), getString(R.string.perf_samsung_sdk_pick)))
|
||||
}
|
||||
|
||||
private fun importSaves(name: String, game: () -> GameSaves.Game) {
|
||||
if (SessionState.running) {
|
||||
android.widget.Toast.makeText(this, R.string.main_saves_close_session, android.widget.Toast.LENGTH_LONG).show()
|
||||
@@ -2222,6 +2249,7 @@ class FrontEndActivity : ComponentActivity() {
|
||||
fsyncFirst = fsyncFirst,
|
||||
gamescopeRealtime = gamescopeRealtime,
|
||||
gpuClockPin = gpuClockPin,
|
||||
performanceBoost = performanceBoost, samsungSdk = samsungSdk,
|
||||
ramCapMb = ramCapMb, vramCapMb = vramCapMb, zramClient = zramClient,
|
||||
deviceRamMb = (android.app.ActivityManager.MemoryInfo()
|
||||
.also((getSystemService(ACTIVITY_SERVICE) as android.app.ActivityManager)::getMemoryInfo).totalMem / (1024 * 1024)).toInt(),
|
||||
@@ -2239,6 +2267,12 @@ class FrontEndActivity : ComponentActivity() {
|
||||
onFsyncFirst = { on -> SessionPrefs.setFsyncFirst(this, on); fsyncFirst = on },
|
||||
onGamescopeRealtime = { on -> SessionPrefs.setGamescopeRealtime(this, on); gamescopeRealtime = on },
|
||||
onGpuClockPin = { on -> SessionPrefs.setGpuClockPin(this, on); gpuClockPin = on },
|
||||
onPerformanceBoost = { on ->
|
||||
SessionPrefs.setPerformanceBoost(this, on)
|
||||
performanceBoost = on
|
||||
if (on && samsungSdk == false) pickSamsungSdk()
|
||||
},
|
||||
onImportSamsungSdk = { pickSamsungSdk() },
|
||||
onRamCapMb = { mb -> SessionPrefs.setRamCapMb(this, mb); ramCapMb = mb },
|
||||
onZramClient = { on -> SessionPrefs.setZramClient(this, on); zramClient = on },
|
||||
onVramCapMb = { mb -> SessionPrefs.setVramCapMb(this, mb); vramCapMb = mb },
|
||||
@@ -2455,6 +2489,8 @@ class FrontEndActivity : ComponentActivity() {
|
||||
zramClient = SessionPrefs.zramClient(this)
|
||||
vramCapMb = SessionPrefs.vramCapMb(this)
|
||||
gpuClockPin = SessionPrefs.gpuClockPin(this)
|
||||
performanceBoost = SessionPrefs.performanceBoost(this)
|
||||
samsungSdk = if (SamsungPerf.offered(this)) SamsungPerf.imported(this) else null
|
||||
prootNoSeccomp = SessionPrefs.prootNoSeccomp(this)
|
||||
prootFastPath = SessionPrefs.prootFastPath(this)
|
||||
cursorComposite = SessionPrefs.cursorComposite(this)
|
||||
|
||||
@@ -36,6 +36,9 @@ import kotlin.math.abs
|
||||
* under 90 fps against 2-7% unboosted. A game with no frames (Factorio's benchmark, a loading screen)
|
||||
* or one falling behind the panel is boosted; one holding pace keeps what it has for [PACE_LEAVE_S]
|
||||
* seconds and is then let go.
|
||||
*
|
||||
* Part of the Performance boost switch, on by default only where it was measured ([measuredHere]).
|
||||
* Another phone's HAL may boost differently, or not at all.
|
||||
*/
|
||||
object GameThreadHints {
|
||||
private const val TAG = "GameThreadHints"
|
||||
@@ -46,21 +49,23 @@ object GameThreadHints {
|
||||
private const val LEAVE = 0.15 // a capped game's main thread idles to ~30% on a prime core; under 0.3 it hopped back and forth
|
||||
private const val MAX_THREADS = 2 // one per prime core; a third would be sped up in place instead
|
||||
private const val TOP_PROCESSES = 3
|
||||
private const val PACE_WINDOW_S = 3
|
||||
private const val PACE_LEAVE_S = 10
|
||||
internal const val PACE_WINDOW_S = 3
|
||||
internal const val PACE_LEAVE_S = 10
|
||||
private const val PACE_TOLERANCE = 0.08
|
||||
|
||||
/** Busy, but not the game: the app itself and the Steam client, whose UI would otherwise hold the prime cores. */
|
||||
private val NOT_GAMES = setOf("steam", "steamwebhelper")
|
||||
|
||||
/** The Odin 3 (CQ8725S), where the boost was measured. */
|
||||
val measuredHere: Boolean get() = Build.VERSION.SDK_INT >= 31 && Build.SOC_MODEL == "CQ8725S"
|
||||
|
||||
private var worker: Thread? = null
|
||||
|
||||
/** At a session's start. The line is for the log. */
|
||||
@Synchronized
|
||||
fun start(context: Context): String {
|
||||
if (!SessionPrefs.gameThreadHints(context)) return "game thread hints: off"
|
||||
// Measured on the Odin 3 (CQ8725S) only; another HAL may boost differently, or not at all.
|
||||
if (Build.VERSION.SDK_INT < 31 || Build.SOC_MODEL != "CQ8725S") return "game thread hints: Odin 3 only"
|
||||
if (!SessionPrefs.performanceBoost(context)) return "game thread hints: off"
|
||||
if (Build.VERSION.SDK_INT < 31) return "game thread hints: needs Android 12"
|
||||
val hm = context.getSystemService(PerformanceHintManager::class.java)
|
||||
?: return "game thread hints: no PerformanceHintManager"
|
||||
val pm = context.getSystemService(PowerManager::class.java)
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.droiddeck.launcher.session
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import com.android.dx.command.dexer.DxContext
|
||||
import com.android.dx.command.dexer.Main
|
||||
import com.droiddeck.launcher.wayland.WaylandCompositor
|
||||
import dalvik.system.DexClassLoader
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.lang.reflect.Method
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
/**
|
||||
* Samsung's Galaxy Performance SDK (com.samsung.sdk.sperf): CPU, GPU and memory-bus clock floors from
|
||||
* Samsung's own service. Its licence does not allow passing the jar on, so a Samsung owner downloads
|
||||
* it from [DOWNLOAD_URL] and imports it; it is turned into dex on the phone and loaded from there.
|
||||
*
|
||||
* The presets are renewed every [RENEW_S] seconds with a [HOLD_MS] timeout, so Samsung's service
|
||||
* lets go on its own if the app dies.
|
||||
*/
|
||||
object SamsungPerf {
|
||||
enum class ImportResult { IMPORTED, INVALID, FAILED }
|
||||
|
||||
private const val TAG = "SamsungPerf"
|
||||
const val DOWNLOAD_URL = "https://developer.samsung.com/galaxy-performance/download.html"
|
||||
private const val SPERF = "com.samsung.sdk.sperf"
|
||||
private val REQUIRED = listOf("SPerf", "PerformanceManager").map { "${SPERF.replace('.', '/')}/$it.class" }
|
||||
private const val PRESETS = 64 or 128 or 256 // PerformanceManager.PRESET_TYPE_CPU | _GPU | _BUS
|
||||
private const val HOLD_MS = 5000
|
||||
private const val RENEW_S = 2
|
||||
|
||||
val samsung: Boolean get() = Build.MANUFACTURER.equals("samsung", ignoreCase = true)
|
||||
|
||||
fun offered(context: Context) = samsung || SessionPrefs.samsungPerfAnyPhone(context)
|
||||
|
||||
fun imported(context: Context) = dexFile(context).isFile
|
||||
|
||||
private fun dir(context: Context) = File(context.filesDir, "samsung-perf")
|
||||
|
||||
private fun dexFile(context: Context) = File(dir(context), "perfsdk.dex.jar")
|
||||
|
||||
@Synchronized
|
||||
fun import(context: Context, source: File): ImportResult {
|
||||
val dir = dir(context).apply { mkdirs() }
|
||||
val jar = File(dir, "perfsdk.jar.staged")
|
||||
val dex = File(dir, "perfsdk.dex.jar.staged")
|
||||
try {
|
||||
if (!extractJar(source, jar)) return ImportResult.INVALID
|
||||
val log = ByteArrayOutputStream()
|
||||
val dx = DxContext(log, log)
|
||||
val args = Main.Arguments(dx).apply {
|
||||
fileNames = arrayOf(jar.path)
|
||||
outName = dex.path
|
||||
jarOutput = true
|
||||
minSdkVersion = 26
|
||||
makeOptionsObjects()
|
||||
}
|
||||
val status = Main(dx).runDx(args)
|
||||
if (status != 0 || !dex.isFile) {
|
||||
Log.w(TAG, "dx exit $status: $log")
|
||||
return ImportResult.FAILED
|
||||
}
|
||||
val target = dexFile(context)
|
||||
target.setWritable(true)
|
||||
target.delete()
|
||||
if (!dex.renameTo(target)) return ImportResult.FAILED
|
||||
target.setReadOnly()
|
||||
sdk = null
|
||||
unusable = false
|
||||
Log.i(TAG, "SDK from $source ready (${target.length()} bytes of dex)")
|
||||
return ImportResult.IMPORTED
|
||||
} catch (t: Throwable) {
|
||||
Log.w(TAG, "could not import $source", t)
|
||||
return ImportResult.FAILED
|
||||
} finally {
|
||||
jar.delete()
|
||||
dex.delete()
|
||||
}
|
||||
}
|
||||
|
||||
/** Samsung's zip also holds the sample app's different perfsdk.jar; Libs/ is the SDK's own. */
|
||||
internal fun extractJar(source: File, target: File): Boolean = try {
|
||||
ZipFile(source).use { zip -> extractJar(zip, source, target) }
|
||||
} catch (_: java.util.zip.ZipException) {
|
||||
false
|
||||
}
|
||||
|
||||
private fun extractJar(zip: ZipFile, source: File, target: File): Boolean {
|
||||
if (REQUIRED.all { zip.getEntry(it) != null }) {
|
||||
source.copyTo(target, overwrite = true)
|
||||
return true
|
||||
}
|
||||
val jars = zip.entries().asSequence()
|
||||
.filter { !it.isDirectory && it.name.endsWith(".jar", ignoreCase = true) }
|
||||
.sortedByDescending { it.name.contains("Libs/") }
|
||||
.toList()
|
||||
for (entry in jars) {
|
||||
zip.getInputStream(entry).use { input -> target.outputStream().use { input.copyTo(it) } }
|
||||
val found = runCatching { ZipFile(target).use { inner -> REQUIRED.all { inner.getEntry(it) != null } } }.getOrDefault(false)
|
||||
if (found) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private class Sdk(val manager: Any, val start: Method, val stop: Method)
|
||||
|
||||
private var sdk: Sdk? = null
|
||||
private var unusable = false
|
||||
|
||||
/** SPerf.initialize is false where Samsung's service is missing. */
|
||||
@Synchronized
|
||||
private fun load(context: Context): Sdk? {
|
||||
sdk?.let { return it }
|
||||
if (unusable || !imported(context)) return null
|
||||
try {
|
||||
val loader = DexClassLoader(dexFile(context).path, context.codeCacheDir.path, null, context.classLoader)
|
||||
val sperf = loader.loadClass("$SPERF.SPerf")
|
||||
runCatching { sperf.getMethod("setDebugModeEnabled", Boolean::class.javaPrimitiveType).invoke(null, false) }
|
||||
if (sperf.getMethod("initialize", Context::class.java).invoke(null, context.applicationContext) != true) {
|
||||
Log.i(TAG, "SDK loaded, but this phone does not support it")
|
||||
unusable = true
|
||||
return null
|
||||
}
|
||||
val pm = loader.loadClass("$SPERF.PerformanceManager")
|
||||
val manager = pm.getMethod("getInstance").invoke(null) ?: return null
|
||||
val int = Int::class.javaPrimitiveType
|
||||
return Sdk(manager, pm.getMethod("start", int, int), pm.getMethod("stop")).also { sdk = it }
|
||||
} catch (t: Throwable) {
|
||||
Log.w(TAG, "SDK not usable", t)
|
||||
unusable = true
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private var worker: Thread? = null
|
||||
|
||||
/** At a session's start. The line is for the log. */
|
||||
@Synchronized
|
||||
fun start(context: Context): String {
|
||||
if (!offered(context)) return "samsung boost: not a Samsung phone"
|
||||
if (!SessionPrefs.performanceBoost(context)) return "samsung boost: off"
|
||||
if (!imported(context)) return "samsung boost: SDK not imported"
|
||||
val app = context.applicationContext
|
||||
worker?.interrupt()
|
||||
worker = Thread({ run(app) }, "samsung-boost").apply { isDaemon = true; start() }
|
||||
return "samsung boost: watching"
|
||||
}
|
||||
|
||||
/** At a session's end. */
|
||||
@Synchronized
|
||||
fun stop() {
|
||||
worker?.interrupt()
|
||||
worker = null
|
||||
}
|
||||
|
||||
private fun run(context: Context) {
|
||||
val sdk = load(context) ?: return
|
||||
val pm = context.getSystemService(PowerManager::class.java)
|
||||
val frames = ArrayDeque<Long>()
|
||||
var pacedFor = 0
|
||||
var boosting = false
|
||||
var tick = 0
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted) {
|
||||
frames.addLast(WaylandCompositor.gameFrameCount())
|
||||
if (frames.size > GameThreadHints.PACE_WINDOW_S + 1) frames.removeFirst()
|
||||
val fps = (frames.last() - frames.first()).toDouble() / (frames.size - 1).coerceAtLeast(1)
|
||||
val paced = frames.size > GameThreadHints.PACE_WINDOW_S && GameThreadHints.holdsPace(fps, SessionState.refreshHz.toDouble())
|
||||
pacedFor = if (paced) pacedFor + 1 else 0
|
||||
val hot = Build.VERSION.SDK_INT >= 29 && pm.currentThermalStatus >= PowerManager.THERMAL_STATUS_SEVERE
|
||||
val want = fps >= 1 && !pm.isPowerSaveMode && !hot && pacedFor < GameThreadHints.PACE_LEAVE_S
|
||||
if (want && (!boosting || tick % RENEW_S == 0)) {
|
||||
val result = sdk.start.invoke(sdk.manager, PRESETS, HOLD_MS)
|
||||
if (!boosting) Log.i(TAG, "boosting (game ${fps.toInt()} fps, start $result)")
|
||||
} else if (!want && boosting) {
|
||||
sdk.stop.invoke(sdk.manager)
|
||||
Log.i(TAG, "let go (game ${fps.toInt()} fps${if (hot) ", hot" else ""}${if (paced) ", holding pace" else ""})")
|
||||
}
|
||||
boosting = want
|
||||
tick++
|
||||
Thread.sleep(1000)
|
||||
}
|
||||
} catch (_: InterruptedException) {
|
||||
} catch (t: Throwable) {
|
||||
Log.w(TAG, "stopped", t)
|
||||
} finally {
|
||||
if (boosting) runCatching { sdk.stop.invoke(sdk.manager) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -615,8 +615,18 @@ object SessionPrefs {
|
||||
prefs(context).edit().putBoolean("gpuClockPin", on).apply()
|
||||
}
|
||||
|
||||
/** Whether the game's busiest threads are named to the power HAL for a boost (GameThreadHints). */
|
||||
fun gameThreadHints(context: Context): Boolean = prefs(context).getBoolean("gameThreadHints", true)
|
||||
/**
|
||||
* The performance boost: the game's busiest threads named to the power HAL (GameThreadHints) and,
|
||||
* on a Samsung phone with Samsung's SDK imported, its clock floors (SamsungPerf).
|
||||
*/
|
||||
fun performanceBoost(context: Context): Boolean = prefs(context).getBoolean("performanceBoost", GameThreadHints.measuredHere)
|
||||
|
||||
fun setPerformanceBoost(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("performanceBoost", on).apply()
|
||||
}
|
||||
|
||||
/** Hidden: offers the Samsung SDK import on any phone, to test the import where there is no Samsung. */
|
||||
fun samsungPerfAnyPhone(context: Context): Boolean = prefs(context).getBoolean("samsungPerfAnyPhone", false)
|
||||
|
||||
fun prootNoSeccomp(context: Context): Boolean = prefs(context).getBoolean("prootNoSeccomp", false)
|
||||
|
||||
|
||||
@@ -499,6 +499,7 @@ class SessionService : Service() {
|
||||
|
||||
Log.i(TAG, GpuClockPin.start(this))
|
||||
Log.i(TAG, GameThreadHints.start(this))
|
||||
Log.i(TAG, SamsungPerf.start(this))
|
||||
|
||||
val size = SessionState.outputSize
|
||||
val guest = ArrayList<String>()
|
||||
@@ -1811,6 +1812,7 @@ class SessionService : Service() {
|
||||
SessionState.stopRequested = false
|
||||
GpuClockPin.stop(this)
|
||||
GameThreadHints.stop()
|
||||
SamsungPerf.stop()
|
||||
suspendOperationPending = false
|
||||
freezePending = false
|
||||
launchWatcher?.stopWatching()
|
||||
@@ -1891,6 +1893,7 @@ class SessionService : Service() {
|
||||
override fun onDestroy() {
|
||||
stopSession(0)
|
||||
GameThreadHints.stop() // also when the session never got as far as running
|
||||
SamsungPerf.stop()
|
||||
if (screenReceiverRegistered) {
|
||||
unregisterReceiver(screenReceiver)
|
||||
screenReceiverRegistered = false
|
||||
|
||||
@@ -19,7 +19,7 @@ object SettingsReset {
|
||||
"gamepadLeft", "gamepadRight", "pipAutoEnter", "unfoldedControls", "unfoldedControlsWithController",
|
||||
"stretch16x9", "picturePosition", "railLabels",
|
||||
"gpuDriverMode", "androidDriver", "linuxDriver", "linuxDriver.steam", "linuxDriver.desktop",
|
||||
"tuSysmem", "glThread", "noGlError", "zinkLazy", "mangoapp", "gpuClockPin", "gamescopeRealtime", "ramCapMb", "vramCapMb", "zramClient",
|
||||
"tuSysmem", "glThread", "noGlError", "zinkLazy", "mangoapp", "gpuClockPin", "performanceBoost", "gamescopeRealtime", "ramCapMb", "vramCapMb", "zramClient",
|
||||
"directAudio", "mic", "wifiDiscovery", "clientCpusOverride", "clientCpus", "gameCpus",
|
||||
"noXalia", "fastSync", "fsyncFirst", "syncFallback", "prootNoSeccomp", "prootFastPath", "cursorComposite",
|
||||
"guestHostname", "fexPreset", "forceSsbs", "logs", "gameDebugLogsEnabled", "storageDiagnostics",
|
||||
|
||||
@@ -46,6 +46,8 @@ fun PerformancePage(
|
||||
fsyncFirst: Boolean,
|
||||
gamescopeRealtime: Boolean,
|
||||
gpuClockPin: Boolean,
|
||||
performanceBoost: Boolean,
|
||||
samsungSdk: Boolean?,
|
||||
ramCapMb: Int,
|
||||
zramClient: Boolean,
|
||||
vramCapMb: Int,
|
||||
@@ -67,6 +69,8 @@ fun PerformancePage(
|
||||
onFsyncFirst: (Boolean) -> Unit,
|
||||
onGamescopeRealtime: (Boolean) -> Unit,
|
||||
onGpuClockPin: (Boolean) -> Unit,
|
||||
onPerformanceBoost: (Boolean) -> Unit,
|
||||
onImportSamsungSdk: () -> Unit,
|
||||
onRamCapMb: (Int) -> Unit,
|
||||
onZramClient: (Boolean) -> Unit,
|
||||
onVramCapMb: (Int) -> Unit,
|
||||
@@ -126,6 +130,20 @@ fun PerformancePage(
|
||||
gamescopeRealtime, onChange = onGamescopeRealtime,
|
||||
)
|
||||
}
|
||||
SettingsGroup(stringResource(R.string.perf_boost_group)) {
|
||||
ToggleRow(
|
||||
host, "boost", stringResource(R.string.perf_boost),
|
||||
stringResource(R.string.perf_boost_hint),
|
||||
performanceBoost, onChange = onPerformanceBoost,
|
||||
)
|
||||
if (samsungSdk != null) {
|
||||
ActionRow(
|
||||
stringResource(R.string.perf_samsung_sdk),
|
||||
stringResource(if (samsungSdk) R.string.perf_samsung_sdk_ready else R.string.perf_samsung_sdk_missing),
|
||||
stringResource(R.string.perf_samsung_sdk_import), onClick = onImportSamsungSdk,
|
||||
)
|
||||
}
|
||||
}
|
||||
SettingsGroup(stringResource(R.string.perf_gpu)) {
|
||||
ToggleRow(
|
||||
host, "gpuclock", stringResource(R.string.perf_gpuclock),
|
||||
|
||||
@@ -597,6 +597,17 @@
|
||||
<string name="perf_noglerror_hint">Turns off per-call GL validation.</string>
|
||||
<string name="perf_gsrealtime">gamescope: realtime GPU queue</string>
|
||||
<string name="perf_gsrealtime_hint">Prioritizes the compositor\'s GPU work. Can smooth frame pacing at some cost to games.</string>
|
||||
<string name="perf_boost_group">Boost</string>
|
||||
<string name="perf_boost">Performance boost</string>
|
||||
<string name="perf_boost_hint">Asks the phone to run the game\'s busiest threads on its fastest cores while the game falls behind. Lets go when the game keeps up, the phone is hot or battery saver is on. Android 12 or newer. More battery use and heat.</string>
|
||||
<string name="perf_samsung_sdk">Samsung performance SDK</string>
|
||||
<string name="perf_samsung_sdk_missing">With the boost on, Samsung phones can also raise their CPU, GPU and memory clocks. Download the SDK from developer.samsung.com/galaxy-performance (free Samsung account) and import the zip.</string>
|
||||
<string name="perf_samsung_sdk_ready">Imported. With the boost on, Samsung\'s CPU, GPU and memory clocks are raised too.</string>
|
||||
<string name="perf_samsung_sdk_import">Import</string>
|
||||
<string name="perf_samsung_sdk_pick">Choose PerformanceSDK_v1.0.0.zip from Samsung</string>
|
||||
<string name="perf_samsung_sdk_imported">Samsung performance SDK imported</string>
|
||||
<string name="perf_samsung_sdk_invalid">That file is not Samsung\'s Galaxy Performance SDK</string>
|
||||
<string name="perf_samsung_sdk_failed">Could not import the Samsung performance SDK</string>
|
||||
<string name="perf_gpu">GPU</string>
|
||||
<string name="perf_memory">Memory</string>
|
||||
<string name="perf_cap_off">Off</string>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.droiddeck.launcher.session
|
||||
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class SamsungPerfTest {
|
||||
@get:Rule val temp = TemporaryFolder()
|
||||
|
||||
private val sdkClasses = listOf("com/samsung/sdk/sperf/SPerf.class", "com/samsung/sdk/sperf/PerformanceManager.class")
|
||||
|
||||
private fun zip(entries: Map<String, ByteArray>): ByteArray {
|
||||
val bytes = ByteArrayOutputStream()
|
||||
ZipOutputStream(bytes).use { out ->
|
||||
entries.forEach { (name, data) -> out.putNextEntry(ZipEntry(name)); out.write(data); out.closeEntry() }
|
||||
}
|
||||
return bytes.toByteArray()
|
||||
}
|
||||
|
||||
private fun file(name: String, data: ByteArray) = File(temp.root, name).apply { writeBytes(data) }
|
||||
|
||||
@Test fun `the SDK jar is taken from Samsung's zip, not the sample's copy`() {
|
||||
val sdkJar = zip(sdkClasses.associateWith { byteArrayOf(1) })
|
||||
val sampleJar = zip(mapOf("com/samsung/sdk/sperf/SPerf.class" to byteArrayOf(2)))
|
||||
val download = file("PerformanceSDK_v1.0.0.zip", zip(mapOf(
|
||||
"Samples/Performance/lib/perfsdk.jar" to sampleJar,
|
||||
"Libs/perfsdk-v1.0.0.jar" to sdkJar,
|
||||
)))
|
||||
val target = File(temp.root, "out.jar")
|
||||
assertTrue(SamsungPerf.extractJar(download, target))
|
||||
assertArrayEquals(sdkJar, target.readBytes())
|
||||
}
|
||||
|
||||
@Test fun `the jar itself is taken as it is`() {
|
||||
val jar = file("perfsdk-v1.0.0.jar", zip(sdkClasses.associateWith { byteArrayOf(1) }))
|
||||
val target = File(temp.root, "out.jar")
|
||||
assertTrue(SamsungPerf.extractJar(jar, target))
|
||||
assertArrayEquals(jar.readBytes(), target.readBytes())
|
||||
}
|
||||
|
||||
@Test fun `other files are refused`() {
|
||||
val target = File(temp.root, "out.jar")
|
||||
assertFalse(SamsungPerf.extractJar(file("Samples.zip", zip(mapOf("a.txt" to byteArrayOf(1)))), target))
|
||||
assertFalse(SamsungPerf.extractJar(file("notes.zip", byteArrayOf(1, 2, 3)), target))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user