Show PowerVR support through existing driver and component controls

This commit is contained in:
wallentx committed 2026-10-11 04:01:57 -05:00
1 parent 8cf0373b5e
commit f9be8f0c44
10 files changed
+161 -33

No files matched your search

@@ -16,6 +16,7 @@ import com.droiddeck.launcher.gpu.DriverPairs
import com.droiddeck.launcher.gpu.GpuInfo
import com.droiddeck.launcher.gpu.LinuxVulkanDriver
import com.droiddeck.launcher.gpu.LinuxVulkanDriverManager
import com.droiddeck.launcher.gpu.SystemVulkanDriver
import com.droiddeck.launcher.gpu.TurnipDriver
import com.droiddeck.launcher.gpu.TurnipReleases
import com.droiddeck.launcher.session.SessionService
@@ -64,7 +65,8 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
autoStatus = autoStatus, releaseStatus = releaseStatus.ifEmpty { activity.getString(R.string.drivers_not_checked) }, checking = releaseChecking,
linuxRows = linuxRows, linuxSelected = linuxSelected, androidRows = androidRows, androidSelected = androidSelected,
linuxDownloads = linuxDownloads, androidDownloads = androidDownloads, canRestoreBundled = canRestoreBundled,
activeBundle = activeBundle,
activeBundle = activeBundle, powerVr = gpu.family == GpuInfo.Family.POWERVR,
systemPowerVr = SystemVulkanDriver.usesDefault(linuxSelected),
)
var androidRows by mutableStateOf<List<DriverRow>>(emptyList())
var androidSelected by mutableStateOf("")
@@ -77,7 +79,7 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
DriverRow(id, it.label, activity.getString(if (linux) R.string.drivers_bundle_sets_display else R.string.drivers_bundle_sets_runtime), true, DriverRow.BUNDLE)
}
linuxRows = LinuxVulkanDriver.optionValues(activity).map { id ->
if (id.isEmpty()) DriverRow("", activity.getString(R.string.gpu_runtime_default), activity.getString(R.string.drivers_runtime_builtin), false)
if (id.isEmpty()) DriverRow("", activity.getString(if (gpu.family == GpuInfo.Family.POWERVR) R.string.gpu_system_powervr else R.string.gpu_runtime_default), activity.getString(R.string.drivers_runtime_builtin), false)
else bundleRow(id, linux = true) ?: DriverRow(
id, lm.getDriverName(id),
listOfNotNull(
@@ -110,6 +112,7 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
/** One line for the settings row that opens this: "Auto · WinNative · Balanced". */
fun summary(): String {
val active = pairRows.firstOrNull { it.active }?.name ?: activeBundle
?: if (SystemVulkanDriver.usesDefault(linuxSelected) && androidSelected == TurnipDriver.AUTO) activity.getString(R.string.gpu_system_powervr) else null
val label = activity.getString(if (mode == SessionPrefs.GPU_DRIVERS_AUTO) R.string.common_auto else R.string.drivers_manual)
return active?.let { activity.getString(R.string.drivers_summary, label, it) } ?: label
}
@@ -152,6 +155,13 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
fun ensureAuto(force: Boolean) {
mode = SessionPrefs.gpuDriverMode(activity)
if (mode != SessionPrefs.GPU_DRIVERS_AUTO || pairBusy != null || releaseChecking) return
if (gpu.family == GpuInfo.Family.POWERVR) {
// Auto owns both choices. Manual imports remain available when Manual is selected.
SessionPrefs.ensurePowerVrAutoDrivers(activity)
autoStatus = activity.getString(R.string.drivers_powervr_builtin)
refreshDrivers()
return
}
if (!force && autoCheckedThisProcess) return
autoCheckedThisProcess = true
if (DriverPairs.recommendedKey(gpu, emptyList()) == null) {
@@ -1672,7 +1672,7 @@ class MainActivity : ComponentActivity() {
// The runtime draws with Turnip, an Adreno driver: on Mali, Xclipse or PowerVR the
// compositor gets no usable Vulkan device and a session is sound over a black screen.
// Said before the download, not after it; the user may still go ahead.
if (installed == null && !com.droiddeck.launcher.core.DeviceSupport.adreno()) { showNonAdreno = release; return }
if (installed == null && !com.droiddeck.launcher.core.DeviceSupport.graphicsAvailable()) { showNonAdreno = release; return }
install(release)
}
@@ -1684,7 +1684,7 @@ class MainActivity : ComponentActivity() {
showPhantomGate = true
return false
}
val warn = installed == null && !com.droiddeck.launcher.core.DeviceSupport.adreno()
val warn = installed == null && !com.droiddeck.launcher.core.DeviceSupport.graphicsAvailable()
if (warn && available != null) { showNonAdreno = available; return false }
startActivity(intent)
return true
@@ -6,6 +6,8 @@ import android.os.Build
import android.os.Environment
import android.os.StatFs
import android.util.Log
import com.droiddeck.launcher.gpu.DeviceInfo
import com.droiddeck.launcher.gpu.SystemVulkanDriver
import com.droiddeck.launcher.gpu.LinuxVulkanDriver
import com.droiddeck.launcher.gpu.LinuxVulkanDriverManager
import com.droiddeck.launcher.gpu.TurnipDriver
@@ -103,7 +105,11 @@ object DeviceReport {
h("GPU")
k("KGSL gpu_model", readSys("/sys/class/kgsl/kgsl-3d0/gpu_model"))
k("KGSL chip id", readSys("/sys/class/kgsl/kgsl-3d0/gpu_chipid"))
k("System Vulkan ICD", if (File("/vendor/lib64/hw/vulkan.adreno.so").exists()) "/vendor/lib64/hw/vulkan.adreno.so" else "not at the usual path")
k("System Vulkan ICD", when {
SystemVulkanDriver.isPowerVr() -> "/vendor/lib64/hw/vulkan.powervr.so"
File("/vendor/lib64/hw/vulkan.adreno.so").exists() -> "/vendor/lib64/hw/vulkan.adreno.so"
else -> "not at the usual path"
})
h("Display")
k("Session output", SessionState.outputSize?.let { "${it.first}x${it.second}" })
@@ -124,16 +130,20 @@ object DeviceReport {
com.droiddeck.launcher.gpu.TurnipReleases.cached(context)))?.let { " (recommended pair: $it)" } ?: ""))
val turnip = TurnipDriver(context)
val androidChoice = SessionPrefs.androidDriver(context)
k("Display driver (chosen)", if (androidChoice.isEmpty()) "Auto -> ${turnip.autoId()}" else androidChoice)
k(" name / version", "${turnip.displayName(if (androidChoice.isEmpty()) turnip.autoId() else androidChoice)} ${turnip.driverVersion(if (androidChoice.isEmpty()) turnip.autoId() else androidChoice)}".trim())
val displayId = androidChoice.ifEmpty { turnip.autoId() }
val displayLabel = if (displayId == "system") DeviceInfo.builtinDriverLabel(SystemVulkanDriver.isPowerVr(), runtime = false)
else "${turnip.displayName(displayId)} ${turnip.driverVersion(displayId)}".trim()
k("Display driver (chosen)", if (androidChoice.isEmpty()) "Auto -> $displayLabel" else androidChoice)
k(" name / version", displayLabel)
k(" imported available", turnip.enumerateImported().ifEmpty { listOf("none") }.joinToString(", "))
val lm = LinuxVulkanDriverManager(context)
run {
val id = SessionPrefs.linuxDriver(context)
val label = if (id.isEmpty()) "runtime default (the Turnip built into the runtime)"
val label = if (id.isEmpty()) DeviceInfo.builtinDriverLabel(SystemVulkanDriver.isPowerVr(), runtime = true)
else "${lm.getDriverName(id)} ${lm.getDriverVersion(id)}".trim() +
lm.getMinGlibc(id).let { if (it.isEmpty()) "" else " (glibc $it+)" } +
if (lm.isInstalled(id)) "" else " [MISSING - falls back to the runtime's own]"
if (lm.isInstalled(id)) "" else if (SystemVulkanDriver.isPowerVr()) " [MISSING - select an installed driver or Auto]"
else " [MISSING - falls back to the runtime's own]"
k("Linux driver", label)
}
k("Runtime's own ICD", LinuxRuntime.vulkanIcd(context)?.path)
@@ -157,10 +157,20 @@ object DeviceInfo {
add("Driver mode", SessionPrefs.gpuDriverMode(context))
val turnip = TurnipDriver(context)
val choice = SessionPrefs.androidDriver(context).ifEmpty { turnip.autoId() }
add("Display driver", "${turnip.displayName(choice)} ${turnip.driverVersion(choice)}".trim())
val powerVr = gpu.family == GpuInfo.Family.POWERVR
add("Display driver", if (choice == "system") builtinDriverLabel(powerVr, runtime = false)
else "${turnip.displayName(choice)} ${turnip.driverVersion(choice)}".trim())
val linux = LinuxVulkanDriverManager(context)
val id = SessionPrefs.linuxDriver(context)
add("Game driver", if (id.isEmpty()) "the runtime's own Turnip" else "${linux.getDriverName(id)} ${linux.getDriverVersion(id)}".trim())
add("Game driver", if (id.isEmpty()) builtinDriverLabel(powerVr, runtime = true) else "${linux.getDriverName(id)} ${linux.getDriverVersion(id)}".trim())
if (gpu.oneUi8Gen2) add("Note", "One UI on an 8 Gen 2: needs the OneUI Turnip build")
})
/** Shared by the device card and the session report, which are written in English. */
internal fun builtinDriverLabel(powerVr: Boolean, runtime: Boolean): String = when {
powerVr -> "System PowerVR"
runtime -> "the runtime's own Turnip"
else -> "System Vulkan"
}
}
@@ -7,6 +7,7 @@ import android.util.Log
import com.droiddeck.launcher.core.Downloader
import com.droiddeck.launcher.core.FileUtils
import com.droiddeck.launcher.gpu.GpuInfo
import com.droiddeck.launcher.gpu.SystemVulkanDriver
import com.droiddeck.launcher.runtime.LinuxRuntime
import com.github.luben.zstd.ZstdInputStream
import com.github.luben.zstd.ZstdOutputStream
@@ -515,6 +516,16 @@ object ComponentsManager {
tmp.renameTo(list)
}
/** DXVK is supplied at game launch by the selected system PowerVR runtime. */
internal fun runtimeManaged(comp: String, systemPowerVr: Boolean): Boolean = comp == "dxvk" && systemPowerVr
private fun runtimeManaged(context: Context, comp: String): Boolean =
runtimeManaged(comp, SystemVulkanDriver.usesDefault(SessionPrefs.linuxDriver(context)))
private fun requireSelectable(context: Context, comp: String) {
check(!runtimeManaged(context, comp)) { context.getString(R.string.cmgr_powervr_dxvk_managed) }
}
// ------------------------------------------------------------------ actions
/** Applies a package now, or queues it when a game is running on that Proton. Returns what happened. */
@@ -522,6 +533,7 @@ object ComponentsManager {
val p = proton(context, protonId)
val wcp = File(packagesDir(context), safeName(file))
val info = packageInfo(context, wcp)
requireSelectable(context, info.comp)
val state = loadState(context)
if (inUse(context, p)) {
state.sub("queued").sub(p.id).put(info.comp, JSONObject().put("kind", "package").put("file", wcp.name).put("label", info.version))
@@ -545,6 +557,7 @@ object ComponentsManager {
}
fun restore(context: Context, protonId: String, comp: String, protonVersion: String): String = synchronized(lock) {
requireSelectable(context, comp)
val p = proton(context, protonId)
val wcp = File(originalsDir(context), "$protonId/${safeName(protonVersion)}/$comp.wcp")
check(wcp.isFile) { context.getString(R.string.cmgr_original_missing) }
@@ -571,7 +584,9 @@ object ComponentsManager {
}
/** Runs the swaps that waited for a game to close. Cheap when nothing is queued. */
fun applyQueued(context: Context): List<String> = synchronized(lock) {
fun applyQueued(context: Context): List<String> = applyQueued(context) { runtimeManaged(context, it) }
internal fun applyQueued(context: Context, managed: (String) -> Boolean): List<String> = synchronized(lock) {
val state = loadState(context)
val queued = state.sub("queued")
if (queued.length() == 0) return emptyList()
@@ -583,6 +598,7 @@ object ComponentsManager {
val comps = queued.optJSONObject(pid) ?: continue
for (comp in comps.keys().asSequence().toList()) {
val q = comps.optJSONObject(comp) ?: continue
if (managed(comp)) continue
runCatching {
if (q.optString("kind") == "original") {
val v = q.optString("protonVersion")
@@ -123,6 +123,7 @@ fun ComponentsPage(
// The GPU drivers tab's Advanced pages: one driver list on its own, full page.
var driverPage by remember { mutableStateOf<String?>(null) }
val gpuTab = comp == GPU_TAB
val managedDxvk = ComponentsManager.runtimeManaged(comp, gpu.systemPowerVr)
when (driverPage) {
"rt" -> {
DriverPage(
@@ -216,16 +217,22 @@ fun ComponentsPage(
) {
Box(Modifier.size(8.dp).background(if (st.queued != null) Color(0xFFFFB86B) else pal.good, CircleShape))
Text(
(if (st.queued != null) stringResource(R.string.comp_next_queued, st.queued) else stringResource(R.string.comp_in_use, label, st.inUse))
.let { if (reappliedAt != null) stringResource(R.string.comp_reapplied, it, reappliedAt) else it },
fontSize = 14.sp, color = colors.onBackground, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f),
(if (managedDxvk) stringResource(R.string.comp_in_use, label, stringResource(R.string.gpu_system_powervr)) +
(st.queued?.let { "\n" + stringResource(R.string.cmgr_powervr_dxvk_queued, it) } ?: "")
else if (st.queued != null) stringResource(R.string.comp_next_queued, st.queued) else stringResource(R.string.comp_in_use, label, st.inUse))
.let { if (reappliedAt != null && !managedDxvk) stringResource(R.string.comp_reapplied, it, reappliedAt) else it },
fontSize = 14.sp, color = colors.onBackground, maxLines = if (managedDxvk && st.queued != null) 3 else 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f),
)
if (st.queued != null) FocusText(stringResource(R.string.common_cancel), pal.signal, onClick = onCancelQueued)
else if (!narrow) Text(
else if (!narrow && !managedDxvk) Text(
if (view.inUseByGame) stringResource(R.string.comp_waits) else stringResource(R.string.comp_applies_next_game),
fontSize = 13.sp, color = colors.onSurfaceVariant, maxLines = 1,
)
}
if (managedDxvk) Text(
stringResource(R.string.cmgr_powervr_dxvk_managed),
fontSize = 13.sp, color = colors.onSurfaceVariant, modifier = Modifier.padding(top = 8.dp),
)
}
// ---- the lists ------------------------------------------------------------------------------
@@ -251,7 +258,7 @@ fun ComponentsPage(
val build = ComponentsManager.safeName(p.version)
val originals = view.originals.filter { it.comp == comp }
val stored = snapshot.packages.filter { it.comp == comp }
val activeIsOriginal = st.activeFile == null
val activeIsOriginal = !managedDxvk && st.activeFile == null
val rows = originals.map { o ->
val isCurrent = o.protonVersion == build
InstalledItem(
@@ -260,7 +267,7 @@ fun ComponentsPage(
"ORIGINAL", activeIsOriginal && isCurrent, !isCurrent, null, o.protonVersion,
)
} + stored.map { s ->
InstalledItem(s.version, stringResource(R.string.common_size_mb, s.size / 1048576.0), "STORED", s.file == st.activeFile, true, s.file, null)
InstalledItem(s.version, stringResource(R.string.common_size_mb, s.size / 1048576.0), "STORED", !managedDxvk && s.file == st.activeFile, true, s.file, null)
}
val storedNames = snapshot.packages.map { it.file }.toSet()
val available = catalog.filter { it.comp == comp && ComponentsManager.safeName(it.file) !in storedNames }
@@ -277,9 +284,9 @@ fun ComponentsPage(
)
rows.forEachIndexed { i, item ->
InstalledLine(
item, modifier = if (i == 0) Modifier.focusRequester(first) else Modifier,
item, enabled = !managedDxvk, modifier = if (i == 0) Modifier.focusRequester(first) else Modifier,
onSelect = {
if (item.selected) return@InstalledLine
if (managedDxvk || item.selected) return@InstalledLine
val waits = if (view.inUseByGame) ctx.getString(R.string.comp_waits_long) else ctx.getString(R.string.comp_applies_long)
if (item.file != null) ask(ctx.getString(R.string.comp_swap_title, label), ctx.getString(R.string.comp_swap_body, item.name, p.name, waits), R.string.comp_swap) { onSwap(item.file) }
else ask(ctx.getString(R.string.comp_restore_title, label), ctx.getString(R.string.comp_restore_body, item.protonVersion, p.name, waits), R.string.common_restore) { onRestore(item.protonVersion!!) }
@@ -309,8 +316,11 @@ fun ComponentsPage(
}
Row(horizontalArrangement = Arrangement.spacedBy(14.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 14.dp)) {
SmallButton(stringResource(R.string.comp_import_wcp), onClick = onImport)
if (!activeIsOriginal && originals.any { it.protonVersion == build }) FocusText(stringResource(R.string.comp_restore_original), pal.signal) {
ask(ctx.getString(R.string.comp_restore_title, label), ctx.getString(R.string.comp_restore_original_body, p.name, label), R.string.common_restore) { onRestore(build) }
if (!activeIsOriginal && originals.any { it.protonVersion == build }) {
if (managedDxvk) SmallButton(stringResource(R.string.comp_restore_original), enabled = false) {}
else FocusText(stringResource(R.string.comp_restore_original), pal.signal) {
ask(ctx.getString(R.string.comp_restore_title, label), ctx.getString(R.string.comp_restore_original_body, p.name, label), R.string.common_restore) { onRestore(build) }
}
}
}
}
@@ -416,11 +426,11 @@ private fun Tag(text: String) {
}
@Composable
private fun InstalledLine(item: InstalledItem, modifier: Modifier = Modifier, onSelect: () -> Unit, onDelete: () -> Unit) {
private fun InstalledLine(item: InstalledItem, enabled: Boolean = true, modifier: Modifier = Modifier, onSelect: () -> Unit, onDelete: () -> Unit) {
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val src = remember { MutableInteractionSource() }
val hot = src.collectIsFocusedAsState().value || src.collectIsHoveredAsState().value
val hot = enabled && (src.collectIsFocusedAsState().value || src.collectIsHoveredAsState().value)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
@@ -430,8 +440,8 @@ private fun InstalledLine(item: InstalledItem, modifier: Modifier = Modifier, on
verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.weight(1f).heightIn(min = 56.dp).then(modifier)
.glideBorder(hot, ROW_SHAPE, pal.signal)
.hoverable(src).clickable(interactionSource = src, indication = null, onClick = onSelect)
.controllerConfirm(onClick = onSelect)
.hoverable(src).clickable(interactionSource = src, indication = null, enabled = enabled, onClick = onSelect)
.controllerConfirm(enabled = enabled, onClick = onSelect)
.padding(start = 14.dp, end = 8.dp, top = 8.dp, bottom = 8.dp),
) {
Box(contentAlignment = Alignment.Center, modifier = Modifier.size(20.dp).border(2.dp, if (item.selected) pal.signal else colors.onSurfaceVariant, CircleShape)) {
@@ -441,7 +451,7 @@ private fun InstalledLine(item: InstalledItem, modifier: Modifier = Modifier, on
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
item.name, fontSize = 15.sp, fontWeight = FontWeight.SemiBold, maxLines = 1, overflow = TextOverflow.Ellipsis,
color = if (item.selected) pal.signal else colors.onBackground, modifier = Modifier.weight(1f, fill = false),
color = if (!enabled) colors.onSurfaceVariant else if (item.selected) pal.signal else colors.onBackground, modifier = Modifier.weight(1f, fill = false),
)
Tag(item.tag)
}
@@ -80,6 +80,9 @@ class GpuDriversState(
val supported: Boolean = true,
val unsupported: Boolean = false,
val auto: Boolean = true,
val powerVr: Boolean = false,
/** The selected runtime supplies DXVK for PowerVR game launches. */
val systemPowerVr: Boolean = false,
val pairs: List<PairRow> = emptyList(),
/** The pair key being installed, and how far along (-1 = unknown). */
val busy: String? = null,
@@ -133,7 +136,7 @@ internal fun GpuDriversPanel(s: GpuDriversState, a: GpuDriversActions, onAdvance
DeviceCard(s)
if (!s.unsupported) {
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
ModeCard(Icons.Outlined.AutoAwesome, stringResource(R.string.common_auto), stringResource(R.string.gpu_auto_hint), s.auto, Modifier.weight(1f)) { a.onAuto(true) }
ModeCard(Icons.Outlined.AutoAwesome, stringResource(R.string.common_auto), stringResource(if (s.powerVr) R.string.gpu_auto_system_hint else R.string.gpu_auto_hint), s.auto, Modifier.weight(1f)) { a.onAuto(true) }
ModeCard(Icons.Outlined.Tune, stringResource(R.string.mode_suspend_manual), stringResource(R.string.gpu_manual_hint), !s.auto, Modifier.weight(1f)) { a.onAuto(false) }
}
if (s.auto) AutoCard(s)
@@ -208,9 +211,9 @@ private fun DeviceCard(s: GpuDriversState) {
else -> AttentionAmber
}
var open by rememberSaveable { mutableStateOf(false) }
var details by remember { mutableStateOf<List<DeviceInfo.Section>?>(null) }
var copied by remember { mutableStateOf(false) }
LaunchedEffect(open) {
var details by remember(s.linuxSelected, s.androidSelected, s.auto) { mutableStateOf<List<DeviceInfo.Section>?>(null) }
var copied by remember(s.linuxSelected, s.androidSelected, s.auto) { mutableStateOf(false) }
LaunchedEffect(open, s.linuxSelected, s.androidSelected, s.auto) {
if (open && details == null) details = withContext(Dispatchers.IO) { DeviceInfo.collect(context) }
}
val src = remember { MutableInteractionSource() }
@@ -308,11 +311,12 @@ private fun AutoCard(s: GpuDriversState) {
) {
Text(stringResource(R.string.gpu_in_use_caps), fontSize = 11.sp, fontWeight = FontWeight.SemiBold, letterSpacing = 1.2.sp, color = colors.onSurfaceVariant)
Text(
active?.let { "${it.name} ${it.version}" } ?: s.activeBundle ?: stringResource(R.string.gpu_builtin),
active?.let { "${it.name} ${it.version}" } ?: s.activeBundle ?: stringResource(if (s.systemPowerVr) R.string.gpu_system_powervr else R.string.gpu_builtin),
fontSize = 16.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground,
)
Text(
if (active != null || s.activeBundle != null) stringResource(R.string.gpu_matched_pair)
if (s.systemPowerVr) stringResource(R.string.gpu_auto_system_hint)
else if (active != null || s.activeBundle != null) stringResource(R.string.gpu_matched_pair)
else recommended?.let { stringResource(R.string.gpu_recommended_for, it.name) } ?: stringResource(R.string.gpu_refresh_hint),
fontSize = 13.sp, color = colors.onSurfaceVariant,
)
@@ -0,0 +1,13 @@
package com.droiddeck.launcher.gpu
import org.junit.Assert.assertEquals
import org.junit.Test
class DeviceInfoTest {
@Test fun builtinLabelsDistinguishPowerVrFromTurnipAndGenericSystemVulkan() {
assertEquals("System PowerVR", DeviceInfo.builtinDriverLabel(powerVr = true, runtime = true))
assertEquals("System PowerVR", DeviceInfo.builtinDriverLabel(powerVr = true, runtime = false))
assertEquals("the runtime's own Turnip", DeviceInfo.builtinDriverLabel(powerVr = false, runtime = true))
assertEquals("System Vulkan", DeviceInfo.builtinDriverLabel(powerVr = false, runtime = false))
}
}
@@ -0,0 +1,14 @@
package com.droiddeck.launcher.session
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class PowerVrComponentsTest {
@Test fun onlyDxvkIsManagedWithTheSystemPowerVrRuntime() {
assertTrue(ComponentsManager.runtimeManaged("dxvk", systemPowerVr = true))
assertFalse(ComponentsManager.runtimeManaged("dxvk", systemPowerVr = false))
assertFalse(ComponentsManager.runtimeManaged("fex", systemPowerVr = true))
assertFalse(ComponentsManager.runtimeManaged("vkd3d", systemPowerVr = true))
}
}
@@ -0,0 +1,41 @@
package com.droiddeck.launcher.session
import android.content.Context
import com.droiddeck.launcher.runtime.LinuxRuntime
import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
import java.io.File
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [28], manifest = Config.NONE)
class PowerVrQueuedComponentsTest {
@Test fun managedDxvkQueueRemainsVisibleAndCanBeCancelled() {
val context: Context = RuntimeEnvironment.getApplication()
val root = LinuxRuntime.rootDir(context)
root.deleteRecursively()
val data = File(context.filesDir, "components")
data.deleteRecursively()
data.mkdirs()
val proton = File(root, "root/.local/share/Steam/compatibilitytools.d/test-proton")
File(proton, "files/lib/wine").mkdirs()
File(proton, "proton").writeText("# test Proton\n")
File(proton, "version").writeText("1 test-build\n")
File(data, "state.json").writeText(JSONObject().put("queued", JSONObject().put("test-proton",
JSONObject().put("dxvk", JSONObject().put("kind", "package").put("file", "chosen.wcp").put("label", "chosen DXVK"))
)).toString())
assertTrue(ComponentsManager.applyQueued(context) { ComponentsManager.runtimeManaged(it, true) }.isEmpty())
val deferred = ComponentsManager.snapshot(context).protons.single().components.getValue("dxvk")
assertEquals("chosen DXVK", deferred.queued)
ComponentsManager.cancelQueued(context, "test-proton", "dxvk")
assertNull(ComponentsManager.snapshot(context).protons.single().components.getValue("dxvk").queued)
}
}