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