android: Add per-game drivers

master
t895 2023-12-10 20:45:02 +07:00
parent 2fce812026
commit f2eb3c579f
14 changed files with 218 additions and 95 deletions

@ -42,7 +42,7 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
if (driverViewModel.selectedDriver > position) { if (driverViewModel.selectedDriver > position) {
driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1) driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
} }
if (GpuDriverHelper.customDriverData == driverData.second) { if (GpuDriverHelper.customDriverSettingData == driverData.second) {
driverViewModel.setSelectedDriverIndex(0) driverViewModel.setSelectedDriverIndex(0)
} }
driverViewModel.driversToDelete.add(driverData.first) driverViewModel.driversToDelete.add(driverData.first)

@ -15,6 +15,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@ -36,6 +37,8 @@ class DriverManagerFragment : Fragment() {
private val homeViewModel: HomeViewModel by activityViewModels() private val homeViewModel: HomeViewModel by activityViewModels()
private val driverViewModel: DriverViewModel by activityViewModels() private val driverViewModel: DriverViewModel by activityViewModels()
private val args by navArgs<DriverManagerFragmentArgs>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
@ -57,7 +60,9 @@ class DriverManagerFragment : Fragment() {
homeViewModel.setNavigationVisibility(visible = false, animated = true) homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false) homeViewModel.setStatusBarShadeVisibility(visible = false)
if (!driverViewModel.isInteractionAllowed) { driverViewModel.onOpenDriverManager(args.game)
if (!driverViewModel.isInteractionAllowed.value) {
DriversLoadingDialogFragment().show( DriversLoadingDialogFragment().show(
childFragmentManager, childFragmentManager,
DriversLoadingDialogFragment.TAG DriversLoadingDialogFragment.TAG
@ -102,10 +107,9 @@ class DriverManagerFragment : Fragment() {
setInsets() setInsets()
} }
// Start installing requested driver override fun onDestroy() {
override fun onStop() { super.onDestroy()
super.onStop() driverViewModel.onCloseDriverManager(args.game)
driverViewModel.onCloseDriverManager()
} }
private fun setInsets() = private fun setInsets() =

@ -47,25 +47,9 @@ class DriversLoadingDialogFragment : DialogFragment() {
viewLifecycleOwner.lifecycleScope.apply { viewLifecycleOwner.lifecycleScope.apply {
launch { launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) { 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()
} }
} }

@ -352,8 +352,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
launch { launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) { repeatOnLifecycle(Lifecycle.State.RESUMED) {
driverViewModel.isDriverReady.collect { driverViewModel.isInteractionAllowed.collect {
if (it && !emulationState.isRunning) { if (it) {
onEmulationStart()
}
}
}
}
}
}
private fun onEmulationStart() {
if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
if (!DirectoryInitialization.areDirectoriesReady) { if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start() DirectoryInitialization.start()
} }
@ -363,10 +373,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
emulationState.run(emulationActivity!!.isActivityRecreated) emulationState.run(emulationActivity!!.isActivityRecreated)
} }
} }
}
}
}
}
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)

@ -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) { if (!args.game.isHomebrew) {
add( add(
SubmenuProperty( SubmenuProperty(

@ -68,6 +68,9 @@ class HomeSettingsFragment : Fragment() {
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 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 mainActivity = requireActivity() as MainActivity
val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply { val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply {
@ -91,13 +94,14 @@ class HomeSettingsFragment : Fragment() {
R.string.install_gpu_driver_description, R.string.install_gpu_driver_description,
R.drawable.ic_build, R.drawable.ic_build,
{ {
binding.root.findNavController() val action = HomeSettingsFragmentDirections
.navigate(R.id.action_homeSettingsFragment_to_driverManagerFragment) .actionHomeSettingsFragmentToDriverManagerFragment(null)
binding.root.findNavController().navigate(action)
}, },
{ GpuDriverHelper.supportsCustomDriverLoading() }, { GpuDriverHelper.supportsCustomDriverLoading() },
R.string.custom_driver_not_supported, R.string.custom_driver_not_supported,
R.string.custom_driver_not_supported_description, R.string.custom_driver_not_supported_description,
driverViewModel.selectedDriverMetadata driverViewModel.selectedDriverTitle
) )
) )
add( add(
@ -212,8 +216,11 @@ class HomeSettingsFragment : Fragment() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
exitTransition = null exitTransition = null
homeViewModel.setNavigationVisibility(visible = true, animated = true) }
homeViewModel.setStatusBarShadeVisibility(visible = true)
override fun onResume() {
super.onResume()
driverViewModel.updateDriverNameForGame(null)
} }
override fun onDestroyView() { override fun onDestroyView() {

@ -7,60 +7,55 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication 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.FileUtil
import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
import org.yuzu.yuzu_emu.utils.NativeConfig
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.File import java.io.File
class DriverViewModel : ViewModel() { class DriverViewModel : ViewModel() {
private val _areDriversLoading = MutableStateFlow(false) private val _areDriversLoading = MutableStateFlow(false)
val areDriversLoading: StateFlow<Boolean> get() = _areDriversLoading
private val _isDriverReady = MutableStateFlow(true) private val _isDriverReady = MutableStateFlow(true)
val isDriverReady: StateFlow<Boolean> get() = _isDriverReady
private val _isDeletingDrivers = MutableStateFlow(false) private val _isDeletingDrivers = MutableStateFlow(false)
val isDeletingDrivers: StateFlow<Boolean> get() = _isDeletingDrivers
private val _driverList = MutableStateFlow(mutableListOf<Pair<String, GpuDriverMetadata>>()) val isInteractionAllowed: StateFlow<Boolean> =
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<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList
var previouslySelectedDriver = 0 var previouslySelectedDriver = 0
var selectedDriver = -1 var selectedDriver = -1
private val _selectedDriverMetadata = // Used for showing which driver is currently installed within the driver manager card
MutableStateFlow( private val _selectedDriverTitle = MutableStateFlow("")
GpuDriverHelper.customDriverData.name val selectedDriverTitle: StateFlow<String> get() = _selectedDriverTitle
?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
)
val selectedDriverMetadata: StateFlow<String> get() = _selectedDriverMetadata
private val _newDriverInstalled = MutableStateFlow(false) private val _newDriverInstalled = MutableStateFlow(false)
val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled
val driversToDelete = mutableListOf<String>() val driversToDelete = mutableListOf<String>()
val isInteractionAllowed
get() = !areDriversLoading.value && isDriverReady.value && !isDeletingDrivers.value
init { init {
_areDriversLoading.value = true val currentDriverMetadata = GpuDriverHelper.installedCustomDriverData
viewModelScope.launch { findSelectedDriver(currentDriverMetadata)
withContext(Dispatchers.IO) {
val drivers = GpuDriverHelper.getDrivers()
val currentDriverMetadata = GpuDriverHelper.customDriverData
for (i in drivers.indices) {
if (drivers[i].second == currentDriverMetadata) {
setSelectedDriverIndex(i)
break
}
}
// If a user had installed a driver before the manager was implemented, this zips // 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 // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can
@ -74,14 +69,21 @@ class DriverViewModel : ViewModel() {
GpuDriverHelper.driverInstallationPath!!, GpuDriverHelper.driverInstallationPath!!,
BufferedOutputStream(driverToSave.outputStream()) BufferedOutputStream(driverToSave.outputStream())
) )
drivers.add(Pair(driverToSave.path, currentDriverMetadata)) _driverList.value.add(Pair(driverToSave.path, currentDriverMetadata))
setSelectedDriverIndex(drivers.size - 1) setSelectedDriverIndex(_driverList.value.size - 1)
} }
_driverList.value = drivers // If a user had installed a driver before the config was reworked to be multiplatform,
_areDriversLoading.value = false // 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) { fun setSelectedDriverIndex(value: Int) {
@ -98,9 +100,9 @@ class DriverViewModel : ViewModel() {
fun addDriver(driverData: Pair<String, GpuDriverMetadata>) { fun addDriver(driverData: Pair<String, GpuDriverMetadata>) {
val driverIndex = _driverList.value.indexOfFirst { it == driverData } val driverIndex = _driverList.value.indexOfFirst { it == driverData }
if (driverIndex == -1) { if (driverIndex == -1) {
setSelectedDriverIndex(_driverList.value.size)
_driverList.value.add(driverData) _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) ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
} else { } else {
setSelectedDriverIndex(driverIndex) setSelectedDriverIndex(driverIndex)
@ -111,8 +113,31 @@ class DriverViewModel : ViewModel() {
_driverList.value.remove(driverData) _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 _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 { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
driversToDelete.forEach { driversToDelete.forEach {
@ -125,23 +150,29 @@ class DriverViewModel : ViewModel() {
_isDeletingDrivers.value = false _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 return
} }
_isDriverReady.value = false
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
if (selectedDriver == 0) { if (selectedDriverMetadata.name == null) {
GpuDriverHelper.installDefaultDriver() GpuDriverHelper.installDefaultDriver()
setDriverReady() setDriverReady()
return@withContext return@withContext
} }
val driverToInstall = File(driverList.value[selectedDriver].first) if (selectedDriverFile.exists()) {
if (driverToInstall.exists()) { GpuDriverHelper.installCustomDriver(selectedDriverFile)
GpuDriverHelper.installCustomDriver(driverToInstall)
} else { } else {
GpuDriverHelper.installDefaultDriver() 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<String, GpuDriverMetadata> ->
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() { private fun setDriverReady() {
_isDriverReady.value = true _isDriverReady.value = true
_selectedDriverMetadata.value = GpuDriverHelper.customDriverData.name _selectedDriverTitle.value = GpuDriverHelper.customDriverSettingData.name
?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
} }
} }

@ -10,6 +10,8 @@ import java.io.File
import java.io.IOException import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication 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.ZipException
import java.util.zip.ZipFile import java.util.zip.ZipFile
@ -44,7 +46,7 @@ object GpuDriverHelper {
NativeLibrary.initializeGpuDriver( NativeLibrary.initializeGpuDriver(
hookLibPath, hookLibPath,
driverInstallationPath, driverInstallationPath,
customDriverData.libraryName, installedCustomDriverData.libraryName,
fileRedirectionPath fileRedirectionPath
) )
} }
@ -190,6 +192,7 @@ object GpuDriverHelper {
} }
} }
} catch (_: ZipException) { } catch (_: ZipException) {
} catch (_: FileNotFoundException) {
} }
return GpuDriverMetadata() return GpuDriverMetadata()
} }
@ -197,9 +200,12 @@ object GpuDriverHelper {
external fun supportsCustomDriverLoading(): Boolean external fun supportsCustomDriverLoading(): Boolean
// Parse the custom driver metadata to retrieve the name. // Parse the custom driver metadata to retrieve the name.
val customDriverData: GpuDriverMetadata val installedCustomDriverData: GpuDriverMetadata
get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME)) get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME))
val customDriverSettingData: GpuDriverMetadata
get() = getMetadataFromZip(File(StringSetting.DRIVER_PATH.getString()))
fun initializeDirectories() { fun initializeDirectories() {
// Ensure the file redirection directory exists. // Ensure the file redirection directory exists.
val fileRedirectionDir = File(fileRedirectionPath!!) val fileRedirectionDir = File(fileRedirectionPath!!)

@ -36,6 +36,7 @@ void AndroidConfig::ReadAndroidValues() {
ReadAndroidUIValues(); ReadAndroidUIValues();
ReadUIValues(); ReadUIValues();
} }
ReadDriverValues();
} }
void AndroidConfig::ReadAndroidUIValues() { void AndroidConfig::ReadAndroidUIValues() {
@ -57,6 +58,7 @@ void AndroidConfig::ReadUIValues() {
void AndroidConfig::ReadPathValues() { void AndroidConfig::ReadPathValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Paths)); BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
AndroidSettings::values.game_dirs.clear();
const int gamedirs_size = BeginArray(std::string("gamedirs")); const int gamedirs_size = BeginArray(std::string("gamedirs"));
for (int i = 0; i < gamedirs_size; ++i) { for (int i = 0; i < gamedirs_size; ++i) {
SetArrayIndex(i); SetArrayIndex(i);
@ -71,11 +73,20 @@ void AndroidConfig::ReadPathValues() {
EndGroup(); EndGroup();
} }
void AndroidConfig::ReadDriverValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::GpuDriver));
ReadCategory(Settings::Category::GpuDriver);
EndGroup();
}
void AndroidConfig::SaveAndroidValues() { void AndroidConfig::SaveAndroidValues() {
if (global) { if (global) {
SaveAndroidUIValues(); SaveAndroidUIValues();
SaveUIValues(); SaveUIValues();
} }
SaveDriverValues();
WriteToIni(); WriteToIni();
} }
@ -111,6 +122,14 @@ void AndroidConfig::SavePathValues() {
EndGroup(); EndGroup();
} }
void AndroidConfig::SaveDriverValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::GpuDriver));
WriteCategory(Settings::Category::GpuDriver);
EndGroup();
}
std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) { std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
auto& map = Settings::values.linkage.by_category; auto& map = Settings::values.linkage.by_category;
if (map.contains(category)) { if (map.contains(category)) {

@ -17,6 +17,7 @@ public:
protected: protected:
void ReadAndroidValues(); void ReadAndroidValues();
void ReadAndroidUIValues(); void ReadAndroidUIValues();
void ReadDriverValues();
void ReadHidbusValues() override {} void ReadHidbusValues() override {}
void ReadDebugControlValues() override {} void ReadDebugControlValues() override {}
void ReadPathValues() override; void ReadPathValues() override;
@ -28,6 +29,7 @@ protected:
void SaveAndroidValues(); void SaveAndroidValues();
void SaveAndroidUIValues(); void SaveAndroidUIValues();
void SaveDriverValues();
void SaveHidbusValues() override {} void SaveHidbusValues() override {}
void SaveDebugControlValues() override {} void SaveDebugControlValues() override {}
void SavePathValues() override; void SavePathValues() override;

@ -30,6 +30,9 @@ struct Values {
Settings::Specialization::Default, Settings::Specialization::Default,
true, true,
true}; true};
Settings::SwitchableSetting<std::string, false> driver_path{linkage, "", "driver_path",
Settings::Category::GpuDriver};
}; };
extern Values values; extern Values values;

@ -111,7 +111,13 @@
<fragment <fragment
android:id="@+id/driverManagerFragment" android:id="@+id/driverManagerFragment"
android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment" android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment"
android:label="DriverManagerFragment" /> android:label="DriverManagerFragment" >
<argument
android:name="game"
app:argType="org.yuzu.yuzu_emu.model.Game"
app:nullable="true"
android:defaultValue="@null" />
</fragment>
<fragment <fragment
android:id="@+id/appletLauncherFragment" android:id="@+id/appletLauncherFragment"
android:name="org.yuzu.yuzu_emu.fragments.AppletLauncherFragment" android:name="org.yuzu.yuzu_emu.fragments.AppletLauncherFragment"
@ -141,6 +147,9 @@
<action <action
android:id="@+id/action_perGamePropertiesFragment_to_addonsFragment" android:id="@+id/action_perGamePropertiesFragment_to_addonsFragment"
app:destination="@id/addonsFragment" /> app:destination="@id/addonsFragment" />
<action
android:id="@+id/action_perGamePropertiesFragment_to_driverManagerFragment"
app:destination="@id/driverManagerFragment" />
</fragment> </fragment>
<action <action
android:id="@+id/action_global_perGamePropertiesFragment" android:id="@+id/action_global_perGamePropertiesFragment"

@ -211,6 +211,8 @@ const char* TranslateCategory(Category category) {
case Category::Debugging: case Category::Debugging:
case Category::DebuggingGraphics: case Category::DebuggingGraphics:
return "Debugging"; return "Debugging";
case Category::GpuDriver:
return "GpuDriver";
case Category::Miscellaneous: case Category::Miscellaneous:
return "Miscellaneous"; return "Miscellaneous";
case Category::Network: case Category::Network:

@ -26,6 +26,7 @@ enum class Category : u32 {
DataStorage, DataStorage,
Debugging, Debugging,
DebuggingGraphics, DebuggingGraphics,
GpuDriver,
Miscellaneous, Miscellaneous,
Network, Network,
WebService, WebService,