diff --git a/.gitmodules b/.gitmodules
index 4ae9400cff..09ed6b807a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -54,3 +54,6 @@
[submodule "Externals/rcheevos/rcheevos"]
path = Externals/rcheevos/rcheevos
url = https://github.com/RetroAchievements/rcheevos.git
+[submodule "Externals/libadrenotools"]
+ path = Externals/libadrenotools
+ url = https://github.com/bylaws/libadrenotools.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index dcb7f1b94a..c837ddd9c9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -728,6 +728,11 @@ if(ENABLE_VULKAN)
if(APPLE AND USE_BUNDLED_MOLTENVK)
add_subdirectory(Externals/MoltenVK)
endif()
+
+
+ if (ANDROID AND _M_ARM_64)
+ add_subdirectory(Externals/libadrenotools)
+ endif()
endif()
if(NOT WIN32 OR (NOT (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")))
diff --git a/Externals/libadrenotools b/Externals/libadrenotools
new file mode 160000
index 0000000000..f4ce3c9618
--- /dev/null
+++ b/Externals/libadrenotools
@@ -0,0 +1 @@
+Subproject commit f4ce3c9618e7ecfcdd238b17dad9a0b888f5de90
diff --git a/Source/Android/app/build.gradle b/Source/Android/app/build.gradle
index 36ed3baf02..3ad867b2a6 100644
--- a/Source/Android/app/build.gradle
+++ b/Source/Android/app/build.gradle
@@ -1,6 +1,7 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
+ id 'org.jetbrains.kotlin.plugin.serialization' version "1.7.20"
}
task copyProfile (type: Copy) {
@@ -110,6 +111,7 @@ android {
externalNativeBuild {
cmake {
path "../../../CMakeLists.txt"
+ version "3.22.1+"
}
}
namespace 'org.dolphinemu.dolphinemu'
@@ -122,7 +124,7 @@ android {
abiFilters "arm64-v8a", "x86_64" //, "armeabi-v7a", "x86"
// Remove the line below if you want to build the C++ unit tests
- targets "main"
+ //targets "main", "hook_impl", "main_hook", "gsl_alloc_hook", "file_redirect_hook"
}
}
}
@@ -160,6 +162,9 @@ dependencies {
// For loading game covers from disk and GameTDB
implementation 'io.coil-kt:coil:2.2.2'
+ // For loading custom GPU drivers
+ implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3"
+
implementation 'com.nononsenseapps:filepicker:4.2.1'
}
diff --git a/Source/Android/app/src/main/AndroidManifest.xml b/Source/Android/app/src/main/AndroidManifest.xml
index f470bafee7..6d33d6158a 100644
--- a/Source/Android/app/src/main/AndroidManifest.xml
+++ b/Source/Android/app/src/main/AndroidManifest.xml
@@ -41,7 +41,8 @@
android:supportsRtl="true"
android:isGame="true"
android:banner="@drawable/banner_tv"
- android:hasFragileUserData="true">
+ android:hasFragileUserData="true"
+ android:extractNativeLibs="true">
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt
index bb479267c9..7833e83542 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt
@@ -62,6 +62,12 @@ enum class StringSetting(
Settings.SECTION_GFX_ENHANCEMENTS,
"PostProcessingShader",
""
+ ),
+ GFX_DRIVER_LIB_NAME(
+ Settings.FILE_GFX,
+ Settings.SECTION_GFX_SETTINGS,
+ "DriverLibName",
+ ""
);
override val isOverridden: Boolean
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt
index 6a5960e1e9..6b4def552c 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt
@@ -49,7 +49,8 @@ enum class MenuTag {
WIIMOTE_MOTION_INPUT_1("wiimote_motion_input", 0),
WIIMOTE_MOTION_INPUT_2("wiimote_motion_input", 1),
WIIMOTE_MOTION_INPUT_3("wiimote_motion_input", 2),
- WIIMOTE_MOTION_INPUT_4("wiimote_motion_input", 3);
+ WIIMOTE_MOTION_INPUT_4("wiimote_motion_input", 3),
+ GPU_DRIVERS("gpu_drivers");
var tag: String
private set
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt
index 4d950d9933..cae9f4e840 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt
@@ -1,5 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
+// GPU driver implementation partially based on:
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
package org.dolphinemu.dolphinemu.features.settings.ui
import android.content.Context
@@ -20,6 +24,7 @@ import androidx.lifecycle.ViewModelProvider
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.snackbar.Snackbar
import org.dolphinemu.dolphinemu.NativeLibrary
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.databinding.ActivitySettingsBinding
@@ -27,6 +32,7 @@ import org.dolphinemu.dolphinemu.features.settings.model.Settings
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsFragment.Companion.newInstance
import org.dolphinemu.dolphinemu.ui.main.MainPresenter
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper
+import org.dolphinemu.dolphinemu.utils.GpuDriverInstallResult
import org.dolphinemu.dolphinemu.utils.InsetsHelper
import org.dolphinemu.dolphinemu.utils.SerializableHelper.serializable
import org.dolphinemu.dolphinemu.utils.ThemeHelper.enableScrollTint
@@ -165,8 +171,21 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
super.onActivityResult(requestCode, resultCode, result)
// If the user picked a file, as opposed to just backing out.
- if (resultCode == RESULT_OK) {
- if (requestCode != MainPresenter.REQUEST_DIRECTORY) {
+ if (resultCode != RESULT_OK) {
+ return
+ }
+
+ when (requestCode) {
+ MainPresenter.REQUEST_DIRECTORY -> {
+ val path = FileBrowserHelper.getSelectedPath(result)
+ fragment!!.adapter!!.onFilePickerConfirmation(path!!)
+ }
+
+ MainPresenter.REQUEST_GAME_FILE
+ or MainPresenter.REQUEST_SD_FILE
+ or MainPresenter.REQUEST_WAD_FILE
+ or MainPresenter.REQUEST_WII_SAVE_FILE
+ or MainPresenter.REQUEST_NAND_BIN_FILE -> {
val uri = canonicalizeIfPossible(result!!.data!!)
val validExtensions: Set =
if (requestCode == MainPresenter.REQUEST_GAME_FILE) FileBrowserHelper.GAME_EXTENSIONS else FileBrowserHelper.RAW_EXTENSION
@@ -178,9 +197,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
contentResolver.takePersistableUriPermission(uri, takeFlags)
fragment!!.adapter!!.onFilePickerConfirmation(uri.toString())
}
- } else {
- val path = FileBrowserHelper.getSelectedPath(result)
- fragment!!.adapter!!.onFilePickerConfirmation(path!!)
}
}
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt
index d9ffd9113f..3b67dbd9e8 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt
@@ -3,10 +3,15 @@
package org.dolphinemu.dolphinemu.features.settings.ui
import android.content.Context
+import android.content.DialogInterface
+import android.content.Intent
+import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
@@ -14,10 +19,15 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.snackbar.Snackbar
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.databinding.FragmentSettingsBinding
import org.dolphinemu.dolphinemu.features.settings.model.Settings
import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem
+import org.dolphinemu.dolphinemu.ui.main.MainActivity
+import org.dolphinemu.dolphinemu.ui.main.MainPresenter
+import org.dolphinemu.dolphinemu.utils.GpuDriverInstallResult
import org.dolphinemu.dolphinemu.utils.SerializableHelper.serializable
import java.util.*
import kotlin.collections.ArrayList
@@ -111,6 +121,11 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
}
override fun loadSubMenu(menuKey: MenuTag) {
+ if (menuKey == MenuTag.GPU_DRIVERS) {
+ showGpuDriverDialog()
+ return
+ }
+
activityView!!.showSettingsFragment(
menuKey,
null,
@@ -170,6 +185,74 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
}
}
+ override fun showGpuDriverDialog() {
+ if (presenter.gpuDriver == null) {
+ return
+ }
+ val msg = "${presenter!!.gpuDriver!!.name} ${presenter!!.gpuDriver!!.driverVersion}"
+
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle(getString(R.string.gpu_driver_dialog_title))
+ .setMessage(msg)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setNeutralButton(R.string.gpu_driver_dialog_system) { _: DialogInterface?, _: Int ->
+ presenter.useSystemDriver()
+ }
+ .setPositiveButton(R.string.gpu_driver_dialog_install) { _: DialogInterface?, _: Int ->
+ askForDriverFile()
+ }
+ .show()
+ }
+
+ private fun askForDriverFile() {
+ val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ type = "application/zip"
+ }
+ startActivityForResult(intent, MainPresenter.REQUEST_GPU_DRIVER)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+
+ // If the user picked a file, as opposed to just backing out.
+ if (resultCode != AppCompatActivity.RESULT_OK) {
+ return
+ }
+
+ if (requestCode != MainPresenter.REQUEST_GPU_DRIVER) {
+ return
+ }
+
+ val uri = data?.data ?: return
+ presenter.installDriver(uri)
+ }
+
+ override fun onDriverInstallDone(result: GpuDriverInstallResult) {
+ val view = binding?.root ?: return
+ Snackbar
+ .make(view, resolveInstallResultString(result), Snackbar.LENGTH_LONG)
+ .show()
+ }
+
+ override fun onDriverUninstallDone() {
+ Toast.makeText(
+ requireContext(),
+ R.string.gpu_driver_dialog_uninstall_done,
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+
+ private fun resolveInstallResultString(result: GpuDriverInstallResult) = when (result) {
+ GpuDriverInstallResult.Success -> getString(R.string.gpu_driver_install_success)
+ GpuDriverInstallResult.InvalidArchive -> getString(R.string.gpu_driver_install_invalid_archive)
+ GpuDriverInstallResult.MissingMetadata -> getString(R.string.gpu_driver_install_missing_metadata)
+ GpuDriverInstallResult.InvalidMetadata -> getString(R.string.gpu_driver_install_invalid_metadata)
+ GpuDriverInstallResult.UnsupportedAndroidVersion -> getString(R.string.gpu_driver_install_unsupported_android_version)
+ GpuDriverInstallResult.AlreadyInstalled -> getString(R.string.gpu_driver_install_already_installed)
+ GpuDriverInstallResult.FileNotFound -> getString(R.string.gpu_driver_install_file_not_found)
+ }
+
companion object {
private const val ARGUMENT_MENU_TAG = "menu_tag"
private const val ARGUMENT_GAME_ID = "game_id"
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt
index 8805916d7a..5ed4238caf 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -4,11 +4,16 @@ package org.dolphinemu.dolphinemu.features.settings.ui
import android.content.Context
import android.content.pm.PackageManager
+import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import androidx.appcompat.app.AppCompatActivity
import androidx.collection.ArraySet
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import org.dolphinemu.dolphinemu.NativeLibrary
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.activities.UserDataActivity
@@ -25,6 +30,7 @@ import org.dolphinemu.dolphinemu.features.input.ui.ProfileDialog
import org.dolphinemu.dolphinemu.features.input.ui.ProfileDialogPresenter
import org.dolphinemu.dolphinemu.features.settings.model.*
import org.dolphinemu.dolphinemu.features.settings.model.view.*
+import org.dolphinemu.dolphinemu.model.GpuDriverMetadata
import org.dolphinemu.dolphinemu.ui.main.MainPresenter
import org.dolphinemu.dolphinemu.utils.*
import java.util.*
@@ -45,6 +51,9 @@ class SettingsFragmentPresenter(
private var controllerNumber = 0
private var controllerType = 0
+ var gpuDriver: GpuDriverMetadata? = null
+ private val libNameSetting: StringSetting = StringSetting.GFX_DRIVER_LIB_NAME
+
fun onCreate(menuTag: MenuTag, gameId: String?, extras: Bundle) {
this.gameId = gameId
this.menuTag = menuTag
@@ -56,6 +65,11 @@ class SettingsFragmentPresenter(
controllerNumber = menuTag.subType
} else if (menuTag.isSerialPort1Menu) {
serialPort1Type = extras.getInt(ARG_SERIALPORT1_TYPE)
+ } else if (menuTag == MenuTag.GRAPHICS) {
+ this.gpuDriver =
+ GpuDriverHelper.getInstalledDriverMetadata() ?: GpuDriverHelper.getSystemDriverMetadata(
+ context.applicationContext
+ )
}
}
@@ -1250,6 +1264,15 @@ class SettingsFragmentPresenter(
MenuTag.ADVANCED_GRAPHICS
)
)
+
+ if (GpuDriverHelper.supportsCustomDriverLoading() && this.gpuDriver != null) {
+ sl.add(
+ SubmenuSetting(
+ context,
+ R.string.gpu_driver_submenu, MenuTag.GPU_DRIVERS
+ )
+ )
+ }
}
private fun addEnhanceSettings(sl: ArrayList) {
@@ -2113,7 +2136,7 @@ class SettingsFragmentPresenter(
profileString: String,
controllerNumber: Int
) {
- val profiles = ProfileDialogPresenter(menuTag).getProfileNames(false)
+ val profiles = ProfileDialogPresenter(menuTag!!).getProfileNames(false)
val profileKey = profileString + "Profile" + (controllerNumber + 1)
sl.add(
StringSingleChoiceSetting(
@@ -2324,6 +2347,45 @@ class SettingsFragmentPresenter(
)
}
+ fun installDriver(uri: Uri) {
+ val context = this.context.applicationContext
+ CoroutineScope(Dispatchers.IO).launch {
+ val stream = context.contentResolver.openInputStream(uri)
+ if (stream == null) {
+ GpuDriverHelper.uninstallDriver()
+ withContext(Dispatchers.Main) {
+ fragmentView.onDriverInstallDone(GpuDriverInstallResult.FileNotFound)
+ }
+ return@launch
+ }
+
+ val result = GpuDriverHelper.installDriver(stream)
+ withContext(Dispatchers.Main) {
+ with(this@SettingsFragmentPresenter) {
+ this.gpuDriver = GpuDriverHelper.getInstalledDriverMetadata()
+ ?: GpuDriverHelper.getSystemDriverMetadata(context) ?: return@withContext
+ this.libNameSetting.setString(this.settings!!, this.gpuDriver!!.libraryName)
+ }
+ fragmentView.onDriverInstallDone(result)
+ }
+ }
+ }
+
+ fun useSystemDriver() {
+ CoroutineScope(Dispatchers.IO).launch {
+ GpuDriverHelper.uninstallDriver()
+ withContext(Dispatchers.Main) {
+ with(this@SettingsFragmentPresenter) {
+ this.gpuDriver =
+ GpuDriverHelper.getInstalledDriverMetadata()
+ ?: GpuDriverHelper.getSystemDriverMetadata(context.applicationContext)
+ this.libNameSetting.setString(this.settings!!, "")
+ }
+ fragmentView.onDriverUninstallDone()
+ }
+ }
+ }
+
companion object {
private val LOG_TYPE_NAMES = NativeLibrary.GetLogTypeNames()
const val ARG_CONTROLLER_TYPE = "controller_type"
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.kt
index fa861153dd..896b316883 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.kt
@@ -6,6 +6,7 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import org.dolphinemu.dolphinemu.features.settings.model.Settings
import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem
+import org.dolphinemu.dolphinemu.utils.GpuDriverInstallResult
/**
* Abstraction for a screen showing a list of settings. Instances of
@@ -111,4 +112,21 @@ interface SettingsFragmentView {
* @param visible Whether the warning should be visible.
*/
fun setOldControllerSettingsWarningVisibility(visible: Boolean)
+
+ /**
+ * Called when the driver installation is finished
+ *
+ * @param result The result of the driver installation
+ */
+ fun onDriverInstallDone(result: GpuDriverInstallResult)
+
+ /**
+ * Called when the driver uninstall process is finished
+ */
+ fun onDriverUninstallDone()
+
+ /**
+ * Shows a dialog asking the user to install or uninstall a GPU driver
+ */
+ fun showGpuDriverDialog()
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/DriverPackageMetadata.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/DriverPackageMetadata.kt
new file mode 100644
index 0000000000..fe142891ad
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/DriverPackageMetadata.kt
@@ -0,0 +1,61 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package org.dolphinemu.dolphinemu.model
+
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerializationException
+import kotlinx.serialization.json.*
+import java.io.File
+
+data class GpuDriverMetadata(
+ val name : String,
+ val author : String,
+ val packageVersion : String,
+ val vendor : String,
+ val driverVersion : String,
+ val minApi : Int,
+ val description : String,
+ val libraryName : String,
+) {
+ private constructor(metadataV1 : GpuDriverMetadataV1) : this(
+ name = metadataV1.name,
+ author = metadataV1.author,
+ packageVersion = metadataV1.packageVersion,
+ vendor = metadataV1.vendor,
+ driverVersion = metadataV1.driverVersion,
+ minApi = metadataV1.minApi,
+ description = metadataV1.description,
+ libraryName = metadataV1.libraryName,
+ )
+
+ val label get() = "${name}-v${packageVersion}"
+
+ companion object {
+ private const val SCHEMA_VERSION_V1 = 1
+
+ fun deserialize(metadataFile : File) : GpuDriverMetadata {
+ val metadataJson = Json.parseToJsonElement(metadataFile.readText())
+
+ return when (metadataJson.jsonObject["schemaVersion"]?.jsonPrimitive?.intOrNull) {
+ SCHEMA_VERSION_V1 -> GpuDriverMetadata(Json.decodeFromJsonElement(metadataJson))
+ else -> throw SerializationException("Unsupported metadata version")
+ }
+ }
+ }
+}
+
+@Serializable
+private data class GpuDriverMetadataV1(
+ val schemaVersion : Int,
+ val name : String,
+ val author : String,
+ val packageVersion : String,
+ val vendor : String,
+ val driverVersion : String,
+ val minApi : Int,
+ val description : String,
+ val libraryName : String,
+)
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt
index ddd2bcd2d0..64695a0ace 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt
@@ -286,6 +286,7 @@ class MainPresenter(private val mainView: MainView, private val activity: Fragme
const val REQUEST_WAD_FILE = 4
const val REQUEST_WII_SAVE_FILE = 5
const val REQUEST_NAND_BIN_FILE = 6
+ const val REQUEST_GPU_DRIVER = 7
private var shouldRescanLibrary = true
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java
index f2bb93d476..d5b34f21da 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java
@@ -18,18 +18,17 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.preference.PreferenceManager;
-import org.dolphinemu.dolphinemu.NativeLibrary;
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.activities.EmulationActivity;
-import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.IntSetting;
-
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import org.dolphinemu.dolphinemu.NativeLibrary;
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
+import org.dolphinemu.dolphinemu.features.settings.model.IntSetting;
+
/**
* A class that spawns its own thread in order perform initialization.
*
@@ -46,6 +45,7 @@ public final class DirectoryInitialization
private static volatile boolean areDirectoriesAvailable = false;
private static String userPath;
private static String sysPath;
+ private static String driverPath;
private static boolean isUsingLegacyUserDirectory = false;
public enum DirectoryInitializationState
@@ -88,8 +88,7 @@ public final class DirectoryInitialization
directoryState.postValue(DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED);
}
- @Nullable
- private static File getLegacyUserDirectoryPath()
+ @Nullable private static File getLegacyUserDirectoryPath()
{
File externalPath = Environment.getExternalStorageDirectory();
if (externalPath == null)
@@ -98,8 +97,7 @@ public final class DirectoryInitialization
return new File(externalPath, "dolphin-emu");
}
- @Nullable
- public static File getUserDirectoryPath(Context context)
+ @Nullable public static File getUserDirectoryPath(Context context)
{
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()))
return null;
@@ -107,8 +105,8 @@ public final class DirectoryInitialization
isUsingLegacyUserDirectory =
preferLegacyUserDirectory(context) && PermissionsHandler.hasWriteAccess(context);
- return isUsingLegacyUserDirectory ?
- getLegacyUserDirectoryPath() : context.getExternalFilesDir(null);
+ return isUsingLegacyUserDirectory ? getLegacyUserDirectoryPath() :
+ context.getExternalFilesDir(null);
}
private static boolean setDolphinUserDirectory(Context context)
@@ -153,6 +151,19 @@ public final class DirectoryInitialization
// Let the native code know where the Sys directory is.
sysPath = sysDirectory.getPath();
SetSysDirectory(sysPath);
+
+ File driverDirectory = new File(context.getFilesDir(), "GPUDrivers");
+ driverDirectory.mkdirs();
+ File driverExtractedDir = new File(driverDirectory, "Extracted");
+ driverExtractedDir.mkdirs();
+ File driverTmpDir = new File(driverDirectory, "Tmp");
+ driverTmpDir.mkdirs();
+ File driverFileRedirectDir = new File(driverDirectory, "FileRedirect");
+ driverFileRedirectDir.mkdirs();
+
+ SetGpuDriverDirectories(driverDirectory.getPath(),
+ context.getApplicationInfo().nativeLibraryDir);
+ DirectoryInitialization.driverPath = driverExtractedDir.getAbsolutePath();
}
private static void deleteDirectoryRecursively(@NonNull final File file)
@@ -213,6 +224,16 @@ public final class DirectoryInitialization
return sysPath;
}
+ public static String getExtractedDriverDirectory()
+ {
+ if (!areDirectoriesAvailable)
+ {
+ throw new IllegalStateException(
+ "DirectoryInitialization must run before accessing the driver directory!");
+ }
+ return driverPath;
+ }
+
public static File getGameListCache(Context context)
{
return new File(context.getExternalCacheDir(), "gamelist.cache");
@@ -235,16 +256,14 @@ public final class DirectoryInitialization
}
catch (IOException e)
{
- Log.error("[DirectoryInitialization] Failed to copy asset file: " + asset +
- e.getMessage());
+ Log.error("[DirectoryInitialization] Failed to copy asset file: " + asset + e.getMessage());
}
return false;
}
private static void copyAssetFolder(String assetFolder, File outputFolder, Context context)
{
- Log.verbose("[DirectoryInitialization] Copying Folder " + assetFolder + " to " +
- outputFolder);
+ Log.verbose("[DirectoryInitialization] Copying Folder " + assetFolder + " to " + outputFolder);
try
{
@@ -267,8 +286,7 @@ public final class DirectoryInitialization
}
createdFolder = true;
}
- copyAssetFolder(assetFolder + File.separator + file, new File(outputFolder, file),
- context);
+ copyAssetFolder(assetFolder + File.separator + file, new File(outputFolder, file), context);
copyAsset(assetFolder + File.separator + file, new File(outputFolder, file), context);
}
}
@@ -340,8 +358,8 @@ public final class DirectoryInitialization
private static boolean preferLegacyUserDirectory(Context context)
{
return PermissionsHandler.isExternalStorageLegacy() &&
- !PermissionsHandler.isWritePermissionDenied() &&
- isExternalFilesDirEmpty(context) && legacyUserDirectoryExists();
+ !PermissionsHandler.isWritePermissionDenied() && isExternalFilesDirEmpty(context) &&
+ legacyUserDirectoryExists();
}
public static boolean isUsingLegacyUserDirectory()
@@ -389,4 +407,6 @@ public final class DirectoryInitialization
}
private static native void SetSysDirectory(String path);
+
+ private static native void SetGpuDriverDirectories(String path, String libPath);
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GpuDriverHelper.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GpuDriverHelper.kt
new file mode 100644
index 0000000000..9de5378e57
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GpuDriverHelper.kt
@@ -0,0 +1,148 @@
+// Copyright 2023 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Partially based on:
+// SPDX-License-Identifier: MPL-2.0
+// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
+
+// Partially based on:
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.utils
+
+import android.content.Context
+import android.os.Build
+import kotlinx.serialization.SerializationException
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.model.GpuDriverMetadata
+import java.io.File
+import java.io.InputStream
+
+private const val GPU_DRIVER_META_FILE = "meta.json"
+
+interface GpuDriverHelper {
+ companion object {
+ /**
+ * Returns information about the system GPU driver.
+ * @return An array containing the driver vendor and the driver version, in this order, or `null` if an error occurred
+ */
+ private external fun getSystemDriverInfo(): Array?
+
+ /**
+ * Queries the driver for custom driver loading support.
+ * @return `true` if the device supports loading custom drivers, `false` otherwise
+ */
+ external fun supportsCustomDriverLoading(): Boolean
+
+ /**
+ * Queries the driver for manual max clock forcing support
+ */
+ external fun supportsForceMaxGpuClocks(): Boolean
+
+ /**
+ * Calls into the driver to force the GPU to run at the maximum possible clock speed
+ * @param force Whether to enable or disable the forced clocks
+ */
+ external fun forceMaxGpuClocks(enable: Boolean)
+
+ /**
+ * Uninstalls the currently installed custom driver
+ */
+ fun uninstallDriver() {
+ File(DirectoryInitialization.getExtractedDriverDirectory())
+ .deleteRecursively()
+
+ File(DirectoryInitialization.getExtractedDriverDirectory()).mkdir()
+ }
+
+ fun getInstalledDriverMetadata(): GpuDriverMetadata? {
+ val metadataFile = File(
+ DirectoryInitialization.getExtractedDriverDirectory(),
+ GPU_DRIVER_META_FILE
+ )
+ if (!metadataFile.exists()) {
+ return null;
+ }
+
+ return try {
+ GpuDriverMetadata.deserialize(metadataFile)
+ } catch (e: SerializationException) {
+ null
+ }
+ }
+
+ /**
+ * Fetches metadata about the system driver.
+ * @return A [GpuDriverMetadata] object containing data about the system driver
+ */
+ fun getSystemDriverMetadata(context: Context): GpuDriverMetadata? {
+ val systemDriverInfo = getSystemDriverInfo()
+ if (systemDriverInfo.isNullOrEmpty()) {
+ return null;
+ }
+
+ return GpuDriverMetadata(
+ name = context.getString(R.string.system_driver),
+ author = "",
+ packageVersion = "",
+ vendor = systemDriverInfo[0],
+ driverVersion = systemDriverInfo[1],
+ minApi = 0,
+ description = context.getString(R.string.system_driver_desc),
+ libraryName = ""
+ )
+ }
+
+ /**
+ * Installs the given driver to the emulator's drivers directory.
+ * @param stream InputStream of a driver package
+ * @return The exit status of the installation process
+ */
+ fun installDriver(stream: InputStream): GpuDriverInstallResult {
+ uninstallDriver()
+
+ val driverDir = File(DirectoryInitialization.getExtractedDriverDirectory())
+ try {
+ ZipUtils.unzip(stream, driverDir)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ uninstallDriver()
+ return GpuDriverInstallResult.InvalidArchive
+ }
+
+ // Check that the metadata file exists
+ val metadataFile = File(driverDir, GPU_DRIVER_META_FILE)
+ if (!metadataFile.isFile) {
+ uninstallDriver()
+ return GpuDriverInstallResult.MissingMetadata
+ }
+
+ // Check that the driver metadata is valid
+ val driverMetadata = try {
+ GpuDriverMetadata.deserialize(metadataFile)
+ } catch (e: SerializationException) {
+ uninstallDriver()
+ return GpuDriverInstallResult.InvalidMetadata
+ }
+
+ // Check that the device satisfies the driver's minimum Android version requirements
+ if (Build.VERSION.SDK_INT < driverMetadata.minApi) {
+ uninstallDriver()
+ return GpuDriverInstallResult.UnsupportedAndroidVersion
+ }
+
+ return GpuDriverInstallResult.Success
+ }
+ }
+}
+
+enum class GpuDriverInstallResult {
+ Success,
+ InvalidArchive,
+ MissingMetadata,
+ InvalidMetadata,
+ UnsupportedAndroidVersion,
+ AlreadyInstalled,
+ FileNotFound
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ZipUtils.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ZipUtils.kt
new file mode 100644
index 0000000000..8ca09fb868
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ZipUtils.kt
@@ -0,0 +1,110 @@
+/*
+ * SPDX-License-Identifier: MPL-2.0
+ * Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
+ */
+
+package org.dolphinemu.dolphinemu.utils
+
+import java.io.*
+import java.util.zip.ZipEntry
+import java.util.zip.ZipFile
+import java.util.zip.ZipInputStream
+
+interface ZipUtils {
+ companion object {
+ /**
+ * Extracts a zip file to the given target directory.
+ * @exception IOException if unzipping fails for any reason
+ */
+ @Throws(IOException::class)
+ fun unzip(file : File, targetDirectory : File) {
+ ZipFile(file).use { zipFile ->
+ for (zipEntry in zipFile.entries()) {
+ val destFile = createNewFile(targetDirectory, zipEntry)
+ // If the zip entry is a file, we need to create its parent directories
+ val destDirectory : File? = if (zipEntry.isDirectory) destFile else destFile.parentFile
+
+ // Create the destination directory
+ if (destDirectory == null || (!destDirectory.isDirectory && !destDirectory.mkdirs()))
+ throw FileNotFoundException("Failed to create destination directory: $destDirectory")
+
+ // If the entry is a directory we don't need to copy anything
+ if (zipEntry.isDirectory)
+ continue
+
+ // Copy bytes to destination
+ try {
+ zipFile.getInputStream(zipEntry).use { inputStream ->
+ destFile.outputStream().use { outputStream ->
+ inputStream.copyTo(outputStream)
+ }
+ }
+ } catch (e : IOException) {
+ if (destFile.exists())
+ destFile.delete()
+ throw e
+ }
+ }
+ }
+ }
+
+ /**
+ * Extracts a zip file from the given stream to the given target directory.
+ *
+ * This method is ~5x slower than [unzip], as [ZipInputStream] uses a fixed `512` bytes buffer for inflation,
+ * instead of `8192` bytes or more used by input streams returned by [ZipFile].
+ * This results in ~8x the amount of JNI calls, producing an increased number of array bounds checking, which kills performance.
+ * Usage of this method is discouraged when possible, [unzip] should be used instead.
+ * Nevertheless, it's the only option when extracting zips obtained from content URIs, as a [File] object cannot be obtained from them.
+ * @exception IOException if unzipping fails for any reason
+ */
+ @Throws(IOException::class)
+ fun unzip(stream : InputStream, targetDirectory : File) {
+ ZipInputStream(BufferedInputStream(stream)).use { zis ->
+ do {
+ // Get the next entry, break if we've reached the end
+ val zipEntry = zis.nextEntry ?: break
+
+ val destFile = createNewFile(targetDirectory, zipEntry)
+ // If the zip entry is a file, we need to create its parent directories
+ val destDirectory : File? = if (zipEntry.isDirectory) destFile else destFile.parentFile
+
+ // Create the destination directory
+ if (destDirectory == null || (!destDirectory.isDirectory && !destDirectory.mkdirs()))
+ throw FileNotFoundException("Failed to create destination directory: $destDirectory")
+
+ // If the entry is a directory we don't need to copy anything
+ if (zipEntry.isDirectory)
+ continue
+
+ // Copy bytes to destination
+ try {
+ BufferedOutputStream(destFile.outputStream()).use { zis.copyTo(it) }
+ } catch (e : IOException) {
+ if (destFile.exists())
+ destFile.delete()
+ throw e
+ }
+ } while (true)
+ }
+ }
+
+ /**
+ * Safely creates a new destination file where the given zip entry will be extracted to.
+ *
+ * @exception IOException if the file was being created outside of the target directory
+ * **see:** [Zip Slip](https://github.com/snyk/zip-slip-vulnerability)
+ */
+ @Throws(IOException::class)
+ private fun createNewFile(destinationDir : File, zipEntry : ZipEntry) : File {
+ val destFile = File(destinationDir, zipEntry.name)
+ val destDirPath = destinationDir.canonicalPath
+ val destFilePath = destFile.canonicalPath
+
+ if (!destFilePath.startsWith(destDirPath + File.separator))
+ throw IOException("Entry is outside of the target dir: " + zipEntry.name)
+
+ return destFile
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml
index 8a2b8dd1b2..0276eaba0f 100644
--- a/Source/Android/app/src/main/res/values/strings.xml
+++ b/Source/Android/app/src/main/res/values/strings.xml
@@ -841,6 +841,23 @@ It can efficiently compress both junk data and encrypted Wii data.
GitHub
Support
\u00A9 2003–2015+ Dolphin Team. \u201cGameCube\u201d and \u201cWii\u201d are trademarks of Nintendo. Dolphin is not affiliated with Nintendo in any way.
+ System driver
+ The GPU driver that is part of the OS.
+
+
+ Select the GPU driver for Dolphin
+ Default
+ Install driver
+ Successfully switched to the system driver
+ GPU Driver
+ Installing the GPU driver…
+ GPU driver installed successfully
+ Failed to unzip the provided driver package
+ The supplied driver package is invalid due to missing metadata.
+ The supplied driver package contains invalid metadata, it may be corrupted.
+ Your device doesn\'t meet the minimum Android version requirements for the supplied driver.
+ The supplied driver package is already installed.
+ The selected file could not be found or accessed.
Emulated USB Devices
diff --git a/Source/Android/jni/CMakeLists.txt b/Source/Android/jni/CMakeLists.txt
index bc48ec26db..7218c64924 100644
--- a/Source/Android/jni/CMakeLists.txt
+++ b/Source/Android/jni/CMakeLists.txt
@@ -30,6 +30,7 @@ add_library(main SHARED
RiivolutionPatches.cpp
SkylanderConfig.cpp
WiiUtils.cpp
+ GpuDriver.cpp
)
target_link_libraries(main
@@ -50,6 +51,12 @@ PRIVATE
"-Wl,--no-whole-archive"
)
+target_include_directories(main
+PRIVATE
+ ${CMAKE_SOURCE_DIR}/Externals/libadrenotools/include
+ ${CMAKE_SOURCE_DIR}/Externals/VulkanMemoryAllocator/include
+)
+
file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/)
file(REMOVE_RECURSE ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/Sys/)
file(COPY ${CMAKE_SOURCE_DIR}/Data/Sys DESTINATION ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/)
diff --git a/Source/Android/jni/GpuDriver.cpp b/Source/Android/jni/GpuDriver.cpp
new file mode 100644
index 0000000000..0631ce81cb
--- /dev/null
+++ b/Source/Android/jni/GpuDriver.cpp
@@ -0,0 +1,147 @@
+// Copyright 2023 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on: Skyline Emulator Project
+// SPDX-License-Identifier: MPL-2.0
+// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
+
+#include
+
+#include "Common/IniFile.h"
+#include "jni/AndroidCommon/AndroidCommon.h"
+#include "jni/AndroidCommon/IDCache.h"
+
+#include
+#include
+#include
+#include
+#include "adrenotools/driver.h"
+
+#include "VideoBackends/Vulkan/VulkanContext.h"
+#include "VideoBackends/Vulkan/VulkanLoader.h"
+
+extern "C" {
+
+#if defined(_M_ARM_64)
+
+JNIEXPORT jobjectArray JNICALL
+Java_org_dolphinemu_dolphinemu_utils_GpuDriverHelper_00024Companion_getSystemDriverInfo(JNIEnv* env,
+ jobject)
+{
+ if (!Vulkan::LoadVulkanLibrary(true))
+ {
+ return nullptr;
+ }
+
+ u32 vk_api_version = 0;
+ VkInstance instance = Vulkan::VulkanContext::CreateVulkanInstance(WindowSystemType::Headless,
+ false, false, &vk_api_version);
+ if (!instance)
+ {
+ return nullptr;
+ }
+
+ if (!Vulkan::LoadVulkanInstanceFunctions(instance))
+ {
+ vkDestroyInstance(instance, nullptr);
+ return nullptr;
+ }
+
+ Vulkan::VulkanContext::GPUList gpu_list = Vulkan::VulkanContext::EnumerateGPUs(instance);
+
+ if (gpu_list.empty())
+ {
+ vkDestroyInstance(instance, nullptr);
+ Vulkan::UnloadVulkanLibrary();
+ return nullptr;
+ }
+
+ VkPhysicalDeviceProperties properties;
+ vkGetPhysicalDeviceProperties(gpu_list.front(), &properties);
+
+ std::string driverId;
+ if (vkGetPhysicalDeviceProperties2 && vk_api_version >= VK_VERSION_1_1)
+ {
+ VkPhysicalDeviceDriverProperties driverProperties;
+ driverProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
+ driverProperties.pNext = nullptr;
+ VkPhysicalDeviceProperties2 properties2;
+ properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
+ properties2.pNext = &driverProperties;
+ vkGetPhysicalDeviceProperties2(gpu_list.front(), &properties2);
+ driverId = fmt::format("{}", driverProperties.driverID);
+ }
+ else
+ {
+ driverId = "Unknown";
+ }
+
+ std::string driverVersion =
+ fmt::format("{}.{}.{}", VK_API_VERSION_MAJOR(properties.driverVersion),
+ VK_API_VERSION_MINOR(properties.driverVersion),
+ VK_API_VERSION_PATCH(properties.driverVersion));
+
+ vkDestroyInstance(instance, nullptr);
+ Vulkan::UnloadVulkanLibrary();
+
+ auto array = env->NewObjectArray(2, env->FindClass("java/lang/String"), nullptr);
+ env->SetObjectArrayElement(array, 0, ToJString(env, driverId));
+ env->SetObjectArrayElement(array, 1, ToJString(env, driverVersion));
+ return array;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_org_dolphinemu_dolphinemu_utils_GpuDriverHelper_00024Companion_supportsCustomDriverLoading(
+ JNIEnv* env, jobject instance)
+{
+ // If the KGSL device exists custom drivers can be loaded using adrenotools
+ return Vulkan::SupportsCustomDriver();
+}
+
+JNIEXPORT jboolean JNICALL
+Java_org_dolphinemu_dolphinemu_utils_GpuDriverHelper_00024Companion_supportsForceMaxGpuClocks(
+ JNIEnv* env, jobject instance)
+{
+ // If the KGSL device exists adrenotools can be used to set GPU turbo mode
+ return Vulkan::SupportsCustomDriver();
+}
+
+JNIEXPORT void JNICALL
+Java_org_dolphinemu_dolphinemu_utils_GpuDriverHelper_00024Companion_forceMaxGpuClocks(
+ JNIEnv* env, jobject instance, jboolean enable)
+{
+ adrenotools_set_turbo(enable);
+}
+
+#else
+
+JNIEXPORT jobjectArray JNICALL
+Java_org_dolphinemu_dolphinemu_utils_GpuDriverHelper_00024Companion_getSystemDriverInfo(
+ JNIEnv* env, jobject instance)
+{
+ auto array = env->NewObjectArray(0, env->FindClass("java/lang/String"), nullptr);
+ return array;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_org_dolphinemu_dolphinemu_utils_GpuDriverHelper_00024Companion_supportsCustomDriverLoading(
+ JNIEnv* env, jobject instance)
+{
+ return false;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_org_dolphinemu_dolphinemu_utils_GpuDriverHelper_00024Companion_supportsForceMaxGpuClocks(
+ JNIEnv* env, jobject instance)
+{
+ return false;
+}
+
+JNIEXPORT void JNICALL
+Java_org_dolphinemu_dolphinemu_utils_GpuDriverHelper_00024Companion_forceMaxGpuClocks(
+ JNIEnv* env, jobject instance, jboolean enable)
+{
+}
+
+#endif
+}
diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp
index 30f0f5995e..d73c9f3d75 100644
--- a/Source/Android/jni/MainAndroid.cpp
+++ b/Source/Android/jni/MainAndroid.cpp
@@ -371,6 +371,15 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_DirectoryInitializat
File::SetSysDirectory(path);
}
+JNIEXPORT void JNICALL
+Java_org_dolphinemu_dolphinemu_utils_DirectoryInitialization_SetGpuDriverDirectories(
+ JNIEnv* env, jclass, jstring jPath, jstring jLibPath)
+{
+ const std::string path = GetJString(env, jPath);
+ const std::string lib_path = GetJString(env, jLibPath);
+ File::SetGpuDriverDirectories(path, lib_path);
+}
+
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetUserDirectory(
JNIEnv* env, jclass, jstring jDirectory)
{
diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h
index b22033a5cd..44ada6dd19 100644
--- a/Source/Core/Common/CommonPaths.h
+++ b/Source/Core/Common/CommonPaths.h
@@ -163,3 +163,10 @@
// Subdirs in Config
#define GRAPHICSMOD_CONFIG_DIR "GraphicMods"
+
+// GPU drivers
+#define GPU_DRIVERS "GpuDrivers"
+#define GPU_DRIVERS_EXTRACTED "Extracted"
+#define GPU_DRIVERS_TMP "Tmp"
+#define GPU_DRIVERS_HOOK "Hook"
+#define GPU_DRIVERS_FILE_REDIRECT "FileRedirect"
diff --git a/Source/Core/Common/DynamicLibrary.cpp b/Source/Core/Common/DynamicLibrary.cpp
index 039b0a034c..7871198dda 100644
--- a/Source/Core/Common/DynamicLibrary.cpp
+++ b/Source/Core/Common/DynamicLibrary.cpp
@@ -24,11 +24,22 @@ DynamicLibrary::DynamicLibrary(const char* filename)
Open(filename);
}
+DynamicLibrary::DynamicLibrary(void* handle)
+{
+ m_handle = handle;
+}
+
DynamicLibrary::~DynamicLibrary()
{
Close();
}
+DynamicLibrary& DynamicLibrary::operator=(void* handle)
+{
+ m_handle = handle;
+ return *this;
+}
+
std::string DynamicLibrary::GetUnprefixedFilename(const char* filename)
{
#if defined(_WIN32)
diff --git a/Source/Core/Common/DynamicLibrary.h b/Source/Core/Common/DynamicLibrary.h
index edfcde8ff3..29cafbafb2 100644
--- a/Source/Core/Common/DynamicLibrary.h
+++ b/Source/Core/Common/DynamicLibrary.h
@@ -21,6 +21,8 @@ public:
// Automatically loads the specified library. Call IsOpen() to check validity before use.
DynamicLibrary(const char* filename);
+ DynamicLibrary(void* handle);
+
// Closes the library.
~DynamicLibrary();
@@ -30,6 +32,8 @@ public:
DynamicLibrary& operator=(const DynamicLibrary&) = delete;
DynamicLibrary& operator=(DynamicLibrary&&) = delete;
+ DynamicLibrary& operator=(void*);
+
// Returns the specified library name with the platform-specific suffix added.
static std::string GetUnprefixedFilename(const char* filename);
diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp
index 9a494ab2e0..f5842c14f9 100644
--- a/Source/Core/Common/FileUtil.cpp
+++ b/Source/Core/Common/FileUtil.cpp
@@ -66,6 +66,8 @@ namespace File
{
#ifdef ANDROID
static std::string s_android_sys_directory;
+static std::string s_android_driver_directory;
+static std::string s_android_lib_directory;
#endif
#ifdef __APPLE__
@@ -796,6 +798,34 @@ void SetSysDirectory(const std::string& path)
s_android_sys_directory);
s_android_sys_directory = path;
}
+
+void SetGpuDriverDirectories(const std::string& path, const std::string& lib_path)
+{
+ INFO_LOG_FMT(COMMON, "Setting Driver directory to {} and library path to {}", path, lib_path);
+ ASSERT_MSG(COMMON, s_android_driver_directory.empty(), "Driver directory already set to {}",
+ s_android_driver_directory);
+ ASSERT_MSG(COMMON, s_android_lib_directory.empty(), "Library directory already set to {}",
+ s_android_lib_directory);
+ s_android_driver_directory = path;
+ s_android_lib_directory = lib_path;
+}
+
+const std::string GetGpuDriverDirectory(unsigned int dir_index)
+{
+ switch (dir_index)
+ {
+ case D_GPU_DRIVERS_EXTRACTED:
+ return s_android_driver_directory + DIR_SEP GPU_DRIVERS_EXTRACTED DIR_SEP;
+ case D_GPU_DRIVERS_TMP:
+ return s_android_driver_directory + DIR_SEP GPU_DRIVERS_TMP DIR_SEP;
+ case D_GPU_DRIVERS_HOOKS:
+ return s_android_lib_directory;
+ case D_GPU_DRIVERS_FILE_REDIRECT:
+ return s_android_driver_directory + DIR_SEP GPU_DRIVERS_FILE_REDIRECT DIR_SEP;
+ }
+ return "";
+}
+
#endif
static std::string s_user_paths[NUM_PATH_INDICES];
diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h
index 2a2e112039..8d5f312d65 100644
--- a/Source/Core/Common/FileUtil.h
+++ b/Source/Core/Common/FileUtil.h
@@ -67,6 +67,10 @@ enum
D_GBAUSER_IDX,
D_GBASAVES_IDX,
D_WIISDCARDSYNCFOLDER_IDX,
+ D_GPU_DRIVERS_EXTRACTED,
+ D_GPU_DRIVERS_TMP,
+ D_GPU_DRIVERS_HOOKS,
+ D_GPU_DRIVERS_FILE_REDIRECT,
FIRST_FILE_USER_PATH_IDX,
F_DOLPHINCONFIG_IDX = FIRST_FILE_USER_PATH_IDX,
F_GCPADCONFIG_IDX,
@@ -228,6 +232,8 @@ const std::string& GetSysDirectory();
#ifdef ANDROID
void SetSysDirectory(const std::string& path);
+void SetGpuDriverDirectories(const std::string& path, const std::string& lib_path);
+const std::string GetGpuDriverDirectory(unsigned int dir_index);
#endif
#ifdef __APPLE__
diff --git a/Source/Core/Core/Config/GraphicsSettings.cpp b/Source/Core/Core/Config/GraphicsSettings.cpp
index ff621b48c2..f4c16b286c 100644
--- a/Source/Core/Core/Config/GraphicsSettings.cpp
+++ b/Source/Core/Core/Config/GraphicsSettings.cpp
@@ -111,6 +111,8 @@ const Info GFX_PREFER_GLES{{System::GFX, "Settings", "PreferGLES"}, false}
const Info GFX_MODS_ENABLE{{System::GFX, "Settings", "EnableMods"}, false};
+const Info GFX_DRIVER_LIB_NAME{{System::GFX, "Settings", "DriverLibName"}, ""};
+
// Graphics.Enhancements
const Info GFX_ENHANCE_FORCE_TEXTURE_FILTERING{
@@ -171,4 +173,5 @@ const Info GFX_HACK_NO_MIPMAPPING{{System::GFX, "Hacks", "NoMipmapping"},
// Graphics.GameSpecific
const Info GFX_PERF_QUERIES_ENABLE{{System::GFX, "GameSpecific", "PerfQueriesEnable"}, false};
+
} // namespace Config
diff --git a/Source/Core/Core/Config/GraphicsSettings.h b/Source/Core/Core/Config/GraphicsSettings.h
index ce10005da9..f45d0c4811 100644
--- a/Source/Core/Core/Config/GraphicsSettings.h
+++ b/Source/Core/Core/Config/GraphicsSettings.h
@@ -148,4 +148,8 @@ extern const Info GFX_HACK_NO_MIPMAPPING;
extern const Info GFX_PERF_QUERIES_ENABLE;
+// Android custom GPU drivers
+
+extern const Info GFX_DRIVER_LIB_NAME;
+
} // namespace Config
diff --git a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
index 7abf9a038b..ec9e1b51cb 100644
--- a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
+++ b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
@@ -48,11 +48,19 @@ PRIVATE
xxhash
)
+if (ANDROID AND _M_ARM_64)
+ target_link_libraries(videovulkan
+ PRIVATE
+ adrenotools
+ )
+endif()
+
# Only include the Vulkan headers when building the Vulkan backend
target_include_directories(videovulkan
PRIVATE
${CMAKE_SOURCE_DIR}/Externals/Vulkan/Include
${CMAKE_SOURCE_DIR}/Externals/VulkanMemoryAllocator/include
+ ${CMAKE_SOURCE_DIR}/Externals/libadrenotools/include
)
if(MSVC)
diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp
index 5b5f2ffaee..ed4d749878 100644
--- a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp
+++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp
@@ -143,7 +143,18 @@ VkInstance VulkanContext::CreateVulkanInstance(WindowSystemType wstype, bool ena
{
// The device itself may not support 1.1, so we check that before using any 1.1 functionality.
app_info.apiVersion = VK_MAKE_VERSION(1, 1, 0);
+ WARN_LOG_FMT(HOST_GPU, "Using Vulkan 1.1, supported: {}.{}",
+ VK_VERSION_MAJOR(supported_api_version),
+ VK_VERSION_MINOR(supported_api_version));
}
+ else
+ {
+ WARN_LOG_FMT(HOST_GPU, "Using Vulkan 1.0");
+ }
+ }
+ else
+ {
+ WARN_LOG_FMT(HOST_GPU, "Using Vulkan 1.0");
}
*out_vk_api_version = app_info.apiVersion;
diff --git a/Source/Core/VideoBackends/Vulkan/VulkanLoader.cpp b/Source/Core/VideoBackends/Vulkan/VulkanLoader.cpp
index 9b62244dfd..34c531fc2f 100644
--- a/Source/Core/VideoBackends/Vulkan/VulkanLoader.cpp
+++ b/Source/Core/VideoBackends/Vulkan/VulkanLoader.cpp
@@ -8,11 +8,18 @@
#include
#include
+#if defined(ANDROID)
+#include
+#include
+#endif
+
#include "Common/CommonFuncs.h"
#include "Common/DynamicLibrary.h"
#include "Common/FileUtil.h"
#include "Common/StringUtil.h"
+#include "VideoCommon/VideoConfig.h"
+
#define VULKAN_MODULE_ENTRY_POINT(name, required) PFN_##name name;
#define VULKAN_INSTANCE_ENTRY_POINT(name, required) PFN_##name name;
#define VULKAN_DEVICE_ENTRY_POINT(name, required) PFN_##name name;
@@ -36,9 +43,9 @@ static void ResetVulkanLibraryFunctionPointers()
static Common::DynamicLibrary s_vulkan_module;
-static bool OpenVulkanLibrary()
+static bool OpenVulkanLibrary(bool force_system_library)
{
-#ifdef __APPLE__
+#if defined(__APPLE__)
// Check if a path to a specific Vulkan library has been specified.
char* libvulkan_env = getenv("LIBVULKAN_PATH");
if (libvulkan_env && s_vulkan_module.Open(libvulkan_env))
@@ -48,6 +55,35 @@ static bool OpenVulkanLibrary()
std::string filename = File::GetBundleDirectory() + "/Contents/Frameworks/libMoltenVK.dylib";
return s_vulkan_module.Open(filename.c_str());
#else
+
+#if defined(ANDROID) && _M_ARM_64
+ const std::string& driver_lib_name = g_Config.customDriverLibraryName;
+
+ if (!force_system_library && !driver_lib_name.empty() && SupportsCustomDriver())
+ {
+ std::string tmp_dir = File::GetGpuDriverDirectory(D_GPU_DRIVERS_TMP);
+ std::string hook_dir = File::GetGpuDriverDirectory(D_GPU_DRIVERS_HOOKS);
+ std::string file_redirect_dir = File::GetGpuDriverDirectory(D_GPU_DRIVERS_FILE_REDIRECT);
+ std::string driver_dir = File::GetGpuDriverDirectory(D_GPU_DRIVERS_EXTRACTED);
+ INFO_LOG_FMT(HOST_GPU, "Loading driver: {}", driver_lib_name);
+
+ s_vulkan_module = adrenotools_open_libvulkan(
+ RTLD_NOW, ADRENOTOOLS_DRIVER_FILE_REDIRECT | ADRENOTOOLS_DRIVER_CUSTOM, tmp_dir.c_str(),
+ hook_dir.c_str(), driver_dir.c_str(), driver_lib_name.c_str(), file_redirect_dir.c_str(),
+ nullptr);
+ if (s_vulkan_module.IsOpen())
+ {
+ INFO_LOG_FMT(HOST_GPU, "Successfully loaded driver: {}", driver_lib_name);
+ return true;
+ }
+ else
+ {
+ WARN_LOG_FMT(HOST_GPU, "Loading driver {} failed.", driver_lib_name);
+ }
+ }
+#endif
+
+ WARN_LOG_FMT(HOST_GPU, "Loading system driver");
std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1);
if (s_vulkan_module.Open(filename.c_str()))
return true;
@@ -58,9 +94,9 @@ static bool OpenVulkanLibrary()
#endif
}
-bool LoadVulkanLibrary()
+bool LoadVulkanLibrary(bool force_system_library)
{
- if (!s_vulkan_module.IsOpen() && !OpenVulkanLibrary())
+ if (!s_vulkan_module.IsOpen() && !OpenVulkanLibrary(force_system_library))
return false;
#define VULKAN_MODULE_ENTRY_POINT(name, required) \
@@ -91,7 +127,7 @@ bool LoadVulkanInstanceFunctions(VkInstance instance)
*func_ptr = vkGetInstanceProcAddr(instance, name);
if (!(*func_ptr) && is_required)
{
- ERROR_LOG_FMT(VIDEO, "Vulkan: Failed to load required instance function {}", name);
+ ERROR_LOG_FMT(HOST_GPU, "Vulkan: Failed to load required instance function {}", name);
required_functions_missing = true;
}
};
@@ -111,7 +147,7 @@ bool LoadVulkanDeviceFunctions(VkDevice device)
*func_ptr = vkGetDeviceProcAddr(device, name);
if (!(*func_ptr) && is_required)
{
- ERROR_LOG_FMT(VIDEO, "Vulkan: Failed to load required device function {}", name);
+ ERROR_LOG_FMT(HOST_GPU, "Vulkan: Failed to load required device function {}", name);
required_functions_missing = true;
}
};
@@ -212,4 +248,18 @@ void LogVulkanResult(Common::Log::LogLevel level, const char* func_name, VkResul
static_cast(res), VkResultToString(res));
}
+#ifdef ANDROID
+static bool CheckKgslPresent()
+{
+ constexpr auto KgslPath{"/dev/kgsl-3d0"};
+
+ return access(KgslPath, F_OK) == 0;
+}
+
+bool SupportsCustomDriver()
+{
+ return android_get_device_api_level() >= 28 && CheckKgslPresent();
+}
+#endif
+
} // namespace Vulkan
diff --git a/Source/Core/VideoBackends/Vulkan/VulkanLoader.h b/Source/Core/VideoBackends/Vulkan/VulkanLoader.h
index 652b512dd4..d1a426d952 100644
--- a/Source/Core/VideoBackends/Vulkan/VulkanLoader.h
+++ b/Source/Core/VideoBackends/Vulkan/VulkanLoader.h
@@ -23,6 +23,10 @@
#include "vulkan/vulkan.h"
+#ifdef ANDROID
+#include
+#endif
+
// Currently, exclusive fullscreen is only supported on Windows.
#if defined(WIN32)
#define SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN 1
@@ -78,11 +82,15 @@
namespace Vulkan
{
-bool LoadVulkanLibrary();
+bool LoadVulkanLibrary(bool force_system_library = false);
bool LoadVulkanInstanceFunctions(VkInstance instance);
bool LoadVulkanDeviceFunctions(VkDevice device);
void UnloadVulkanLibrary();
+#ifdef ANDROID
+bool SupportsCustomDriver();
+#endif
+
const char* VkResultToString(VkResult res);
void LogVulkanResult(Common::Log::LogLevel level, const char* func_name, VkResult res,
const char* msg);
diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp
index 38ee680bf3..9284c8171f 100644
--- a/Source/Core/VideoCommon/VideoConfig.cpp
+++ b/Source/Core/VideoCommon/VideoConfig.cpp
@@ -172,6 +172,8 @@ void VideoConfig::Refresh()
bPerfQueriesEnable = Config::Get(Config::GFX_PERF_QUERIES_ENABLE);
bGraphicMods = Config::Get(Config::GFX_MODS_ENABLE);
+
+ customDriverLibraryName = Config::Get(Config::GFX_DRIVER_LIB_NAME);
}
void VideoConfig::VerifyValidity()
diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h
index a479276e6c..33f2a06ebe 100644
--- a/Source/Core/VideoCommon/VideoConfig.h
+++ b/Source/Core/VideoCommon/VideoConfig.h
@@ -214,6 +214,9 @@ struct VideoConfig final
int iShaderCompilerThreads = 0;
int iShaderPrecompilerThreads = 0;
+ // Loading custom drivers on Android
+ std::string customDriverLibraryName;
+
// Static config per API
// TODO: Move this out of VideoConfig
struct