Merge pull request #11867 from K0bin/adrenotools

Implement loading custom drivers on Android
This commit is contained in:
JMC47 2023-06-11 14:17:39 -04:00 committed by GitHub
commit 5c0581e990
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 904 additions and 36 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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")))

1
Externals/libadrenotools vendored Submodule

@ -0,0 +1 @@
Subproject commit f4ce3c9618e7ecfcdd238b17dad9a0b888f5de90

View File

@ -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'
}

View File

@ -41,7 +41,8 @@
android:supportsRtl="true"
android:isGame="true"
android:banner="@drawable/banner_tv"
android:hasFragileUserData="true">
android:hasFragileUserData="true"
android:extractNativeLibs="true">
<meta-data
android:name="android.max_aspect"
android:value="2.1"/>

View File

@ -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

View File

@ -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

View File

@ -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<String> =
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!!)
}
}
}

View File

@ -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"

View File

@ -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<SettingsItem>) {
@ -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"

View File

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

View File

@ -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<GpuDriverMetadataV1>(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,
)

View File

@ -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

View File

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

View File

@ -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<String>?
/**
* 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
}

View File

@ -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
}
}
}

View File

@ -841,6 +841,23 @@ It can efficiently compress both junk data and encrypted Wii data.
<string name="about_github"><a href="https://github.com/dolphin-emu/dolphin">GitHub</a></string>
<string name="about_support"><a href="https://forums.dolphin-emu.org/">Support</a></string>
<string name="about_copyright_warning">\u00A9 20032015+ Dolphin Team. \u201cGameCube\u201d and \u201cWii\u201d are trademarks of Nintendo. Dolphin is not affiliated with Nintendo in any way.</string>
<string name="system_driver">System driver</string>
<string name="system_driver_desc">The GPU driver that is part of the OS.</string>
<!-- Custom GPU drivers -->
<string name="gpu_driver_dialog_title">Select the GPU driver for Dolphin</string>
<string name="gpu_driver_dialog_system">Default</string>
<string name="gpu_driver_dialog_install">Install driver</string>
<string name="gpu_driver_dialog_uninstall_done">Successfully switched to the system driver</string>
<string name="gpu_driver_submenu">GPU Driver</string>
<string name="gpu_driver_install_inprogress">Installing the GPU driver…</string>
<string name="gpu_driver_install_success">GPU driver installed successfully</string>
<string name="gpu_driver_install_invalid_archive">Failed to unzip the provided driver package</string>
<string name="gpu_driver_install_missing_metadata">The supplied driver package is invalid due to missing metadata.</string>
<string name="gpu_driver_install_invalid_metadata">The supplied driver package contains invalid metadata, it may be corrupted.</string>
<string name="gpu_driver_install_unsupported_android_version">Your device doesn\'t meet the minimum Android version requirements for the supplied driver.</string>
<string name="gpu_driver_install_already_installed">The supplied driver package is already installed.</string>
<string name="gpu_driver_install_file_not_found">The selected file could not be found or accessed.</string>
<!-- Emulated USB Devices -->
<string name="emulated_usb_devices">Emulated USB Devices</string>

View File

@ -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/)

View File

@ -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 <jni.h>
#include "Common/IniFile.h"
#include "jni/AndroidCommon/AndroidCommon.h"
#include "jni/AndroidCommon/IDCache.h"
#include <dlfcn.h>
#include <fcntl.h>
#include <jni.h>
#include <unistd.h>
#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
}

View File

@ -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)
{

View File

@ -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"

View File

@ -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)

View File

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

View File

@ -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];

View File

@ -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__

View File

@ -111,6 +111,8 @@ const Info<bool> GFX_PREFER_GLES{{System::GFX, "Settings", "PreferGLES"}, false}
const Info<bool> GFX_MODS_ENABLE{{System::GFX, "Settings", "EnableMods"}, false};
const Info<std::string> GFX_DRIVER_LIB_NAME{{System::GFX, "Settings", "DriverLibName"}, ""};
// Graphics.Enhancements
const Info<TextureFilteringMode> GFX_ENHANCE_FORCE_TEXTURE_FILTERING{
@ -171,4 +173,5 @@ const Info<bool> GFX_HACK_NO_MIPMAPPING{{System::GFX, "Hacks", "NoMipmapping"},
// Graphics.GameSpecific
const Info<bool> GFX_PERF_QUERIES_ENABLE{{System::GFX, "GameSpecific", "PerfQueriesEnable"}, false};
} // namespace Config

View File

@ -148,4 +148,8 @@ extern const Info<bool> GFX_HACK_NO_MIPMAPPING;
extern const Info<bool> GFX_PERF_QUERIES_ENABLE;
// Android custom GPU drivers
extern const Info<std::string> GFX_DRIVER_LIB_NAME;
} // namespace Config

View File

@ -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)

View File

@ -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;

View File

@ -8,11 +8,18 @@
#include <cstdarg>
#include <cstdlib>
#if defined(ANDROID)
#include <adrenotools/driver.h>
#include <dlfcn.h>
#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<int>(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

View File

@ -23,6 +23,10 @@
#include "vulkan/vulkan.h"
#ifdef ANDROID
#include <unistd.h>
#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);

View File

@ -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()

View File

@ -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