android: frontend: Add support for GPU driver selection.
This commit is contained in:
parent
4c38220a64
commit
b0f8aef057
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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: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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Reference in New Issue