Merge pull request #11867 from K0bin/adrenotools
Implement loading custom drivers on Android
This commit is contained in:
commit
5c0581e990
|
@ -54,3 +54,6 @@
|
||||||
[submodule "Externals/rcheevos/rcheevos"]
|
[submodule "Externals/rcheevos/rcheevos"]
|
||||||
path = Externals/rcheevos/rcheevos
|
path = Externals/rcheevos/rcheevos
|
||||||
url = https://github.com/RetroAchievements/rcheevos.git
|
url = https://github.com/RetroAchievements/rcheevos.git
|
||||||
|
[submodule "Externals/libadrenotools"]
|
||||||
|
path = Externals/libadrenotools
|
||||||
|
url = https://github.com/bylaws/libadrenotools.git
|
||||||
|
|
|
@ -728,6 +728,11 @@ if(ENABLE_VULKAN)
|
||||||
if(APPLE AND USE_BUNDLED_MOLTENVK)
|
if(APPLE AND USE_BUNDLED_MOLTENVK)
|
||||||
add_subdirectory(Externals/MoltenVK)
|
add_subdirectory(Externals/MoltenVK)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
if (ANDROID AND _M_ARM_64)
|
||||||
|
add_subdirectory(Externals/libadrenotools)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT WIN32 OR (NOT (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")))
|
if(NOT WIN32 OR (NOT (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")))
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit f4ce3c9618e7ecfcdd238b17dad9a0b888f5de90
|
|
@ -1,6 +1,7 @@
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'org.jetbrains.kotlin.android'
|
id 'org.jetbrains.kotlin.android'
|
||||||
|
id 'org.jetbrains.kotlin.plugin.serialization' version "1.7.20"
|
||||||
}
|
}
|
||||||
|
|
||||||
task copyProfile (type: Copy) {
|
task copyProfile (type: Copy) {
|
||||||
|
@ -110,6 +111,7 @@ android {
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path "../../../CMakeLists.txt"
|
path "../../../CMakeLists.txt"
|
||||||
|
version "3.22.1+"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace 'org.dolphinemu.dolphinemu'
|
namespace 'org.dolphinemu.dolphinemu'
|
||||||
|
@ -122,7 +124,7 @@ android {
|
||||||
abiFilters "arm64-v8a", "x86_64" //, "armeabi-v7a", "x86"
|
abiFilters "arm64-v8a", "x86_64" //, "armeabi-v7a", "x86"
|
||||||
|
|
||||||
// Remove the line below if you want to build the C++ unit tests
|
// 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
|
// For loading game covers from disk and GameTDB
|
||||||
implementation 'io.coil-kt:coil:2.2.2'
|
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'
|
implementation 'com.nononsenseapps:filepicker:4.2.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,8 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:isGame="true"
|
android:isGame="true"
|
||||||
android:banner="@drawable/banner_tv"
|
android:banner="@drawable/banner_tv"
|
||||||
android:hasFragileUserData="true">
|
android:hasFragileUserData="true"
|
||||||
|
android:extractNativeLibs="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.max_aspect"
|
android:name="android.max_aspect"
|
||||||
android:value="2.1"/>
|
android:value="2.1"/>
|
||||||
|
|
|
@ -62,6 +62,12 @@ enum class StringSetting(
|
||||||
Settings.SECTION_GFX_ENHANCEMENTS,
|
Settings.SECTION_GFX_ENHANCEMENTS,
|
||||||
"PostProcessingShader",
|
"PostProcessingShader",
|
||||||
""
|
""
|
||||||
|
),
|
||||||
|
GFX_DRIVER_LIB_NAME(
|
||||||
|
Settings.FILE_GFX,
|
||||||
|
Settings.SECTION_GFX_SETTINGS,
|
||||||
|
"DriverLibName",
|
||||||
|
""
|
||||||
);
|
);
|
||||||
|
|
||||||
override val isOverridden: Boolean
|
override val isOverridden: Boolean
|
||||||
|
|
|
@ -49,7 +49,8 @@ enum class MenuTag {
|
||||||
WIIMOTE_MOTION_INPUT_1("wiimote_motion_input", 0),
|
WIIMOTE_MOTION_INPUT_1("wiimote_motion_input", 0),
|
||||||
WIIMOTE_MOTION_INPUT_2("wiimote_motion_input", 1),
|
WIIMOTE_MOTION_INPUT_2("wiimote_motion_input", 1),
|
||||||
WIIMOTE_MOTION_INPUT_3("wiimote_motion_input", 2),
|
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
|
var tag: String
|
||||||
private set
|
private set
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// 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
|
package org.dolphinemu.dolphinemu.features.settings.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -20,6 +24,7 @@ import androidx.lifecycle.ViewModelProvider
|
||||||
import com.google.android.material.appbar.CollapsingToolbarLayout
|
import com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import org.dolphinemu.dolphinemu.NativeLibrary
|
import org.dolphinemu.dolphinemu.NativeLibrary
|
||||||
import org.dolphinemu.dolphinemu.R
|
import org.dolphinemu.dolphinemu.R
|
||||||
import org.dolphinemu.dolphinemu.databinding.ActivitySettingsBinding
|
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.features.settings.ui.SettingsFragment.Companion.newInstance
|
||||||
import org.dolphinemu.dolphinemu.ui.main.MainPresenter
|
import org.dolphinemu.dolphinemu.ui.main.MainPresenter
|
||||||
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper
|
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper
|
||||||
|
import org.dolphinemu.dolphinemu.utils.GpuDriverInstallResult
|
||||||
import org.dolphinemu.dolphinemu.utils.InsetsHelper
|
import org.dolphinemu.dolphinemu.utils.InsetsHelper
|
||||||
import org.dolphinemu.dolphinemu.utils.SerializableHelper.serializable
|
import org.dolphinemu.dolphinemu.utils.SerializableHelper.serializable
|
||||||
import org.dolphinemu.dolphinemu.utils.ThemeHelper.enableScrollTint
|
import org.dolphinemu.dolphinemu.utils.ThemeHelper.enableScrollTint
|
||||||
|
@ -165,8 +171,21 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||||
super.onActivityResult(requestCode, resultCode, result)
|
super.onActivityResult(requestCode, resultCode, result)
|
||||||
|
|
||||||
// If the user picked a file, as opposed to just backing out.
|
// If the user picked a file, as opposed to just backing out.
|
||||||
if (resultCode == RESULT_OK) {
|
if (resultCode != RESULT_OK) {
|
||||||
if (requestCode != MainPresenter.REQUEST_DIRECTORY) {
|
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 uri = canonicalizeIfPossible(result!!.data!!)
|
||||||
val validExtensions: Set<String> =
|
val validExtensions: Set<String> =
|
||||||
if (requestCode == MainPresenter.REQUEST_GAME_FILE) FileBrowserHelper.GAME_EXTENSIONS else FileBrowserHelper.RAW_EXTENSION
|
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)
|
contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||||
fragment!!.adapter!!.onFilePickerConfirmation(uri.toString())
|
fragment!!.adapter!!.onFilePickerConfirmation(uri.toString())
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
val path = FileBrowserHelper.getSelectedPath(result)
|
|
||||||
fragment!!.adapter!!.onFilePickerConfirmation(path!!)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,15 @@
|
||||||
package org.dolphinemu.dolphinemu.features.settings.ui
|
package org.dolphinemu.dolphinemu.features.settings.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
|
@ -14,10 +19,15 @@ import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
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.R
|
||||||
import org.dolphinemu.dolphinemu.databinding.FragmentSettingsBinding
|
import org.dolphinemu.dolphinemu.databinding.FragmentSettingsBinding
|
||||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings
|
import org.dolphinemu.dolphinemu.features.settings.model.Settings
|
||||||
import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem
|
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 org.dolphinemu.dolphinemu.utils.SerializableHelper.serializable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
@ -111,6 +121,11 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadSubMenu(menuKey: MenuTag) {
|
override fun loadSubMenu(menuKey: MenuTag) {
|
||||||
|
if (menuKey == MenuTag.GPU_DRIVERS) {
|
||||||
|
showGpuDriverDialog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
activityView!!.showSettingsFragment(
|
activityView!!.showSettingsFragment(
|
||||||
menuKey,
|
menuKey,
|
||||||
null,
|
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 {
|
companion object {
|
||||||
private const val ARGUMENT_MENU_TAG = "menu_tag"
|
private const val ARGUMENT_MENU_TAG = "menu_tag"
|
||||||
private const val ARGUMENT_GAME_ID = "game_id"
|
private const val ARGUMENT_GAME_ID = "game_id"
|
||||||
|
|
|
@ -4,11 +4,16 @@ package org.dolphinemu.dolphinemu.features.settings.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.collection.ArraySet
|
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.NativeLibrary
|
||||||
import org.dolphinemu.dolphinemu.R
|
import org.dolphinemu.dolphinemu.R
|
||||||
import org.dolphinemu.dolphinemu.activities.UserDataActivity
|
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.input.ui.ProfileDialogPresenter
|
||||||
import org.dolphinemu.dolphinemu.features.settings.model.*
|
import org.dolphinemu.dolphinemu.features.settings.model.*
|
||||||
import org.dolphinemu.dolphinemu.features.settings.model.view.*
|
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.ui.main.MainPresenter
|
||||||
import org.dolphinemu.dolphinemu.utils.*
|
import org.dolphinemu.dolphinemu.utils.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -45,6 +51,9 @@ class SettingsFragmentPresenter(
|
||||||
private var controllerNumber = 0
|
private var controllerNumber = 0
|
||||||
private var controllerType = 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) {
|
fun onCreate(menuTag: MenuTag, gameId: String?, extras: Bundle) {
|
||||||
this.gameId = gameId
|
this.gameId = gameId
|
||||||
this.menuTag = menuTag
|
this.menuTag = menuTag
|
||||||
|
@ -56,6 +65,11 @@ class SettingsFragmentPresenter(
|
||||||
controllerNumber = menuTag.subType
|
controllerNumber = menuTag.subType
|
||||||
} else if (menuTag.isSerialPort1Menu) {
|
} else if (menuTag.isSerialPort1Menu) {
|
||||||
serialPort1Type = extras.getInt(ARG_SERIALPORT1_TYPE)
|
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
|
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>) {
|
private fun addEnhanceSettings(sl: ArrayList<SettingsItem>) {
|
||||||
|
@ -2113,7 +2136,7 @@ class SettingsFragmentPresenter(
|
||||||
profileString: String,
|
profileString: String,
|
||||||
controllerNumber: Int
|
controllerNumber: Int
|
||||||
) {
|
) {
|
||||||
val profiles = ProfileDialogPresenter(menuTag).getProfileNames(false)
|
val profiles = ProfileDialogPresenter(menuTag!!).getProfileNames(false)
|
||||||
val profileKey = profileString + "Profile" + (controllerNumber + 1)
|
val profileKey = profileString + "Profile" + (controllerNumber + 1)
|
||||||
sl.add(
|
sl.add(
|
||||||
StringSingleChoiceSetting(
|
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 {
|
companion object {
|
||||||
private val LOG_TYPE_NAMES = NativeLibrary.GetLogTypeNames()
|
private val LOG_TYPE_NAMES = NativeLibrary.GetLogTypeNames()
|
||||||
const val ARG_CONTROLLER_TYPE = "controller_type"
|
const val ARG_CONTROLLER_TYPE = "controller_type"
|
||||||
|
|
|
@ -6,6 +6,7 @@ import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings
|
import org.dolphinemu.dolphinemu.features.settings.model.Settings
|
||||||
import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem
|
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
|
* 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.
|
* @param visible Whether the warning should be visible.
|
||||||
*/
|
*/
|
||||||
fun setOldControllerSettingsWarningVisibility(visible: Boolean)
|
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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
|
@ -286,6 +286,7 @@ class MainPresenter(private val mainView: MainView, private val activity: Fragme
|
||||||
const val REQUEST_WAD_FILE = 4
|
const val REQUEST_WAD_FILE = 4
|
||||||
const val REQUEST_WII_SAVE_FILE = 5
|
const val REQUEST_WII_SAVE_FILE = 5
|
||||||
const val REQUEST_NAND_BIN_FILE = 6
|
const val REQUEST_NAND_BIN_FILE = 6
|
||||||
|
const val REQUEST_GPU_DRIVER = 7
|
||||||
|
|
||||||
private var shouldRescanLibrary = true
|
private var shouldRescanLibrary = true
|
||||||
|
|
||||||
|
|
|
@ -18,18 +18,17 @@ import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.preference.PreferenceManager;
|
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.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
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.
|
* 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 volatile boolean areDirectoriesAvailable = false;
|
||||||
private static String userPath;
|
private static String userPath;
|
||||||
private static String sysPath;
|
private static String sysPath;
|
||||||
|
private static String driverPath;
|
||||||
private static boolean isUsingLegacyUserDirectory = false;
|
private static boolean isUsingLegacyUserDirectory = false;
|
||||||
|
|
||||||
public enum DirectoryInitializationState
|
public enum DirectoryInitializationState
|
||||||
|
@ -88,8 +88,7 @@ public final class DirectoryInitialization
|
||||||
directoryState.postValue(DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED);
|
directoryState.postValue(DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable private static File getLegacyUserDirectoryPath()
|
||||||
private static File getLegacyUserDirectoryPath()
|
|
||||||
{
|
{
|
||||||
File externalPath = Environment.getExternalStorageDirectory();
|
File externalPath = Environment.getExternalStorageDirectory();
|
||||||
if (externalPath == null)
|
if (externalPath == null)
|
||||||
|
@ -98,8 +97,7 @@ public final class DirectoryInitialization
|
||||||
return new File(externalPath, "dolphin-emu");
|
return new File(externalPath, "dolphin-emu");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable public static File getUserDirectoryPath(Context context)
|
||||||
public static File getUserDirectoryPath(Context context)
|
|
||||||
{
|
{
|
||||||
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()))
|
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()))
|
||||||
return null;
|
return null;
|
||||||
|
@ -107,8 +105,8 @@ public final class DirectoryInitialization
|
||||||
isUsingLegacyUserDirectory =
|
isUsingLegacyUserDirectory =
|
||||||
preferLegacyUserDirectory(context) && PermissionsHandler.hasWriteAccess(context);
|
preferLegacyUserDirectory(context) && PermissionsHandler.hasWriteAccess(context);
|
||||||
|
|
||||||
return isUsingLegacyUserDirectory ?
|
return isUsingLegacyUserDirectory ? getLegacyUserDirectoryPath() :
|
||||||
getLegacyUserDirectoryPath() : context.getExternalFilesDir(null);
|
context.getExternalFilesDir(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean setDolphinUserDirectory(Context context)
|
private static boolean setDolphinUserDirectory(Context context)
|
||||||
|
@ -153,6 +151,19 @@ public final class DirectoryInitialization
|
||||||
// Let the native code know where the Sys directory is.
|
// Let the native code know where the Sys directory is.
|
||||||
sysPath = sysDirectory.getPath();
|
sysPath = sysDirectory.getPath();
|
||||||
SetSysDirectory(sysPath);
|
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)
|
private static void deleteDirectoryRecursively(@NonNull final File file)
|
||||||
|
@ -213,6 +224,16 @@ public final class DirectoryInitialization
|
||||||
return sysPath;
|
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)
|
public static File getGameListCache(Context context)
|
||||||
{
|
{
|
||||||
return new File(context.getExternalCacheDir(), "gamelist.cache");
|
return new File(context.getExternalCacheDir(), "gamelist.cache");
|
||||||
|
@ -235,16 +256,14 @@ public final class DirectoryInitialization
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
Log.error("[DirectoryInitialization] Failed to copy asset file: " + asset +
|
Log.error("[DirectoryInitialization] Failed to copy asset file: " + asset + e.getMessage());
|
||||||
e.getMessage());
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void copyAssetFolder(String assetFolder, File outputFolder, Context context)
|
private static void copyAssetFolder(String assetFolder, File outputFolder, Context context)
|
||||||
{
|
{
|
||||||
Log.verbose("[DirectoryInitialization] Copying Folder " + assetFolder + " to " +
|
Log.verbose("[DirectoryInitialization] Copying Folder " + assetFolder + " to " + outputFolder);
|
||||||
outputFolder);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -267,8 +286,7 @@ public final class DirectoryInitialization
|
||||||
}
|
}
|
||||||
createdFolder = true;
|
createdFolder = true;
|
||||||
}
|
}
|
||||||
copyAssetFolder(assetFolder + File.separator + file, new File(outputFolder, file),
|
copyAssetFolder(assetFolder + File.separator + file, new File(outputFolder, file), context);
|
||||||
context);
|
|
||||||
copyAsset(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)
|
private static boolean preferLegacyUserDirectory(Context context)
|
||||||
{
|
{
|
||||||
return PermissionsHandler.isExternalStorageLegacy() &&
|
return PermissionsHandler.isExternalStorageLegacy() &&
|
||||||
!PermissionsHandler.isWritePermissionDenied() &&
|
!PermissionsHandler.isWritePermissionDenied() && isExternalFilesDirEmpty(context) &&
|
||||||
isExternalFilesDirEmpty(context) && legacyUserDirectoryExists();
|
legacyUserDirectoryExists();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isUsingLegacyUserDirectory()
|
public static boolean isUsingLegacyUserDirectory()
|
||||||
|
@ -389,4 +407,6 @@ public final class DirectoryInitialization
|
||||||
}
|
}
|
||||||
|
|
||||||
private static native void SetSysDirectory(String path);
|
private static native void SetSysDirectory(String path);
|
||||||
|
|
||||||
|
private static native void SetGpuDriverDirectories(String path, String libPath);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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_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_support"><a href="https://forums.dolphin-emu.org/">Support</a></string>
|
||||||
<string name="about_copyright_warning">\u00A9 2003–2015+ Dolphin Team. \u201cGameCube\u201d and \u201cWii\u201d are trademarks of Nintendo. Dolphin is not affiliated with Nintendo in any way.</string>
|
<string name="about_copyright_warning">\u00A9 2003–2015+ 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 -->
|
<!-- Emulated USB Devices -->
|
||||||
<string name="emulated_usb_devices">Emulated USB Devices</string>
|
<string name="emulated_usb_devices">Emulated USB Devices</string>
|
||||||
|
|
|
@ -30,6 +30,7 @@ add_library(main SHARED
|
||||||
RiivolutionPatches.cpp
|
RiivolutionPatches.cpp
|
||||||
SkylanderConfig.cpp
|
SkylanderConfig.cpp
|
||||||
WiiUtils.cpp
|
WiiUtils.cpp
|
||||||
|
GpuDriver.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(main
|
target_link_libraries(main
|
||||||
|
@ -50,6 +51,12 @@ PRIVATE
|
||||||
"-Wl,--no-whole-archive"
|
"-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(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(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/)
|
file(COPY ${CMAKE_SOURCE_DIR}/Data/Sys DESTINATION ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -371,6 +371,15 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_DirectoryInitializat
|
||||||
File::SetSysDirectory(path);
|
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(
|
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetUserDirectory(
|
||||||
JNIEnv* env, jclass, jstring jDirectory)
|
JNIEnv* env, jclass, jstring jDirectory)
|
||||||
{
|
{
|
||||||
|
|
|
@ -163,3 +163,10 @@
|
||||||
|
|
||||||
// Subdirs in Config
|
// Subdirs in Config
|
||||||
#define GRAPHICSMOD_CONFIG_DIR "GraphicMods"
|
#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"
|
||||||
|
|
|
@ -24,11 +24,22 @@ DynamicLibrary::DynamicLibrary(const char* filename)
|
||||||
Open(filename);
|
Open(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DynamicLibrary::DynamicLibrary(void* handle)
|
||||||
|
{
|
||||||
|
m_handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
DynamicLibrary::~DynamicLibrary()
|
DynamicLibrary::~DynamicLibrary()
|
||||||
{
|
{
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DynamicLibrary& DynamicLibrary::operator=(void* handle)
|
||||||
|
{
|
||||||
|
m_handle = handle;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
std::string DynamicLibrary::GetUnprefixedFilename(const char* filename)
|
std::string DynamicLibrary::GetUnprefixedFilename(const char* filename)
|
||||||
{
|
{
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
|
|
|
@ -21,6 +21,8 @@ public:
|
||||||
// Automatically loads the specified library. Call IsOpen() to check validity before use.
|
// Automatically loads the specified library. Call IsOpen() to check validity before use.
|
||||||
DynamicLibrary(const char* filename);
|
DynamicLibrary(const char* filename);
|
||||||
|
|
||||||
|
DynamicLibrary(void* handle);
|
||||||
|
|
||||||
// Closes the library.
|
// Closes the library.
|
||||||
~DynamicLibrary();
|
~DynamicLibrary();
|
||||||
|
|
||||||
|
@ -30,6 +32,8 @@ public:
|
||||||
DynamicLibrary& operator=(const DynamicLibrary&) = delete;
|
DynamicLibrary& operator=(const DynamicLibrary&) = delete;
|
||||||
DynamicLibrary& operator=(DynamicLibrary&&) = delete;
|
DynamicLibrary& operator=(DynamicLibrary&&) = delete;
|
||||||
|
|
||||||
|
DynamicLibrary& operator=(void*);
|
||||||
|
|
||||||
// Returns the specified library name with the platform-specific suffix added.
|
// Returns the specified library name with the platform-specific suffix added.
|
||||||
static std::string GetUnprefixedFilename(const char* filename);
|
static std::string GetUnprefixedFilename(const char* filename);
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,8 @@ namespace File
|
||||||
{
|
{
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
static std::string s_android_sys_directory;
|
static std::string s_android_sys_directory;
|
||||||
|
static std::string s_android_driver_directory;
|
||||||
|
static std::string s_android_lib_directory;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
@ -796,6 +798,34 @@ void SetSysDirectory(const std::string& path)
|
||||||
s_android_sys_directory);
|
s_android_sys_directory);
|
||||||
s_android_sys_directory = path;
|
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
|
#endif
|
||||||
|
|
||||||
static std::string s_user_paths[NUM_PATH_INDICES];
|
static std::string s_user_paths[NUM_PATH_INDICES];
|
||||||
|
|
|
@ -67,6 +67,10 @@ enum
|
||||||
D_GBAUSER_IDX,
|
D_GBAUSER_IDX,
|
||||||
D_GBASAVES_IDX,
|
D_GBASAVES_IDX,
|
||||||
D_WIISDCARDSYNCFOLDER_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,
|
FIRST_FILE_USER_PATH_IDX,
|
||||||
F_DOLPHINCONFIG_IDX = FIRST_FILE_USER_PATH_IDX,
|
F_DOLPHINCONFIG_IDX = FIRST_FILE_USER_PATH_IDX,
|
||||||
F_GCPADCONFIG_IDX,
|
F_GCPADCONFIG_IDX,
|
||||||
|
@ -228,6 +232,8 @@ const std::string& GetSysDirectory();
|
||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
void SetSysDirectory(const std::string& path);
|
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
|
#endif
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
|
|
@ -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<bool> GFX_MODS_ENABLE{{System::GFX, "Settings", "EnableMods"}, false};
|
||||||
|
|
||||||
|
const Info<std::string> GFX_DRIVER_LIB_NAME{{System::GFX, "Settings", "DriverLibName"}, ""};
|
||||||
|
|
||||||
// Graphics.Enhancements
|
// Graphics.Enhancements
|
||||||
|
|
||||||
const Info<TextureFilteringMode> GFX_ENHANCE_FORCE_TEXTURE_FILTERING{
|
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
|
// Graphics.GameSpecific
|
||||||
|
|
||||||
const Info<bool> GFX_PERF_QUERIES_ENABLE{{System::GFX, "GameSpecific", "PerfQueriesEnable"}, false};
|
const Info<bool> GFX_PERF_QUERIES_ENABLE{{System::GFX, "GameSpecific", "PerfQueriesEnable"}, false};
|
||||||
|
|
||||||
} // namespace Config
|
} // namespace Config
|
||||||
|
|
|
@ -148,4 +148,8 @@ extern const Info<bool> GFX_HACK_NO_MIPMAPPING;
|
||||||
|
|
||||||
extern const Info<bool> GFX_PERF_QUERIES_ENABLE;
|
extern const Info<bool> GFX_PERF_QUERIES_ENABLE;
|
||||||
|
|
||||||
|
// Android custom GPU drivers
|
||||||
|
|
||||||
|
extern const Info<std::string> GFX_DRIVER_LIB_NAME;
|
||||||
|
|
||||||
} // namespace Config
|
} // namespace Config
|
||||||
|
|
|
@ -48,11 +48,19 @@ PRIVATE
|
||||||
xxhash
|
xxhash
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (ANDROID AND _M_ARM_64)
|
||||||
|
target_link_libraries(videovulkan
|
||||||
|
PRIVATE
|
||||||
|
adrenotools
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Only include the Vulkan headers when building the Vulkan backend
|
# Only include the Vulkan headers when building the Vulkan backend
|
||||||
target_include_directories(videovulkan
|
target_include_directories(videovulkan
|
||||||
PRIVATE
|
PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/Externals/Vulkan/Include
|
${CMAKE_SOURCE_DIR}/Externals/Vulkan/Include
|
||||||
${CMAKE_SOURCE_DIR}/Externals/VulkanMemoryAllocator/include
|
${CMAKE_SOURCE_DIR}/Externals/VulkanMemoryAllocator/include
|
||||||
|
${CMAKE_SOURCE_DIR}/Externals/libadrenotools/include
|
||||||
)
|
)
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
|
|
|
@ -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.
|
// 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);
|
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;
|
*out_vk_api_version = app_info.apiVersion;
|
||||||
|
|
|
@ -8,11 +8,18 @@
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#if defined(ANDROID)
|
||||||
|
#include <adrenotools/driver.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "Common/CommonFuncs.h"
|
#include "Common/CommonFuncs.h"
|
||||||
#include "Common/DynamicLibrary.h"
|
#include "Common/DynamicLibrary.h"
|
||||||
#include "Common/FileUtil.h"
|
#include "Common/FileUtil.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/VideoConfig.h"
|
||||||
|
|
||||||
#define VULKAN_MODULE_ENTRY_POINT(name, required) PFN_##name name;
|
#define VULKAN_MODULE_ENTRY_POINT(name, required) PFN_##name name;
|
||||||
#define VULKAN_INSTANCE_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;
|
#define VULKAN_DEVICE_ENTRY_POINT(name, required) PFN_##name name;
|
||||||
|
@ -36,9 +43,9 @@ static void ResetVulkanLibraryFunctionPointers()
|
||||||
|
|
||||||
static Common::DynamicLibrary s_vulkan_module;
|
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.
|
// Check if a path to a specific Vulkan library has been specified.
|
||||||
char* libvulkan_env = getenv("LIBVULKAN_PATH");
|
char* libvulkan_env = getenv("LIBVULKAN_PATH");
|
||||||
if (libvulkan_env && s_vulkan_module.Open(libvulkan_env))
|
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";
|
std::string filename = File::GetBundleDirectory() + "/Contents/Frameworks/libMoltenVK.dylib";
|
||||||
return s_vulkan_module.Open(filename.c_str());
|
return s_vulkan_module.Open(filename.c_str());
|
||||||
#else
|
#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);
|
std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1);
|
||||||
if (s_vulkan_module.Open(filename.c_str()))
|
if (s_vulkan_module.Open(filename.c_str()))
|
||||||
return true;
|
return true;
|
||||||
|
@ -58,9 +94,9 @@ static bool OpenVulkanLibrary()
|
||||||
#endif
|
#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;
|
return false;
|
||||||
|
|
||||||
#define VULKAN_MODULE_ENTRY_POINT(name, required) \
|
#define VULKAN_MODULE_ENTRY_POINT(name, required) \
|
||||||
|
@ -91,7 +127,7 @@ bool LoadVulkanInstanceFunctions(VkInstance instance)
|
||||||
*func_ptr = vkGetInstanceProcAddr(instance, name);
|
*func_ptr = vkGetInstanceProcAddr(instance, name);
|
||||||
if (!(*func_ptr) && is_required)
|
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;
|
required_functions_missing = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -111,7 +147,7 @@ bool LoadVulkanDeviceFunctions(VkDevice device)
|
||||||
*func_ptr = vkGetDeviceProcAddr(device, name);
|
*func_ptr = vkGetDeviceProcAddr(device, name);
|
||||||
if (!(*func_ptr) && is_required)
|
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;
|
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));
|
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
|
} // namespace Vulkan
|
||||||
|
|
|
@ -23,6 +23,10 @@
|
||||||
|
|
||||||
#include "vulkan/vulkan.h"
|
#include "vulkan/vulkan.h"
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// Currently, exclusive fullscreen is only supported on Windows.
|
// Currently, exclusive fullscreen is only supported on Windows.
|
||||||
#if defined(WIN32)
|
#if defined(WIN32)
|
||||||
#define SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN 1
|
#define SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN 1
|
||||||
|
@ -78,11 +82,15 @@
|
||||||
|
|
||||||
namespace Vulkan
|
namespace Vulkan
|
||||||
{
|
{
|
||||||
bool LoadVulkanLibrary();
|
bool LoadVulkanLibrary(bool force_system_library = false);
|
||||||
bool LoadVulkanInstanceFunctions(VkInstance instance);
|
bool LoadVulkanInstanceFunctions(VkInstance instance);
|
||||||
bool LoadVulkanDeviceFunctions(VkDevice device);
|
bool LoadVulkanDeviceFunctions(VkDevice device);
|
||||||
void UnloadVulkanLibrary();
|
void UnloadVulkanLibrary();
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
bool SupportsCustomDriver();
|
||||||
|
#endif
|
||||||
|
|
||||||
const char* VkResultToString(VkResult res);
|
const char* VkResultToString(VkResult res);
|
||||||
void LogVulkanResult(Common::Log::LogLevel level, const char* func_name, VkResult res,
|
void LogVulkanResult(Common::Log::LogLevel level, const char* func_name, VkResult res,
|
||||||
const char* msg);
|
const char* msg);
|
||||||
|
|
|
@ -172,6 +172,8 @@ void VideoConfig::Refresh()
|
||||||
bPerfQueriesEnable = Config::Get(Config::GFX_PERF_QUERIES_ENABLE);
|
bPerfQueriesEnable = Config::Get(Config::GFX_PERF_QUERIES_ENABLE);
|
||||||
|
|
||||||
bGraphicMods = Config::Get(Config::GFX_MODS_ENABLE);
|
bGraphicMods = Config::Get(Config::GFX_MODS_ENABLE);
|
||||||
|
|
||||||
|
customDriverLibraryName = Config::Get(Config::GFX_DRIVER_LIB_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoConfig::VerifyValidity()
|
void VideoConfig::VerifyValidity()
|
||||||
|
|
|
@ -214,6 +214,9 @@ struct VideoConfig final
|
||||||
int iShaderCompilerThreads = 0;
|
int iShaderCompilerThreads = 0;
|
||||||
int iShaderPrecompilerThreads = 0;
|
int iShaderPrecompilerThreads = 0;
|
||||||
|
|
||||||
|
// Loading custom drivers on Android
|
||||||
|
std::string customDriverLibraryName;
|
||||||
|
|
||||||
// Static config per API
|
// Static config per API
|
||||||
// TODO: Move this out of VideoConfig
|
// TODO: Move this out of VideoConfig
|
||||||
struct
|
struct
|
||||||
|
|
Loading…
Reference in New Issue