From f2eb3c579f2de15410681014b47779d44d77fd48 Mon Sep 17 00:00:00 2001 From: t895 Date: Sun, 10 Dec 2023 20:45:02 -0500 Subject: [PATCH] android: Add per-game drivers --- .../yuzu/yuzu_emu/adapters/DriverAdapter.kt | 2 +- .../fragments/DriverManagerFragment.kt | 14 +- .../fragments/DriversLoadingDialogFragment.kt | 18 +- .../yuzu_emu/fragments/EmulationFragment.kt | 24 ++- .../fragments/GamePropertiesFragment.kt | 15 ++ .../fragments/HomeSettingsFragment.kt | 17 +- .../yuzu/yuzu_emu/model/DriverViewModel.kt | 175 ++++++++++++------ .../yuzu/yuzu_emu/utils/GpuDriverHelper.kt | 10 +- .../app/src/main/jni/android_config.cpp | 19 ++ src/android/app/src/main/jni/android_config.h | 2 + .../app/src/main/jni/android_settings.h | 3 + .../main/res/navigation/home_navigation.xml | 11 +- src/common/settings.cpp | 2 + src/common/settings_common.h | 1 + 14 files changed, 218 insertions(+), 95 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt index 0e818cab9..d290a656c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt @@ -42,7 +42,7 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) : if (driverViewModel.selectedDriver > position) { driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1) } - if (GpuDriverHelper.customDriverData == driverData.second) { + if (GpuDriverHelper.customDriverSettingData == driverData.second) { driverViewModel.setSelectedDriverIndex(0) } driverViewModel.driversToDelete.add(driverData.first) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt index df21d74b2..cc71254dc 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt @@ -15,6 +15,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.findNavController +import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.GridLayoutManager import com.google.android.material.transition.MaterialSharedAxis import kotlinx.coroutines.flow.collectLatest @@ -36,6 +37,8 @@ class DriverManagerFragment : Fragment() { private val homeViewModel: HomeViewModel by activityViewModels() private val driverViewModel: DriverViewModel by activityViewModels() + private val args by navArgs() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) @@ -57,7 +60,9 @@ class DriverManagerFragment : Fragment() { homeViewModel.setNavigationVisibility(visible = false, animated = true) homeViewModel.setStatusBarShadeVisibility(visible = false) - if (!driverViewModel.isInteractionAllowed) { + driverViewModel.onOpenDriverManager(args.game) + + if (!driverViewModel.isInteractionAllowed.value) { DriversLoadingDialogFragment().show( childFragmentManager, DriversLoadingDialogFragment.TAG @@ -102,10 +107,9 @@ class DriverManagerFragment : Fragment() { setInsets() } - // Start installing requested driver - override fun onStop() { - super.onStop() - driverViewModel.onCloseDriverManager() + override fun onDestroy() { + super.onDestroy() + driverViewModel.onCloseDriverManager(args.game) } private fun setInsets() = diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt index f8c34346a..6a47b29f0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt @@ -47,25 +47,9 @@ class DriversLoadingDialogFragment : DialogFragment() { viewLifecycleOwner.lifecycleScope.apply { launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { - driverViewModel.areDriversLoading.collect { checkForDismiss() } + driverViewModel.isInteractionAllowed.collect { if (it) dismiss() } } } - launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - driverViewModel.isDriverReady.collect { checkForDismiss() } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - driverViewModel.isDeletingDrivers.collect { checkForDismiss() } - } - } - } - } - - private fun checkForDismiss() { - if (driverViewModel.isInteractionAllowed) { - dismiss() } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 6466442d5..c40f5f41a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -352,15 +352,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { - driverViewModel.isDriverReady.collect { - if (it && !emulationState.isRunning) { - if (!DirectoryInitialization.areDirectoriesReady) { - DirectoryInitialization.start() - } - - updateScreenLayout() - - emulationState.run(emulationActivity!!.isActivityRecreated) + driverViewModel.isInteractionAllowed.collect { + if (it) { + onEmulationStart() } } } @@ -368,6 +362,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } + private fun onEmulationStart() { + if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) { + if (!DirectoryInitialization.areDirectoriesReady) { + DirectoryInitialization.start() + } + + updateScreenLayout() + + emulationState.run(emulationActivity!!.isActivityRecreated) + } + } + override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) if (_binding == null) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt index 485989e2e..e062425a1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt @@ -148,6 +148,21 @@ class GamePropertiesFragment : Fragment() { } ) + if (GpuDriverHelper.supportsCustomDriverLoading()) { + add( + SubmenuProperty( + R.string.gpu_driver_manager, + R.string.install_gpu_driver_description, + R.drawable.ic_build, + detailsFlow = driverViewModel.selectedDriverTitle + ) { + val action = GamePropertiesFragmentDirections + .actionPerGamePropertiesFragmentToDriverManagerFragment(args.game) + binding.root.findNavController().navigate(action) + } + ) + } + if (!args.game.isHomebrew) { add( SubmenuProperty( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index 3addc2e63..6ddd758e6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt @@ -68,6 +68,9 @@ class HomeSettingsFragment : Fragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + homeViewModel.setNavigationVisibility(visible = true, animated = true) + homeViewModel.setStatusBarShadeVisibility(visible = true) mainActivity = requireActivity() as MainActivity val optionsList: MutableList = mutableListOf().apply { @@ -91,13 +94,14 @@ class HomeSettingsFragment : Fragment() { R.string.install_gpu_driver_description, R.drawable.ic_build, { - binding.root.findNavController() - .navigate(R.id.action_homeSettingsFragment_to_driverManagerFragment) + val action = HomeSettingsFragmentDirections + .actionHomeSettingsFragmentToDriverManagerFragment(null) + binding.root.findNavController().navigate(action) }, { GpuDriverHelper.supportsCustomDriverLoading() }, R.string.custom_driver_not_supported, R.string.custom_driver_not_supported_description, - driverViewModel.selectedDriverMetadata + driverViewModel.selectedDriverTitle ) ) add( @@ -212,8 +216,11 @@ class HomeSettingsFragment : Fragment() { override fun onStart() { super.onStart() exitTransition = null - homeViewModel.setNavigationVisibility(visible = true, animated = true) - homeViewModel.setStatusBarShadeVisibility(visible = true) + } + + override fun onResume() { + super.onResume() + driverViewModel.updateDriverNameForGame(null) } override fun onDestroyView() { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt index 62945ad65..76accf8f3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt @@ -7,81 +7,83 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication +import org.yuzu.yuzu_emu.features.settings.model.StringSetting +import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.GpuDriverMetadata +import org.yuzu.yuzu_emu.utils.NativeConfig import java.io.BufferedOutputStream import java.io.File class DriverViewModel : ViewModel() { private val _areDriversLoading = MutableStateFlow(false) - val areDriversLoading: StateFlow get() = _areDriversLoading - private val _isDriverReady = MutableStateFlow(true) - val isDriverReady: StateFlow get() = _isDriverReady - private val _isDeletingDrivers = MutableStateFlow(false) - val isDeletingDrivers: StateFlow get() = _isDeletingDrivers - private val _driverList = MutableStateFlow(mutableListOf>()) + val isInteractionAllowed: StateFlow = + combine( + _areDriversLoading, + _isDriverReady, + _isDeletingDrivers + ) { loading, ready, deleting -> + !loading && ready && !deleting + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = false) + + private val _driverList = MutableStateFlow(GpuDriverHelper.getDrivers()) val driverList: StateFlow>> get() = _driverList var previouslySelectedDriver = 0 var selectedDriver = -1 - private val _selectedDriverMetadata = - MutableStateFlow( - GpuDriverHelper.customDriverData.name - ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) - ) - val selectedDriverMetadata: StateFlow get() = _selectedDriverMetadata + // Used for showing which driver is currently installed within the driver manager card + private val _selectedDriverTitle = MutableStateFlow("") + val selectedDriverTitle: StateFlow get() = _selectedDriverTitle private val _newDriverInstalled = MutableStateFlow(false) val newDriverInstalled: StateFlow get() = _newDriverInstalled val driversToDelete = mutableListOf() - val isInteractionAllowed - get() = !areDriversLoading.value && isDriverReady.value && !isDeletingDrivers.value - init { - _areDriversLoading.value = true - viewModelScope.launch { - withContext(Dispatchers.IO) { - val drivers = GpuDriverHelper.getDrivers() - val currentDriverMetadata = GpuDriverHelper.customDriverData - for (i in drivers.indices) { - if (drivers[i].second == currentDriverMetadata) { - setSelectedDriverIndex(i) - break - } - } + val currentDriverMetadata = GpuDriverHelper.installedCustomDriverData + findSelectedDriver(currentDriverMetadata) - // If a user had installed a driver before the manager was implemented, this zips - // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can - // be indexed and exported as expected. - if (selectedDriver == -1) { - val driverToSave = - File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip") - driverToSave.createNewFile() - FileUtil.zipFromInternalStorage( - File(GpuDriverHelper.driverInstallationPath!!), - GpuDriverHelper.driverInstallationPath!!, - BufferedOutputStream(driverToSave.outputStream()) - ) - drivers.add(Pair(driverToSave.path, currentDriverMetadata)) - setSelectedDriverIndex(drivers.size - 1) - } - - _driverList.value = drivers - _areDriversLoading.value = false - } + // If a user had installed a driver before the manager was implemented, this zips + // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can + // be indexed and exported as expected. + if (selectedDriver == -1) { + val driverToSave = + File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip") + driverToSave.createNewFile() + FileUtil.zipFromInternalStorage( + File(GpuDriverHelper.driverInstallationPath!!), + GpuDriverHelper.driverInstallationPath!!, + BufferedOutputStream(driverToSave.outputStream()) + ) + _driverList.value.add(Pair(driverToSave.path, currentDriverMetadata)) + setSelectedDriverIndex(_driverList.value.size - 1) } + + // If a user had installed a driver before the config was reworked to be multiplatform, + // we have save the path of the previously selected driver to the new setting. + if (StringSetting.DRIVER_PATH.getString(true).isEmpty() && selectedDriver > 0 && + StringSetting.DRIVER_PATH.global + ) { + StringSetting.DRIVER_PATH.setString(_driverList.value[selectedDriver].first) + NativeConfig.saveGlobalConfig() + } else { + findSelectedDriver(GpuDriverHelper.customDriverSettingData) + } + updateDriverNameForGame(null) } fun setSelectedDriverIndex(value: Int) { @@ -98,9 +100,9 @@ class DriverViewModel : ViewModel() { fun addDriver(driverData: Pair) { val driverIndex = _driverList.value.indexOfFirst { it == driverData } if (driverIndex == -1) { - setSelectedDriverIndex(_driverList.value.size) _driverList.value.add(driverData) - _selectedDriverMetadata.value = driverData.second.name + setSelectedDriverIndex(_driverList.value.size - 1) + _selectedDriverTitle.value = driverData.second.name ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) } else { setSelectedDriverIndex(driverIndex) @@ -111,8 +113,31 @@ class DriverViewModel : ViewModel() { _driverList.value.remove(driverData) } - fun onCloseDriverManager() { + fun onOpenDriverManager(game: Game?) { + if (game != null) { + SettingsFile.loadCustomConfig(game) + } + + val driverPath = StringSetting.DRIVER_PATH.getString() + if (driverPath.isEmpty()) { + setSelectedDriverIndex(0) + } else { + findSelectedDriver(GpuDriverHelper.getMetadataFromZip(File(driverPath))) + } + } + + fun onCloseDriverManager(game: Game?) { _isDeletingDrivers.value = true + StringSetting.DRIVER_PATH.setString(driverList.value[selectedDriver].first) + updateDriverNameForGame(game) + if (game == null) { + NativeConfig.saveGlobalConfig() + } else { + NativeConfig.savePerGameConfig() + NativeConfig.unloadPerGameConfig() + NativeConfig.reloadGlobalConfig() + } + viewModelScope.launch { withContext(Dispatchers.IO) { driversToDelete.forEach { @@ -125,23 +150,29 @@ class DriverViewModel : ViewModel() { _isDeletingDrivers.value = false } } + } - if (GpuDriverHelper.customDriverData == driverList.value[selectedDriver].second) { + // It is the Emulation Fragment's responsibility to load per-game settings so that this function + // knows what driver to load. + fun onLaunchGame() { + _isDriverReady.value = false + + val selectedDriverFile = File(StringSetting.DRIVER_PATH.getString()) + val selectedDriverMetadata = GpuDriverHelper.customDriverSettingData + if (GpuDriverHelper.installedCustomDriverData == selectedDriverMetadata) { return } - _isDriverReady.value = false viewModelScope.launch { withContext(Dispatchers.IO) { - if (selectedDriver == 0) { + if (selectedDriverMetadata.name == null) { GpuDriverHelper.installDefaultDriver() setDriverReady() return@withContext } - val driverToInstall = File(driverList.value[selectedDriver].first) - if (driverToInstall.exists()) { - GpuDriverHelper.installCustomDriver(driverToInstall) + if (selectedDriverFile.exists()) { + GpuDriverHelper.installCustomDriver(selectedDriverFile) } else { GpuDriverHelper.installDefaultDriver() } @@ -150,9 +181,43 @@ class DriverViewModel : ViewModel() { } } + private fun findSelectedDriver(currentDriverMetadata: GpuDriverMetadata) { + if (driverList.value.size == 1) { + setSelectedDriverIndex(0) + return + } + + driverList.value.forEachIndexed { i: Int, driver: Pair -> + if (driver.second == currentDriverMetadata) { + setSelectedDriverIndex(i) + return + } + } + } + + fun updateDriverNameForGame(game: Game?) { + if (!GpuDriverHelper.supportsCustomDriverLoading()) { + return + } + + if (game == null || NativeConfig.isPerGameConfigLoaded()) { + updateName() + } else { + SettingsFile.loadCustomConfig(game) + updateName() + NativeConfig.unloadPerGameConfig() + NativeConfig.reloadGlobalConfig() + } + } + + private fun updateName() { + _selectedDriverTitle.value = GpuDriverHelper.customDriverSettingData.name + ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) + } + private fun setDriverReady() { _isDriverReady.value = true - _selectedDriverMetadata.value = GpuDriverHelper.customDriverData.name + _selectedDriverTitle.value = GpuDriverHelper.customDriverSettingData.name ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt index f6882ce6c..685272288 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt @@ -10,6 +10,8 @@ import java.io.File import java.io.IOException import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.YuzuApplication +import org.yuzu.yuzu_emu.features.settings.model.StringSetting +import java.io.FileNotFoundException import java.util.zip.ZipException import java.util.zip.ZipFile @@ -44,7 +46,7 @@ object GpuDriverHelper { NativeLibrary.initializeGpuDriver( hookLibPath, driverInstallationPath, - customDriverData.libraryName, + installedCustomDriverData.libraryName, fileRedirectionPath ) } @@ -190,6 +192,7 @@ object GpuDriverHelper { } } } catch (_: ZipException) { + } catch (_: FileNotFoundException) { } return GpuDriverMetadata() } @@ -197,9 +200,12 @@ object GpuDriverHelper { external fun supportsCustomDriverLoading(): Boolean // Parse the custom driver metadata to retrieve the name. - val customDriverData: GpuDriverMetadata + val installedCustomDriverData: GpuDriverMetadata get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME)) + val customDriverSettingData: GpuDriverMetadata + get() = getMetadataFromZip(File(StringSetting.DRIVER_PATH.getString())) + fun initializeDirectories() { // Ensure the file redirection directory exists. val fileRedirectionDir = File(fileRedirectionPath!!) diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp index 767d8ea83..9c3a5a9b2 100644 --- a/src/android/app/src/main/jni/android_config.cpp +++ b/src/android/app/src/main/jni/android_config.cpp @@ -36,6 +36,7 @@ void AndroidConfig::ReadAndroidValues() { ReadAndroidUIValues(); ReadUIValues(); } + ReadDriverValues(); } void AndroidConfig::ReadAndroidUIValues() { @@ -57,6 +58,7 @@ void AndroidConfig::ReadUIValues() { void AndroidConfig::ReadPathValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Paths)); + AndroidSettings::values.game_dirs.clear(); const int gamedirs_size = BeginArray(std::string("gamedirs")); for (int i = 0; i < gamedirs_size; ++i) { SetArrayIndex(i); @@ -71,11 +73,20 @@ void AndroidConfig::ReadPathValues() { EndGroup(); } +void AndroidConfig::ReadDriverValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::GpuDriver)); + + ReadCategory(Settings::Category::GpuDriver); + + EndGroup(); +} + void AndroidConfig::SaveAndroidValues() { if (global) { SaveAndroidUIValues(); SaveUIValues(); } + SaveDriverValues(); WriteToIni(); } @@ -111,6 +122,14 @@ void AndroidConfig::SavePathValues() { EndGroup(); } +void AndroidConfig::SaveDriverValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::GpuDriver)); + + WriteCategory(Settings::Category::GpuDriver); + + EndGroup(); +} + std::vector& AndroidConfig::FindRelevantList(Settings::Category category) { auto& map = Settings::values.linkage.by_category; if (map.contains(category)) { diff --git a/src/android/app/src/main/jni/android_config.h b/src/android/app/src/main/jni/android_config.h index f490be016..2c12874e1 100644 --- a/src/android/app/src/main/jni/android_config.h +++ b/src/android/app/src/main/jni/android_config.h @@ -17,6 +17,7 @@ public: protected: void ReadAndroidValues(); void ReadAndroidUIValues(); + void ReadDriverValues(); void ReadHidbusValues() override {} void ReadDebugControlValues() override {} void ReadPathValues() override; @@ -28,6 +29,7 @@ protected: void SaveAndroidValues(); void SaveAndroidUIValues(); + void SaveDriverValues(); void SaveHidbusValues() override {} void SaveDebugControlValues() override {} void SavePathValues() override; diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index fc0523206..3733f5a3c 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h @@ -30,6 +30,9 @@ struct Values { Settings::Specialization::Default, true, true}; + + Settings::SwitchableSetting driver_path{linkage, "", "driver_path", + Settings::Category::GpuDriver}; }; extern Values values; diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml index 226cf5600..37a03a8d1 100644 --- a/src/android/app/src/main/res/navigation/home_navigation.xml +++ b/src/android/app/src/main/res/navigation/home_navigation.xml @@ -111,7 +111,13 @@ + android:label="DriverManagerFragment" > + + +