Sfoglia il codice sorgente

android: frontend: Add support for GPU driver selection.

bunnei 3 anni fa
parent
commit
b0f8aef057

+ 1 - 0
src/android/app/src/main/AndroidManifest.xml

@@ -27,6 +27,7 @@
         android:supportsRtl="true"
         android:supportsRtl="true"
         android:isGame="true"
         android:isGame="true"
         android:banner="@mipmap/ic_launcher"
         android:banner="@mipmap/ic_launcher"
+        android:extractNativeLibs="true"
         android:requestLegacyExternalStorage="true">
         android:requestLegacyExternalStorage="true">
 
 
         <activity
         <activity

+ 1 - 1
src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java

@@ -181,7 +181,7 @@ public final class NativeLibrary {
 
 
     public static native void SetAppDirectory(String directory);
     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();
     public static native boolean ReloadKeys();
 
 

+ 2 - 1
src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.java

@@ -13,6 +13,7 @@ import android.os.Build;
 import org.yuzu.yuzu_emu.model.GameDatabase;
 import org.yuzu.yuzu_emu.model.GameDatabase;
 import org.yuzu.yuzu_emu.utils.DocumentsTree;
 import org.yuzu.yuzu_emu.utils.DocumentsTree;
 import org.yuzu.yuzu_emu.utils.DirectoryInitialization;
 import org.yuzu.yuzu_emu.utils.DirectoryInitialization;
+import org.yuzu.yuzu_emu.utils.GpuDriverHelper;
 
 
 public class YuzuApplication extends Application {
 public class YuzuApplication extends Application {
     public static GameDatabase databaseHelper;
     public static GameDatabase databaseHelper;
@@ -43,7 +44,7 @@ public class YuzuApplication extends Application {
         documentsTree = new DocumentsTree();
         documentsTree = new DocumentsTree();
 
 
         DirectoryInitialization.start(getApplicationContext());
         DirectoryInitialization.start(getApplicationContext());
-
+        GpuDriverHelper.initializeDriverParameters(getApplicationContext());
         NativeLibrary.LogDeviceInfo();
         NativeLibrary.LogDeviceInfo();
 
 
         // TODO(bunnei): Disable notifications until we support app suspension.
         // TODO(bunnei): Disable notifications until we support app suspension.

+ 52 - 1
src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java

@@ -9,6 +9,7 @@ import android.view.MenuItem;
 import android.widget.Toast;
 import android.widget.Toast;
 
 
 import androidx.annotation.NonNull;
 import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.appcompat.widget.Toolbar;
 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.DirectoryInitialization;
 import org.yuzu.yuzu_emu.utils.FileBrowserHelper;
 import org.yuzu.yuzu_emu.utils.FileBrowserHelper;
 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.PicassoUtils;
 import org.yuzu.yuzu_emu.utils.PicassoUtils;
 import org.yuzu.yuzu_emu.utils.StartupHandler;
 import org.yuzu.yuzu_emu.utils.StartupHandler;
 import org.yuzu.yuzu_emu.utils.ThemeUtil;
 import org.yuzu.yuzu_emu.utils.ThemeUtil;
@@ -128,6 +130,41 @@ public final class MainActivity extends AppCompatActivity implements MainView {
                         MainPresenter.REQUEST_INSTALL_KEYS,
                         MainPresenter.REQUEST_INSTALL_KEYS,
                         R.string.install_keys);
                         R.string.install_keys);
                 break;
                 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();
                             Toast.makeText(this, R.string.install_keys_success, Toast.LENGTH_SHORT).show();
                             refreshFragment();
                             refreshFragment();
                         } else {
                         } 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);
                             launchFileListActivity(MainPresenter.REQUEST_INSTALL_KEYS);
                         }
                         }
                     }
                     }
                 }
                 }
                 break;
                 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;
         }
         }
     }
     }
 
 

+ 5 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.java

@@ -12,6 +12,7 @@ import org.yuzu.yuzu_emu.utils.AddDirectoryHelper;
 public final class MainPresenter {
 public final class MainPresenter {
     public static final int REQUEST_ADD_DIRECTORY = 1;
     public static final int REQUEST_ADD_DIRECTORY = 1;
     public static final int REQUEST_INSTALL_KEYS = 2;
     public static final int REQUEST_INSTALL_KEYS = 2;
+    public static final int REQUEST_SELECT_GPU_DRIVER = 3;
     private final MainView mView;
     private final MainView mView;
     private String mDirToAdd;
     private String mDirToAdd;
     private long mLastClickTime = 0;
     private long mLastClickTime = 0;
@@ -51,6 +52,10 @@ public final class MainPresenter {
             case R.id.button_install_keys:
             case R.id.button_install_keys:
                 launchFileListActivity(REQUEST_INSTALL_KEYS);
                 launchFileListActivity(REQUEST_INSTALL_KEYS);
                 return true;
                 return true;
+
+            case R.id.button_select_gpu_driver:
+                launchFileListActivity(REQUEST_SELECT_GPU_DRIVER);
+                return true;
         }
         }
 
 
         return false;
         return false;

+ 130 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.java

@@ -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();
+    }
+}

+ 45 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.java

@@ -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);
+    }
+
+}

+ 5 - 0
src/android/app/src/main/res/menu/menu_game_grid.xml

@@ -18,6 +18,11 @@
                 android:icon="@drawable/ic_install"
                 android:icon="@drawable/ic_install"
                 android:title="@string/install_keys"
                 android:title="@string/install_keys"
                 app:showAsAction="ifRoom" />
                 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>
         </menu>
     </item>
     </item>
     <item
     <item

+ 10 - 0
src/android/app/src/main/res/values/strings.xml

@@ -53,6 +53,16 @@
     <string name="install_keys_success">Keys successfully installed</string>
     <string name="install_keys_success">Keys successfully installed</string>
     <string name="install_keys_failure">Keys file (prod.keys) is invalid</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 -->
     <!-- Preferences Screen -->
     <string name="preferences_settings">Settings</string>
     <string name="preferences_settings">Settings</string>
     <string name="preferences_general">General</string>
     <string name="preferences_general">General</string>