t895 2 лет назад
Родитель
Сommit
f2eb3c579f

+ 1 - 1
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)

+ 9 - 5
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<DriverManagerFragmentArgs>()
+
     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() =

+ 1 - 17
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()
         }
     }
 

+ 15 - 9
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) {

+ 15 - 0
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(

+ 12 - 5
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<HomeSetting> = mutableListOf<HomeSetting>().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() {

+ 120 - 55
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<Boolean> get() = _areDriversLoading
-
     private val _isDriverReady = MutableStateFlow(true)
-    val isDriverReady: StateFlow<Boolean> get() = _isDriverReady
-
     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
 
     var previouslySelectedDriver = 0
     var selectedDriver = -1
 
-    private val _selectedDriverMetadata =
-        MutableStateFlow(
-            GpuDriverHelper.customDriverData.name
-                ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
-        )
-    val selectedDriverMetadata: StateFlow<String> get() = _selectedDriverMetadata
+    // Used for showing which driver is currently installed within the driver manager card
+    private val _selectedDriverTitle = MutableStateFlow("")
+    val selectedDriverTitle: StateFlow<String> get() = _selectedDriverTitle
 
     private val _newDriverInstalled = MutableStateFlow(false)
     val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled
 
     val driversToDelete = mutableListOf<String>()
 
-    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
-                    }
-                }
-
-                // 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)
-                }
+        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())
+            )
+            _driverList.value.add(Pair(driverToSave.path, currentDriverMetadata))
+            setSelectedDriverIndex(_driverList.value.size - 1)
+        }
 
-                _driverList.value = drivers
-                _areDriversLoading.value = false
-            }
+        // 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<String, GpuDriverMetadata>) {
         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
             }
         }
+    }
+
+    // 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
 
-        if (GpuDriverHelper.customDriverData == driverList.value[selectedDriver].second) {
+        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<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() {
         _isDriverReady.value = true
-        _selectedDriverMetadata.value = GpuDriverHelper.customDriverData.name
+        _selectedDriverTitle.value = GpuDriverHelper.customDriverSettingData.name
             ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
     }
 }

+ 8 - 2
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!!)

+ 19 - 0
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<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
     auto& map = Settings::values.linkage.by_category;
     if (map.contains(category)) {

+ 2 - 0
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;

+ 3 - 0
src/android/app/src/main/jni/android_settings.h

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

+ 10 - 1
src/android/app/src/main/res/navigation/home_navigation.xml

@@ -111,7 +111,13 @@
     <fragment
         android:id="@+id/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
         android:id="@+id/appletLauncherFragment"
         android:name="org.yuzu.yuzu_emu.fragments.AppletLauncherFragment"
@@ -141,6 +147,9 @@
         <action
             android:id="@+id/action_perGamePropertiesFragment_to_addonsFragment"
             app:destination="@id/addonsFragment" />
+        <action
+            android:id="@+id/action_perGamePropertiesFragment_to_driverManagerFragment"
+            app:destination="@id/driverManagerFragment" />
     </fragment>
     <action
         android:id="@+id/action_global_perGamePropertiesFragment"

+ 2 - 0
src/common/settings.cpp

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

+ 1 - 0
src/common/settings_common.h

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