mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Show PowerVR support through existing driver and component controls
This commit is contained in:
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user