android: frontend: Add support for GPU driver selection.
This commit is contained in:
		| @@ -27,6 +27,7 @@ | ||||
|         android:supportsRtl="true" | ||||
|         android:isGame="true" | ||||
|         android:banner="@mipmap/ic_launcher" | ||||
|         android:extractNativeLibs="true" | ||||
|         android:requestLegacyExternalStorage="true"> | ||||
|  | ||||
|         <activity | ||||
|   | ||||
| @@ -181,7 +181,7 @@ public final class NativeLibrary { | ||||
|  | ||||
|     public static native void SetAppDirectory(String directory); | ||||
|  | ||||
|     public static native void SetGpuDriverParameters(String hookLibDir, String customDriverDir, String customDriverName, String fileRedirectDir); | ||||
|     public static native void InitializeGpuDriver(String hookLibDir, String customDriverDir, String customDriverName, String fileRedirectDir); | ||||
|  | ||||
|     public static native boolean ReloadKeys(); | ||||
|  | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import android.os.Build; | ||||
| import org.yuzu.yuzu_emu.model.GameDatabase; | ||||
| import org.yuzu.yuzu_emu.utils.DocumentsTree; | ||||
| import org.yuzu.yuzu_emu.utils.DirectoryInitialization; | ||||
| import org.yuzu.yuzu_emu.utils.GpuDriverHelper; | ||||
|  | ||||
| public class YuzuApplication extends Application { | ||||
|     public static GameDatabase databaseHelper; | ||||
| @@ -43,7 +44,7 @@ public class YuzuApplication extends Application { | ||||
|         documentsTree = new DocumentsTree(); | ||||
|  | ||||
|         DirectoryInitialization.start(getApplicationContext()); | ||||
|  | ||||
|         GpuDriverHelper.initializeDriverParameters(getApplicationContext()); | ||||
|         NativeLibrary.LogDeviceInfo(); | ||||
|  | ||||
|         // TODO(bunnei): Disable notifications until we support app suspension. | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import android.view.MenuItem; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.appcompat.app.AlertDialog; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
|  | ||||
| @@ -22,6 +23,7 @@ import org.yuzu.yuzu_emu.utils.AddDirectoryHelper; | ||||
| import org.yuzu.yuzu_emu.utils.DirectoryInitialization; | ||||
| import org.yuzu.yuzu_emu.utils.FileBrowserHelper; | ||||
| import org.yuzu.yuzu_emu.utils.FileUtil; | ||||
| import org.yuzu.yuzu_emu.utils.GpuDriverHelper; | ||||
| import org.yuzu.yuzu_emu.utils.PicassoUtils; | ||||
| import org.yuzu.yuzu_emu.utils.StartupHandler; | ||||
| import org.yuzu.yuzu_emu.utils.ThemeUtil; | ||||
| @@ -128,6 +130,41 @@ public final class MainActivity extends AppCompatActivity implements MainView { | ||||
|                         MainPresenter.REQUEST_INSTALL_KEYS, | ||||
|                         R.string.install_keys); | ||||
|                 break; | ||||
|             case MainPresenter.REQUEST_SELECT_GPU_DRIVER: | ||||
|                 AlertDialog.Builder builder = new AlertDialog.Builder(this); | ||||
|  | ||||
|                 // Get the driver name for the dialog message. | ||||
|                 String driverName = GpuDriverHelper.getCustomDriverName(); | ||||
|                 if (driverName == null) { | ||||
|                     driverName = getString(R.string.system_gpu_driver); | ||||
|                 } | ||||
|  | ||||
|                 // Set the dialog message and title. | ||||
|                 builder.setTitle(getString(R.string.select_gpu_driver_title)); | ||||
|                 builder.setMessage(driverName); | ||||
|  | ||||
|                 // Cancel button is a no-op. | ||||
|                 builder.setNegativeButton(android.R.string.cancel, null); | ||||
|  | ||||
|                 // Select the default system driver. | ||||
|                 builder.setPositiveButton(R.string.select_gpu_driver_default, (dialogInterface, i) -> | ||||
|                 { | ||||
|                     GpuDriverHelper.installDefaultDriver(this); | ||||
|                     Toast.makeText(this, R.string.select_gpu_driver_use_default, Toast.LENGTH_SHORT).show(); | ||||
|                 }); | ||||
|  | ||||
|                 // Use the file picker to install a custom driver. | ||||
|                 builder.setNeutralButton(R.string.select_gpu_driver_install, (dialogInterface, i) -> { | ||||
|                     FileBrowserHelper.openFilePicker(this, | ||||
|                             MainPresenter.REQUEST_SELECT_GPU_DRIVER, | ||||
|                             R.string.select_gpu_driver); | ||||
|                 }); | ||||
|  | ||||
|                 // Show the dialog. | ||||
|                 AlertDialog alertDialog = builder.create(); | ||||
|                 alertDialog.show(); | ||||
|  | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -163,12 +200,26 @@ public final class MainActivity extends AppCompatActivity implements MainView { | ||||
|                             Toast.makeText(this, R.string.install_keys_success, Toast.LENGTH_SHORT).show(); | ||||
|                             refreshFragment(); | ||||
|                         } else { | ||||
|                             Toast.makeText(this, R.string.install_keys_failure, Toast.LENGTH_SHORT).show(); | ||||
|                             Toast.makeText(this, R.string.install_keys_failure, Toast.LENGTH_LONG).show(); | ||||
|                             launchFileListActivity(MainPresenter.REQUEST_INSTALL_KEYS); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 break; | ||||
|  | ||||
|             case MainPresenter.REQUEST_SELECT_GPU_DRIVER: | ||||
|                 if (resultCode == MainActivity.RESULT_OK) { | ||||
|                     int takeFlags = (Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); | ||||
|                     getContentResolver().takePersistableUriPermission(Uri.parse(result.getDataString()), takeFlags); | ||||
|                     GpuDriverHelper.installCustomDriver(this, result.getData()); | ||||
|                     String driverName = GpuDriverHelper.getCustomDriverName(); | ||||
|                     if (driverName != null) { | ||||
|                         Toast.makeText(this, getString(R.string.select_gpu_driver_install_success) + " " + driverName, Toast.LENGTH_SHORT).show(); | ||||
|                     } else { | ||||
|                         Toast.makeText(this, R.string.select_gpu_driver_error, Toast.LENGTH_LONG).show(); | ||||
|                     } | ||||
|                 } | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import org.yuzu.yuzu_emu.utils.AddDirectoryHelper; | ||||
| public final class MainPresenter { | ||||
|     public static final int REQUEST_ADD_DIRECTORY = 1; | ||||
|     public static final int REQUEST_INSTALL_KEYS = 2; | ||||
|     public static final int REQUEST_SELECT_GPU_DRIVER = 3; | ||||
|     private final MainView mView; | ||||
|     private String mDirToAdd; | ||||
|     private long mLastClickTime = 0; | ||||
| @@ -51,6 +52,10 @@ public final class MainPresenter { | ||||
|             case R.id.button_install_keys: | ||||
|                 launchFileListActivity(REQUEST_INSTALL_KEYS); | ||||
|                 return true; | ||||
|  | ||||
|             case R.id.button_select_gpu_driver: | ||||
|                 launchFileListActivity(REQUEST_SELECT_GPU_DRIVER); | ||||
|                 return true; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|   | ||||
| @@ -0,0 +1,130 @@ | ||||
| package org.yuzu.yuzu_emu.utils; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.net.Uri; | ||||
|  | ||||
| import org.yuzu.yuzu_emu.NativeLibrary; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.util.zip.ZipEntry; | ||||
| import java.util.zip.ZipInputStream; | ||||
|  | ||||
| public class GpuDriverHelper { | ||||
|     private static final String META_JSON_FILENAME = "meta.json"; | ||||
|     private static final String DRIVER_INTERNAL_FILENAME = "gpu_driver.zip"; | ||||
|     private static String fileRedirectionPath; | ||||
|     private static String driverInstallationPath; | ||||
|     private static String hookLibPath; | ||||
|  | ||||
|     private static void unzip(String zipFilePath, String destDir) throws IOException { | ||||
|         File dir = new File(destDir); | ||||
|  | ||||
|         // Create output directory if it doesn't exist | ||||
|         if (!dir.exists()) dir.mkdirs(); | ||||
|  | ||||
|         // Unpack the files. | ||||
|         ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFilePath)); | ||||
|         byte[] buffer = new byte[1024]; | ||||
|         ZipEntry ze = zis.getNextEntry(); | ||||
|         while (ze != null) { | ||||
|             String fileName = ze.getName(); | ||||
|             File newFile = new File(destDir + fileName); | ||||
|             newFile.getParentFile().mkdirs(); | ||||
|             FileOutputStream fos = new FileOutputStream(newFile); | ||||
|             int len; | ||||
|             while ((len = zis.read(buffer)) > 0) { | ||||
|                 fos.write(buffer, 0, len); | ||||
|             } | ||||
|             fos.close(); | ||||
|             zis.closeEntry(); | ||||
|             ze = zis.getNextEntry(); | ||||
|         } | ||||
|         zis.closeEntry(); | ||||
|     } | ||||
|  | ||||
|     public static void initializeDriverParameters(Context context) { | ||||
|         try { | ||||
|             // Initialize the file redirection directory. | ||||
|             fileRedirectionPath = context.getExternalFilesDir(null).getCanonicalPath() + "/gpu/vk_file_redirect/"; | ||||
|  | ||||
|             // Initialize the driver installation directory. | ||||
|             driverInstallationPath = context.getFilesDir().getCanonicalPath() + "/gpu_driver/"; | ||||
|         } catch (IOException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|  | ||||
|         // Initialize directories. | ||||
|         initializeDirectories(); | ||||
|  | ||||
|         // Initialize hook libraries directory. | ||||
|         hookLibPath = context.getApplicationInfo().nativeLibraryDir + "/"; | ||||
|  | ||||
|         // Initialize GPU driver. | ||||
|         NativeLibrary.InitializeGpuDriver(hookLibPath, driverInstallationPath, getCustomDriverLibraryName(), fileRedirectionPath); | ||||
|     } | ||||
|  | ||||
|     public static void installDefaultDriver(Context context) { | ||||
|         // Removing the installed driver will result in the backend using the default system driver. | ||||
|         File driverInstallationDir = new File(driverInstallationPath); | ||||
|         deleteRecursive(driverInstallationDir); | ||||
|         initializeDriverParameters(context); | ||||
|     } | ||||
|  | ||||
|     public static void installCustomDriver(Context context, Uri driverPathUri) { | ||||
|         // Revert to system default in the event the specified driver is bad. | ||||
|         installDefaultDriver(context); | ||||
|  | ||||
|         // Ensure we have directories. | ||||
|         initializeDirectories(); | ||||
|  | ||||
|         // Copy the zip file URI into our private storage. | ||||
|         FileUtil.copyUriToInternalStorage(context, driverPathUri, driverInstallationPath, DRIVER_INTERNAL_FILENAME); | ||||
|  | ||||
|         // Unzip the driver. | ||||
|         try { | ||||
|             unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath); | ||||
|         } catch (IOException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|  | ||||
|         // Initialize the driver parameters. | ||||
|         initializeDriverParameters(context); | ||||
|     } | ||||
|  | ||||
|     public static String getCustomDriverName() { | ||||
|         // Parse the custom driver metadata to retrieve the name. | ||||
|         GpuDriverMetadata metadata = new GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME); | ||||
|         return metadata.name; | ||||
|     } | ||||
|  | ||||
|     private static String getCustomDriverLibraryName() { | ||||
|         // Parse the custom driver metadata to retrieve the library name. | ||||
|         GpuDriverMetadata metadata = new GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME); | ||||
|         return metadata.libraryName; | ||||
|     } | ||||
|  | ||||
|     private static void initializeDirectories() { | ||||
|         // Ensure the file redirection directory exists. | ||||
|         File fileRedirectionDir = new File(fileRedirectionPath); | ||||
|         if (!fileRedirectionDir.exists()) { | ||||
|             fileRedirectionDir.mkdirs(); | ||||
|         } | ||||
|         // Ensure the driver installation directory exists. | ||||
|         File driverInstallationDir = new File(driverInstallationPath); | ||||
|         if (!driverInstallationDir.exists()) { | ||||
|             driverInstallationDir.mkdirs(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void deleteRecursive(File fileOrDirectory) { | ||||
|         if (fileOrDirectory.isDirectory()) { | ||||
|             for (File child : fileOrDirectory.listFiles()) { | ||||
|                 deleteRecursive(child); | ||||
|             } | ||||
|         } | ||||
|         fileOrDirectory.delete(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,45 @@ | ||||
| package org.yuzu.yuzu_emu.utils; | ||||
|  | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.nio.file.Paths; | ||||
|  | ||||
| public class GpuDriverMetadata { | ||||
|  | ||||
|     public String name; | ||||
|     public String description; | ||||
|     public String author; | ||||
|     public String vendor; | ||||
|     public String driverVersion; | ||||
|     public int minApi; | ||||
|     public String libraryName; | ||||
|  | ||||
|     public GpuDriverMetadata(String metadataFilePath) { | ||||
|         try { | ||||
|             JSONObject json = new JSONObject(getStringFromFile(metadataFilePath)); | ||||
|             name = json.getString("name"); | ||||
|             description = json.getString("description"); | ||||
|             author = json.getString("author"); | ||||
|             vendor = json.getString("vendor"); | ||||
|             driverVersion = json.getString("driverVersion"); | ||||
|             minApi = json.getInt("minApi"); | ||||
|             libraryName = json.getString("libraryName"); | ||||
|         } catch (JSONException e) { | ||||
|             // JSON is malformed, ignore and treat as unsupported metadata. | ||||
|         } catch (IOException e) { | ||||
|             // File is inaccessible, ignore and treat as unsupported metadata. | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static String getStringFromFile(String filePath) throws IOException { | ||||
|         Path path = Paths.get(filePath); | ||||
|         byte[] bytes = Files.readAllBytes(path); | ||||
|         return new String(bytes, StandardCharsets.UTF_8); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -18,6 +18,11 @@ | ||||
|                 android:icon="@drawable/ic_install" | ||||
|                 android:title="@string/install_keys" | ||||
|                 app:showAsAction="ifRoom" /> | ||||
|             <item | ||||
|                 android:id="@+id/button_select_gpu_driver" | ||||
|                 android:icon="@drawable/ic_settings_core" | ||||
|                 android:title="@string/select_gpu_driver" | ||||
|                 app:showAsAction="ifRoom" /> | ||||
|         </menu> | ||||
|     </item> | ||||
|     <item | ||||
|   | ||||
| @@ -53,6 +53,16 @@ | ||||
|     <string name="install_keys_success">Keys successfully installed</string> | ||||
|     <string name="install_keys_failure">Keys file (prod.keys) is invalid</string> | ||||
|  | ||||
|     <!-- GPU driver installation --> | ||||
|     <string name="select_gpu_driver">Select GPU driver</string> | ||||
|     <string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string> | ||||
|     <string name="select_gpu_driver_install">Install</string> | ||||
|     <string name="select_gpu_driver_default">Default</string> | ||||
|     <string name="select_gpu_driver_install_success">Installed</string> | ||||
|     <string name="select_gpu_driver_use_default">Using default GPU driver</string> | ||||
|     <string name="select_gpu_driver_error">Invalid driver selected, using system default!</string> | ||||
|     <string name="system_gpu_driver">System GPU driver</string> | ||||
|  | ||||
|     <!-- Preferences Screen --> | ||||
|     <string name="preferences_settings">Settings</string> | ||||
|     <string name="preferences_general">General</string> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 bunnei
					bunnei