Compare commits
159 Commits
android-15
...
android-17
Author | SHA1 | Date | |
---|---|---|---|
7645aa3839 | |||
6d509702bf | |||
0fb1090e30 | |||
93f4696e2a | |||
4d2090a9a9 | |||
c8f0c34202 | |||
09bfc852dc | |||
ace74bd066 | |||
f6ee53af14 | |||
6c6cb5745f | |||
3262c0f747 | |||
9323a1f9b2 | |||
f02a8d0ae9 | |||
8517d7cb44 | |||
cb4b4f3d6e | |||
21e7f86697 | |||
347b3bd18d | |||
755c45777f | |||
d677052e8c | |||
95bfc542aa | |||
d0c60605ab | |||
6697b665ca | |||
12178c694a | |||
de1e5584b3 | |||
1559984f77 | |||
467ac4fdfe | |||
69b7100dac | |||
14dc41d4b3 | |||
ad049f13aa | |||
20e0407235 | |||
4f569fd568 | |||
553dac2ae0 | |||
96abe0d7d3 | |||
47e44a6693 | |||
cf8c7d4ed3 | |||
5165ed9efd | |||
05e3db3ac9 | |||
e3491a9ee8 | |||
6a1ddc5028 | |||
b1d4804c07 | |||
c57ae803a6 | |||
db7b2bc8f1 | |||
31bf57a310 | |||
cae675343c | |||
35501ba41c | |||
419055e484 | |||
91290b9be4 | |||
820f113d9e | |||
373a1ff2ce | |||
4d6b6ba76c | |||
4aa713e861 | |||
9e9aed41be | |||
3d268b8480 | |||
ad7445d4cc | |||
3a30271219 | |||
bb5196aaae | |||
d3070cafa7 | |||
5cd3b6f58c | |||
bedc758fe7 | |||
76701185ad | |||
f1cb14eb54 | |||
f4f4a469a9 | |||
9e5b4052ed | |||
234867b84d | |||
61e8c5f798 | |||
4b60aec190 | |||
bbc0ed118d | |||
ecfba79d98 | |||
310834aea2 | |||
6a1fa9bb17 | |||
db8a601cf8 | |||
1bb76201e6 | |||
372bca5945 | |||
93c19a40bf | |||
d0a75580da | |||
345ec25532 | |||
a94721fde0 | |||
816c7a8d1f | |||
efe52db690 | |||
d61df0f400 | |||
b14547b8b6 | |||
97ad3e7530 | |||
0589a32f75 | |||
617dc0f822 | |||
fcfa8b680b | |||
94244437de | |||
53956a2990 | |||
a7731abb72 | |||
50fd029eaa | |||
a2b567dfd6 | |||
b770f6a985 | |||
797e8fdbc3 | |||
b8c5027686 | |||
65e646eeba | |||
fba3fa705d | |||
09e8fb75ce | |||
6ca530a721 | |||
e01c535178 | |||
7239547ead | |||
7fc06260d1 | |||
e357896674 | |||
225f4f40cb | |||
927be75616 | |||
00965e6c34 | |||
4bf1f217ae | |||
fcc85abe27 | |||
6851e93296 | |||
67660972c9 | |||
ffbba74c91 | |||
2b0cf73bf0 | |||
a093f3d47a | |||
4f600f746a | |||
360418f1a1 | |||
3bc7575c47 | |||
fde8dc1652 | |||
b8f83aa4bf | |||
85b1e17df6 | |||
4144c517a5 | |||
8ad5f2c506 | |||
2a3f84aaf2 | |||
030e6b3980 | |||
e8ad603cd9 | |||
b560ade663 | |||
d10464de30 | |||
64f68e9635 | |||
462ba1b360 | |||
4a86a55174 | |||
86d26914a2 | |||
6ae4177b25 | |||
f6bf8b3ed3 | |||
345fb6b226 | |||
87a9dc9489 | |||
6c6e8b8de0 | |||
5acffe75df | |||
ac222ceba2 | |||
f9d4827102 | |||
7ea7c72dde | |||
809230f634 | |||
698c854d5b | |||
ca5b135ddf | |||
dbddc627d4 | |||
62fc386bb4 | |||
f2eb3c579f | |||
2fce812026 | |||
e975f3cde9 | |||
6b5fb2063f | |||
70c3d36536 | |||
d590cfb9d0 | |||
ded419ef2b | |||
4c3f898789 | |||
46c259bb20 | |||
adc3079613 | |||
15bebf1695 | |||
5c840334b8 | |||
a05c242429 | |||
bd59934350 | |||
abfebe5cc4 | |||
a22a025c5b | |||
d5de9402ee |
@ -6,7 +6,12 @@
|
||||
export NDK_CCACHE="$(which ccache)"
|
||||
ccache -s
|
||||
|
||||
BUILD_FLAVOR=mainline
|
||||
BUILD_FLAVOR="mainline"
|
||||
|
||||
BUILD_TYPE="release"
|
||||
if [ "${GITHUB_REPOSITORY}" == "yuzu-emu/yuzu" ]; then
|
||||
BUILD_TYPE="relWithDebInfo"
|
||||
fi
|
||||
|
||||
if [ ! -z "${ANDROID_KEYSTORE_B64}" ]; then
|
||||
export ANDROID_KEYSTORE_FILE="${GITHUB_WORKSPACE}/ks.jks"
|
||||
@ -15,7 +20,7 @@ fi
|
||||
|
||||
cd src/android
|
||||
chmod +x ./gradlew
|
||||
./gradlew "assemble${BUILD_FLAVOR}Release" "bundle${BUILD_FLAVOR}Release"
|
||||
./gradlew "assemble${BUILD_FLAVOR}${BUILD_TYPE}" "bundle${BUILD_FLAVOR}${BUILD_TYPE}"
|
||||
|
||||
ccache -s
|
||||
|
||||
|
@ -7,9 +7,16 @@
|
||||
|
||||
REV_NAME="yuzu-${GITDATE}-${GITREV}"
|
||||
|
||||
BUILD_FLAVOR=mainline
|
||||
BUILD_FLAVOR="mainline"
|
||||
|
||||
cp src/android/app/build/outputs/apk/"${BUILD_FLAVOR}/release/app-${BUILD_FLAVOR}-release.apk" \
|
||||
BUILD_TYPE_LOWER="release"
|
||||
BUILD_TYPE_UPPER="Release"
|
||||
if [ "${GITHUB_REPOSITORY}" == "yuzu-emu/yuzu" ]; then
|
||||
BUILD_TYPE_LOWER="relWithDebInfo"
|
||||
BUILD_TYPE_UPPER="RelWithDebInfo"
|
||||
fi
|
||||
|
||||
cp src/android/app/build/outputs/apk/"${BUILD_FLAVOR}/${BUILD_TYPE_LOWER}/app-${BUILD_FLAVOR}-${BUILD_TYPE_LOWER}.apk" \
|
||||
"artifacts/${REV_NAME}.apk"
|
||||
cp src/android/app/build/outputs/bundle/"${BUILD_FLAVOR}Release"/"app-${BUILD_FLAVOR}-release.aab" \
|
||||
cp src/android/app/build/outputs/bundle/"${BUILD_FLAVOR}${BUILD_TYPE_UPPER}"/"app-${BUILD_FLAVOR}-${BUILD_TYPE_LOWER}.aab" \
|
||||
"artifacts/${REV_NAME}.aab"
|
||||
|
3
.github/workflows/verify.yml
vendored
3
.github/workflows/verify.yml
vendored
@ -79,7 +79,8 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
brew install autoconf automake boost@1.83 ccache ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@5 sdl2 speexdsp zlib zlib zstd
|
||||
# workaround for https://github.com/actions/setup-python/issues/577
|
||||
brew install autoconf automake boost@1.83 ccache ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@5 sdl2 speexdsp zlib zlib zstd || brew link --overwrite python@3.12
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
|
@ -142,6 +142,9 @@ if (YUZU_USE_BUNDLED_VCPKG)
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
list(APPEND VCPKG_MANIFEST_FEATURES "web-service")
|
||||
endif()
|
||||
if (ANDROID)
|
||||
list(APPEND VCPKG_MANIFEST_FEATURES "android")
|
||||
endif()
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
|
||||
elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
|
||||
@ -302,7 +305,7 @@ find_package(ZLIB 1.2 REQUIRED)
|
||||
find_package(zstd 1.5 REQUIRED)
|
||||
|
||||
if (NOT YUZU_USE_EXTERNAL_VULKAN_HEADERS)
|
||||
find_package(Vulkan 1.3.256 REQUIRED)
|
||||
find_package(Vulkan 1.3.274 REQUIRED)
|
||||
endif()
|
||||
|
||||
if (ENABLE_LIBUSB)
|
||||
|
@ -1,6 +1,10 @@
|
||||
| Pull Request | Commit | Title | Author | Merged? |
|
||||
|----|----|----|----|----|
|
||||
| [12335](https://github.com/yuzu-emu/yuzu//pull/12335) | [`edcd6d547`](https://github.com/yuzu-emu/yuzu//pull/12335/files) | android: Game Properties | [t895](https://github.com/t895/) | Yes |
|
||||
| [12454](https://github.com/yuzu-emu/yuzu//pull/12454) | [`3a4e7d45f`](https://github.com/yuzu-emu/yuzu//pull/12454/files) | core_timing: minor refactors | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [12466](https://github.com/yuzu-emu/yuzu//pull/12466) | [`adb2af0a2`](https://github.com/yuzu-emu/yuzu//pull/12466/files) | core: track separate heap allocation for linux | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [12501](https://github.com/yuzu-emu/yuzu//pull/12501) | [`d1c99c5d5`](https://github.com/yuzu-emu/yuzu//pull/12501/files) | ips_layer: prevent out of bounds access with offset exceeding module size | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [12513](https://github.com/yuzu-emu/yuzu//pull/12513) | [`558192abf`](https://github.com/yuzu-emu/yuzu//pull/12513/files) | jit: use code memory handles correctly | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||
| [12518](https://github.com/yuzu-emu/yuzu//pull/12518) | [`aa4d15594`](https://github.com/yuzu-emu/yuzu//pull/12518/files) | android: Migrate remaining settings to ini | [t895](https://github.com/t895/) | Yes |
|
||||
|
||||
|
||||
End of merge log. You can find the original README.md below the break.
|
||||
|
2
externals/Vulkan-Headers
vendored
2
externals/Vulkan-Headers
vendored
Submodule externals/Vulkan-Headers updated: df60f03168...80207f9da8
2
externals/vcpkg
vendored
2
externals/vcpkg
vendored
Submodule externals/vcpkg updated: ef2eef1734...a42af01b72
@ -10,7 +10,7 @@ plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("kotlin-parcelize")
|
||||
kotlin("plugin.serialization") version "1.8.21"
|
||||
kotlin("plugin.serialization") version "1.9.20"
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
|
||||
}
|
||||
@ -174,7 +174,8 @@ android {
|
||||
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
||||
"-DYUZU_USE_BUNDLED_VCPKG=ON",
|
||||
"-DYUZU_USE_BUNDLED_FFMPEG=ON",
|
||||
"-DYUZU_ENABLE_LTO=ON"
|
||||
"-DYUZU_ENABLE_LTO=ON",
|
||||
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
|
||||
)
|
||||
|
||||
abiFilters("arm64-v8a", "x86_64")
|
||||
|
@ -49,6 +49,7 @@ import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.NfcReader
|
||||
import org.yuzu.yuzu_emu.utils.ThemeHelper
|
||||
import java.text.NumberFormat
|
||||
@ -170,6 +171,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
stopMotionSensorListener()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
NativeConfig.saveGlobalConfig()
|
||||
}
|
||||
|
||||
override fun onUserLeaveHint() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) {
|
||||
|
@ -14,7 +14,7 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
|
||||
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
|
||||
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
|
||||
import org.yuzu.yuzu_emu.model.GameProperty
|
||||
import org.yuzu.yuzu_emu.model.InstallableProperty
|
||||
@ -42,7 +42,7 @@ class GamePropertiesAdapter(
|
||||
}
|
||||
|
||||
else -> InstallablePropertyViewHolder(
|
||||
CardInstallableBinding.inflate(
|
||||
CardInstallableIconBinding.inflate(
|
||||
inflater,
|
||||
parent,
|
||||
false
|
||||
@ -107,13 +107,20 @@ class GamePropertiesAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
inner class InstallablePropertyViewHolder(val binding: CardInstallableBinding) :
|
||||
inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) :
|
||||
GamePropertyViewHolder(binding.root) {
|
||||
override fun bind(property: GameProperty) {
|
||||
val installableProperty = property as InstallableProperty
|
||||
|
||||
binding.title.setText(installableProperty.titleId)
|
||||
binding.description.setText(installableProperty.descriptionId)
|
||||
binding.icon.setImageDrawable(
|
||||
ResourcesCompat.getDrawable(
|
||||
binding.icon.context.resources,
|
||||
installableProperty.iconId,
|
||||
binding.icon.context.theme
|
||||
)
|
||||
)
|
||||
|
||||
if (installableProperty.install != null) {
|
||||
binding.buttonInstall.visibility = View.VISIBLE
|
||||
|
@ -22,6 +22,9 @@ interface AbstractSetting {
|
||||
get() = NativeConfig.usingGlobal(key)
|
||||
set(value) = NativeConfig.setGlobal(key, value)
|
||||
|
||||
val isSaveable: Boolean
|
||||
get() = NativeConfig.getIsSaveable(key)
|
||||
|
||||
fun getValueAsString(needsGlobal: Boolean = false): String
|
||||
|
||||
fun reset()
|
||||
|
@ -18,7 +18,14 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
||||
RENDERER_DEBUG("debug"),
|
||||
PICTURE_IN_PICTURE("picture_in_picture"),
|
||||
USE_CUSTOM_RTC("custom_rtc_enabled");
|
||||
USE_CUSTOM_RTC("custom_rtc_enabled"),
|
||||
BLACK_BACKGROUNDS("black_backgrounds"),
|
||||
JOYSTICK_REL_CENTER("joystick_rel_center"),
|
||||
DPAD_SLIDE("dpad_slide"),
|
||||
HAPTIC_FEEDBACK("haptic_feedback"),
|
||||
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
|
||||
SHOW_INPUT_OVERLAY("show_input_overlay"),
|
||||
TOUCHSCREEN("touchscreen");
|
||||
|
||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||
NativeConfig.getBoolean(key, needsGlobal)
|
||||
@ -32,8 +39,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
|
||||
override val defaultValue: Boolean by lazy { NativeConfig.getDefaultToString(key).toBoolean() }
|
||||
|
||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
||||
if (getBoolean(needsGlobal)) "1" else "0"
|
||||
override fun getValueAsString(needsGlobal: Boolean): String = getBoolean(needsGlobal).toString()
|
||||
|
||||
override fun reset() = NativeConfig.setBoolean(key, defaultValue)
|
||||
}
|
||||
|
@ -18,7 +18,12 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
RENDERER_ANTI_ALIASING("anti_aliasing"),
|
||||
RENDERER_SCREEN_LAYOUT("screen_layout"),
|
||||
RENDERER_ASPECT_RATIO("aspect_ratio"),
|
||||
AUDIO_OUTPUT_ENGINE("output_engine");
|
||||
AUDIO_OUTPUT_ENGINE("output_engine"),
|
||||
MAX_ANISOTROPY("max_anisotropy"),
|
||||
THEME("theme"),
|
||||
THEME_MODE("theme_mode"),
|
||||
OVERLAY_SCALE("control_scale"),
|
||||
OVERLAY_OPACITY("control_opacity");
|
||||
|
||||
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
|
||||
|
||||
|
@ -15,18 +15,10 @@ object Settings {
|
||||
SECTION_DEBUG(R.string.preferences_debug);
|
||||
}
|
||||
|
||||
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
||||
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
|
||||
|
||||
const val PREF_OVERLAY_VERSION = "OverlayVersion"
|
||||
const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
|
||||
const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
|
||||
const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
|
||||
val overlayLayoutPrefs = listOf(
|
||||
PREF_LANDSCAPE_OVERLAY_VERSION,
|
||||
PREF_PORTRAIT_OVERLAY_VERSION,
|
||||
PREF_FOLDABLE_OVERLAY_VERSION
|
||||
)
|
||||
|
||||
// Deprecated input overlay preference keys
|
||||
const val PREF_CONTROL_SCALE = "controlScale"
|
||||
const val PREF_CONTROL_OPACITY = "controlOpacity"
|
||||
const val PREF_TOUCH_ENABLED = "isTouchEnabled"
|
||||
@ -47,23 +39,12 @@ object Settings {
|
||||
const val PREF_BUTTON_STICK_R = "buttonToggle14"
|
||||
const val PREF_BUTTON_HOME = "buttonToggle15"
|
||||
const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
|
||||
|
||||
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
|
||||
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
|
||||
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
|
||||
const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
|
||||
const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
|
||||
|
||||
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
||||
const val PREF_THEME = "Theme"
|
||||
const val PREF_THEME_MODE = "ThemeMode"
|
||||
const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
|
||||
|
||||
val overlayPreferences = listOf(
|
||||
PREF_OVERLAY_VERSION,
|
||||
PREF_CONTROL_SCALE,
|
||||
PREF_CONTROL_OPACITY,
|
||||
PREF_TOUCH_ENABLED,
|
||||
PREF_BUTTON_A,
|
||||
PREF_BUTTON_B,
|
||||
PREF_BUTTON_X,
|
||||
@ -83,6 +64,21 @@ object Settings {
|
||||
PREF_BUTTON_STICK_R
|
||||
)
|
||||
|
||||
// Deprecated layout preference keys
|
||||
const val PREF_LANDSCAPE_SUFFIX = "_Landscape"
|
||||
const val PREF_PORTRAIT_SUFFIX = "_Portrait"
|
||||
const val PREF_FOLDABLE_SUFFIX = "_Foldable"
|
||||
val overlayLayoutSuffixes = listOf(
|
||||
PREF_LANDSCAPE_SUFFIX,
|
||||
PREF_PORTRAIT_SUFFIX,
|
||||
PREF_FOLDABLE_SUFFIX
|
||||
)
|
||||
|
||||
// Deprecated theme preference keys
|
||||
const val PREF_THEME = "Theme"
|
||||
const val PREF_THEME_MODE = "ThemeMode"
|
||||
const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
|
||||
|
||||
const val LayoutOption_Unspecified = 0
|
||||
const val LayoutOption_MobilePortrait = 4
|
||||
const val LayoutOption_MobileLandscape = 5
|
||||
|
@ -30,6 +30,11 @@ abstract class SettingsItem(
|
||||
|
||||
val isEditable: Boolean
|
||||
get() {
|
||||
// Can't edit settings that aren't saveable in per-game config even if they are switchable
|
||||
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!NativeLibrary.isRunning()) return true
|
||||
|
||||
// Prevent editing settings that were modified in per-game config while editing global
|
||||
@ -37,6 +42,7 @@ abstract class SettingsItem(
|
||||
if (!NativeConfig.isPerGameConfigLoaded() && !setting.global) {
|
||||
return false
|
||||
}
|
||||
|
||||
return setting.isRuntimeModifiable
|
||||
}
|
||||
|
||||
@ -59,6 +65,7 @@ abstract class SettingsItem(
|
||||
val emptySetting = object : AbstractSetting {
|
||||
override val key: String = ""
|
||||
override val defaultValue: Any = false
|
||||
override val isSaveable = true
|
||||
override fun getValueAsString(needsGlobal: Boolean): String = ""
|
||||
override fun reset() {}
|
||||
}
|
||||
@ -236,6 +243,15 @@ abstract class SettingsItem(
|
||||
R.string.renderer_reactive_flushing_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.MAX_ANISOTROPY,
|
||||
R.string.anisotropic_filtering,
|
||||
R.string.anisotropic_filtering_description,
|
||||
R.array.anisoEntries,
|
||||
R.array.anisoValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.AUDIO_OUTPUT_ENGINE,
|
||||
@ -291,6 +307,7 @@ abstract class SettingsItem(
|
||||
|
||||
override val key: String = FASTMEM_COMBINED
|
||||
override val isRuntimeModifiable: Boolean = false
|
||||
override val pairedSettingKey = BooleanSetting.CPU_DEBUG_MODE.key
|
||||
override val defaultValue: Boolean = true
|
||||
override val isSwitchable: Boolean = true
|
||||
override var global: Boolean
|
||||
@ -303,6 +320,8 @@ abstract class SettingsItem(
|
||||
BooleanSetting.FASTMEM_EXCLUSIVES.global = value
|
||||
}
|
||||
|
||||
override val isSaveable = true
|
||||
|
||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
||||
getBoolean().toString()
|
||||
|
||||
|
@ -3,10 +3,8 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.ui
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
@ -29,9 +27,6 @@ class SettingsFragmentPresenter(
|
||||
) {
|
||||
private var settingsList = ArrayList<SettingsItem>()
|
||||
|
||||
private val preferences: SharedPreferences
|
||||
get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
||||
// Extension for altering settings list based on each setting's properties
|
||||
fun ArrayList<SettingsItem>.add(key: String) {
|
||||
val item = SettingsItem.settingsItems[key]!!
|
||||
@ -149,6 +144,7 @@ class SettingsFragmentPresenter(
|
||||
add(IntSetting.RENDERER_VSYNC.key)
|
||||
add(IntSetting.RENDERER_SCALING_FILTER.key)
|
||||
add(IntSetting.RENDERER_ANTI_ALIASING.key)
|
||||
add(IntSetting.MAX_ANISOTROPY.key)
|
||||
add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
|
||||
add(IntSetting.RENDERER_ASPECT_RATIO.key)
|
||||
add(BooleanSetting.PICTURE_IN_PICTURE.key)
|
||||
@ -169,25 +165,19 @@ class SettingsFragmentPresenter(
|
||||
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
val theme: AbstractIntSetting = object : AbstractIntSetting {
|
||||
override fun getInt(needsGlobal: Boolean): Int =
|
||||
preferences.getInt(Settings.PREF_THEME, 0)
|
||||
|
||||
override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME.getInt()
|
||||
override fun setInt(value: Int) {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_THEME, value)
|
||||
.apply()
|
||||
IntSetting.THEME.setInt(value)
|
||||
settingsViewModel.setShouldRecreate(true)
|
||||
}
|
||||
|
||||
override val key: String = Settings.PREF_THEME
|
||||
override val isRuntimeModifiable: Boolean = false
|
||||
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
|
||||
override val defaultValue: Int = 0
|
||||
override fun reset() {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_THEME, defaultValue)
|
||||
.apply()
|
||||
}
|
||||
override val key: String = IntSetting.THEME.key
|
||||
override val isRuntimeModifiable: Boolean = IntSetting.THEME.isRuntimeModifiable
|
||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
||||
IntSetting.THEME.getValueAsString()
|
||||
|
||||
override val defaultValue: Int = IntSetting.THEME.defaultValue
|
||||
override fun reset() = IntSetting.THEME.setInt(defaultValue)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
@ -213,24 +203,22 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
|
||||
val themeMode: AbstractIntSetting = object : AbstractIntSetting {
|
||||
override fun getInt(needsGlobal: Boolean): Int =
|
||||
preferences.getInt(Settings.PREF_THEME_MODE, -1)
|
||||
|
||||
override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME_MODE.getInt()
|
||||
override fun setInt(value: Int) {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_THEME_MODE, value)
|
||||
.apply()
|
||||
IntSetting.THEME_MODE.setInt(value)
|
||||
settingsViewModel.setShouldRecreate(true)
|
||||
}
|
||||
|
||||
override val key: String = Settings.PREF_THEME_MODE
|
||||
override val isRuntimeModifiable: Boolean = false
|
||||
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
|
||||
override val defaultValue: Int = -1
|
||||
override val key: String = IntSetting.THEME_MODE.key
|
||||
override val isRuntimeModifiable: Boolean =
|
||||
IntSetting.THEME_MODE.isRuntimeModifiable
|
||||
|
||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
||||
IntSetting.THEME_MODE.getValueAsString()
|
||||
|
||||
override val defaultValue: Int = IntSetting.THEME_MODE.defaultValue
|
||||
override fun reset() {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
|
||||
.apply()
|
||||
IntSetting.THEME_MODE.setInt(defaultValue)
|
||||
settingsViewModel.setShouldRecreate(true)
|
||||
}
|
||||
}
|
||||
@ -247,25 +235,24 @@ class SettingsFragmentPresenter(
|
||||
|
||||
val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
|
||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||
preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
|
||||
BooleanSetting.BLACK_BACKGROUNDS.getBoolean()
|
||||
|
||||
override fun setBoolean(value: Boolean) {
|
||||
preferences.edit()
|
||||
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
|
||||
.apply()
|
||||
BooleanSetting.BLACK_BACKGROUNDS.setBoolean(value)
|
||||
settingsViewModel.setShouldRecreate(true)
|
||||
}
|
||||
|
||||
override val key: String = Settings.PREF_BLACK_BACKGROUNDS
|
||||
override val isRuntimeModifiable: Boolean = false
|
||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
||||
getBoolean().toString()
|
||||
override val key: String = BooleanSetting.BLACK_BACKGROUNDS.key
|
||||
override val isRuntimeModifiable: Boolean =
|
||||
BooleanSetting.BLACK_BACKGROUNDS.isRuntimeModifiable
|
||||
|
||||
override val defaultValue: Boolean = false
|
||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
||||
BooleanSetting.BLACK_BACKGROUNDS.getValueAsString()
|
||||
|
||||
override val defaultValue: Boolean = BooleanSetting.BLACK_BACKGROUNDS.defaultValue
|
||||
override fun reset() {
|
||||
preferences.edit()
|
||||
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
|
||||
.apply()
|
||||
BooleanSetting.BLACK_BACKGROUNDS
|
||||
.setBoolean(BooleanSetting.BLACK_BACKGROUNDS.defaultValue)
|
||||
settingsViewModel.setShouldRecreate(true)
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
@ -33,7 +32,6 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.window.layout.FoldingFeature
|
||||
import androidx.window.layout.WindowInfoTracker
|
||||
import androidx.window.layout.WindowLayoutInfo
|
||||
@ -46,22 +44,22 @@ import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||
import org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayControl
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import java.lang.NullPointerException
|
||||
|
||||
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
private lateinit var preferences: SharedPreferences
|
||||
private lateinit var emulationState: EmulationState
|
||||
private var emulationActivity: EmulationActivity? = null
|
||||
private var perfStatsUpdater: (() -> Unit)? = null
|
||||
@ -128,8 +126,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
return
|
||||
}
|
||||
|
||||
if (args.custom) {
|
||||
SettingsFile.loadCustomConfig(args.game!!)
|
||||
// Always load custom settings when launching a game from an intent
|
||||
if (args.custom || intentGame != null) {
|
||||
SettingsFile.loadCustomConfig(game)
|
||||
NativeConfig.unloadPerGameConfig()
|
||||
} else {
|
||||
NativeConfig.reloadGlobalConfig()
|
||||
@ -140,7 +139,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
// So this fragment doesn't restart on configuration changes; i.e. rotation.
|
||||
retainInstance = true
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
emulationState = EmulationState(game.path)
|
||||
}
|
||||
|
||||
@ -381,24 +379,25 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
updateScreenLayout()
|
||||
val showInputOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
|
||||
if (emulationActivity?.isInPictureInPictureMode == true) {
|
||||
if (binding.drawerLayout.isOpen) {
|
||||
binding.drawerLayout.close()
|
||||
}
|
||||
if (EmulationMenuSettings.showOverlay) {
|
||||
if (showInputOverlay) {
|
||||
binding.surfaceInputOverlay.visibility = View.INVISIBLE
|
||||
}
|
||||
} else {
|
||||
if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
|
||||
if (showInputOverlay && emulationViewModel.emulationStarted.value) {
|
||||
binding.surfaceInputOverlay.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.surfaceInputOverlay.visibility = View.INVISIBLE
|
||||
}
|
||||
if (!isInFoldableLayout) {
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
binding.surfaceInputOverlay.layout = InputOverlay.PORTRAIT
|
||||
binding.surfaceInputOverlay.layout = OverlayLayout.Portrait
|
||||
} else {
|
||||
binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE
|
||||
binding.surfaceInputOverlay.layout = OverlayLayout.Landscape
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -422,17 +421,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
private fun resetInputOverlay() {
|
||||
preferences.edit()
|
||||
.remove(Settings.PREF_CONTROL_SCALE)
|
||||
.remove(Settings.PREF_CONTROL_OPACITY)
|
||||
.apply()
|
||||
IntSetting.OVERLAY_SCALE.reset()
|
||||
IntSetting.OVERLAY_OPACITY.reset()
|
||||
binding.surfaceInputOverlay.post {
|
||||
binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateShowFpsOverlay() {
|
||||
if (EmulationMenuSettings.showFps) {
|
||||
if (BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()) {
|
||||
val SYSTEM_FPS = 0
|
||||
val FPS = 1
|
||||
val FRAMETIME = 2
|
||||
@ -495,7 +492,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
binding.inGameMenu.layoutParams.height = it.bounds.bottom
|
||||
|
||||
isInFoldableLayout = true
|
||||
binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE
|
||||
binding.surfaceInputOverlay.layout = OverlayLayout.Foldable
|
||||
}
|
||||
}
|
||||
it.isSeparating
|
||||
@ -534,18 +531,22 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu)
|
||||
|
||||
popup.menu.apply {
|
||||
findItem(R.id.menu_toggle_fps).isChecked = EmulationMenuSettings.showFps
|
||||
findItem(R.id.menu_rel_stick_center).isChecked = EmulationMenuSettings.joystickRelCenter
|
||||
findItem(R.id.menu_dpad_slide).isChecked = EmulationMenuSettings.dpadSlide
|
||||
findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay
|
||||
findItem(R.id.menu_haptics).isChecked = EmulationMenuSettings.hapticFeedback
|
||||
findItem(R.id.menu_toggle_fps).isChecked =
|
||||
BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
|
||||
findItem(R.id.menu_rel_stick_center).isChecked =
|
||||
BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()
|
||||
findItem(R.id.menu_dpad_slide).isChecked = BooleanSetting.DPAD_SLIDE.getBoolean()
|
||||
findItem(R.id.menu_show_overlay).isChecked =
|
||||
BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
|
||||
findItem(R.id.menu_haptics).isChecked = BooleanSetting.HAPTIC_FEEDBACK.getBoolean()
|
||||
findItem(R.id.menu_touchscreen).isChecked = BooleanSetting.TOUCHSCREEN.getBoolean()
|
||||
}
|
||||
|
||||
popup.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_toggle_fps -> {
|
||||
it.isChecked = !it.isChecked
|
||||
EmulationMenuSettings.showFps = it.isChecked
|
||||
BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(it.isChecked)
|
||||
updateShowFpsOverlay()
|
||||
true
|
||||
}
|
||||
@ -563,11 +564,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
R.id.menu_toggle_controls -> {
|
||||
val preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
val optionsArray = BooleanArray(Settings.overlayPreferences.size)
|
||||
Settings.overlayPreferences.forEachIndexed { i, _ ->
|
||||
optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 15)
|
||||
val overlayControlData = NativeConfig.getOverlayControlData()
|
||||
val optionsArray = BooleanArray(overlayControlData.size)
|
||||
overlayControlData.forEachIndexed { i, _ ->
|
||||
optionsArray[i] = overlayControlData.firstOrNull { data ->
|
||||
OverlayControl.entries[i].id == data.id
|
||||
}?.enabled == true
|
||||
}
|
||||
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
@ -576,11 +578,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
R.array.gamepadButtons,
|
||||
optionsArray
|
||||
) { _, indexSelected, isChecked ->
|
||||
preferences.edit()
|
||||
.putBoolean("buttonToggle$indexSelected", isChecked)
|
||||
.apply()
|
||||
overlayControlData.firstOrNull { data ->
|
||||
OverlayControl.entries[indexSelected].id == data.id
|
||||
}?.enabled = isChecked
|
||||
}
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
NativeConfig.setOverlayControlData(overlayControlData)
|
||||
NativeConfig.saveGlobalConfig()
|
||||
binding.surfaceInputOverlay.refreshControls()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
@ -591,12 +595,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||
.setOnClickListener {
|
||||
val isChecked = !optionsArray[0]
|
||||
Settings.overlayPreferences.forEachIndexed { i, _ ->
|
||||
overlayControlData.forEachIndexed { i, _ ->
|
||||
optionsArray[i] = isChecked
|
||||
dialog.listView.setItemChecked(i, isChecked)
|
||||
preferences.edit()
|
||||
.putBoolean("buttonToggle$i", isChecked)
|
||||
.apply()
|
||||
overlayControlData[i].enabled = isChecked
|
||||
}
|
||||
}
|
||||
true
|
||||
@ -604,26 +606,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
R.id.menu_show_overlay -> {
|
||||
it.isChecked = !it.isChecked
|
||||
EmulationMenuSettings.showOverlay = it.isChecked
|
||||
BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(it.isChecked)
|
||||
binding.surfaceInputOverlay.refreshControls()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_rel_stick_center -> {
|
||||
it.isChecked = !it.isChecked
|
||||
EmulationMenuSettings.joystickRelCenter = it.isChecked
|
||||
BooleanSetting.JOYSTICK_REL_CENTER.setBoolean(it.isChecked)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_dpad_slide -> {
|
||||
it.isChecked = !it.isChecked
|
||||
EmulationMenuSettings.dpadSlide = it.isChecked
|
||||
BooleanSetting.DPAD_SLIDE.setBoolean(it.isChecked)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_haptics -> {
|
||||
it.isChecked = !it.isChecked
|
||||
EmulationMenuSettings.hapticFeedback = it.isChecked
|
||||
BooleanSetting.HAPTIC_FEEDBACK.setBoolean(it.isChecked)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_touchscreen -> {
|
||||
it.isChecked = !it.isChecked
|
||||
BooleanSetting.TOUCHSCREEN.setBoolean(it.isChecked)
|
||||
true
|
||||
}
|
||||
|
||||
@ -666,6 +674,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
NativeConfig.saveGlobalConfig()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@ -674,7 +683,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
adjustBinding.apply {
|
||||
inputScaleSlider.apply {
|
||||
valueTo = 150F
|
||||
value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
|
||||
value = IntSetting.OVERLAY_SCALE.getInt().toFloat()
|
||||
addOnChangeListener(
|
||||
Slider.OnChangeListener { _, value, _ ->
|
||||
inputScaleValue.text = "${value.toInt()}%"
|
||||
@ -684,7 +693,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
inputOpacitySlider.apply {
|
||||
valueTo = 100F
|
||||
value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat()
|
||||
value = IntSetting.OVERLAY_OPACITY.getInt().toFloat()
|
||||
addOnChangeListener(
|
||||
Slider.OnChangeListener { _, value, _ ->
|
||||
inputOpacityValue.text = "${value.toInt()}%"
|
||||
@ -708,16 +717,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
private fun setControlScale(scale: Int) {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_CONTROL_SCALE, scale)
|
||||
.apply()
|
||||
IntSetting.OVERLAY_SCALE.setInt(scale)
|
||||
binding.surfaceInputOverlay.refreshControls()
|
||||
}
|
||||
|
||||
private fun setControlOpacity(opacity: Int) {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_CONTROL_OPACITY, opacity)
|
||||
.apply()
|
||||
IntSetting.OVERLAY_OPACITY.setInt(opacity)
|
||||
binding.surfaceInputOverlay.refreshControls()
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
@ -73,6 +74,8 @@ class GamePropertiesFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
||||
@ -99,12 +102,24 @@ class GamePropertiesFragment : Fragment() {
|
||||
|
||||
reloadList()
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
homeViewModel.openImportSaves.collect {
|
||||
if (it) {
|
||||
importSaves.launch(arrayOf("application/zip"))
|
||||
homeViewModel.setOpenImportSaves(false)
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
homeViewModel.openImportSaves.collect {
|
||||
if (it) {
|
||||
importSaves.launch(arrayOf("application/zip"))
|
||||
homeViewModel.setOpenImportSaves(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
homeViewModel.reloadPropertiesList.collect {
|
||||
if (it) {
|
||||
reloadList()
|
||||
homeViewModel.reloadPropertiesList(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -179,6 +194,7 @@ class GamePropertiesFragment : Fragment() {
|
||||
InstallableProperty(
|
||||
R.string.save_data,
|
||||
R.string.save_data_description,
|
||||
R.drawable.ic_save,
|
||||
{
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
@ -214,7 +230,7 @@ class GamePropertiesFragment : Fragment() {
|
||||
R.string.save_data_deleted_successfully,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
reloadList()
|
||||
homeViewModel.reloadPropertiesList(true)
|
||||
}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
@ -242,13 +258,20 @@ class GamePropertiesFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
) {
|
||||
shaderCacheDir.deleteRecursively()
|
||||
Toast.makeText(
|
||||
YuzuApplication.appContext,
|
||||
R.string.cleared_shaders_successfully,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
reloadList()
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.clear_shader_cache,
|
||||
descriptionId = R.string.clear_shader_cache_warning_description,
|
||||
positiveAction = {
|
||||
shaderCacheDir.deleteRecursively()
|
||||
Toast.makeText(
|
||||
YuzuApplication.appContext,
|
||||
R.string.cleared_shaders_successfully,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
homeViewModel.reloadPropertiesList(true)
|
||||
}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -388,7 +411,7 @@ class GamePropertiesFragment : Fragment() {
|
||||
getString(R.string.save_file_imported_success),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
reloadList()
|
||||
homeViewModel.reloadPropertiesList(true)
|
||||
}
|
||||
|
||||
cacheSaveDir.deleteRecursively()
|
||||
|
@ -15,7 +15,7 @@ import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
|
||||
|
||||
class LaunchGameDialogFragment : DialogFragment() {
|
||||
private var selectedItem = 0
|
||||
private var selectedItem = 1
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val game = requireArguments().parcelable<Game>(GAME)
|
||||
@ -32,7 +32,7 @@ class LaunchGameDialogFragment : DialogFragment() {
|
||||
.actionGlobalEmulationActivity(game, selectedItem != 0)
|
||||
requireParentFragment().findNavController().navigate(action)
|
||||
}
|
||||
.setSingleChoiceItems(launchOptions, 0) { _: DialogInterface, i: Int ->
|
||||
.setSingleChoiceItems(launchOptions, 1) { _: DialogInterface, i: Int ->
|
||||
selectedItem = i
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
|
@ -10,17 +10,18 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
interface GameProperty {
|
||||
@get:StringRes
|
||||
val titleId: Int
|
||||
get() = -1
|
||||
|
||||
@get:StringRes
|
||||
val descriptionId: Int
|
||||
get() = -1
|
||||
|
||||
@get:DrawableRes
|
||||
val iconId: Int
|
||||
}
|
||||
|
||||
data class SubmenuProperty(
|
||||
override val titleId: Int,
|
||||
override val descriptionId: Int,
|
||||
@DrawableRes val iconId: Int,
|
||||
override val iconId: Int,
|
||||
val details: (() -> String)? = null,
|
||||
val detailsFlow: StateFlow<String>? = null,
|
||||
val action: () -> Unit
|
||||
@ -29,6 +30,7 @@ data class SubmenuProperty(
|
||||
data class InstallableProperty(
|
||||
override val titleId: Int,
|
||||
override val descriptionId: Int,
|
||||
override val iconId: Int,
|
||||
val install: (() -> Unit)? = null,
|
||||
val export: (() -> Unit)? = null
|
||||
) : GameProperty
|
||||
|
@ -28,6 +28,9 @@ class HomeViewModel : ViewModel() {
|
||||
private val _contentToInstall = MutableStateFlow<List<Uri>?>(null)
|
||||
val contentToInstall get() = _contentToInstall.asStateFlow()
|
||||
|
||||
private val _reloadPropertiesList = MutableStateFlow(false)
|
||||
val reloadPropertiesList get() = _reloadPropertiesList.asStateFlow()
|
||||
|
||||
var navigatedToSetup = false
|
||||
|
||||
fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
|
||||
@ -59,4 +62,8 @@ class HomeViewModel : ViewModel() {
|
||||
fun setContentToInstall(documents: List<Uri>?) {
|
||||
_contentToInstall.value = documents
|
||||
}
|
||||
|
||||
fun reloadPropertiesList(reload: Boolean) {
|
||||
_reloadPropertiesList.value = reload
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@ import android.graphics.Rect
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.view.MotionEvent
|
||||
import org.yuzu.yuzu_emu.NativeLibrary.ButtonState
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
|
||||
|
||||
/**
|
||||
* Custom [BitmapDrawable] that is capable
|
||||
@ -25,7 +26,7 @@ class InputOverlayDrawableButton(
|
||||
defaultStateBitmap: Bitmap,
|
||||
pressedStateBitmap: Bitmap,
|
||||
val buttonId: Int,
|
||||
val prefId: String
|
||||
val overlayControlData: OverlayControlData
|
||||
) {
|
||||
// The ID value what motion event is tracking
|
||||
var trackId: Int
|
||||
|
@ -14,7 +14,7 @@ import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
|
||||
/**
|
||||
* Custom [BitmapDrawable] that is capable
|
||||
@ -125,7 +125,7 @@ class InputOverlayDrawableJoystick(
|
||||
pressedState = true
|
||||
outerBitmap.alpha = 0
|
||||
boundsBoxBitmap.alpha = opacity
|
||||
if (EmulationMenuSettings.joystickRelCenter) {
|
||||
if (BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()) {
|
||||
virtBounds.offset(
|
||||
xPosition - virtBounds.centerX(),
|
||||
yPosition - virtBounds.centerY()
|
||||
|
@ -0,0 +1,188 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.overlay.model
|
||||
|
||||
import androidx.annotation.IntegerRes
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
|
||||
enum class OverlayControl(
|
||||
val id: String,
|
||||
val defaultVisibility: Boolean,
|
||||
@IntegerRes val defaultLandscapePositionResources: Pair<Int, Int>,
|
||||
@IntegerRes val defaultPortraitPositionResources: Pair<Int, Int>,
|
||||
@IntegerRes val defaultFoldablePositionResources: Pair<Int, Int>
|
||||
) {
|
||||
BUTTON_A(
|
||||
"button_a",
|
||||
true,
|
||||
Pair(R.integer.BUTTON_A_X, R.integer.BUTTON_A_Y),
|
||||
Pair(R.integer.BUTTON_A_X_PORTRAIT, R.integer.BUTTON_A_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE)
|
||||
),
|
||||
BUTTON_B(
|
||||
"button_b",
|
||||
true,
|
||||
Pair(R.integer.BUTTON_B_X, R.integer.BUTTON_B_Y),
|
||||
Pair(R.integer.BUTTON_B_X_PORTRAIT, R.integer.BUTTON_B_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE)
|
||||
),
|
||||
BUTTON_X(
|
||||
"button_x",
|
||||
true,
|
||||
Pair(R.integer.BUTTON_X_X, R.integer.BUTTON_X_Y),
|
||||
Pair(R.integer.BUTTON_X_X_PORTRAIT, R.integer.BUTTON_X_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE)
|
||||
),
|
||||
BUTTON_Y(
|
||||
"button_y",
|
||||
true,
|
||||
Pair(R.integer.BUTTON_Y_X, R.integer.BUTTON_Y_Y),
|
||||
Pair(R.integer.BUTTON_Y_X_PORTRAIT, R.integer.BUTTON_Y_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE)
|
||||
),
|
||||
BUTTON_PLUS(
|
||||
"button_plus",
|
||||
true,
|
||||
Pair(R.integer.BUTTON_PLUS_X, R.integer.BUTTON_PLUS_Y),
|
||||
Pair(R.integer.BUTTON_PLUS_X_PORTRAIT, R.integer.BUTTON_PLUS_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE)
|
||||
),
|
||||
BUTTON_MINUS(
|
||||
"button_minus",
|
||||
true,
|
||||
Pair(R.integer.BUTTON_MINUS_X, R.integer.BUTTON_MINUS_Y),
|
||||
Pair(R.integer.BUTTON_MINUS_X_PORTRAIT, R.integer.BUTTON_MINUS_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE)
|
||||
),
|
||||
BUTTON_HOME(
|
||||
"button_home",
|
||||
false,
|
||||
Pair(R.integer.BUTTON_HOME_X, R.integer.BUTTON_HOME_Y),
|
||||
Pair(R.integer.BUTTON_HOME_X_PORTRAIT, R.integer.BUTTON_HOME_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE)
|
||||
),
|
||||
BUTTON_CAPTURE(
|
||||
"button_capture",
|
||||
false,
|
||||
Pair(R.integer.BUTTON_CAPTURE_X, R.integer.BUTTON_CAPTURE_Y),
|
||||
Pair(R.integer.BUTTON_CAPTURE_X_PORTRAIT, R.integer.BUTTON_CAPTURE_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE)
|
||||
),
|
||||
BUTTON_L(
|
||||
"button_l",
|
||||
true,
|
||||
Pair(R.integer.BUTTON_L_X, R.integer.BUTTON_L_Y),
|
||||
Pair(R.integer.BUTTON_L_X_PORTRAIT, R.integer.BUTTON_L_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE)
|
||||
),
|
||||
BUTTON_R(
|
||||
"button_r",
|
||||
true,
|
||||
Pair(R.integer.BUTTON_R_X, R.integer.BUTTON_R_Y),
|
||||
Pair(R.integer.BUTTON_R_X_PORTRAIT, R.integer.BUTTON_R_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE)
|
||||
),
|
||||
BUTTON_ZL(
|
||||
"button_zl",
|
||||
true,
|
||||
Pair(R.integer.BUTTON_ZL_X, R.integer.BUTTON_ZL_Y),
|
||||
Pair(R.integer.BUTTON_ZL_X_PORTRAIT, R.integer.BUTTON_ZL_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE)
|
||||
),
|
||||
BUTTON_ZR(
|
||||
"button_zr",
|
||||
true,
|
||||
Pair(R.integer.BUTTON_ZR_X, R.integer.BUTTON_ZR_Y),
|
||||
Pair(R.integer.BUTTON_ZR_X_PORTRAIT, R.integer.BUTTON_ZR_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE)
|
||||
),
|
||||
BUTTON_STICK_L(
|
||||
"button_stick_l",
|
||||
true,
|
||||
Pair(R.integer.BUTTON_STICK_L_X, R.integer.BUTTON_STICK_L_Y),
|
||||
Pair(R.integer.BUTTON_STICK_L_X_PORTRAIT, R.integer.BUTTON_STICK_L_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE)
|
||||
),
|
||||
BUTTON_STICK_R(
|
||||
"button_stick_r",
|
||||
true,
|
||||
Pair(R.integer.BUTTON_STICK_R_X, R.integer.BUTTON_STICK_R_Y),
|
||||
Pair(R.integer.BUTTON_STICK_R_X_PORTRAIT, R.integer.BUTTON_STICK_R_Y_PORTRAIT),
|
||||
Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE)
|
||||
),
|
||||
STICK_L(
|
||||
"stick_l",
|
||||
true,
|
||||
Pair(R.integer.STICK_L_X, R.integer.STICK_L_Y),
|
||||
Pair(R.integer.STICK_L_X_PORTRAIT, R.integer.STICK_L_Y_PORTRAIT),
|
||||
Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE)
|
||||
),
|
||||
STICK_R(
|
||||
"stick_r",
|
||||
true,
|
||||
Pair(R.integer.STICK_R_X, R.integer.STICK_R_Y),
|
||||
Pair(R.integer.STICK_R_X_PORTRAIT, R.integer.STICK_R_Y_PORTRAIT),
|
||||
Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE)
|
||||
),
|
||||
COMBINED_DPAD(
|
||||
"combined_dpad",
|
||||
true,
|
||||
Pair(R.integer.COMBINED_DPAD_X, R.integer.COMBINED_DPAD_Y),
|
||||
Pair(R.integer.COMBINED_DPAD_X_PORTRAIT, R.integer.COMBINED_DPAD_Y_PORTRAIT),
|
||||
Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE)
|
||||
);
|
||||
|
||||
fun getDefaultPositionForLayout(layout: OverlayLayout): Pair<Double, Double> {
|
||||
val rawResourcePair: Pair<Int, Int>
|
||||
YuzuApplication.appContext.resources.apply {
|
||||
rawResourcePair = when (layout) {
|
||||
OverlayLayout.Landscape -> {
|
||||
Pair(
|
||||
getInteger(this@OverlayControl.defaultLandscapePositionResources.first),
|
||||
getInteger(this@OverlayControl.defaultLandscapePositionResources.second)
|
||||
)
|
||||
}
|
||||
|
||||
OverlayLayout.Portrait -> {
|
||||
Pair(
|
||||
getInteger(this@OverlayControl.defaultPortraitPositionResources.first),
|
||||
getInteger(this@OverlayControl.defaultPortraitPositionResources.second)
|
||||
)
|
||||
}
|
||||
|
||||
OverlayLayout.Foldable -> {
|
||||
Pair(
|
||||
getInteger(this@OverlayControl.defaultFoldablePositionResources.first),
|
||||
getInteger(this@OverlayControl.defaultFoldablePositionResources.second)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(
|
||||
rawResourcePair.first.toDouble() / 1000,
|
||||
rawResourcePair.second.toDouble() / 1000
|
||||
)
|
||||
}
|
||||
|
||||
fun toOverlayControlData(): OverlayControlData =
|
||||
OverlayControlData(
|
||||
id,
|
||||
defaultVisibility,
|
||||
getDefaultPositionForLayout(OverlayLayout.Landscape),
|
||||
getDefaultPositionForLayout(OverlayLayout.Portrait),
|
||||
getDefaultPositionForLayout(OverlayLayout.Foldable)
|
||||
)
|
||||
|
||||
companion object {
|
||||
val map: HashMap<String, OverlayControl> by lazy {
|
||||
val hashMap = hashMapOf<String, OverlayControl>()
|
||||
entries.forEach { hashMap[it.id] = it }
|
||||
hashMap
|
||||
}
|
||||
|
||||
fun from(id: String): OverlayControl? = map[id]
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.overlay.model
|
||||
|
||||
data class OverlayControlData(
|
||||
val id: String,
|
||||
var enabled: Boolean,
|
||||
var landscapePosition: Pair<Double, Double>,
|
||||
var portraitPosition: Pair<Double, Double>,
|
||||
var foldablePosition: Pair<Double, Double>
|
||||
) {
|
||||
fun positionFromLayout(layout: OverlayLayout): Pair<Double, Double> =
|
||||
when (layout) {
|
||||
OverlayLayout.Landscape -> landscapePosition
|
||||
OverlayLayout.Portrait -> portraitPosition
|
||||
OverlayLayout.Foldable -> foldablePosition
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.overlay.model
|
||||
|
||||
import androidx.annotation.IntegerRes
|
||||
|
||||
data class OverlayControlDefault(
|
||||
val buttonId: String,
|
||||
@IntegerRes val landscapePositionResource: Pair<Int, Int>,
|
||||
@IntegerRes val portraitPositionResource: Pair<Int, Int>,
|
||||
@IntegerRes val foldablePositionResource: Pair<Int, Int>
|
||||
)
|
@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.overlay.model
|
||||
|
||||
enum class OverlayLayout(val id: String) {
|
||||
Landscape("Landscape"),
|
||||
Portrait("Portrait"),
|
||||
Foldable("Foldable")
|
||||
}
|
@ -91,18 +91,20 @@ class GamesFragment : Fragment() {
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it }
|
||||
gamesViewModel.isReloading.collect {
|
||||
binding.swipeRefresh.isRefreshing = it
|
||||
if (gamesViewModel.games.value.isEmpty() && !it) {
|
||||
binding.noticeText.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noticeText.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.games.collectLatest {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(it)
|
||||
if (it.isEmpty()) {
|
||||
binding.noticeText.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noticeText.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,17 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import androidx.preference.PreferenceManager
|
||||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayControl
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
|
||||
import org.yuzu.yuzu_emu.utils.PreferenceUtil.migratePreference
|
||||
|
||||
object DirectoryInitialization {
|
||||
private var userPath: String? = null
|
||||
@ -17,6 +25,7 @@ object DirectoryInitialization {
|
||||
initializeInternalStorage()
|
||||
NativeLibrary.initializeSystem(false)
|
||||
NativeConfig.initializeGlobalConfig()
|
||||
migrateSettings()
|
||||
areDirectoriesReady = true
|
||||
}
|
||||
}
|
||||
@ -35,4 +44,170 @@ object DirectoryInitialization {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateSettings() {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
var saveConfig = false
|
||||
val theme = preferences.migratePreference<Int>(Settings.PREF_THEME)
|
||||
if (theme != null) {
|
||||
IntSetting.THEME.setInt(theme)
|
||||
saveConfig = true
|
||||
}
|
||||
|
||||
val themeMode = preferences.migratePreference<Int>(Settings.PREF_THEME_MODE)
|
||||
if (themeMode != null) {
|
||||
IntSetting.THEME_MODE.setInt(themeMode)
|
||||
saveConfig = true
|
||||
}
|
||||
|
||||
val blackBackgrounds =
|
||||
preferences.migratePreference<Boolean>(Settings.PREF_BLACK_BACKGROUNDS)
|
||||
if (blackBackgrounds != null) {
|
||||
BooleanSetting.BLACK_BACKGROUNDS.setBoolean(blackBackgrounds)
|
||||
saveConfig = true
|
||||
}
|
||||
|
||||
val joystickRelCenter =
|
||||
preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER)
|
||||
if (joystickRelCenter != null) {
|
||||
BooleanSetting.JOYSTICK_REL_CENTER.setBoolean(joystickRelCenter)
|
||||
saveConfig = true
|
||||
}
|
||||
|
||||
val dpadSlide =
|
||||
preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE)
|
||||
if (dpadSlide != null) {
|
||||
BooleanSetting.DPAD_SLIDE.setBoolean(dpadSlide)
|
||||
saveConfig = true
|
||||
}
|
||||
|
||||
val hapticFeedback =
|
||||
preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_HAPTICS)
|
||||
if (hapticFeedback != null) {
|
||||
BooleanSetting.HAPTIC_FEEDBACK.setBoolean(hapticFeedback)
|
||||
saveConfig = true
|
||||
}
|
||||
|
||||
val showPerformanceOverlay =
|
||||
preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_SHOW_FPS)
|
||||
if (showPerformanceOverlay != null) {
|
||||
BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(showPerformanceOverlay)
|
||||
saveConfig = true
|
||||
}
|
||||
|
||||
val showInputOverlay =
|
||||
preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY)
|
||||
if (showInputOverlay != null) {
|
||||
BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(showInputOverlay)
|
||||
saveConfig = true
|
||||
}
|
||||
|
||||
val overlayOpacity = preferences.migratePreference<Int>(Settings.PREF_CONTROL_OPACITY)
|
||||
if (overlayOpacity != null) {
|
||||
IntSetting.OVERLAY_OPACITY.setInt(overlayOpacity)
|
||||
saveConfig = true
|
||||
}
|
||||
|
||||
val overlayScale = preferences.migratePreference<Int>(Settings.PREF_CONTROL_SCALE)
|
||||
if (overlayScale != null) {
|
||||
IntSetting.OVERLAY_SCALE.setInt(overlayScale)
|
||||
saveConfig = true
|
||||
}
|
||||
|
||||
var setOverlayData = false
|
||||
val overlayControlData = NativeConfig.getOverlayControlData()
|
||||
if (overlayControlData.isEmpty()) {
|
||||
val overlayControlDataMap =
|
||||
NativeConfig.getOverlayControlData().associateBy { it.id }.toMutableMap()
|
||||
for (button in Settings.overlayPreferences) {
|
||||
val buttonId = convertButtonId(button)
|
||||
var buttonEnabled = preferences.migratePreference<Boolean>(button)
|
||||
if (buttonEnabled == null) {
|
||||
buttonEnabled = OverlayControl.map[buttonId]?.defaultVisibility == true
|
||||
}
|
||||
|
||||
var landscapeXPosition = preferences.migratePreference<Float>(
|
||||
"$button-X${Settings.PREF_LANDSCAPE_SUFFIX}"
|
||||
)?.toDouble()
|
||||
var landscapeYPosition = preferences.migratePreference<Float>(
|
||||
"$button-Y${Settings.PREF_LANDSCAPE_SUFFIX}"
|
||||
)?.toDouble()
|
||||
if (landscapeXPosition == null || landscapeYPosition == null) {
|
||||
val landscapePosition = OverlayControl.map[buttonId]
|
||||
?.getDefaultPositionForLayout(OverlayLayout.Landscape) ?: Pair(0.0, 0.0)
|
||||
landscapeXPosition = landscapePosition.first
|
||||
landscapeYPosition = landscapePosition.second
|
||||
}
|
||||
|
||||
var portraitXPosition = preferences.migratePreference<Float>(
|
||||
"$button-X${Settings.PREF_PORTRAIT_SUFFIX}"
|
||||
)?.toDouble()
|
||||
var portraitYPosition = preferences.migratePreference<Float>(
|
||||
"$button-Y${Settings.PREF_PORTRAIT_SUFFIX}"
|
||||
)?.toDouble()
|
||||
if (portraitXPosition == null || portraitYPosition == null) {
|
||||
val portraitPosition = OverlayControl.map[buttonId]
|
||||
?.getDefaultPositionForLayout(OverlayLayout.Portrait) ?: Pair(0.0, 0.0)
|
||||
portraitXPosition = portraitPosition.first
|
||||
portraitYPosition = portraitPosition.second
|
||||
}
|
||||
|
||||
var foldableXPosition = preferences.migratePreference<Float>(
|
||||
"$button-X${Settings.PREF_FOLDABLE_SUFFIX}"
|
||||
)?.toDouble()
|
||||
var foldableYPosition = preferences.migratePreference<Float>(
|
||||
"$button-Y${Settings.PREF_FOLDABLE_SUFFIX}"
|
||||
)?.toDouble()
|
||||
if (foldableXPosition == null || foldableYPosition == null) {
|
||||
val foldablePosition = OverlayControl.map[buttonId]
|
||||
?.getDefaultPositionForLayout(OverlayLayout.Foldable) ?: Pair(0.0, 0.0)
|
||||
foldableXPosition = foldablePosition.first
|
||||
foldableYPosition = foldablePosition.second
|
||||
}
|
||||
|
||||
val controlData = OverlayControlData(
|
||||
buttonId,
|
||||
buttonEnabled,
|
||||
Pair(landscapeXPosition, landscapeYPosition),
|
||||
Pair(portraitXPosition, portraitYPosition),
|
||||
Pair(foldableXPosition, foldableYPosition)
|
||||
)
|
||||
overlayControlDataMap[buttonId] = controlData
|
||||
setOverlayData = true
|
||||
}
|
||||
|
||||
if (setOverlayData) {
|
||||
NativeConfig.setOverlayControlData(
|
||||
overlayControlDataMap.map { it.value }.toTypedArray()
|
||||
)
|
||||
saveConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
if (saveConfig) {
|
||||
NativeConfig.saveGlobalConfig()
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertButtonId(buttonId: String): String =
|
||||
when (buttonId) {
|
||||
Settings.PREF_BUTTON_A -> OverlayControl.BUTTON_A.id
|
||||
Settings.PREF_BUTTON_B -> OverlayControl.BUTTON_B.id
|
||||
Settings.PREF_BUTTON_X -> OverlayControl.BUTTON_X.id
|
||||
Settings.PREF_BUTTON_Y -> OverlayControl.BUTTON_Y.id
|
||||
Settings.PREF_BUTTON_L -> OverlayControl.BUTTON_L.id
|
||||
Settings.PREF_BUTTON_R -> OverlayControl.BUTTON_R.id
|
||||
Settings.PREF_BUTTON_ZL -> OverlayControl.BUTTON_ZL.id
|
||||
Settings.PREF_BUTTON_ZR -> OverlayControl.BUTTON_ZR.id
|
||||
Settings.PREF_BUTTON_PLUS -> OverlayControl.BUTTON_PLUS.id
|
||||
Settings.PREF_BUTTON_MINUS -> OverlayControl.BUTTON_MINUS.id
|
||||
Settings.PREF_BUTTON_DPAD -> OverlayControl.COMBINED_DPAD.id
|
||||
Settings.PREF_STICK_L -> OverlayControl.STICK_L.id
|
||||
Settings.PREF_STICK_R -> OverlayControl.STICK_R.id
|
||||
Settings.PREF_BUTTON_HOME -> OverlayControl.BUTTON_HOME.id
|
||||
Settings.PREF_BUTTON_SCREENSHOT -> OverlayControl.BUTTON_CAPTURE.id
|
||||
Settings.PREF_BUTTON_STICK_L -> OverlayControl.BUTTON_STICK_L.id
|
||||
Settings.PREF_BUTTON_STICK_R -> OverlayControl.BUTTON_STICK_R.id
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
@ -1,50 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
|
||||
object EmulationMenuSettings {
|
||||
private val preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
||||
var joystickRelCenter: Boolean
|
||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true)
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, value)
|
||||
.apply()
|
||||
}
|
||||
var dpadSlide: Boolean
|
||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, true)
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, value)
|
||||
.apply()
|
||||
}
|
||||
var hapticFeedback: Boolean
|
||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, false)
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, value)
|
||||
.apply()
|
||||
}
|
||||
|
||||
var showFps: Boolean
|
||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false)
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, value)
|
||||
.apply()
|
||||
}
|
||||
var showOverlay: Boolean
|
||||
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, true)
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, value)
|
||||
.apply()
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import org.yuzu.yuzu_emu.model.GameDir
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
|
||||
|
||||
object NativeConfig {
|
||||
/**
|
||||
@ -110,6 +111,8 @@ object NativeConfig {
|
||||
@Synchronized
|
||||
external fun setGlobal(key: String, global: Boolean)
|
||||
|
||||
external fun getIsSaveable(key: String): Boolean
|
||||
|
||||
external fun getDefaultToString(key: String): String
|
||||
|
||||
/**
|
||||
@ -148,4 +151,21 @@ object NativeConfig {
|
||||
*/
|
||||
@Synchronized
|
||||
external fun setDisabledAddons(programId: String, disabledAddons: Array<String>)
|
||||
|
||||
/**
|
||||
* Gets an array of [OverlayControlData] from settings
|
||||
*
|
||||
* @return An array of [OverlayControlData]
|
||||
*/
|
||||
@Synchronized
|
||||
external fun getOverlayControlData(): Array<OverlayControlData>
|
||||
|
||||
/**
|
||||
* Clears the AndroidSettings::values.overlay_control_data array and replaces its values
|
||||
* with [overlayControlData]
|
||||
*
|
||||
* @param overlayControlData Replacement array of [OverlayControlData]
|
||||
*/
|
||||
@Synchronized
|
||||
external fun setOverlayControlData(overlayControlData: Array<OverlayControlData>)
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.content.SharedPreferences
|
||||
|
||||
object PreferenceUtil {
|
||||
/**
|
||||
* Retrieves a shared preference value and then deletes the value in storage.
|
||||
* @param key Associated key for the value in this preferences instance
|
||||
* @return Typed value associated with [key]. Null if no such key exists.
|
||||
*/
|
||||
inline fun <reified T> SharedPreferences.migratePreference(key: String): T? {
|
||||
if (!this.contains(key)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val value: Any = when (T::class) {
|
||||
String::class -> this.getString(key, "")!!
|
||||
|
||||
Boolean::class -> this.getBoolean(key, false)
|
||||
|
||||
Int::class -> this.getInt(key, 0)
|
||||
|
||||
Float::class -> this.getFloat(key, 0f)
|
||||
|
||||
Long::class -> this.getLong(key, 0)
|
||||
|
||||
else -> throw IllegalStateException("Tried to migrate preference with invalid type!")
|
||||
}
|
||||
deletePreference(key)
|
||||
return value as T
|
||||
}
|
||||
|
||||
fun SharedPreferences.deletePreference(key: String) = this.edit().remove(key).apply()
|
||||
}
|
@ -5,38 +5,38 @@ package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlin.math.roundToInt
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.ui.main.ThemeProvider
|
||||
|
||||
object ThemeHelper {
|
||||
const val SYSTEM_BAR_ALPHA = 0.9f
|
||||
|
||||
private const val DEFAULT = 0
|
||||
private const val MATERIAL_YOU = 1
|
||||
|
||||
fun setTheme(activity: AppCompatActivity) {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
setThemeMode(activity)
|
||||
when (preferences.getInt(Settings.PREF_THEME, 0)) {
|
||||
DEFAULT -> activity.setTheme(R.style.Theme_Yuzu_Main)
|
||||
MATERIAL_YOU -> activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou)
|
||||
when (Theme.from(IntSetting.THEME.getInt())) {
|
||||
Theme.Default -> activity.setTheme(R.style.Theme_Yuzu_Main)
|
||||
Theme.MaterialYou -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou)
|
||||
} else {
|
||||
activity.setTheme(R.style.Theme_Yuzu_Main)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Using a specific night mode check because this could apply incorrectly when using the
|
||||
// light app mode, dark system mode, and black backgrounds. Launching the settings activity
|
||||
// will then show light mode colors/navigation bars but with black backgrounds.
|
||||
if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) &&
|
||||
isNightMode(activity)
|
||||
) {
|
||||
if (BooleanSetting.BLACK_BACKGROUNDS.getBoolean() && isNightMode(activity)) {
|
||||
activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark)
|
||||
}
|
||||
}
|
||||
@ -60,8 +60,7 @@ object ThemeHelper {
|
||||
}
|
||||
|
||||
fun setThemeMode(activity: AppCompatActivity) {
|
||||
val themeMode = PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
|
||||
.getInt(Settings.PREF_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
val themeMode = IntSetting.THEME_MODE.getInt()
|
||||
activity.delegate.localNightMode = themeMode
|
||||
val windowController = WindowCompat.getInsetsController(
|
||||
activity.window,
|
||||
@ -95,3 +94,12 @@ object ThemeHelper {
|
||||
windowController.isAppearanceLightNavigationBars = false
|
||||
}
|
||||
}
|
||||
|
||||
enum class Theme(val int: Int) {
|
||||
Default(0),
|
||||
MaterialYou(1);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): Theme = entries.firstOrNull { it.int == int } ?: Default
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <jni.h>
|
||||
|
||||
#include "common/string_util.h"
|
||||
#include "jni/id_cache.h"
|
||||
|
||||
std::string GetJString(JNIEnv* env, jstring jstr) {
|
||||
if (!jstr) {
|
||||
@ -33,3 +34,11 @@ jstring ToJString(JNIEnv* env, std::string_view str) {
|
||||
jstring ToJString(JNIEnv* env, std::u16string_view str) {
|
||||
return ToJString(env, Common::UTF16ToUTF8(str));
|
||||
}
|
||||
|
||||
double GetJDouble(JNIEnv* env, jobject jdouble) {
|
||||
return env->GetDoubleField(jdouble, IDCache::GetDoubleValueField());
|
||||
}
|
||||
|
||||
jobject ToJDouble(JNIEnv* env, double value) {
|
||||
return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value);
|
||||
}
|
||||
|
@ -10,3 +10,6 @@
|
||||
std::string GetJString(JNIEnv* env, jstring jstr);
|
||||
jstring ToJString(JNIEnv* env, std::string_view str);
|
||||
jstring ToJString(JNIEnv* env, std::u16string_view str);
|
||||
|
||||
double GetJDouble(JNIEnv* env, jobject jdouble);
|
||||
jobject ToJDouble(JNIEnv* env, double value);
|
||||
|
@ -35,6 +35,7 @@ void AndroidConfig::ReadAndroidValues() {
|
||||
if (global) {
|
||||
ReadAndroidUIValues();
|
||||
ReadUIValues();
|
||||
ReadOverlayValues();
|
||||
}
|
||||
ReadDriverValues();
|
||||
}
|
||||
@ -81,10 +82,42 @@ void AndroidConfig::ReadDriverValues() {
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void AndroidConfig::ReadOverlayValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Overlay));
|
||||
|
||||
ReadCategory(Settings::Category::Overlay);
|
||||
|
||||
AndroidSettings::values.overlay_control_data.clear();
|
||||
const int control_data_size = BeginArray("control_data");
|
||||
for (int i = 0; i < control_data_size; ++i) {
|
||||
SetArrayIndex(i);
|
||||
AndroidSettings::OverlayControlData control_data;
|
||||
control_data.id = ReadStringSetting(std::string("id"));
|
||||
control_data.enabled = ReadBooleanSetting(std::string("enabled"));
|
||||
control_data.landscape_position.first =
|
||||
ReadDoubleSetting(std::string("landscape\\x_position"));
|
||||
control_data.landscape_position.second =
|
||||
ReadDoubleSetting(std::string("landscape\\y_position"));
|
||||
control_data.portrait_position.first =
|
||||
ReadDoubleSetting(std::string("portrait\\x_position"));
|
||||
control_data.portrait_position.second =
|
||||
ReadDoubleSetting(std::string("portrait\\y_position"));
|
||||
control_data.foldable_position.first =
|
||||
ReadDoubleSetting(std::string("foldable\\x_position"));
|
||||
control_data.foldable_position.second =
|
||||
ReadDoubleSetting(std::string("foldable\\y_position"));
|
||||
AndroidSettings::values.overlay_control_data.push_back(control_data);
|
||||
}
|
||||
EndArray();
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void AndroidConfig::SaveAndroidValues() {
|
||||
if (global) {
|
||||
SaveAndroidUIValues();
|
||||
SaveUIValues();
|
||||
SaveOverlayValues();
|
||||
}
|
||||
SaveDriverValues();
|
||||
|
||||
@ -114,8 +147,9 @@ void AndroidConfig::SavePathValues() {
|
||||
for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) {
|
||||
SetArrayIndex(i);
|
||||
const auto& game_dir = AndroidSettings::values.game_dirs[i];
|
||||
WriteSetting(std::string("path"), game_dir.path);
|
||||
WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false));
|
||||
WriteStringSetting(std::string("path"), game_dir.path);
|
||||
WriteBooleanSetting(std::string("deep_scan"), game_dir.deep_scan,
|
||||
std::make_optional(false));
|
||||
}
|
||||
EndArray();
|
||||
|
||||
@ -130,6 +164,35 @@ void AndroidConfig::SaveDriverValues() {
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void AndroidConfig::SaveOverlayValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Overlay));
|
||||
|
||||
WriteCategory(Settings::Category::Overlay);
|
||||
|
||||
BeginArray("control_data");
|
||||
for (size_t i = 0; i < AndroidSettings::values.overlay_control_data.size(); ++i) {
|
||||
SetArrayIndex(i);
|
||||
const auto& control_data = AndroidSettings::values.overlay_control_data[i];
|
||||
WriteStringSetting(std::string("id"), control_data.id);
|
||||
WriteBooleanSetting(std::string("enabled"), control_data.enabled);
|
||||
WriteDoubleSetting(std::string("landscape\\x_position"),
|
||||
control_data.landscape_position.first);
|
||||
WriteDoubleSetting(std::string("landscape\\y_position"),
|
||||
control_data.landscape_position.second);
|
||||
WriteDoubleSetting(std::string("portrait\\x_position"),
|
||||
control_data.portrait_position.first);
|
||||
WriteDoubleSetting(std::string("portrait\\y_position"),
|
||||
control_data.portrait_position.second);
|
||||
WriteDoubleSetting(std::string("foldable\\x_position"),
|
||||
control_data.foldable_position.first);
|
||||
WriteDoubleSetting(std::string("foldable\\y_position"),
|
||||
control_data.foldable_position.second);
|
||||
}
|
||||
EndArray();
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
|
||||
auto& map = Settings::values.linkage.by_category;
|
||||
if (map.contains(category)) {
|
||||
|
@ -18,6 +18,7 @@ protected:
|
||||
void ReadAndroidValues();
|
||||
void ReadAndroidUIValues();
|
||||
void ReadDriverValues();
|
||||
void ReadOverlayValues();
|
||||
void ReadHidbusValues() override {}
|
||||
void ReadDebugControlValues() override {}
|
||||
void ReadPathValues() override;
|
||||
@ -30,6 +31,7 @@ protected:
|
||||
void SaveAndroidValues();
|
||||
void SaveAndroidUIValues();
|
||||
void SaveDriverValues();
|
||||
void SaveOverlayValues();
|
||||
void SaveHidbusValues() override {}
|
||||
void SaveDebugControlValues() override {}
|
||||
void SavePathValues() override;
|
||||
|
@ -14,6 +14,14 @@ struct GameDir {
|
||||
bool deep_scan = false;
|
||||
};
|
||||
|
||||
struct OverlayControlData {
|
||||
std::string id;
|
||||
bool enabled;
|
||||
std::pair<double, double> landscape_position;
|
||||
std::pair<double, double> portrait_position;
|
||||
std::pair<double, double> foldable_position;
|
||||
};
|
||||
|
||||
struct Values {
|
||||
Settings::Linkage linkage;
|
||||
|
||||
@ -33,6 +41,28 @@ struct Values {
|
||||
|
||||
Settings::SwitchableSetting<std::string, false> driver_path{linkage, "", "driver_path",
|
||||
Settings::Category::GpuDriver};
|
||||
|
||||
Settings::Setting<s32> theme{linkage, 0, "theme", Settings::Category::Android};
|
||||
Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
|
||||
Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
|
||||
Settings::Category::Android};
|
||||
|
||||
// Input/performance overlay settings
|
||||
std::vector<OverlayControlData> overlay_control_data;
|
||||
Settings::Setting<s32> overlay_scale{linkage, 50, "control_scale", Settings::Category::Overlay};
|
||||
Settings::Setting<s32> overlay_opacity{linkage, 100, "control_opacity",
|
||||
Settings::Category::Overlay};
|
||||
|
||||
Settings::Setting<bool> joystick_rel_center{linkage, true, "joystick_rel_center",
|
||||
Settings::Category::Overlay};
|
||||
Settings::Setting<bool> dpad_slide{linkage, true, "dpad_slide", Settings::Category::Overlay};
|
||||
Settings::Setting<bool> haptic_feedback{linkage, true, "haptic_feedback",
|
||||
Settings::Category::Overlay};
|
||||
Settings::Setting<bool> show_performance_overlay{linkage, true, "show_performance_overlay",
|
||||
Settings::Category::Overlay};
|
||||
Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
|
||||
Settings::Category::Overlay};
|
||||
Settings::Setting<bool> touchscreen{linkage, true, "touchscreen", Settings::Category::Overlay};
|
||||
};
|
||||
|
||||
extern Values values;
|
||||
|
@ -89,13 +89,13 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobj
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto fileType = loader->GetFileType();
|
||||
if (fileType == Loader::FileType::Unknown || fileType == Loader::FileType::Error) {
|
||||
const auto file_type = loader->GetFileType();
|
||||
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 programId = 0;
|
||||
Loader::ResultStatus res = loader->ReadProgramId(programId);
|
||||
u64 program_id = 0;
|
||||
Loader::ResultStatus res = loader->ReadProgramId(program_id);
|
||||
if (res != Loader::ResultStatus::Success) {
|
||||
return false;
|
||||
}
|
||||
|
@ -35,6 +35,18 @@ static jmethodID s_pair_constructor;
|
||||
static jfieldID s_pair_first_field;
|
||||
static jfieldID s_pair_second_field;
|
||||
|
||||
static jclass s_overlay_control_data_class;
|
||||
static jmethodID s_overlay_control_data_constructor;
|
||||
static jfieldID s_overlay_control_data_id_field;
|
||||
static jfieldID s_overlay_control_data_enabled_field;
|
||||
static jfieldID s_overlay_control_data_landscape_position_field;
|
||||
static jfieldID s_overlay_control_data_portrait_position_field;
|
||||
static jfieldID s_overlay_control_data_foldable_position_field;
|
||||
|
||||
static jclass s_double_class;
|
||||
static jmethodID s_double_constructor;
|
||||
static jfieldID s_double_value_field;
|
||||
|
||||
static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
|
||||
|
||||
namespace IDCache {
|
||||
@ -146,6 +158,46 @@ jfieldID GetPairSecondField() {
|
||||
return s_pair_second_field;
|
||||
}
|
||||
|
||||
jclass GetOverlayControlDataClass() {
|
||||
return s_overlay_control_data_class;
|
||||
}
|
||||
|
||||
jmethodID GetOverlayControlDataConstructor() {
|
||||
return s_overlay_control_data_constructor;
|
||||
}
|
||||
|
||||
jfieldID GetOverlayControlDataIdField() {
|
||||
return s_overlay_control_data_id_field;
|
||||
}
|
||||
|
||||
jfieldID GetOverlayControlDataEnabledField() {
|
||||
return s_overlay_control_data_enabled_field;
|
||||
}
|
||||
|
||||
jfieldID GetOverlayControlDataLandscapePositionField() {
|
||||
return s_overlay_control_data_landscape_position_field;
|
||||
}
|
||||
|
||||
jfieldID GetOverlayControlDataPortraitPositionField() {
|
||||
return s_overlay_control_data_portrait_position_field;
|
||||
}
|
||||
|
||||
jfieldID GetOverlayControlDataFoldablePositionField() {
|
||||
return s_overlay_control_data_foldable_position_field;
|
||||
}
|
||||
|
||||
jclass GetDoubleClass() {
|
||||
return s_double_class;
|
||||
}
|
||||
|
||||
jmethodID GetDoubleConstructor() {
|
||||
return s_double_constructor;
|
||||
}
|
||||
|
||||
jfieldID GetDoubleValueField() {
|
||||
return s_double_value_field;
|
||||
}
|
||||
|
||||
} // namespace IDCache
|
||||
|
||||
#ifdef __cplusplus
|
||||
@ -207,6 +259,31 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;");
|
||||
env->DeleteLocalRef(pair_class);
|
||||
|
||||
const jclass overlay_control_data_class =
|
||||
env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData");
|
||||
s_overlay_control_data_class =
|
||||
reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
|
||||
s_overlay_control_data_constructor =
|
||||
env->GetMethodID(overlay_control_data_class, "<init>",
|
||||
"(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V");
|
||||
s_overlay_control_data_id_field =
|
||||
env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
|
||||
s_overlay_control_data_enabled_field =
|
||||
env->GetFieldID(overlay_control_data_class, "enabled", "Z");
|
||||
s_overlay_control_data_landscape_position_field =
|
||||
env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;");
|
||||
s_overlay_control_data_portrait_position_field =
|
||||
env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
|
||||
s_overlay_control_data_foldable_position_field =
|
||||
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
|
||||
env->DeleteLocalRef(overlay_control_data_class);
|
||||
|
||||
const jclass double_class = env->FindClass("java/lang/Double");
|
||||
s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class));
|
||||
s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V");
|
||||
s_double_value_field = env->GetFieldID(double_class, "value", "D");
|
||||
env->DeleteLocalRef(double_class);
|
||||
|
||||
// Initialize Android Storage
|
||||
Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
|
||||
|
||||
@ -231,6 +308,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
|
||||
env->DeleteGlobalRef(s_game_class);
|
||||
env->DeleteGlobalRef(s_string_class);
|
||||
env->DeleteGlobalRef(s_pair_class);
|
||||
env->DeleteGlobalRef(s_overlay_control_data_class);
|
||||
env->DeleteGlobalRef(s_double_class);
|
||||
|
||||
// UnInitialize applets
|
||||
SoftwareKeyboard::CleanupJNI(env);
|
||||
|
@ -35,4 +35,16 @@ jmethodID GetPairConstructor();
|
||||
jfieldID GetPairFirstField();
|
||||
jfieldID GetPairSecondField();
|
||||
|
||||
jclass GetOverlayControlDataClass();
|
||||
jmethodID GetOverlayControlDataConstructor();
|
||||
jfieldID GetOverlayControlDataIdField();
|
||||
jfieldID GetOverlayControlDataEnabledField();
|
||||
jfieldID GetOverlayControlDataLandscapePositionField();
|
||||
jfieldID GetOverlayControlDataPortraitPositionField();
|
||||
jfieldID GetOverlayControlDataFoldablePositionField();
|
||||
|
||||
jclass GetDoubleClass();
|
||||
jmethodID GetDoubleConstructor();
|
||||
jfieldID GetDoubleValueField();
|
||||
|
||||
} // namespace IDCache
|
||||
|
@ -80,7 +80,7 @@ Core::System& EmulationSession::System() {
|
||||
return m_system;
|
||||
}
|
||||
|
||||
FileSys::ManualContentProvider* EmulationSession::ContentProvider() {
|
||||
FileSys::ManualContentProvider* EmulationSession::GetContentProvider() {
|
||||
return m_manual_provider.get();
|
||||
}
|
||||
|
||||
@ -296,9 +296,6 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
|
||||
// Initialize filesystem.
|
||||
ConfigureFilesystemProvider(filepath);
|
||||
|
||||
// Initialize account manager
|
||||
m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
|
||||
|
||||
// Load the ROM.
|
||||
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
|
||||
if (m_load_result != Core::SystemResultStatus::Success) {
|
||||
@ -463,15 +460,13 @@ void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) {
|
||||
static_cast<jint>(result));
|
||||
}
|
||||
|
||||
u64 EmulationSession::getProgramId(JNIEnv* env, jstring jprogramId) {
|
||||
u64 program_id;
|
||||
u64 EmulationSession::GetProgramId(JNIEnv* env, jstring jprogramId) {
|
||||
auto program_id_string = GetJString(env, jprogramId);
|
||||
if (program_id_string.empty()) {
|
||||
program_id = 0;
|
||||
} else {
|
||||
program_id = std::stoull(program_id_string);
|
||||
try {
|
||||
return std::stoull(program_id_string);
|
||||
} catch (...) {
|
||||
return 0;
|
||||
}
|
||||
return program_id;
|
||||
}
|
||||
|
||||
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
||||
@ -526,16 +521,16 @@ int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj,
|
||||
jstring jprogramId,
|
||||
jstring jupdatePath) {
|
||||
u64 programId = EmulationSession::getProgramId(env, jprogramId);
|
||||
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
std::string updatePath = GetJString(env, jupdatePath);
|
||||
std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>(
|
||||
EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(updatePath,
|
||||
FileSys::Mode::Read));
|
||||
for (const auto& item : nsp->GetNCAs()) {
|
||||
for (const auto& ncaDetails : item.second) {
|
||||
if (ncaDetails.second->GetName().ends_with(".cnmt.nca")) {
|
||||
auto updateId = ncaDetails.second->GetTitleId() & ~0xFFFULL;
|
||||
if (updateId == programId) {
|
||||
for (const auto& nca_details : item.second) {
|
||||
if (nca_details.second->GetName().ends_with(".cnmt.nca")) {
|
||||
auto update_id = nca_details.second->GetTitleId() & ~0xFFFULL;
|
||||
if (update_id == program_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -770,8 +765,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
|
||||
auto vfs_nand_dir = EmulationSession::GetInstance().System().GetFilesystem()->OpenDirectory(
|
||||
Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
|
||||
|
||||
Service::Account::ProfileManager manager;
|
||||
const auto user_id = manager.GetUser(static_cast<std::size_t>(0));
|
||||
const auto user_id = EmulationSession::GetInstance().System().GetProfileManager().GetUser(
|
||||
static_cast<std::size_t>(0));
|
||||
ASSERT(user_id);
|
||||
|
||||
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
|
||||
@ -840,8 +835,8 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env,
|
||||
}
|
||||
|
||||
auto& system = EmulationSession::GetInstance().System();
|
||||
auto programId = EmulationSession::getProgramId(env, jprogramId);
|
||||
const FileSys::PatchManager pm{programId, system.GetFileSystemController(),
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
const FileSys::PatchManager pm{program_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
const auto loader = Loader::GetLoader(system, vFile);
|
||||
|
||||
@ -849,11 +844,11 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env,
|
||||
loader->ReadUpdateRaw(update_raw);
|
||||
|
||||
auto addons = pm.GetPatchVersionNames(update_raw);
|
||||
auto emptyString = ToJString(env, "");
|
||||
auto emptyStringPair = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
|
||||
emptyString, emptyString);
|
||||
auto jemptyString = ToJString(env, "");
|
||||
auto jemptyStringPair = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
|
||||
jemptyString, jemptyString);
|
||||
jobjectArray jaddonsArray =
|
||||
env->NewObjectArray(addons.size(), IDCache::GetPairClass(), emptyStringPair);
|
||||
env->NewObjectArray(addons.size(), IDCache::GetPairClass(), jemptyStringPair);
|
||||
int i = 0;
|
||||
for (const auto& addon : addons) {
|
||||
jobject jaddon = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
|
||||
@ -866,7 +861,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env,
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj,
|
||||
jstring jprogramId) {
|
||||
auto programId = EmulationSession::getProgramId(env, jprogramId);
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
|
||||
auto& system = EmulationSession::GetInstance().System();
|
||||
|
||||
@ -881,7 +876,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject j
|
||||
|
||||
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
|
||||
system, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
|
||||
programId, user_id->AsU128(), 0);
|
||||
program_id, user_id->AsU128(), 0);
|
||||
return ToJString(env, user_save_data_path);
|
||||
}
|
||||
|
||||
@ -891,7 +886,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* e
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, jobject jobj) {
|
||||
EmulationSession::GetInstance().ContentProvider()->ClearAllEntries();
|
||||
EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
@ -21,7 +21,7 @@ public:
|
||||
static EmulationSession& GetInstance();
|
||||
const Core::System& System() const;
|
||||
Core::System& System();
|
||||
FileSys::ManualContentProvider* ContentProvider();
|
||||
FileSys::ManualContentProvider* GetContentProvider();
|
||||
|
||||
const EmuWindow_Android& Window() const;
|
||||
EmuWindow_Android& Window();
|
||||
@ -55,7 +55,7 @@ public:
|
||||
|
||||
static void OnEmulationStarted();
|
||||
|
||||
static u64 getProgramId(JNIEnv* env, jstring jprogramId);
|
||||
static u64 GetProgramId(JNIEnv* env, jstring jprogramId);
|
||||
|
||||
private:
|
||||
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
|
||||
@ -76,7 +76,6 @@ private:
|
||||
std::atomic<bool> m_is_running = false;
|
||||
std::atomic<bool> m_is_paused = false;
|
||||
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
|
||||
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
|
||||
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
|
||||
|
||||
// GPU driver parameters
|
||||
|
@ -21,13 +21,13 @@ std::unique_ptr<AndroidConfig> per_game_config;
|
||||
template <typename T>
|
||||
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
|
||||
auto key = GetJString(env, jkey);
|
||||
auto basicSetting = Settings::values.linkage.by_key[key];
|
||||
if (basicSetting != 0) {
|
||||
return static_cast<Settings::Setting<T>*>(basicSetting);
|
||||
auto basic_setting = Settings::values.linkage.by_key[key];
|
||||
if (basic_setting != 0) {
|
||||
return static_cast<Settings::Setting<T>*>(basic_setting);
|
||||
}
|
||||
auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
|
||||
if (basicAndroidSetting != 0) {
|
||||
return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
|
||||
auto basic_android_setting = AndroidSettings::values.linkage.by_key[key];
|
||||
if (basic_android_setting != 0) {
|
||||
return static_cast<Settings::Setting<T>*>(basic_android_setting);
|
||||
}
|
||||
LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
|
||||
return nullptr;
|
||||
@ -54,7 +54,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jo
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializePerGameConfig(JNIEnv* env, jobject obj,
|
||||
jstring jprogramId,
|
||||
jstring jfileName) {
|
||||
auto program_id = EmulationSession::getProgramId(env, jprogramId);
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
auto file_name = GetJString(env, jfileName);
|
||||
const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id);
|
||||
per_game_config =
|
||||
@ -249,6 +249,15 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setGlobal(JNIEnv* env, jobject o
|
||||
}
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsSaveable(JNIEnv* env, jobject obj,
|
||||
jstring jkey) {
|
||||
auto setting = getSetting<std::string>(env, jkey);
|
||||
if (setting != nullptr) {
|
||||
return setting->Save();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDefaultToString(JNIEnv* env, jobject obj,
|
||||
jstring jkey) {
|
||||
auto setting = getSetting<std::string>(env, jkey);
|
||||
@ -311,7 +320,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_addGameDir(JNIEnv* env, jobject
|
||||
|
||||
jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv* env, jobject obj,
|
||||
jstring jprogramId) {
|
||||
auto program_id = EmulationSession::getProgramId(env, jprogramId);
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
auto& disabledAddons = Settings::values.disabled_addons[program_id];
|
||||
jobjectArray jdisabledAddonsArray =
|
||||
env->NewObjectArray(disabledAddons.size(), IDCache::GetStringClass(), ToJString(env, ""));
|
||||
@ -324,7 +333,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, jobject obj,
|
||||
jstring jprogramId,
|
||||
jobjectArray jdisabledAddons) {
|
||||
auto program_id = EmulationSession::getProgramId(env, jprogramId);
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
Settings::values.disabled_addons[program_id].clear();
|
||||
std::vector<std::string> disabled_addons;
|
||||
const int size = env->GetArrayLength(jdisabledAddons);
|
||||
@ -335,4 +344,74 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, j
|
||||
Settings::values.disabled_addons[program_id] = disabled_addons;
|
||||
}
|
||||
|
||||
jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getOverlayControlData(JNIEnv* env,
|
||||
jobject obj) {
|
||||
jobjectArray joverlayControlDataArray =
|
||||
env->NewObjectArray(AndroidSettings::values.overlay_control_data.size(),
|
||||
IDCache::GetOverlayControlDataClass(), nullptr);
|
||||
for (size_t i = 0; i < AndroidSettings::values.overlay_control_data.size(); ++i) {
|
||||
const auto& control_data = AndroidSettings::values.overlay_control_data[i];
|
||||
jobject jlandscapePosition =
|
||||
env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
|
||||
ToJDouble(env, control_data.landscape_position.first),
|
||||
ToJDouble(env, control_data.landscape_position.second));
|
||||
jobject jportraitPosition =
|
||||
env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
|
||||
ToJDouble(env, control_data.portrait_position.first),
|
||||
ToJDouble(env, control_data.portrait_position.second));
|
||||
jobject jfoldablePosition =
|
||||
env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
|
||||
ToJDouble(env, control_data.foldable_position.first),
|
||||
ToJDouble(env, control_data.foldable_position.second));
|
||||
|
||||
jobject jcontrolData = env->NewObject(
|
||||
IDCache::GetOverlayControlDataClass(), IDCache::GetOverlayControlDataConstructor(),
|
||||
ToJString(env, control_data.id), control_data.enabled, jlandscapePosition,
|
||||
jportraitPosition, jfoldablePosition);
|
||||
env->SetObjectArrayElement(joverlayControlDataArray, i, jcontrolData);
|
||||
}
|
||||
return joverlayControlDataArray;
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setOverlayControlData(
|
||||
JNIEnv* env, jobject obj, jobjectArray joverlayControlDataArray) {
|
||||
AndroidSettings::values.overlay_control_data.clear();
|
||||
int size = env->GetArrayLength(joverlayControlDataArray);
|
||||
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
jobject joverlayControlData = env->GetObjectArrayElement(joverlayControlDataArray, i);
|
||||
jstring jidString = static_cast<jstring>(
|
||||
env->GetObjectField(joverlayControlData, IDCache::GetOverlayControlDataIdField()));
|
||||
bool enabled = static_cast<bool>(env->GetBooleanField(
|
||||
joverlayControlData, IDCache::GetOverlayControlDataEnabledField()));
|
||||
|
||||
jobject jlandscapePosition = env->GetObjectField(
|
||||
joverlayControlData, IDCache::GetOverlayControlDataLandscapePositionField());
|
||||
std::pair<double, double> landscape_position = std::make_pair(
|
||||
GetJDouble(env, env->GetObjectField(jlandscapePosition, IDCache::GetPairFirstField())),
|
||||
GetJDouble(env,
|
||||
env->GetObjectField(jlandscapePosition, IDCache::GetPairSecondField())));
|
||||
|
||||
jobject jportraitPosition = env->GetObjectField(
|
||||
joverlayControlData, IDCache::GetOverlayControlDataPortraitPositionField());
|
||||
std::pair<double, double> portrait_position = std::make_pair(
|
||||
GetJDouble(env, env->GetObjectField(jportraitPosition, IDCache::GetPairFirstField())),
|
||||
GetJDouble(env, env->GetObjectField(jportraitPosition, IDCache::GetPairSecondField())));
|
||||
|
||||
jobject jfoldablePosition = env->GetObjectField(
|
||||
joverlayControlData, IDCache::GetOverlayControlDataFoldablePositionField());
|
||||
std::pair<double, double> foldable_position = std::make_pair(
|
||||
GetJDouble(env, env->GetObjectField(jfoldablePosition, IDCache::GetPairFirstField())),
|
||||
GetJDouble(env, env->GetObjectField(jfoldablePosition, IDCache::GetPairSecondField())));
|
||||
|
||||
AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{
|
||||
GetJString(env, jidString), enabled, landscape_position, portrait_position,
|
||||
foldable_position});
|
||||
}
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
@ -1,10 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L647,120Q663,120 677.5,126Q692,132 703,143L817,257Q828,268 834,282.5Q840,297 840,313L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM760,314L646,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,314ZM480,720Q530,720 565,685Q600,650 600,600Q600,550 565,515Q530,480 480,480Q430,480 395,515Q360,550 360,600Q360,650 395,685Q430,720 480,720ZM280,400L560,400Q577,400 588.5,388.5Q600,377 600,360L600,280Q600,263 588.5,251.5Q577,240 560,240L280,240Q263,240 251.5,251.5Q240,263 240,280L240,360Q240,377 251.5,388.5Q263,400 280,400ZM200,314L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200L200,200L200,314Z"/>
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z" />
|
||||
</vector>
|
||||
|
@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="?attr/materialCardViewOutlinedStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingVertical="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
app:tint="?attr/colorOnSurface"
|
||||
tools:src="@drawable/ic_settings" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/user_data"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/description"
|
||||
style="@style/TextAppearance.Material3.BodyMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/user_data_description"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_export"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:contentDescription="@string/export"
|
||||
android:tooltipText="@string/export"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_export"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_install"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="12dp"
|
||||
android:contentDescription="@string/string_import"
|
||||
android:tooltipText="@string/string_import"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_import"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="?attr/materialCardViewOutlinedStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingVertical="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
app:tint="?attr/colorOnSurface"
|
||||
tools:src="@drawable/ic_settings" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/user_data"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/description"
|
||||
style="@style/TextAppearance.Material3.BodyMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/user_data_description"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_install"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:contentDescription="@string/string_import"
|
||||
android:tooltipText="@string/string_import"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_import"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_export"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginTop="8dp"
|
||||
android:contentDescription="@string/export"
|
||||
android:tooltipText="@string/export"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_export"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
@ -24,7 +24,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:layout_weight="1">
|
||||
|
||||
|
@ -38,6 +38,11 @@
|
||||
android:title="@string/emulation_haptics"
|
||||
android:checkable="true" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_touchscreen"
|
||||
android:title="@string/touchscreen"
|
||||
android:checkable="true" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_reset_overlay"
|
||||
android:title="@string/emulation_touch_overlay_reset" />
|
||||
|
@ -212,19 +212,19 @@
|
||||
<item>B</item>
|
||||
<item>X</item>
|
||||
<item>Y</item>
|
||||
<item>+</item>
|
||||
<item>-</item>
|
||||
<item>@string/gamepad_home</item>
|
||||
<item>@string/gamepad_screenshot</item>
|
||||
<item>L</item>
|
||||
<item>R</item>
|
||||
<item>ZL</item>
|
||||
<item>ZR</item>
|
||||
<item>+</item>
|
||||
<item>-</item>
|
||||
<item>@string/gamepad_d_pad</item>
|
||||
<item>@string/gamepad_left_stick</item>
|
||||
<item>@string/gamepad_right_stick</item>
|
||||
<item>L3</item>
|
||||
<item>R3</item>
|
||||
<item>@string/gamepad_home</item>
|
||||
<item>@string/gamepad_screenshot</item>
|
||||
<item>@string/gamepad_d_pad</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="themeEntries">
|
||||
@ -256,13 +256,32 @@
|
||||
|
||||
<string-array name="outputEngineEntries">
|
||||
<item>@string/auto</item>
|
||||
<item>@string/oboe</item>
|
||||
<item>@string/cubeb</item>
|
||||
<item>@string/string_null</item>
|
||||
</string-array>
|
||||
<integer-array name="outputEngineValues">
|
||||
<item>0</item>
|
||||
<item>4</item>
|
||||
<item>1</item>
|
||||
<item>3</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="anisoEntries">
|
||||
<item>@string/auto</item>
|
||||
<item>@string/slider_default</item>
|
||||
<item>@string/multiplier_two</item>
|
||||
<item>@string/multiplier_four</item>
|
||||
<item>@string/multiplier_eight</item>
|
||||
<item>@string/multiplier_sixteen</item>
|
||||
</string-array>
|
||||
<integer-array name="anisoValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
</integer-array>
|
||||
|
||||
</resources>
|
||||
|
@ -3,111 +3,111 @@
|
||||
<integer name="grid_columns">1</integer>
|
||||
|
||||
<!-- Default SWITCH landscape layout -->
|
||||
<integer name="SWITCH_BUTTON_A_X">760</integer>
|
||||
<integer name="SWITCH_BUTTON_A_Y">790</integer>
|
||||
<integer name="SWITCH_BUTTON_B_X">710</integer>
|
||||
<integer name="SWITCH_BUTTON_B_Y">900</integer>
|
||||
<integer name="SWITCH_BUTTON_X_X">710</integer>
|
||||
<integer name="SWITCH_BUTTON_X_Y">680</integer>
|
||||
<integer name="SWITCH_BUTTON_Y_X">660</integer>
|
||||
<integer name="SWITCH_BUTTON_Y_Y">790</integer>
|
||||
<integer name="SWITCH_STICK_L_X">100</integer>
|
||||
<integer name="SWITCH_STICK_L_Y">670</integer>
|
||||
<integer name="SWITCH_STICK_R_X">900</integer>
|
||||
<integer name="SWITCH_STICK_R_Y">670</integer>
|
||||
<integer name="SWITCH_TRIGGER_L_X">70</integer>
|
||||
<integer name="SWITCH_TRIGGER_L_Y">220</integer>
|
||||
<integer name="SWITCH_TRIGGER_R_X">930</integer>
|
||||
<integer name="SWITCH_TRIGGER_R_Y">220</integer>
|
||||
<integer name="SWITCH_TRIGGER_ZL_X">70</integer>
|
||||
<integer name="SWITCH_TRIGGER_ZL_Y">90</integer>
|
||||
<integer name="SWITCH_TRIGGER_ZR_X">930</integer>
|
||||
<integer name="SWITCH_TRIGGER_ZR_Y">90</integer>
|
||||
<integer name="SWITCH_BUTTON_MINUS_X">460</integer>
|
||||
<integer name="SWITCH_BUTTON_MINUS_Y">950</integer>
|
||||
<integer name="SWITCH_BUTTON_PLUS_X">540</integer>
|
||||
<integer name="SWITCH_BUTTON_PLUS_Y">950</integer>
|
||||
<integer name="SWITCH_BUTTON_HOME_X">600</integer>
|
||||
<integer name="SWITCH_BUTTON_HOME_Y">950</integer>
|
||||
<integer name="SWITCH_BUTTON_CAPTURE_X">400</integer>
|
||||
<integer name="SWITCH_BUTTON_CAPTURE_Y">950</integer>
|
||||
<integer name="SWITCH_BUTTON_DPAD_X">260</integer>
|
||||
<integer name="SWITCH_BUTTON_DPAD_Y">790</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_L_X">870</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_L_Y">400</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_R_X">960</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_R_Y">430</integer>
|
||||
<integer name="BUTTON_A_X">760</integer>
|
||||
<integer name="BUTTON_A_Y">790</integer>
|
||||
<integer name="BUTTON_B_X">710</integer>
|
||||
<integer name="BUTTON_B_Y">900</integer>
|
||||
<integer name="BUTTON_X_X">710</integer>
|
||||
<integer name="BUTTON_X_Y">680</integer>
|
||||
<integer name="BUTTON_Y_X">660</integer>
|
||||
<integer name="BUTTON_Y_Y">790</integer>
|
||||
<integer name="BUTTON_PLUS_X">540</integer>
|
||||
<integer name="BUTTON_PLUS_Y">950</integer>
|
||||
<integer name="BUTTON_MINUS_X">460</integer>
|
||||
<integer name="BUTTON_MINUS_Y">950</integer>
|
||||
<integer name="BUTTON_HOME_X">600</integer>
|
||||
<integer name="BUTTON_HOME_Y">950</integer>
|
||||
<integer name="BUTTON_CAPTURE_X">400</integer>
|
||||
<integer name="BUTTON_CAPTURE_Y">950</integer>
|
||||
<integer name="BUTTON_L_X">70</integer>
|
||||
<integer name="BUTTON_L_Y">220</integer>
|
||||
<integer name="BUTTON_R_X">930</integer>
|
||||
<integer name="BUTTON_R_Y">220</integer>
|
||||
<integer name="BUTTON_ZL_X">70</integer>
|
||||
<integer name="BUTTON_ZL_Y">90</integer>
|
||||
<integer name="BUTTON_ZR_X">930</integer>
|
||||
<integer name="BUTTON_ZR_Y">90</integer>
|
||||
<integer name="BUTTON_STICK_L_X">870</integer>
|
||||
<integer name="BUTTON_STICK_L_Y">400</integer>
|
||||
<integer name="BUTTON_STICK_R_X">960</integer>
|
||||
<integer name="BUTTON_STICK_R_Y">430</integer>
|
||||
<integer name="STICK_L_X">100</integer>
|
||||
<integer name="STICK_L_Y">670</integer>
|
||||
<integer name="STICK_R_X">900</integer>
|
||||
<integer name="STICK_R_Y">670</integer>
|
||||
<integer name="COMBINED_DPAD_X">260</integer>
|
||||
<integer name="COMBINED_DPAD_Y">790</integer>
|
||||
|
||||
<!-- Default SWITCH portrait layout -->
|
||||
<integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer>
|
||||
<integer name="SWITCH_BUTTON_A_Y_PORTRAIT">840</integer>
|
||||
<integer name="SWITCH_BUTTON_B_X_PORTRAIT">740</integer>
|
||||
<integer name="SWITCH_BUTTON_B_Y_PORTRAIT">880</integer>
|
||||
<integer name="SWITCH_BUTTON_X_X_PORTRAIT">740</integer>
|
||||
<integer name="SWITCH_BUTTON_X_Y_PORTRAIT">800</integer>
|
||||
<integer name="SWITCH_BUTTON_Y_X_PORTRAIT">640</integer>
|
||||
<integer name="SWITCH_BUTTON_Y_Y_PORTRAIT">840</integer>
|
||||
<integer name="SWITCH_STICK_L_X_PORTRAIT">180</integer>
|
||||
<integer name="SWITCH_STICK_L_Y_PORTRAIT">660</integer>
|
||||
<integer name="SWITCH_STICK_R_X_PORTRAIT">820</integer>
|
||||
<integer name="SWITCH_STICK_R_Y_PORTRAIT">660</integer>
|
||||
<integer name="SWITCH_TRIGGER_L_X_PORTRAIT">140</integer>
|
||||
<integer name="SWITCH_TRIGGER_L_Y_PORTRAIT">260</integer>
|
||||
<integer name="SWITCH_TRIGGER_R_X_PORTRAIT">860</integer>
|
||||
<integer name="SWITCH_TRIGGER_R_Y_PORTRAIT">260</integer>
|
||||
<integer name="SWITCH_TRIGGER_ZL_X_PORTRAIT">140</integer>
|
||||
<integer name="SWITCH_TRIGGER_ZL_Y_PORTRAIT">200</integer>
|
||||
<integer name="SWITCH_TRIGGER_ZR_X_PORTRAIT">860</integer>
|
||||
<integer name="SWITCH_TRIGGER_ZR_Y_PORTRAIT">200</integer>
|
||||
<integer name="SWITCH_BUTTON_MINUS_X_PORTRAIT">440</integer>
|
||||
<integer name="SWITCH_BUTTON_MINUS_Y_PORTRAIT">950</integer>
|
||||
<integer name="SWITCH_BUTTON_PLUS_X_PORTRAIT">560</integer>
|
||||
<integer name="SWITCH_BUTTON_PLUS_Y_PORTRAIT">950</integer>
|
||||
<integer name="SWITCH_BUTTON_HOME_X_PORTRAIT">680</integer>
|
||||
<integer name="SWITCH_BUTTON_HOME_Y_PORTRAIT">950</integer>
|
||||
<integer name="SWITCH_BUTTON_CAPTURE_X_PORTRAIT">320</integer>
|
||||
<integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer>
|
||||
<integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer>
|
||||
<integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_L_X_PORTRAIT">730</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_L_Y_PORTRAIT">510</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_R_X_PORTRAIT">900</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_R_Y_PORTRAIT">540</integer>
|
||||
<integer name="BUTTON_A_X_PORTRAIT">840</integer>
|
||||
<integer name="BUTTON_A_Y_PORTRAIT">840</integer>
|
||||
<integer name="BUTTON_B_X_PORTRAIT">740</integer>
|
||||
<integer name="BUTTON_B_Y_PORTRAIT">880</integer>
|
||||
<integer name="BUTTON_X_X_PORTRAIT">740</integer>
|
||||
<integer name="BUTTON_X_Y_PORTRAIT">800</integer>
|
||||
<integer name="BUTTON_Y_X_PORTRAIT">640</integer>
|
||||
<integer name="BUTTON_Y_Y_PORTRAIT">840</integer>
|
||||
<integer name="BUTTON_PLUS_Y_PORTRAIT">950</integer>
|
||||
<integer name="BUTTON_MINUS_X_PORTRAIT">440</integer>
|
||||
<integer name="BUTTON_MINUS_Y_PORTRAIT">950</integer>
|
||||
<integer name="BUTTON_HOME_X_PORTRAIT">680</integer>
|
||||
<integer name="BUTTON_HOME_Y_PORTRAIT">950</integer>
|
||||
<integer name="BUTTON_CAPTURE_X_PORTRAIT">320</integer>
|
||||
<integer name="BUTTON_CAPTURE_Y_PORTRAIT">950</integer>
|
||||
<integer name="BUTTON_L_X_PORTRAIT">140</integer>
|
||||
<integer name="BUTTON_L_Y_PORTRAIT">260</integer>
|
||||
<integer name="BUTTON_R_X_PORTRAIT">860</integer>
|
||||
<integer name="BUTTON_R_Y_PORTRAIT">260</integer>
|
||||
<integer name="BUTTON_ZL_X_PORTRAIT">140</integer>
|
||||
<integer name="BUTTON_ZL_Y_PORTRAIT">200</integer>
|
||||
<integer name="BUTTON_ZR_X_PORTRAIT">860</integer>
|
||||
<integer name="BUTTON_ZR_Y_PORTRAIT">200</integer>
|
||||
<integer name="BUTTON_PLUS_X_PORTRAIT">560</integer>
|
||||
<integer name="BUTTON_STICK_L_X_PORTRAIT">730</integer>
|
||||
<integer name="BUTTON_STICK_L_Y_PORTRAIT">510</integer>
|
||||
<integer name="BUTTON_STICK_R_X_PORTRAIT">900</integer>
|
||||
<integer name="BUTTON_STICK_R_Y_PORTRAIT">540</integer>
|
||||
<integer name="STICK_L_X_PORTRAIT">180</integer>
|
||||
<integer name="STICK_L_Y_PORTRAIT">660</integer>
|
||||
<integer name="STICK_R_X_PORTRAIT">820</integer>
|
||||
<integer name="STICK_R_Y_PORTRAIT">660</integer>
|
||||
<integer name="COMBINED_DPAD_X_PORTRAIT">240</integer>
|
||||
<integer name="COMBINED_DPAD_Y_PORTRAIT">840</integer>
|
||||
|
||||
<!-- Default SWITCH foldable layout -->
|
||||
<integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer>
|
||||
<integer name="SWITCH_BUTTON_A_Y_FOLDABLE">390</integer>
|
||||
<integer name="SWITCH_BUTTON_B_X_FOLDABLE">740</integer>
|
||||
<integer name="SWITCH_BUTTON_B_Y_FOLDABLE">430</integer>
|
||||
<integer name="SWITCH_BUTTON_X_X_FOLDABLE">740</integer>
|
||||
<integer name="SWITCH_BUTTON_X_Y_FOLDABLE">350</integer>
|
||||
<integer name="SWITCH_BUTTON_Y_X_FOLDABLE">640</integer>
|
||||
<integer name="SWITCH_BUTTON_Y_Y_FOLDABLE">390</integer>
|
||||
<integer name="SWITCH_STICK_L_X_FOLDABLE">180</integer>
|
||||
<integer name="SWITCH_STICK_L_Y_FOLDABLE">250</integer>
|
||||
<integer name="SWITCH_STICK_R_X_FOLDABLE">820</integer>
|
||||
<integer name="SWITCH_STICK_R_Y_FOLDABLE">250</integer>
|
||||
<integer name="SWITCH_TRIGGER_L_X_FOLDABLE">140</integer>
|
||||
<integer name="SWITCH_TRIGGER_L_Y_FOLDABLE">130</integer>
|
||||
<integer name="SWITCH_TRIGGER_R_X_FOLDABLE">860</integer>
|
||||
<integer name="SWITCH_TRIGGER_R_Y_FOLDABLE">130</integer>
|
||||
<integer name="SWITCH_TRIGGER_ZL_X_FOLDABLE">140</integer>
|
||||
<integer name="SWITCH_TRIGGER_ZL_Y_FOLDABLE">70</integer>
|
||||
<integer name="SWITCH_TRIGGER_ZR_X_FOLDABLE">860</integer>
|
||||
<integer name="SWITCH_TRIGGER_ZR_Y_FOLDABLE">70</integer>
|
||||
<integer name="SWITCH_BUTTON_MINUS_X_FOLDABLE">440</integer>
|
||||
<integer name="SWITCH_BUTTON_MINUS_Y_FOLDABLE">470</integer>
|
||||
<integer name="SWITCH_BUTTON_PLUS_X_FOLDABLE">560</integer>
|
||||
<integer name="SWITCH_BUTTON_PLUS_Y_FOLDABLE">470</integer>
|
||||
<integer name="SWITCH_BUTTON_HOME_X_FOLDABLE">680</integer>
|
||||
<integer name="SWITCH_BUTTON_HOME_Y_FOLDABLE">470</integer>
|
||||
<integer name="SWITCH_BUTTON_CAPTURE_X_FOLDABLE">320</integer>
|
||||
<integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer>
|
||||
<integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer>
|
||||
<integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_L_X_FOLDABLE">550</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_L_Y_FOLDABLE">210</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_R_X_FOLDABLE">550</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_R_Y_FOLDABLE">280</integer>
|
||||
<integer name="BUTTON_A_X_FOLDABLE">840</integer>
|
||||
<integer name="BUTTON_A_Y_FOLDABLE">390</integer>
|
||||
<integer name="BUTTON_B_X_FOLDABLE">740</integer>
|
||||
<integer name="BUTTON_B_Y_FOLDABLE">430</integer>
|
||||
<integer name="BUTTON_X_X_FOLDABLE">740</integer>
|
||||
<integer name="BUTTON_X_Y_FOLDABLE">350</integer>
|
||||
<integer name="BUTTON_Y_X_FOLDABLE">640</integer>
|
||||
<integer name="BUTTON_Y_Y_FOLDABLE">390</integer>
|
||||
<integer name="BUTTON_PLUS_X_FOLDABLE">560</integer>
|
||||
<integer name="BUTTON_PLUS_Y_FOLDABLE">470</integer>
|
||||
<integer name="BUTTON_MINUS_X_FOLDABLE">440</integer>
|
||||
<integer name="BUTTON_MINUS_Y_FOLDABLE">470</integer>
|
||||
<integer name="BUTTON_HOME_X_FOLDABLE">680</integer>
|
||||
<integer name="BUTTON_HOME_Y_FOLDABLE">470</integer>
|
||||
<integer name="BUTTON_CAPTURE_X_FOLDABLE">320</integer>
|
||||
<integer name="BUTTON_CAPTURE_Y_FOLDABLE">470</integer>
|
||||
<integer name="BUTTON_L_X_FOLDABLE">140</integer>
|
||||
<integer name="BUTTON_L_Y_FOLDABLE">130</integer>
|
||||
<integer name="BUTTON_R_X_FOLDABLE">860</integer>
|
||||
<integer name="BUTTON_R_Y_FOLDABLE">130</integer>
|
||||
<integer name="BUTTON_ZL_X_FOLDABLE">140</integer>
|
||||
<integer name="BUTTON_ZL_Y_FOLDABLE">70</integer>
|
||||
<integer name="BUTTON_ZR_X_FOLDABLE">860</integer>
|
||||
<integer name="BUTTON_ZR_Y_FOLDABLE">70</integer>
|
||||
<integer name="BUTTON_STICK_L_X_FOLDABLE">550</integer>
|
||||
<integer name="BUTTON_STICK_L_Y_FOLDABLE">210</integer>
|
||||
<integer name="BUTTON_STICK_R_X_FOLDABLE">550</integer>
|
||||
<integer name="BUTTON_STICK_R_Y_FOLDABLE">280</integer>
|
||||
<integer name="STICK_L_X_FOLDABLE">180</integer>
|
||||
<integer name="STICK_L_Y_FOLDABLE">250</integer>
|
||||
<integer name="STICK_R_X_FOLDABLE">820</integer>
|
||||
<integer name="STICK_R_Y_FOLDABLE">250</integer>
|
||||
<integer name="COMBINED_DPAD_X_FOLDABLE">240</integer>
|
||||
<integer name="COMBINED_DPAD_Y_FOLDABLE">390</integer>
|
||||
|
||||
</resources>
|
||||
|
@ -225,6 +225,8 @@
|
||||
<string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string>
|
||||
<string name="use_disk_shader_cache">Disk shader cache</string>
|
||||
<string name="use_disk_shader_cache_description">Reduces stuttering by locally storing and loading generated shaders.</string>
|
||||
<string name="anisotropic_filtering">Anisotropic filtering</string>
|
||||
<string name="anisotropic_filtering_description">Improves the quality of textures when viewed at oblique angles</string>
|
||||
|
||||
<!-- Debug settings strings -->
|
||||
<string name="cpu">CPU</string>
|
||||
@ -314,6 +316,7 @@
|
||||
<string name="add_ons_description">Toggle mods, updates and DLC</string>
|
||||
<string name="clear_shader_cache">Clear shader cache</string>
|
||||
<string name="clear_shader_cache_description">Removes all shaders built while playing this game</string>
|
||||
<string name="clear_shader_cache_warning_description">You will experience more stuttering as the shader cache regenerates</string>
|
||||
<string name="cleared_shaders_successfully">Cleared shaders successfully</string>
|
||||
<string name="addons_game">Addons: %1$s</string>
|
||||
<string name="save_data">Save data</string>
|
||||
@ -334,7 +337,7 @@
|
||||
<string name="addon_installed_successfully">Addon installed successfully</string>
|
||||
<string name="verifying_content">Verifying content…</string>
|
||||
<string name="content_install_notice">Content install notice</string>
|
||||
<string name="content_install_notice_description">The content that you selected does not match this game.\nInstall anyways?</string>
|
||||
<string name="content_install_notice_description">The content that you selected does not match this game.\nInstall anyway?</string>
|
||||
|
||||
<!-- ROM loading errors -->
|
||||
<string name="loader_error_encrypted">Your ROM is encrypted</string>
|
||||
@ -363,6 +366,7 @@
|
||||
<string name="emulation_pause">Pause emulation</string>
|
||||
<string name="emulation_unpause">Unpause emulation</string>
|
||||
<string name="emulation_input_overlay">Overlay options</string>
|
||||
<string name="touchscreen">Touchscreen</string>
|
||||
|
||||
<string name="load_settings">Loading settings…</string>
|
||||
|
||||
@ -502,8 +506,15 @@
|
||||
<string name="theme_mode_dark">Dark</string>
|
||||
|
||||
<!-- Audio output engines -->
|
||||
<string name="oboe">oboe</string>
|
||||
<string name="cubeb">cubeb</string>
|
||||
|
||||
<!-- Anisotropic filtering options -->
|
||||
<string name="multiplier_two">2x</string>
|
||||
<string name="multiplier_four">4x</string>
|
||||
<string name="multiplier_eight">8x</string>
|
||||
<string name="multiplier_sixteen">16x</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">Black backgrounds</string>
|
||||
<string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string>
|
||||
|
@ -5,7 +5,7 @@
|
||||
plugins {
|
||||
id("com.android.application") version "8.1.2" apply false
|
||||
id("com.android.library") version "8.1.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.8.21" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.20" apply false
|
||||
}
|
||||
|
||||
tasks.register("clean").configure {
|
||||
|
@ -253,6 +253,17 @@ if (ENABLE_SDL2)
|
||||
target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
target_sources(audio_core PRIVATE
|
||||
sink/oboe_sink.cpp
|
||||
sink/oboe_sink.h
|
||||
)
|
||||
|
||||
# FIXME: this port seems broken, it cannot be imported with find_package(oboe REQUIRED)
|
||||
target_link_libraries(audio_core PRIVATE "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib/liboboe.a")
|
||||
target_compile_definitions(audio_core PRIVATE HAVE_OBOE)
|
||||
endif()
|
||||
|
||||
if (YUZU_USE_PRECOMPILED_HEADERS)
|
||||
target_precompile_headers(audio_core PRIVATE precompiled_headers.h)
|
||||
endif()
|
||||
|
@ -18,9 +18,7 @@ constexpr auto INCREMENT_TIME{5ms};
|
||||
DeviceSession::DeviceSession(Core::System& system_)
|
||||
: system{system_}, thread_event{Core::Timing::CreateEvent(
|
||||
"AudioOutSampleTick",
|
||||
[this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
|
||||
return ThreadFunc();
|
||||
})} {}
|
||||
[this](s64 time, std::chrono::nanoseconds) { return ThreadFunc(); })} {}
|
||||
|
||||
DeviceSession::~DeviceSession() {
|
||||
Finalize();
|
||||
|
@ -253,8 +253,9 @@ CubebSink::~CubebSink() {
|
||||
#endif
|
||||
}
|
||||
|
||||
SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels,
|
||||
SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels_,
|
||||
const std::string& name, StreamType type) {
|
||||
system_channels = system_channels_;
|
||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
|
||||
ctx, device_channels, system_channels, output_device, input_device, name, type, system));
|
||||
|
||||
|
223
src/audio_core/sink/oboe_sink.cpp
Normal file
223
src/audio_core/sink/oboe_sink.cpp
Normal file
@ -0,0 +1,223 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include <oboe/Oboe.h>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/sink/oboe_sink.h"
|
||||
#include "audio_core/sink/sink_stream.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
|
||||
namespace AudioCore::Sink {
|
||||
|
||||
class OboeSinkStream final : public SinkStream,
|
||||
public oboe::AudioStreamDataCallback,
|
||||
public oboe::AudioStreamErrorCallback {
|
||||
public:
|
||||
explicit OboeSinkStream(Core::System& system_, StreamType type_, const std::string& name_,
|
||||
u32 system_channels_)
|
||||
: SinkStream(system_, type_) {
|
||||
name = name_;
|
||||
system_channels = system_channels_;
|
||||
|
||||
this->OpenStream();
|
||||
}
|
||||
|
||||
~OboeSinkStream() override {
|
||||
LOG_INFO(Audio_Sink, "Destroyed Oboe stream");
|
||||
}
|
||||
|
||||
void Finalize() override {
|
||||
this->Stop();
|
||||
m_stream.reset();
|
||||
}
|
||||
|
||||
void Start(bool resume = false) override {
|
||||
if (!m_stream || !paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
paused = false;
|
||||
|
||||
if (m_stream->start() != oboe::Result::OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error starting Oboe stream");
|
||||
}
|
||||
}
|
||||
|
||||
void Stop() override {
|
||||
if (!m_stream || paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->SignalPause();
|
||||
|
||||
if (m_stream->stop() != oboe::Result::OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error stopping Oboe stream");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
static s32 QueryChannelCount(oboe::Direction direction) {
|
||||
std::shared_ptr<oboe::AudioStream> temp_stream;
|
||||
oboe::AudioStreamBuilder builder;
|
||||
|
||||
const auto result = ConfigureBuilder(builder, direction)->openStream(temp_stream);
|
||||
ASSERT(result == oboe::Result::OK);
|
||||
|
||||
return temp_stream->getChannelCount() >= 6 ? 6 : 2;
|
||||
}
|
||||
|
||||
protected:
|
||||
oboe::DataCallbackResult onAudioReady(oboe::AudioStream*, void* audio_data,
|
||||
s32 num_buffer_frames) override {
|
||||
const size_t num_channels = this->GetDeviceChannels();
|
||||
const size_t frame_size = num_channels;
|
||||
const size_t num_frames = static_cast<size_t>(num_buffer_frames);
|
||||
|
||||
if (type == StreamType::In) {
|
||||
std::span<const s16> input_buffer{reinterpret_cast<const s16*>(audio_data),
|
||||
num_frames * frame_size};
|
||||
this->ProcessAudioIn(input_buffer, num_frames);
|
||||
} else {
|
||||
std::span<s16> output_buffer{reinterpret_cast<s16*>(audio_data),
|
||||
num_frames * frame_size};
|
||||
this->ProcessAudioOutAndRender(output_buffer, num_frames);
|
||||
}
|
||||
|
||||
return oboe::DataCallbackResult::Continue;
|
||||
}
|
||||
|
||||
void onErrorAfterClose(oboe::AudioStream*, oboe::Result) override {
|
||||
LOG_INFO(Audio_Sink, "Audio stream closed, reinitializing");
|
||||
|
||||
if (this->OpenStream()) {
|
||||
m_stream->start();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static oboe::AudioStreamBuilder* ConfigureBuilder(oboe::AudioStreamBuilder& builder,
|
||||
oboe::Direction direction) {
|
||||
// TODO: investigate callback delay issues when using AAudio
|
||||
return builder.setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
||||
->setAudioApi(oboe::AudioApi::OpenSLES)
|
||||
->setDirection(direction)
|
||||
->setSampleRate(TargetSampleRate)
|
||||
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
|
||||
->setFormat(oboe::AudioFormat::I16)
|
||||
->setFormatConversionAllowed(true)
|
||||
->setUsage(oboe::Usage::Game)
|
||||
->setBufferCapacityInFrames(TargetSampleCount * 2);
|
||||
}
|
||||
|
||||
bool OpenStream() {
|
||||
const auto direction = [&]() {
|
||||
switch (type) {
|
||||
case StreamType::In:
|
||||
return oboe::Direction::Input;
|
||||
case StreamType::Out:
|
||||
case StreamType::Render:
|
||||
return oboe::Direction::Output;
|
||||
default:
|
||||
ASSERT(false);
|
||||
return oboe::Direction::Output;
|
||||
}
|
||||
}();
|
||||
|
||||
const auto expected_channels = QueryChannelCount(direction);
|
||||
const auto expected_mask = [&]() {
|
||||
switch (expected_channels) {
|
||||
case 1:
|
||||
return oboe::ChannelMask::Mono;
|
||||
case 2:
|
||||
return oboe::ChannelMask::Stereo;
|
||||
case 6:
|
||||
return oboe::ChannelMask::CM5Point1;
|
||||
default:
|
||||
ASSERT(false);
|
||||
return oboe::ChannelMask::Unspecified;
|
||||
}
|
||||
}();
|
||||
|
||||
oboe::AudioStreamBuilder builder;
|
||||
const auto result = ConfigureBuilder(builder, direction)
|
||||
->setChannelCount(expected_channels)
|
||||
->setChannelMask(expected_mask)
|
||||
->setChannelConversionAllowed(true)
|
||||
->setDataCallback(this)
|
||||
->setErrorCallback(this)
|
||||
->openStream(m_stream);
|
||||
ASSERT(result == oboe::Result::OK);
|
||||
return result == oboe::Result::OK && this->SetStreamProperties();
|
||||
}
|
||||
|
||||
bool SetStreamProperties() {
|
||||
ASSERT(m_stream);
|
||||
|
||||
m_stream->setBufferSizeInFrames(TargetSampleCount * 2);
|
||||
device_channels = m_stream->getChannelCount();
|
||||
|
||||
const auto sample_rate = m_stream->getSampleRate();
|
||||
const auto buffer_capacity = m_stream->getBufferCapacityInFrames();
|
||||
const auto stream_backend =
|
||||
m_stream->getAudioApi() == oboe::AudioApi::AAudio ? "AAudio" : "OpenSLES";
|
||||
|
||||
LOG_INFO(Audio_Sink, "Opened Oboe {} stream with {} channels sample rate {} capacity {}",
|
||||
stream_backend, device_channels, sample_rate, buffer_capacity);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<oboe::AudioStream> m_stream{};
|
||||
};
|
||||
|
||||
OboeSink::OboeSink() {
|
||||
// TODO: This is not generally knowable
|
||||
// The channel count is distinct based on direction and can change
|
||||
device_channels = OboeSinkStream::QueryChannelCount(oboe::Direction::Output);
|
||||
}
|
||||
|
||||
OboeSink::~OboeSink() = default;
|
||||
|
||||
SinkStream* OboeSink::AcquireSinkStream(Core::System& system, u32 system_channels,
|
||||
const std::string& name, StreamType type) {
|
||||
SinkStreamPtr& stream = sink_streams.emplace_back(
|
||||
std::make_unique<OboeSinkStream>(system, type, name, system_channels));
|
||||
|
||||
return stream.get();
|
||||
}
|
||||
|
||||
void OboeSink::CloseStream(SinkStream* to_remove) {
|
||||
sink_streams.remove_if([&](auto& stream) { return stream.get() == to_remove; });
|
||||
}
|
||||
|
||||
void OboeSink::CloseStreams() {
|
||||
sink_streams.clear();
|
||||
}
|
||||
|
||||
f32 OboeSink::GetDeviceVolume() const {
|
||||
if (sink_streams.empty()) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return sink_streams.front()->GetDeviceVolume();
|
||||
}
|
||||
|
||||
void OboeSink::SetDeviceVolume(f32 volume) {
|
||||
for (auto& stream : sink_streams) {
|
||||
stream->SetDeviceVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
void OboeSink::SetSystemVolume(f32 volume) {
|
||||
for (auto& stream : sink_streams) {
|
||||
stream->SetSystemVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore::Sink
|
75
src/audio_core/sink/oboe_sink.h
Normal file
75
src/audio_core/sink/oboe_sink.h
Normal file
@ -0,0 +1,75 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/sink/sink.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace AudioCore::Sink {
|
||||
class SinkStream;
|
||||
|
||||
class OboeSink final : public Sink {
|
||||
public:
|
||||
explicit OboeSink();
|
||||
~OboeSink() override;
|
||||
|
||||
/**
|
||||
* Create a new sink stream.
|
||||
*
|
||||
* @param system - Core system.
|
||||
* @param system_channels - Number of channels the audio system expects.
|
||||
* May differ from the device's channel count.
|
||||
* @param name - Name of this stream.
|
||||
* @param type - Type of this stream, render/in/out.
|
||||
*
|
||||
* @return A pointer to the created SinkStream
|
||||
*/
|
||||
SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
|
||||
const std::string& name, StreamType type) override;
|
||||
|
||||
/**
|
||||
* Close a given stream.
|
||||
*
|
||||
* @param stream - The stream to close.
|
||||
*/
|
||||
void CloseStream(SinkStream* stream) override;
|
||||
|
||||
/**
|
||||
* Close all streams.
|
||||
*/
|
||||
void CloseStreams() override;
|
||||
|
||||
/**
|
||||
* Get the device volume. Set from calls to the IAudioDevice service.
|
||||
*
|
||||
* @return Volume of the device.
|
||||
*/
|
||||
f32 GetDeviceVolume() const override;
|
||||
|
||||
/**
|
||||
* Set the device volume. Set from calls to the IAudioDevice service.
|
||||
*
|
||||
* @param volume - New volume of the device.
|
||||
*/
|
||||
void SetDeviceVolume(f32 volume) override;
|
||||
|
||||
/**
|
||||
* Set the system volume. Comes from the audio system using this stream.
|
||||
*
|
||||
* @param volume - New volume of the system.
|
||||
*/
|
||||
void SetSystemVolume(f32 volume) override;
|
||||
|
||||
private:
|
||||
/// List of streams managed by this sink
|
||||
std::list<SinkStreamPtr> sink_streams{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::Sink
|
@ -168,8 +168,9 @@ SDLSink::SDLSink(std::string_view target_device_name) {
|
||||
|
||||
SDLSink::~SDLSink() = default;
|
||||
|
||||
SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels,
|
||||
SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels_,
|
||||
const std::string&, StreamType type) {
|
||||
system_channels = system_channels_;
|
||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
|
||||
device_channels, system_channels, output_device, input_device, type, system));
|
||||
return stream.get();
|
||||
|
@ -85,9 +85,21 @@ public:
|
||||
*/
|
||||
virtual void SetSystemVolume(f32 volume) = 0;
|
||||
|
||||
/**
|
||||
* Get the number of channels the game has set, can be different to the host hardware's support.
|
||||
* Either 2 or 6.
|
||||
*
|
||||
* @return Number of device channels.
|
||||
*/
|
||||
u32 GetSystemChannels() const {
|
||||
return system_channels;
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Number of device channels supported by the hardware
|
||||
u32 device_channels{2};
|
||||
/// Number of channels the game is sending
|
||||
u32 system_channels{2};
|
||||
};
|
||||
|
||||
using SinkPtr = std::unique_ptr<Sink>;
|
||||
|
@ -7,6 +7,9 @@
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/sink/sink_details.h"
|
||||
#ifdef HAVE_OBOE
|
||||
#include "audio_core/sink/oboe_sink.h"
|
||||
#endif
|
||||
#ifdef HAVE_CUBEB
|
||||
#include "audio_core/sink/cubeb_sink.h"
|
||||
#endif
|
||||
@ -36,6 +39,16 @@ struct SinkDetails {
|
||||
|
||||
// sink_details is ordered in terms of desirability, with the best choice at the top.
|
||||
constexpr SinkDetails sink_details[] = {
|
||||
#ifdef HAVE_OBOE
|
||||
SinkDetails{
|
||||
Settings::AudioEngine::Oboe,
|
||||
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||
return std::make_unique<OboeSink>();
|
||||
},
|
||||
[](bool capture) { return std::vector<std::string>{"Default"}; },
|
||||
[]() { return true; },
|
||||
},
|
||||
#endif
|
||||
#ifdef HAVE_CUBEB
|
||||
SinkDetails{
|
||||
Settings::AudioEngine::Cubeb,
|
||||
|
@ -40,29 +40,36 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::span<s16> samples) {
|
||||
|
||||
if (system_channels == 6 && device_channels == 2) {
|
||||
// We're given 6 channels, but our device only outputs 2, so downmix.
|
||||
static constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
|
||||
// Front = 1.0
|
||||
// Center = 0.596
|
||||
// LFE = 0.354
|
||||
// Back = 0.707
|
||||
static constexpr std::array<f32, 4> down_mix_coeff{1.0, 0.596f, 0.354f, 0.707f};
|
||||
|
||||
for (u32 read_index = 0, write_index = 0; read_index < samples.size();
|
||||
read_index += system_channels, write_index += device_channels) {
|
||||
const auto fl =
|
||||
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontLeft)]);
|
||||
const auto fr =
|
||||
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontRight)]);
|
||||
const auto c =
|
||||
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::Center)]);
|
||||
const auto lfe =
|
||||
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::LFE)]);
|
||||
const auto bl =
|
||||
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::BackLeft)]);
|
||||
const auto br =
|
||||
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::BackRight)]);
|
||||
|
||||
const auto left_sample{
|
||||
((Common::FixedPoint<49, 15>(
|
||||
samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
|
||||
down_mix_coeff[0] +
|
||||
samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
|
||||
samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
|
||||
samples[read_index + static_cast<u32>(Channels::BackLeft)] * down_mix_coeff[3]) *
|
||||
volume)
|
||||
.to_int()};
|
||||
static_cast<s32>((fl * down_mix_coeff[0] + c * down_mix_coeff[1] +
|
||||
lfe * down_mix_coeff[2] + bl * down_mix_coeff[3]) *
|
||||
volume)};
|
||||
|
||||
const auto right_sample{
|
||||
((Common::FixedPoint<49, 15>(
|
||||
samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
|
||||
down_mix_coeff[0] +
|
||||
samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
|
||||
samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
|
||||
samples[read_index + static_cast<u32>(Channels::BackRight)] * down_mix_coeff[3]) *
|
||||
volume)
|
||||
.to_int()};
|
||||
static_cast<s32>((fr * down_mix_coeff[0] + c * down_mix_coeff[1] +
|
||||
lfe * down_mix_coeff[2] + br * down_mix_coeff[3]) *
|
||||
volume)};
|
||||
|
||||
samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
|
||||
static_cast<s16>(std::clamp(left_sample, min, max));
|
||||
|
@ -64,6 +64,8 @@ add_library(common STATIC
|
||||
fs/path_util.cpp
|
||||
fs/path_util.h
|
||||
hash.h
|
||||
heap_tracker.cpp
|
||||
heap_tracker.h
|
||||
hex_util.cpp
|
||||
hex_util.h
|
||||
host_memory.cpp
|
||||
|
@ -3,16 +3,19 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/logging/backend.h"
|
||||
|
||||
#include "common/settings.h"
|
||||
|
||||
void assert_fail_impl() {
|
||||
if (Settings::values.use_debug_asserts) {
|
||||
Common::Log::Stop();
|
||||
Crash();
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void unreachable_impl() {
|
||||
Common::Log::Stop();
|
||||
Crash();
|
||||
throw std::runtime_error("Unreachable code");
|
||||
}
|
||||
|
@ -123,6 +123,12 @@ namespace Common {
|
||||
return u32(a) | u32(b) << 8 | u32(c) << 16 | u32(d) << 24;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u64 MakeMagic(char a, char b, char c, char d, char e, char f, char g,
|
||||
char h) {
|
||||
return u64(a) << 0 | u64(b) << 8 | u64(c) << 16 | u64(d) << 24 | u64(e) << 32 | u64(f) << 40 |
|
||||
u64(g) << 48 | u64(h) << 56;
|
||||
}
|
||||
|
||||
// std::size() does not support zero-size C arrays. We're fixing that.
|
||||
template <class C>
|
||||
constexpr auto Size(const C& c) -> decltype(c.size()) {
|
||||
|
@ -354,18 +354,36 @@ std::string_view RemoveTrailingSlash(std::string_view path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitPathComponents(std::string_view filename) {
|
||||
std::string copy(filename);
|
||||
std::replace(copy.begin(), copy.end(), '\\', '/');
|
||||
std::vector<std::string> out;
|
||||
|
||||
std::stringstream stream(copy);
|
||||
std::string item;
|
||||
while (std::getline(stream, item, '/')) {
|
||||
out.push_back(std::move(item));
|
||||
template <typename F>
|
||||
static void ForEachPathComponent(std::string_view filename, F&& cb) {
|
||||
const char* component_begin = filename.data();
|
||||
const char* const end = component_begin + filename.size();
|
||||
for (const char* it = component_begin; it != end; ++it) {
|
||||
const char c = *it;
|
||||
if (c == '\\' || c == '/') {
|
||||
if (component_begin != it) {
|
||||
cb(std::string_view{component_begin, it});
|
||||
}
|
||||
component_begin = it + 1;
|
||||
}
|
||||
}
|
||||
if (component_begin != end) {
|
||||
cb(std::string_view{component_begin, end});
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
std::vector<std::string_view> SplitPathComponents(std::string_view filename) {
|
||||
std::vector<std::string_view> components;
|
||||
ForEachPathComponent(filename, [&](auto component) { components.emplace_back(component); });
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitPathComponentsCopy(std::string_view filename) {
|
||||
std::vector<std::string> components;
|
||||
ForEachPathComponent(filename, [&](auto component) { components.emplace_back(component); });
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
|
||||
@ -400,9 +418,9 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
|
||||
return std::string(RemoveTrailingSlash(path));
|
||||
}
|
||||
|
||||
std::string_view GetParentPath(std::string_view path) {
|
||||
std::string GetParentPath(std::string_view path) {
|
||||
if (path.empty()) {
|
||||
return path;
|
||||
return std::string(path);
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
@ -421,7 +439,7 @@ std::string_view GetParentPath(std::string_view path) {
|
||||
name_index = std::max(name_bck_index, name_fwd_index);
|
||||
}
|
||||
|
||||
return path.substr(0, name_index);
|
||||
return std::string(path.substr(0, name_index));
|
||||
}
|
||||
|
||||
std::string_view GetPathWithoutTop(std::string_view path) {
|
||||
|
@ -289,7 +289,11 @@ enum class DirectorySeparator {
|
||||
|
||||
// Splits the path on '/' or '\' and put the components into a vector
|
||||
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
|
||||
[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename);
|
||||
[[nodiscard]] std::vector<std::string_view> SplitPathComponents(std::string_view filename);
|
||||
|
||||
// Splits the path on '/' or '\' and put the components into a vector
|
||||
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
|
||||
[[nodiscard]] std::vector<std::string> SplitPathComponentsCopy(std::string_view filename);
|
||||
|
||||
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
|
||||
// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
|
||||
@ -298,7 +302,7 @@ enum class DirectorySeparator {
|
||||
DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
|
||||
|
||||
// Gets all of the text up to the last '/' or '\' in the path.
|
||||
[[nodiscard]] std::string_view GetParentPath(std::string_view path);
|
||||
[[nodiscard]] std::string GetParentPath(std::string_view path);
|
||||
|
||||
// Gets all of the text after the first '/' or '\' in the path.
|
||||
[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path);
|
||||
|
281
src/common/heap_tracker.cpp
Normal file
281
src/common/heap_tracker.cpp
Normal file
@ -0,0 +1,281 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
#include "common/heap_tracker.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace {
|
||||
|
||||
s64 GetMaxPermissibleResidentMapCount() {
|
||||
// Default value.
|
||||
s64 value = 65530;
|
||||
|
||||
// Try to read how many mappings we can make.
|
||||
std::ifstream s("/proc/sys/vm/max_map_count");
|
||||
s >> value;
|
||||
|
||||
// Print, for debug.
|
||||
LOG_INFO(HW_Memory, "Current maximum map count: {}", value);
|
||||
|
||||
// Allow 20000 maps for other code and to account for split inaccuracy.
|
||||
return std::max<s64>(value - 20000, 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
HeapTracker::HeapTracker(Common::HostMemory& buffer)
|
||||
: m_buffer(buffer), m_max_resident_map_count(GetMaxPermissibleResidentMapCount()) {}
|
||||
HeapTracker::~HeapTracker() = default;
|
||||
|
||||
void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length,
|
||||
MemoryPermission perm, bool is_separate_heap) {
|
||||
// When mapping other memory, map pages immediately.
|
||||
if (!is_separate_heap) {
|
||||
m_buffer.Map(virtual_offset, host_offset, length, perm, false);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
// We are mapping part of a separate heap.
|
||||
std::scoped_lock lk{m_lock};
|
||||
|
||||
auto* const map = new SeparateHeapMap{
|
||||
.vaddr = virtual_offset,
|
||||
.paddr = host_offset,
|
||||
.size = length,
|
||||
.tick = m_tick++,
|
||||
.perm = perm,
|
||||
.is_resident = false,
|
||||
};
|
||||
|
||||
// Insert into mappings.
|
||||
m_map_count++;
|
||||
m_mappings.insert(*map);
|
||||
}
|
||||
|
||||
// Finally, map.
|
||||
this->DeferredMapSeparateHeap(virtual_offset);
|
||||
}
|
||||
|
||||
void HeapTracker::Unmap(size_t virtual_offset, size_t size, bool is_separate_heap) {
|
||||
// If this is a separate heap...
|
||||
if (is_separate_heap) {
|
||||
std::scoped_lock lk{m_lock};
|
||||
|
||||
const SeparateHeapMap key{
|
||||
.vaddr = virtual_offset,
|
||||
};
|
||||
|
||||
// Split at the boundaries of the region we are removing.
|
||||
this->SplitHeapMapLocked(virtual_offset);
|
||||
this->SplitHeapMapLocked(virtual_offset + size);
|
||||
|
||||
// Erase all mappings in range.
|
||||
auto it = m_mappings.find(key);
|
||||
while (it != m_mappings.end() && it->vaddr < virtual_offset + size) {
|
||||
// Get underlying item.
|
||||
auto* const item = std::addressof(*it);
|
||||
|
||||
// If resident, erase from resident map.
|
||||
if (item->is_resident) {
|
||||
ASSERT(--m_resident_map_count >= 0);
|
||||
m_resident_mappings.erase(m_resident_mappings.iterator_to(*item));
|
||||
}
|
||||
|
||||
// Erase from map.
|
||||
ASSERT(--m_map_count >= 0);
|
||||
it = m_mappings.erase(it);
|
||||
|
||||
// Free the item.
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
// Unmap pages.
|
||||
m_buffer.Unmap(virtual_offset, size, false);
|
||||
}
|
||||
|
||||
void HeapTracker::Protect(size_t virtual_offset, size_t size, MemoryPermission perm) {
|
||||
// Ensure no rebuild occurs while reprotecting.
|
||||
std::shared_lock lk{m_rebuild_lock};
|
||||
|
||||
// Split at the boundaries of the region we are reprotecting.
|
||||
this->SplitHeapMap(virtual_offset, size);
|
||||
|
||||
// Declare tracking variables.
|
||||
const VAddr end = virtual_offset + size;
|
||||
VAddr cur = virtual_offset;
|
||||
|
||||
while (cur < end) {
|
||||
VAddr next = cur;
|
||||
bool should_protect = false;
|
||||
|
||||
{
|
||||
std::scoped_lock lk2{m_lock};
|
||||
|
||||
const SeparateHeapMap key{
|
||||
.vaddr = next,
|
||||
};
|
||||
|
||||
// Try to get the next mapping corresponding to this address.
|
||||
const auto it = m_mappings.nfind(key);
|
||||
|
||||
if (it == m_mappings.end()) {
|
||||
// There are no separate heap mappings remaining.
|
||||
next = end;
|
||||
should_protect = true;
|
||||
} else if (it->vaddr == cur) {
|
||||
// We are in range.
|
||||
// Update permission bits.
|
||||
it->perm = perm;
|
||||
|
||||
// Determine next address and whether we should protect.
|
||||
next = cur + it->size;
|
||||
should_protect = it->is_resident;
|
||||
} else /* if (it->vaddr > cur) */ {
|
||||
// We weren't in range, but there is a block coming up that will be.
|
||||
next = it->vaddr;
|
||||
should_protect = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Clamp to end.
|
||||
next = std::min(next, end);
|
||||
|
||||
// Reprotect, if we need to.
|
||||
if (should_protect) {
|
||||
m_buffer.Protect(cur, next - cur, perm);
|
||||
}
|
||||
|
||||
// Advance.
|
||||
cur = next;
|
||||
}
|
||||
}
|
||||
|
||||
bool HeapTracker::DeferredMapSeparateHeap(u8* fault_address) {
|
||||
if (m_buffer.IsInVirtualRange(fault_address)) {
|
||||
return this->DeferredMapSeparateHeap(fault_address - m_buffer.VirtualBasePointer());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HeapTracker::DeferredMapSeparateHeap(size_t virtual_offset) {
|
||||
bool rebuild_required = false;
|
||||
|
||||
{
|
||||
std::scoped_lock lk{m_lock};
|
||||
|
||||
// Check to ensure this was a non-resident separate heap mapping.
|
||||
const auto it = this->GetNearestHeapMapLocked(virtual_offset);
|
||||
if (it == m_mappings.end() || it->is_resident) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update tick before possible rebuild.
|
||||
it->tick = m_tick++;
|
||||
|
||||
// Check if we need to rebuild.
|
||||
if (m_resident_map_count > m_max_resident_map_count) {
|
||||
rebuild_required = true;
|
||||
}
|
||||
|
||||
// Map the area.
|
||||
m_buffer.Map(it->vaddr, it->paddr, it->size, it->perm, false);
|
||||
|
||||
// This map is now resident.
|
||||
it->is_resident = true;
|
||||
m_resident_map_count++;
|
||||
m_resident_mappings.insert(*it);
|
||||
}
|
||||
|
||||
if (rebuild_required) {
|
||||
// A rebuild was required, so perform it now.
|
||||
this->RebuildSeparateHeapAddressSpace();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HeapTracker::RebuildSeparateHeapAddressSpace() {
|
||||
std::scoped_lock lk{m_rebuild_lock, m_lock};
|
||||
|
||||
ASSERT(!m_resident_mappings.empty());
|
||||
|
||||
// Dump half of the mappings.
|
||||
//
|
||||
// Despite being worse in theory, this has proven to be better in practice than more
|
||||
// regularly dumping a smaller amount, because it significantly reduces average case
|
||||
// lock contention.
|
||||
const size_t desired_count = std::min(m_resident_map_count, m_max_resident_map_count) / 2;
|
||||
const size_t evict_count = m_resident_map_count - desired_count;
|
||||
auto it = m_resident_mappings.begin();
|
||||
|
||||
for (size_t i = 0; i < evict_count && it != m_resident_mappings.end(); i++) {
|
||||
// Unmark and unmap.
|
||||
it->is_resident = false;
|
||||
m_buffer.Unmap(it->vaddr, it->size, false);
|
||||
|
||||
// Advance.
|
||||
ASSERT(--m_resident_map_count >= 0);
|
||||
it = m_resident_mappings.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void HeapTracker::SplitHeapMap(VAddr offset, size_t size) {
|
||||
std::scoped_lock lk{m_lock};
|
||||
|
||||
this->SplitHeapMapLocked(offset);
|
||||
this->SplitHeapMapLocked(offset + size);
|
||||
}
|
||||
|
||||
void HeapTracker::SplitHeapMapLocked(VAddr offset) {
|
||||
const auto it = this->GetNearestHeapMapLocked(offset);
|
||||
if (it == m_mappings.end() || it->vaddr == offset) {
|
||||
// Not contained or no split required.
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache the original values.
|
||||
auto* const left = std::addressof(*it);
|
||||
const size_t orig_size = left->size;
|
||||
|
||||
// Adjust the left map.
|
||||
const size_t left_size = offset - left->vaddr;
|
||||
left->size = left_size;
|
||||
|
||||
// Create the new right map.
|
||||
auto* const right = new SeparateHeapMap{
|
||||
.vaddr = left->vaddr + left_size,
|
||||
.paddr = left->paddr + left_size,
|
||||
.size = orig_size - left_size,
|
||||
.tick = left->tick,
|
||||
.perm = left->perm,
|
||||
.is_resident = left->is_resident,
|
||||
};
|
||||
|
||||
// Insert the new right map.
|
||||
m_map_count++;
|
||||
m_mappings.insert(*right);
|
||||
|
||||
// If resident, also insert into resident map.
|
||||
if (right->is_resident) {
|
||||
m_resident_map_count++;
|
||||
m_resident_mappings.insert(*right);
|
||||
}
|
||||
}
|
||||
|
||||
HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) {
|
||||
const SeparateHeapMap key{
|
||||
.vaddr = offset,
|
||||
};
|
||||
|
||||
return m_mappings.find(key);
|
||||
}
|
||||
|
||||
} // namespace Common
|
98
src/common/heap_tracker.h
Normal file
98
src/common/heap_tracker.h
Normal file
@ -0,0 +1,98 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <shared_mutex>
|
||||
|
||||
#include "common/host_memory.h"
|
||||
#include "common/intrusive_red_black_tree.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
struct SeparateHeapMap {
|
||||
Common::IntrusiveRedBlackTreeNode addr_node{};
|
||||
Common::IntrusiveRedBlackTreeNode tick_node{};
|
||||
VAddr vaddr{};
|
||||
PAddr paddr{};
|
||||
size_t size{};
|
||||
size_t tick{};
|
||||
MemoryPermission perm{};
|
||||
bool is_resident{};
|
||||
};
|
||||
|
||||
struct SeparateHeapMapAddrComparator {
|
||||
static constexpr int Compare(const SeparateHeapMap& lhs, const SeparateHeapMap& rhs) {
|
||||
if (lhs.vaddr < rhs.vaddr) {
|
||||
return -1;
|
||||
} else if (lhs.vaddr <= (rhs.vaddr + rhs.size - 1)) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct SeparateHeapMapTickComparator {
|
||||
static constexpr int Compare(const SeparateHeapMap& lhs, const SeparateHeapMap& rhs) {
|
||||
if (lhs.tick < rhs.tick) {
|
||||
return -1;
|
||||
} else if (lhs.tick > rhs.tick) {
|
||||
return 1;
|
||||
} else {
|
||||
return SeparateHeapMapAddrComparator::Compare(lhs, rhs);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class HeapTracker {
|
||||
public:
|
||||
explicit HeapTracker(Common::HostMemory& buffer);
|
||||
~HeapTracker();
|
||||
|
||||
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perm,
|
||||
bool is_separate_heap);
|
||||
void Unmap(size_t virtual_offset, size_t size, bool is_separate_heap);
|
||||
void Protect(size_t virtual_offset, size_t length, MemoryPermission perm);
|
||||
u8* VirtualBasePointer() {
|
||||
return m_buffer.VirtualBasePointer();
|
||||
}
|
||||
|
||||
bool DeferredMapSeparateHeap(u8* fault_address);
|
||||
bool DeferredMapSeparateHeap(size_t virtual_offset);
|
||||
|
||||
private:
|
||||
using AddrTreeTraits =
|
||||
Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&SeparateHeapMap::addr_node>;
|
||||
using AddrTree = AddrTreeTraits::TreeType<SeparateHeapMapAddrComparator>;
|
||||
|
||||
using TickTreeTraits =
|
||||
Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&SeparateHeapMap::tick_node>;
|
||||
using TickTree = TickTreeTraits::TreeType<SeparateHeapMapTickComparator>;
|
||||
|
||||
AddrTree m_mappings{};
|
||||
TickTree m_resident_mappings{};
|
||||
|
||||
private:
|
||||
void SplitHeapMap(VAddr offset, size_t size);
|
||||
void SplitHeapMapLocked(VAddr offset);
|
||||
|
||||
AddrTree::iterator GetNearestHeapMapLocked(VAddr offset);
|
||||
|
||||
void RebuildSeparateHeapAddressSpace();
|
||||
|
||||
private:
|
||||
Common::HostMemory& m_buffer;
|
||||
const s64 m_max_resident_map_count;
|
||||
|
||||
std::shared_mutex m_rebuild_lock{};
|
||||
std::mutex m_lock{};
|
||||
s64 m_map_count{};
|
||||
s64 m_resident_map_count{};
|
||||
size_t m_tick{};
|
||||
};
|
||||
|
||||
} // namespace Common
|
@ -11,10 +11,6 @@
|
||||
|
||||
#elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv
|
||||
|
||||
#ifdef ANDROID
|
||||
#include <android/sharedmem.h>
|
||||
#endif
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
@ -193,6 +189,11 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool ClearBackingRegion(size_t physical_offset, size_t length) {
|
||||
// TODO: This does not seem to be possible on Windows.
|
||||
return false;
|
||||
}
|
||||
|
||||
void EnableDirectMappedAddress() {
|
||||
// TODO
|
||||
UNREACHABLE();
|
||||
@ -442,9 +443,7 @@ public:
|
||||
}
|
||||
|
||||
// Backing memory initialization
|
||||
#ifdef ANDROID
|
||||
fd = ASharedMemory_create("HostMemory", backing_size);
|
||||
#elif defined(__FreeBSD__) && __FreeBSD__ < 13
|
||||
#if defined(__FreeBSD__) && __FreeBSD__ < 13
|
||||
// XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30
|
||||
fd = shm_open(SHM_ANON, O_RDWR, 0600);
|
||||
#else
|
||||
@ -455,7 +454,6 @@ public:
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
|
||||
#ifndef ANDROID
|
||||
// Defined to extend the file with zeros
|
||||
int ret = ftruncate(fd, backing_size);
|
||||
if (ret != 0) {
|
||||
@ -463,7 +461,6 @@ public:
|
||||
strerror(errno));
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
#endif
|
||||
|
||||
backing_base = static_cast<u8*>(
|
||||
mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
|
||||
@ -552,6 +549,19 @@ public:
|
||||
ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno));
|
||||
}
|
||||
|
||||
bool ClearBackingRegion(size_t physical_offset, size_t length) {
|
||||
#ifdef __linux__
|
||||
// Set MADV_REMOVE on backing map to destroy it instantly.
|
||||
// This also deletes the area from the backing file.
|
||||
int ret = madvise(backing_base + physical_offset, length, MADV_REMOVE);
|
||||
ASSERT_MSG(ret == 0, "madvise failed: {}", strerror(errno));
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void EnableDirectMappedAddress() {
|
||||
virtual_base = nullptr;
|
||||
}
|
||||
@ -623,6 +633,10 @@ public:
|
||||
|
||||
void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {}
|
||||
|
||||
bool ClearBackingRegion(size_t physical_offset, size_t length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void EnableDirectMappedAddress() {}
|
||||
|
||||
u8* backing_base{nullptr};
|
||||
@ -665,7 +679,7 @@ HostMemory::HostMemory(HostMemory&&) noexcept = default;
|
||||
HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default;
|
||||
|
||||
void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length,
|
||||
MemoryPermission perms) {
|
||||
MemoryPermission perms, bool separate_heap) {
|
||||
ASSERT(virtual_offset % PageAlignment == 0);
|
||||
ASSERT(host_offset % PageAlignment == 0);
|
||||
ASSERT(length % PageAlignment == 0);
|
||||
@ -677,7 +691,7 @@ void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length,
|
||||
impl->Map(virtual_offset + virtual_base_offset, host_offset, length, perms);
|
||||
}
|
||||
|
||||
void HostMemory::Unmap(size_t virtual_offset, size_t length) {
|
||||
void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap) {
|
||||
ASSERT(virtual_offset % PageAlignment == 0);
|
||||
ASSERT(length % PageAlignment == 0);
|
||||
ASSERT(virtual_offset + length <= virtual_size);
|
||||
@ -687,17 +701,25 @@ void HostMemory::Unmap(size_t virtual_offset, size_t length) {
|
||||
impl->Unmap(virtual_offset + virtual_base_offset, length);
|
||||
}
|
||||
|
||||
void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool write,
|
||||
bool execute) {
|
||||
void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission perm) {
|
||||
ASSERT(virtual_offset % PageAlignment == 0);
|
||||
ASSERT(length % PageAlignment == 0);
|
||||
ASSERT(virtual_offset + length <= virtual_size);
|
||||
if (length == 0 || !virtual_base || !impl) {
|
||||
return;
|
||||
}
|
||||
const bool read = True(perm & MemoryPermission::Read);
|
||||
const bool write = True(perm & MemoryPermission::Write);
|
||||
const bool execute = True(perm & MemoryPermission::Execute);
|
||||
impl->Protect(virtual_offset + virtual_base_offset, length, read, write, execute);
|
||||
}
|
||||
|
||||
void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 fill_value) {
|
||||
if (!impl || fill_value != 0 || !impl->ClearBackingRegion(physical_offset, length)) {
|
||||
std::memset(backing_base + physical_offset, fill_value, length);
|
||||
}
|
||||
}
|
||||
|
||||
void HostMemory::EnableDirectMappedAddress() {
|
||||
if (impl) {
|
||||
impl->EnableDirectMappedAddress();
|
||||
|
@ -40,14 +40,17 @@ public:
|
||||
HostMemory(HostMemory&& other) noexcept;
|
||||
HostMemory& operator=(HostMemory&& other) noexcept;
|
||||
|
||||
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms);
|
||||
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms,
|
||||
bool separate_heap);
|
||||
|
||||
void Unmap(size_t virtual_offset, size_t length);
|
||||
void Unmap(size_t virtual_offset, size_t length, bool separate_heap);
|
||||
|
||||
void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute = false);
|
||||
void Protect(size_t virtual_offset, size_t length, MemoryPermission perms);
|
||||
|
||||
void EnableDirectMappedAddress();
|
||||
|
||||
void ClearBackingRegion(size_t physical_offset, size_t length, u32 fill_value);
|
||||
|
||||
[[nodiscard]] u8* BackingBasePointer() noexcept {
|
||||
return backing_base;
|
||||
}
|
||||
@ -62,6 +65,10 @@ public:
|
||||
return virtual_base;
|
||||
}
|
||||
|
||||
bool IsInVirtualRange(void* address) const noexcept {
|
||||
return address >= virtual_base && address < virtual_base + virtual_size;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t backing_size{};
|
||||
size_t virtual_size{};
|
||||
|
@ -208,6 +208,10 @@ public:
|
||||
instance->StartBackendThread();
|
||||
}
|
||||
|
||||
static void Stop() {
|
||||
instance->StopBackendThread();
|
||||
}
|
||||
|
||||
Impl(const Impl&) = delete;
|
||||
Impl& operator=(const Impl&) = delete;
|
||||
|
||||
@ -259,6 +263,15 @@ private:
|
||||
});
|
||||
}
|
||||
|
||||
void StopBackendThread() {
|
||||
backend_thread.request_stop();
|
||||
if (backend_thread.joinable()) {
|
||||
backend_thread.join();
|
||||
}
|
||||
|
||||
ForEachBackend([](Backend& backend) { backend.Flush(); });
|
||||
}
|
||||
|
||||
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
|
||||
const char* function, std::string&& message) const {
|
||||
using std::chrono::duration_cast;
|
||||
@ -313,6 +326,10 @@ void Start() {
|
||||
Impl::Start();
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
Impl::Stop();
|
||||
}
|
||||
|
||||
void DisableLoggingInTests() {
|
||||
initialization_in_progress_suppress_logging = true;
|
||||
}
|
||||
|
@ -14,6 +14,9 @@ void Initialize();
|
||||
|
||||
void Start();
|
||||
|
||||
/// Explicitly stops the logger thread and flushes the buffers
|
||||
void Stop();
|
||||
|
||||
void DisableLoggingInTests();
|
||||
|
||||
/**
|
||||
|
@ -103,7 +103,7 @@ private:
|
||||
// Having them on the same cache-line would result in false-sharing between them.
|
||||
// TODO: Remove this ifdef whenever clang and GCC support
|
||||
// std::hardware_destructive_interference_size.
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1911
|
||||
#ifdef __cpp_lib_hardware_interference_size
|
||||
alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_read_index{0};
|
||||
alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_write_index{0};
|
||||
#else
|
||||
|
@ -199,6 +199,8 @@ const char* TranslateCategory(Category category) {
|
||||
case Category::CpuDebug:
|
||||
case Category::CpuUnsafe:
|
||||
return "Cpu";
|
||||
case Category::Overlay:
|
||||
return "Overlay";
|
||||
case Category::Renderer:
|
||||
case Category::RendererAdvanced:
|
||||
case Category::RendererDebug:
|
||||
|
@ -256,7 +256,7 @@ struct Values {
|
||||
AstcDecodeMode::CpuAsynchronous,
|
||||
"accelerate_astc",
|
||||
Category::Renderer};
|
||||
Setting<VSyncMode, true> vsync_mode{
|
||||
SwitchableSetting<VSyncMode, true> vsync_mode{
|
||||
linkage, VSyncMode::Fifo, VSyncMode::Immediate, VSyncMode::FifoRelaxed,
|
||||
"use_vsync", Category::Renderer, Specialization::RuntimeList, true,
|
||||
true};
|
||||
|
@ -18,6 +18,7 @@ enum class Category : u32 {
|
||||
Cpu,
|
||||
CpuDebug,
|
||||
CpuUnsafe,
|
||||
Overlay,
|
||||
Renderer,
|
||||
RendererAdvanced,
|
||||
RendererDebug,
|
||||
|
@ -82,16 +82,15 @@ enum class AudioEngine : u32 {
|
||||
Cubeb,
|
||||
Sdl2,
|
||||
Null,
|
||||
Oboe,
|
||||
};
|
||||
|
||||
template <>
|
||||
inline std::vector<std::pair<std::string, AudioEngine>>
|
||||
EnumMetadata<AudioEngine>::Canonicalizations() {
|
||||
return {
|
||||
{"auto", AudioEngine::Auto},
|
||||
{"cubeb", AudioEngine::Cubeb},
|
||||
{"sdl2", AudioEngine::Sdl2},
|
||||
{"null", AudioEngine::Null},
|
||||
{"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2},
|
||||
{"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -549,6 +549,11 @@ add_library(core STATIC
|
||||
hle/service/hid/xcd.cpp
|
||||
hle/service/hid/xcd.h
|
||||
hle/service/hid/errors.h
|
||||
hle/service/hid/controllers/types/debug_pad_types.h
|
||||
hle/service/hid/controllers/types/keyboard_types.h
|
||||
hle/service/hid/controllers/types/mouse_types.h
|
||||
hle/service/hid/controllers/types/npad_types.h
|
||||
hle/service/hid/controllers/types/touch_types.h
|
||||
hle/service/hid/controllers/applet_resource.cpp
|
||||
hle/service/hid/controllers/applet_resource.h
|
||||
hle/service/hid/controllers/console_six_axis.cpp
|
||||
@ -569,14 +574,15 @@ add_library(core STATIC
|
||||
hle/service/hid/controllers/palma.h
|
||||
hle/service/hid/controllers/seven_six_axis.cpp
|
||||
hle/service/hid/controllers/seven_six_axis.h
|
||||
hle/service/hid/controllers/shared_memory_format.h
|
||||
hle/service/hid/controllers/shared_memory_holder.cpp
|
||||
hle/service/hid/controllers/shared_memory_holder.h
|
||||
hle/service/hid/controllers/six_axis.cpp
|
||||
hle/service/hid/controllers/six_axis.h
|
||||
hle/service/hid/controllers/stubbed.cpp
|
||||
hle/service/hid/controllers/stubbed.h
|
||||
hle/service/hid/controllers/touchscreen.cpp
|
||||
hle/service/hid/controllers/touchscreen.h
|
||||
hle/service/hid/controllers/xpad.cpp
|
||||
hle/service/hid/controllers/xpad.h
|
||||
hle/service/hid/hidbus/hidbus_base.cpp
|
||||
hle/service/hid/hidbus/hidbus_base.h
|
||||
hle/service/hid/hidbus/ringcon.cpp
|
||||
@ -784,6 +790,12 @@ add_library(core STATIC
|
||||
hle/service/service.h
|
||||
hle/service/set/set.cpp
|
||||
hle/service/set/set.h
|
||||
hle/service/set/appln_settings.cpp
|
||||
hle/service/set/appln_settings.h
|
||||
hle/service/set/device_settings.cpp
|
||||
hle/service/set/device_settings.h
|
||||
hle/service/set/private_settings.cpp
|
||||
hle/service/set/private_settings.h
|
||||
hle/service/set/set_cal.cpp
|
||||
hle/service/set/set_cal.h
|
||||
hle/service/set/set_fd.cpp
|
||||
@ -792,6 +804,8 @@ add_library(core STATIC
|
||||
hle/service/set/set_sys.h
|
||||
hle/service/set/settings.cpp
|
||||
hle/service/set/settings.h
|
||||
hle/service/set/system_settings.cpp
|
||||
hle/service/set/system_settings.h
|
||||
hle/service/sm/sm.cpp
|
||||
hle/service/sm/sm.h
|
||||
hle/service/sm/sm_controller.cpp
|
||||
@ -947,19 +961,24 @@ if (HAS_NCE)
|
||||
set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp")
|
||||
|
||||
target_sources(core PRIVATE
|
||||
arm/nce/arm_nce_asm_definitions.h
|
||||
arm/nce/arm_nce.cpp
|
||||
arm/nce/arm_nce.h
|
||||
arm/nce/arm_nce.s
|
||||
arm/nce/guest_context.h
|
||||
arm/nce/instructions.h
|
||||
arm/nce/interpreter_visitor.cpp
|
||||
arm/nce/interpreter_visitor.h
|
||||
arm/nce/patcher.cpp
|
||||
arm/nce/patcher.h
|
||||
arm/nce/instructions.h
|
||||
arm/nce/visitor_base.h
|
||||
)
|
||||
target_link_libraries(core PRIVATE merry::oaknut)
|
||||
target_link_libraries(core PRIVATE merry::mcl merry::oaknut)
|
||||
endif()
|
||||
|
||||
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
|
||||
target_sources(core PRIVATE
|
||||
arm/dynarmic/arm_dynarmic.cpp
|
||||
arm/dynarmic/arm_dynarmic.h
|
||||
arm/dynarmic/arm_dynarmic_64.cpp
|
||||
arm/dynarmic/arm_dynarmic_64.h
|
||||
@ -969,6 +988,8 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
|
||||
arm/dynarmic/dynarmic_cp15.h
|
||||
arm/dynarmic/dynarmic_exclusive_monitor.cpp
|
||||
arm/dynarmic/dynarmic_exclusive_monitor.h
|
||||
hle/service/jit/jit_code_memory.cpp
|
||||
hle/service/jit/jit_code_memory.h
|
||||
hle/service/jit/jit_context.cpp
|
||||
hle/service/jit/jit_context.h
|
||||
hle/service/jit/jit.cpp
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
namespace Core {
|
||||
|
||||
void ArmInterface::LogBacktrace(const Kernel::KProcess* process) const {
|
||||
void ArmInterface::LogBacktrace(Kernel::KProcess* process) const {
|
||||
Kernel::Svc::ThreadContext ctx;
|
||||
this->GetContext(ctx);
|
||||
|
||||
|
@ -95,7 +95,7 @@ public:
|
||||
virtual void SignalInterrupt(Kernel::KThread* thread) = 0;
|
||||
|
||||
// Stack trace generation.
|
||||
void LogBacktrace(const Kernel::KProcess* process) const;
|
||||
void LogBacktrace(Kernel::KProcess* process) const;
|
||||
|
||||
// Debug functionality.
|
||||
virtual const Kernel::DebugWatchpoint* HaltedWatchpoint() const = 0;
|
||||
|
@ -79,7 +79,7 @@ constexpr std::array<u64, 2> SegmentBases{
|
||||
0x7100000000ULL,
|
||||
};
|
||||
|
||||
void SymbolicateBacktrace(const Kernel::KProcess* process, std::vector<BacktraceEntry>& out) {
|
||||
void SymbolicateBacktrace(Kernel::KProcess* process, std::vector<BacktraceEntry>& out) {
|
||||
auto modules = FindModules(process);
|
||||
|
||||
const bool is_64 = process->Is64Bit();
|
||||
@ -118,7 +118,7 @@ void SymbolicateBacktrace(const Kernel::KProcess* process, std::vector<Backtrace
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<BacktraceEntry> GetAArch64Backtrace(const Kernel::KProcess* process,
|
||||
std::vector<BacktraceEntry> GetAArch64Backtrace(Kernel::KProcess* process,
|
||||
const Kernel::Svc::ThreadContext& ctx) {
|
||||
std::vector<BacktraceEntry> out;
|
||||
auto& memory = process->GetMemory();
|
||||
@ -144,7 +144,7 @@ std::vector<BacktraceEntry> GetAArch64Backtrace(const Kernel::KProcess* process,
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<BacktraceEntry> GetAArch32Backtrace(const Kernel::KProcess* process,
|
||||
std::vector<BacktraceEntry> GetAArch32Backtrace(Kernel::KProcess* process,
|
||||
const Kernel::Svc::ThreadContext& ctx) {
|
||||
std::vector<BacktraceEntry> out;
|
||||
auto& memory = process->GetMemory();
|
||||
@ -173,7 +173,7 @@ std::vector<BacktraceEntry> GetAArch32Backtrace(const Kernel::KProcess* process,
|
||||
} // namespace
|
||||
|
||||
std::optional<std::string> GetThreadName(const Kernel::KThread* thread) {
|
||||
const auto* process = thread->GetOwnerProcess();
|
||||
auto* process = thread->GetOwnerProcess();
|
||||
if (process->Is64Bit()) {
|
||||
return GetNameFromThreadType64(process->GetMemory(), *thread);
|
||||
} else {
|
||||
@ -248,7 +248,7 @@ Kernel::KProcessAddress GetModuleEnd(const Kernel::KProcess* process,
|
||||
return cur_addr - 1;
|
||||
}
|
||||
|
||||
Loader::AppLoader::Modules FindModules(const Kernel::KProcess* process) {
|
||||
Loader::AppLoader::Modules FindModules(Kernel::KProcess* process) {
|
||||
Loader::AppLoader::Modules modules;
|
||||
|
||||
auto& page_table = process->GetPageTable();
|
||||
@ -312,7 +312,7 @@ Loader::AppLoader::Modules FindModules(const Kernel::KProcess* process) {
|
||||
return modules;
|
||||
}
|
||||
|
||||
Kernel::KProcessAddress FindMainModuleEntrypoint(const Kernel::KProcess* process) {
|
||||
Kernel::KProcessAddress FindMainModuleEntrypoint(Kernel::KProcess* process) {
|
||||
// Do we have any loaded executable sections?
|
||||
auto modules = FindModules(process);
|
||||
|
||||
@ -337,7 +337,7 @@ void InvalidateInstructionCacheRange(const Kernel::KProcess* process, u64 addres
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<BacktraceEntry> GetBacktraceFromContext(const Kernel::KProcess* process,
|
||||
std::vector<BacktraceEntry> GetBacktraceFromContext(Kernel::KProcess* process,
|
||||
const Kernel::Svc::ThreadContext& ctx) {
|
||||
if (process->Is64Bit()) {
|
||||
return GetAArch64Backtrace(process, ctx);
|
||||
|
@ -14,9 +14,9 @@ std::optional<std::string> GetThreadName(const Kernel::KThread* thread);
|
||||
std::string_view GetThreadWaitReason(const Kernel::KThread* thread);
|
||||
std::string GetThreadState(const Kernel::KThread* thread);
|
||||
|
||||
Loader::AppLoader::Modules FindModules(const Kernel::KProcess* process);
|
||||
Loader::AppLoader::Modules FindModules(Kernel::KProcess* process);
|
||||
Kernel::KProcessAddress GetModuleEnd(const Kernel::KProcess* process, Kernel::KProcessAddress base);
|
||||
Kernel::KProcessAddress FindMainModuleEntrypoint(const Kernel::KProcess* process);
|
||||
Kernel::KProcessAddress FindMainModuleEntrypoint(Kernel::KProcess* process);
|
||||
|
||||
void InvalidateInstructionCacheRange(const Kernel::KProcess* process, u64 address, u64 size);
|
||||
|
||||
@ -28,7 +28,7 @@ struct BacktraceEntry {
|
||||
std::string name;
|
||||
};
|
||||
|
||||
std::vector<BacktraceEntry> GetBacktraceFromContext(const Kernel::KProcess* process,
|
||||
std::vector<BacktraceEntry> GetBacktraceFromContext(Kernel::KProcess* process,
|
||||
const Kernel::Svc::ThreadContext& ctx);
|
||||
std::vector<BacktraceEntry> GetBacktrace(const Kernel::KThread* thread);
|
||||
|
||||
|
49
src/core/arm/dynarmic/arm_dynarmic.cpp
Normal file
49
src/core/arm/dynarmic/arm_dynarmic.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include "common/signal_chain.h"
|
||||
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
namespace {
|
||||
|
||||
thread_local Core::Memory::Memory* g_current_memory{};
|
||||
std::once_flag g_registered{};
|
||||
struct sigaction g_old_segv {};
|
||||
|
||||
void HandleSigSegv(int sig, siginfo_t* info, void* ctx) {
|
||||
if (g_current_memory && g_current_memory->InvalidateSeparateHeap(info->si_addr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return g_old_segv.sa_sigaction(sig, info, ctx);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ScopedJitExecution::ScopedJitExecution(Kernel::KProcess* process) {
|
||||
g_current_memory = std::addressof(process->GetMemory());
|
||||
}
|
||||
|
||||
ScopedJitExecution::~ScopedJitExecution() {
|
||||
g_current_memory = nullptr;
|
||||
}
|
||||
|
||||
void ScopedJitExecution::RegisterHandler() {
|
||||
std::call_once(g_registered, [] {
|
||||
struct sigaction sa {};
|
||||
sa.sa_sigaction = &HandleSigSegv;
|
||||
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||
Common::SigAction(SIGSEGV, std::addressof(sa), std::addressof(g_old_segv));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
#endif
|
@ -26,4 +26,24 @@ constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) {
|
||||
return static_cast<HaltReason>(hr);
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
class ScopedJitExecution {
|
||||
public:
|
||||
explicit ScopedJitExecution(Kernel::KProcess* process);
|
||||
~ScopedJitExecution();
|
||||
static void RegisterHandler();
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class ScopedJitExecution {
|
||||
public:
|
||||
explicit ScopedJitExecution(Kernel::KProcess* process) {}
|
||||
~ScopedJitExecution() {}
|
||||
static void RegisterHandler() {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace Core
|
||||
|
@ -15,7 +15,7 @@ using namespace Common::Literals;
|
||||
|
||||
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
|
||||
public:
|
||||
explicit DynarmicCallbacks32(ArmDynarmic32& parent, const Kernel::KProcess* process)
|
||||
explicit DynarmicCallbacks32(ArmDynarmic32& parent, Kernel::KProcess* process)
|
||||
: m_parent{parent}, m_memory(process->GetMemory()),
|
||||
m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()},
|
||||
m_check_memory_access{m_debugger_enabled ||
|
||||
@ -169,7 +169,7 @@ public:
|
||||
|
||||
ArmDynarmic32& m_parent;
|
||||
Core::Memory::Memory& m_memory;
|
||||
const Kernel::KProcess* m_process{};
|
||||
Kernel::KProcess* m_process{};
|
||||
const bool m_debugger_enabled{};
|
||||
const bool m_check_memory_access{};
|
||||
static constexpr u64 MinimumRunCycles = 10000U;
|
||||
@ -331,11 +331,15 @@ bool ArmDynarmic32::IsInThumbMode() const {
|
||||
}
|
||||
|
||||
HaltReason ArmDynarmic32::RunThread(Kernel::KThread* thread) {
|
||||
ScopedJitExecution sj(thread->GetOwnerProcess());
|
||||
|
||||
m_jit->ClearExclusiveState();
|
||||
return TranslateHaltReason(m_jit->Run());
|
||||
}
|
||||
|
||||
HaltReason ArmDynarmic32::StepThread(Kernel::KThread* thread) {
|
||||
ScopedJitExecution sj(thread->GetOwnerProcess());
|
||||
|
||||
m_jit->ClearExclusiveState();
|
||||
return TranslateHaltReason(m_jit->Step());
|
||||
}
|
||||
@ -370,13 +374,14 @@ void ArmDynarmic32::RewindBreakpointInstruction() {
|
||||
this->SetContext(m_breakpoint_context);
|
||||
}
|
||||
|
||||
ArmDynarmic32::ArmDynarmic32(System& system, bool uses_wall_clock, const Kernel::KProcess* process,
|
||||
ArmDynarmic32::ArmDynarmic32(System& system, bool uses_wall_clock, Kernel::KProcess* process,
|
||||
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index)
|
||||
: ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor},
|
||||
m_cb(std::make_unique<DynarmicCallbacks32>(*this, process)),
|
||||
m_cp15(std::make_shared<DynarmicCP15>(*this)), m_core_index{core_index} {
|
||||
auto& page_table_impl = process->GetPageTable().GetBasePageTable().GetImpl();
|
||||
m_jit = MakeJit(&page_table_impl);
|
||||
ScopedJitExecution::RegisterHandler();
|
||||
}
|
||||
|
||||
ArmDynarmic32::~ArmDynarmic32() = default;
|
||||
|
@ -20,7 +20,7 @@ class System;
|
||||
|
||||
class ArmDynarmic32 final : public ArmInterface {
|
||||
public:
|
||||
ArmDynarmic32(System& system, bool uses_wall_clock, const Kernel::KProcess* process,
|
||||
ArmDynarmic32(System& system, bool uses_wall_clock, Kernel::KProcess* process,
|
||||
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
||||
~ArmDynarmic32() override;
|
||||
|
||||
|
@ -15,7 +15,7 @@ using namespace Common::Literals;
|
||||
|
||||
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
|
||||
public:
|
||||
explicit DynarmicCallbacks64(ArmDynarmic64& parent, const Kernel::KProcess* process)
|
||||
explicit DynarmicCallbacks64(ArmDynarmic64& parent, Kernel::KProcess* process)
|
||||
: m_parent{parent}, m_memory(process->GetMemory()),
|
||||
m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()},
|
||||
m_check_memory_access{m_debugger_enabled ||
|
||||
@ -216,7 +216,7 @@ public:
|
||||
Core::Memory::Memory& m_memory;
|
||||
u64 m_tpidrro_el0{};
|
||||
u64 m_tpidr_el0{};
|
||||
const Kernel::KProcess* m_process{};
|
||||
Kernel::KProcess* m_process{};
|
||||
const bool m_debugger_enabled{};
|
||||
const bool m_check_memory_access{};
|
||||
static constexpr u64 MinimumRunCycles = 10000U;
|
||||
@ -362,11 +362,15 @@ std::shared_ptr<Dynarmic::A64::Jit> ArmDynarmic64::MakeJit(Common::PageTable* pa
|
||||
}
|
||||
|
||||
HaltReason ArmDynarmic64::RunThread(Kernel::KThread* thread) {
|
||||
ScopedJitExecution sj(thread->GetOwnerProcess());
|
||||
|
||||
m_jit->ClearExclusiveState();
|
||||
return TranslateHaltReason(m_jit->Run());
|
||||
}
|
||||
|
||||
HaltReason ArmDynarmic64::StepThread(Kernel::KThread* thread) {
|
||||
ScopedJitExecution sj(thread->GetOwnerProcess());
|
||||
|
||||
m_jit->ClearExclusiveState();
|
||||
return TranslateHaltReason(m_jit->Step());
|
||||
}
|
||||
@ -399,13 +403,14 @@ void ArmDynarmic64::RewindBreakpointInstruction() {
|
||||
this->SetContext(m_breakpoint_context);
|
||||
}
|
||||
|
||||
ArmDynarmic64::ArmDynarmic64(System& system, bool uses_wall_clock, const Kernel::KProcess* process,
|
||||
ArmDynarmic64::ArmDynarmic64(System& system, bool uses_wall_clock, Kernel::KProcess* process,
|
||||
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index)
|
||||
: ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor},
|
||||
m_cb(std::make_unique<DynarmicCallbacks64>(*this, process)), m_core_index{core_index} {
|
||||
auto& page_table = process->GetPageTable().GetBasePageTable();
|
||||
auto& page_table_impl = page_table.GetImpl();
|
||||
m_jit = MakeJit(&page_table_impl, page_table.GetAddressSpaceWidth());
|
||||
ScopedJitExecution::RegisterHandler();
|
||||
}
|
||||
|
||||
ArmDynarmic64::~ArmDynarmic64() = default;
|
||||
|
@ -25,7 +25,7 @@ class System;
|
||||
|
||||
class ArmDynarmic64 final : public ArmInterface {
|
||||
public:
|
||||
ArmDynarmic64(System& system, bool uses_wall_clock, const Kernel::KProcess* process,
|
||||
ArmDynarmic64(System& system, bool uses_wall_clock, Kernel::KProcess* process,
|
||||
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
||||
~ArmDynarmic64() override;
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
#include "common/signal_chain.h"
|
||||
#include "core/arm/nce/arm_nce.h"
|
||||
#include "core/arm/nce/guest_context.h"
|
||||
#include "core/arm/nce/interpreter_visitor.h"
|
||||
#include "core/arm/nce/patcher.h"
|
||||
#include "core/core.h"
|
||||
#include "core/memory.h"
|
||||
@ -21,7 +21,8 @@ namespace Core {
|
||||
|
||||
namespace {
|
||||
|
||||
struct sigaction g_orig_action;
|
||||
struct sigaction g_orig_bus_action;
|
||||
struct sigaction g_orig_segv_action;
|
||||
|
||||
// Verify assembly offsets.
|
||||
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
|
||||
@ -37,6 +38,9 @@ fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) {
|
||||
return reinterpret_cast<fpsimd_context*>(header);
|
||||
}
|
||||
|
||||
using namespace Common::Literals;
|
||||
constexpr u32 StackSize = 128_KiB;
|
||||
|
||||
} // namespace
|
||||
|
||||
void* ArmNce::RestoreGuestContext(void* raw_context) {
|
||||
@ -104,19 +108,10 @@ void ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
|
||||
host_ctx.regs[0] = guest_ctx->esr_el1.exchange(0);
|
||||
}
|
||||
|
||||
bool ArmNce::HandleGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||
bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
|
||||
auto* info = static_cast<siginfo_t*>(raw_info);
|
||||
|
||||
// Try to handle an invalid access.
|
||||
// TODO: handle accesses which split a page?
|
||||
const Common::ProcessAddress addr =
|
||||
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
|
||||
if (guest_ctx->system->ApplicationMemory().InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
|
||||
// We handled the access successfully and are returning to guest code.
|
||||
return true;
|
||||
}
|
||||
|
||||
// We can't handle the access, so determine why we crashed.
|
||||
const bool is_prefetch_abort = host_ctx.pc == reinterpret_cast<u64>(info->si_addr);
|
||||
|
||||
@ -143,8 +138,44 @@ bool ArmNce::HandleGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw
|
||||
return false;
|
||||
}
|
||||
|
||||
void ArmNce::HandleHostFault(int sig, void* raw_info, void* raw_context) {
|
||||
return g_orig_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
||||
bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
|
||||
auto* fpctx = GetFloatingPointState(host_ctx);
|
||||
auto& memory = guest_ctx->system->ApplicationMemory();
|
||||
|
||||
// Match and execute an instruction.
|
||||
auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx);
|
||||
if (next_pc) {
|
||||
host_ctx.pc = *next_pc;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We couldn't handle the access.
|
||||
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
|
||||
}
|
||||
|
||||
bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||
auto* info = static_cast<siginfo_t*>(raw_info);
|
||||
|
||||
// Try to handle an invalid access.
|
||||
// TODO: handle accesses which split a page?
|
||||
const Common::ProcessAddress addr =
|
||||
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
|
||||
if (guest_ctx->system->ApplicationMemory().InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
|
||||
// We handled the access successfully and are returning to guest code.
|
||||
return true;
|
||||
}
|
||||
|
||||
// We couldn't handle the access.
|
||||
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
|
||||
}
|
||||
|
||||
void ArmNce::HandleHostAlignmentFault(int sig, void* raw_info, void* raw_context) {
|
||||
return g_orig_bus_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
||||
}
|
||||
|
||||
void ArmNce::HandleHostAccessFault(int sig, void* raw_info, void* raw_context) {
|
||||
return g_orig_segv_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
||||
}
|
||||
|
||||
void ArmNce::LockThread(Kernel::KThread* thread) {
|
||||
@ -225,18 +256,31 @@ ArmNce::ArmNce(System& system, bool uses_wall_clock, std::size_t core_index)
|
||||
ArmNce::~ArmNce() = default;
|
||||
|
||||
void ArmNce::Initialize() {
|
||||
m_thread_id = gettid();
|
||||
if (m_thread_id == -1) {
|
||||
m_thread_id = gettid();
|
||||
}
|
||||
|
||||
// Setup our signals
|
||||
static std::once_flag signals;
|
||||
std::call_once(signals, [] {
|
||||
// Configure signal stack.
|
||||
if (!m_stack) {
|
||||
m_stack = std::make_unique<u8[]>(StackSize);
|
||||
|
||||
stack_t ss{};
|
||||
ss.ss_sp = m_stack.get();
|
||||
ss.ss_size = StackSize;
|
||||
sigaltstack(&ss, nullptr);
|
||||
}
|
||||
|
||||
// Set up signals.
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [] {
|
||||
using HandlerType = decltype(sigaction::sa_sigaction);
|
||||
|
||||
sigset_t signal_mask;
|
||||
sigemptyset(&signal_mask);
|
||||
sigaddset(&signal_mask, ReturnToRunCodeByExceptionLevelChangeSignal);
|
||||
sigaddset(&signal_mask, BreakFromRunCodeSignal);
|
||||
sigaddset(&signal_mask, GuestFaultSignal);
|
||||
sigaddset(&signal_mask, GuestAlignmentFaultSignal);
|
||||
sigaddset(&signal_mask, GuestAccessFaultSignal);
|
||||
|
||||
struct sigaction return_to_run_code_action {};
|
||||
return_to_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||
@ -253,18 +297,19 @@ void ArmNce::Initialize() {
|
||||
break_from_run_code_action.sa_mask = signal_mask;
|
||||
Common::SigAction(BreakFromRunCodeSignal, &break_from_run_code_action, nullptr);
|
||||
|
||||
struct sigaction fault_action {};
|
||||
fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
|
||||
fault_action.sa_sigaction = reinterpret_cast<HandlerType>(&ArmNce::GuestFaultSignalHandler);
|
||||
fault_action.sa_mask = signal_mask;
|
||||
Common::SigAction(GuestFaultSignal, &fault_action, &g_orig_action);
|
||||
struct sigaction alignment_fault_action {};
|
||||
alignment_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||
alignment_fault_action.sa_sigaction =
|
||||
reinterpret_cast<HandlerType>(&ArmNce::GuestAlignmentFaultSignalHandler);
|
||||
alignment_fault_action.sa_mask = signal_mask;
|
||||
Common::SigAction(GuestAlignmentFaultSignal, &alignment_fault_action, nullptr);
|
||||
|
||||
// Simplify call for g_orig_action.
|
||||
// These fields occupy the same space in memory, so this should be a no-op in practice.
|
||||
if (!(g_orig_action.sa_flags & SA_SIGINFO)) {
|
||||
g_orig_action.sa_sigaction =
|
||||
reinterpret_cast<decltype(g_orig_action.sa_sigaction)>(g_orig_action.sa_handler);
|
||||
}
|
||||
struct sigaction access_fault_action {};
|
||||
access_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
|
||||
access_fault_action.sa_sigaction =
|
||||
reinterpret_cast<HandlerType>(&ArmNce::GuestAccessFaultSignalHandler);
|
||||
access_fault_action.sa_mask = signal_mask;
|
||||
Common::SigAction(GuestAccessFaultSignal, &access_fault_action, &g_orig_segv_action);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,8 @@ private:
|
||||
static void ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info,
|
||||
void* raw_context);
|
||||
static void BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context);
|
||||
static void GuestFaultSignalHandler(int sig, void* info, void* raw_context);
|
||||
static void GuestAlignmentFaultSignalHandler(int sig, void* info, void* raw_context);
|
||||
static void GuestAccessFaultSignalHandler(int sig, void* info, void* raw_context);
|
||||
|
||||
static void LockThreadParameters(void* tpidr);
|
||||
static void UnlockThreadParameters(void* tpidr);
|
||||
@ -70,8 +71,11 @@ private:
|
||||
// C++ implementation functions for assembly definitions.
|
||||
static void* RestoreGuestContext(void* raw_context);
|
||||
static void SaveGuestContext(GuestContext* ctx, void* raw_context);
|
||||
static bool HandleGuestFault(GuestContext* ctx, void* info, void* raw_context);
|
||||
static void HandleHostFault(int sig, void* info, void* raw_context);
|
||||
static bool HandleFailedGuestFault(GuestContext* ctx, void* info, void* raw_context);
|
||||
static bool HandleGuestAlignmentFault(GuestContext* ctx, void* info, void* raw_context);
|
||||
static bool HandleGuestAccessFault(GuestContext* ctx, void* info, void* raw_context);
|
||||
static void HandleHostAlignmentFault(int sig, void* info, void* raw_context);
|
||||
static void HandleHostAccessFault(int sig, void* info, void* raw_context);
|
||||
|
||||
public:
|
||||
Core::System& m_system;
|
||||
@ -83,6 +87,9 @@ public:
|
||||
// Core context.
|
||||
GuestContext m_guest_ctx{};
|
||||
Kernel::KThread* m_running_thread{};
|
||||
|
||||
// Stack for signal processing.
|
||||
std::unique_ptr<u8[]> m_stack{};
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
@ -130,11 +130,11 @@ _ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_:
|
||||
ret
|
||||
|
||||
|
||||
/* static void Core::ArmNce::GuestFaultSignalHandler(int sig, void* info, void* raw_context) */
|
||||
.section .text._ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_, "ax", %progbits
|
||||
.global _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_
|
||||
.type _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_, %function
|
||||
_ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_:
|
||||
/* static void Core::ArmNce::GuestAlignmentFaultSignalHandler(int sig, void* info, void* raw_context) */
|
||||
.section .text._ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_, "ax", %progbits
|
||||
.global _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_
|
||||
.type _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_, %function
|
||||
_ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_:
|
||||
/* Check to see if we have the correct TLS magic. */
|
||||
mrs x8, tpidr_el0
|
||||
ldr w9, [x8, #(TpidrEl0TlsMagic)]
|
||||
@ -146,7 +146,7 @@ _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_:
|
||||
|
||||
/* Incorrect TLS magic, so this is a host fault. */
|
||||
/* Tail call the handler. */
|
||||
b _ZN4Core6ArmNce15HandleHostFaultEiPvS1_
|
||||
b _ZN4Core6ArmNce24HandleHostAlignmentFaultEiPvS1_
|
||||
|
||||
1:
|
||||
/* Correct TLS magic, so this is a guest fault. */
|
||||
@ -163,7 +163,53 @@ _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_:
|
||||
msr tpidr_el0, x3
|
||||
|
||||
/* Call the handler. */
|
||||
bl _ZN4Core6ArmNce16HandleGuestFaultEPNS_12GuestContextEPvS3_
|
||||
bl _ZN4Core6ArmNce25HandleGuestAlignmentFaultEPNS_12GuestContextEPvS3_
|
||||
|
||||
/* If the handler returned false, we want to preserve the host tpidr_el0. */
|
||||
cbz x0, 2f
|
||||
|
||||
/* Otherwise, restore guest tpidr_el0. */
|
||||
msr tpidr_el0, x19
|
||||
|
||||
2:
|
||||
ldr x19, [sp, #0x10]
|
||||
ldp x29, x30, [sp], #0x20
|
||||
ret
|
||||
|
||||
/* static void Core::ArmNce::GuestAccessFaultSignalHandler(int sig, void* info, void* raw_context) */
|
||||
.section .text._ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_, "ax", %progbits
|
||||
.global _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_
|
||||
.type _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_, %function
|
||||
_ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_:
|
||||
/* Check to see if we have the correct TLS magic. */
|
||||
mrs x8, tpidr_el0
|
||||
ldr w9, [x8, #(TpidrEl0TlsMagic)]
|
||||
|
||||
LOAD_IMMEDIATE_32(w10, TlsMagic)
|
||||
|
||||
cmp w9, w10
|
||||
b.eq 1f
|
||||
|
||||
/* Incorrect TLS magic, so this is a host fault. */
|
||||
/* Tail call the handler. */
|
||||
b _ZN4Core6ArmNce21HandleHostAccessFaultEiPvS1_
|
||||
|
||||
1:
|
||||
/* Correct TLS magic, so this is a guest fault. */
|
||||
stp x29, x30, [sp, #-0x20]!
|
||||
str x19, [sp, #0x10]
|
||||
mov x29, sp
|
||||
|
||||
/* Save the old tpidr_el0. */
|
||||
mov x19, x8
|
||||
|
||||
/* Restore host tpidr_el0. */
|
||||
ldr x0, [x8, #(TpidrEl0NativeContext)]
|
||||
ldr x3, [x0, #(GuestContextHostContext + HostContextTpidrEl0)]
|
||||
msr tpidr_el0, x3
|
||||
|
||||
/* Call the handler. */
|
||||
bl _ZN4Core6ArmNce22HandleGuestAccessFaultEPNS_12GuestContextEPvS3_
|
||||
|
||||
/* If the handler returned false, we want to preserve the host tpidr_el0. */
|
||||
cbz x0, 2f
|
||||
|
@ -10,7 +10,8 @@
|
||||
|
||||
#define ReturnToRunCodeByExceptionLevelChangeSignal SIGUSR2
|
||||
#define BreakFromRunCodeSignal SIGURG
|
||||
#define GuestFaultSignal SIGSEGV
|
||||
#define GuestAccessFaultSignal SIGSEGV
|
||||
#define GuestAlignmentFaultSignal SIGBUS
|
||||
|
||||
#define GuestContextSp 0xF8
|
||||
#define GuestContextHostContext 0x320
|
||||
|
824
src/core/arm/nce/interpreter_visitor.cpp
Normal file
824
src/core/arm/nce/interpreter_visitor.cpp
Normal file
@ -0,0 +1,824 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2023 merryhime <https://mary.rs>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/bit_cast.h"
|
||||
#include "core/arm/nce/interpreter_visitor.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
template <u32 BitSize>
|
||||
u64 SignExtendToLong(u64 value) {
|
||||
u64 mask = 1ULL << (BitSize - 1);
|
||||
value &= (1ULL << BitSize) - 1;
|
||||
return (value ^ mask) - mask;
|
||||
}
|
||||
|
||||
static u64 SignExtendToLong(u64 value, u64 bitsize) {
|
||||
switch (bitsize) {
|
||||
case 8:
|
||||
return SignExtendToLong<8>(value);
|
||||
case 16:
|
||||
return SignExtendToLong<16>(value);
|
||||
case 32:
|
||||
return SignExtendToLong<32>(value);
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
template <u64 BitSize>
|
||||
u32 SignExtendToWord(u32 value) {
|
||||
u32 mask = 1ULL << (BitSize - 1);
|
||||
value &= (1ULL << BitSize) - 1;
|
||||
return (value ^ mask) - mask;
|
||||
}
|
||||
|
||||
static u32 SignExtendToWord(u32 value, u64 bitsize) {
|
||||
switch (bitsize) {
|
||||
case 8:
|
||||
return SignExtendToWord<8>(value);
|
||||
case 16:
|
||||
return SignExtendToWord<16>(value);
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
static u64 SignExtend(u64 value, u64 bitsize, u64 regsize) {
|
||||
if (regsize == 64) {
|
||||
return SignExtendToLong(value, bitsize);
|
||||
} else {
|
||||
return SignExtendToWord(static_cast<u32>(value), bitsize);
|
||||
}
|
||||
}
|
||||
|
||||
static u128 VectorGetElement(u128 value, u64 bitsize) {
|
||||
switch (bitsize) {
|
||||
case 8:
|
||||
return {value[0] & ((1ULL << 8) - 1), 0};
|
||||
case 16:
|
||||
return {value[0] & ((1ULL << 16) - 1), 0};
|
||||
case 32:
|
||||
return {value[0] & ((1ULL << 32) - 1), 0};
|
||||
case 64:
|
||||
return {value[0], 0};
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
u64 InterpreterVisitor::ExtendReg(size_t bitsize, Reg reg, Imm<3> option, u8 shift) {
|
||||
ASSERT(shift <= 4);
|
||||
ASSERT(bitsize == 32 || bitsize == 64);
|
||||
u64 val = this->GetReg(reg);
|
||||
size_t len;
|
||||
u64 extended;
|
||||
bool signed_extend;
|
||||
|
||||
switch (option.ZeroExtend()) {
|
||||
case 0b000: { // UXTB
|
||||
val &= ((1ULL << 8) - 1);
|
||||
len = 8;
|
||||
signed_extend = false;
|
||||
break;
|
||||
}
|
||||
case 0b001: { // UXTH
|
||||
val &= ((1ULL << 16) - 1);
|
||||
len = 16;
|
||||
signed_extend = false;
|
||||
break;
|
||||
}
|
||||
case 0b010: { // UXTW
|
||||
val &= ((1ULL << 32) - 1);
|
||||
len = 32;
|
||||
signed_extend = false;
|
||||
break;
|
||||
}
|
||||
case 0b011: { // UXTX
|
||||
len = 64;
|
||||
signed_extend = false;
|
||||
break;
|
||||
}
|
||||
case 0b100: { // SXTB
|
||||
val &= ((1ULL << 8) - 1);
|
||||
len = 8;
|
||||
signed_extend = true;
|
||||
break;
|
||||
}
|
||||
case 0b101: { // SXTH
|
||||
val &= ((1ULL << 16) - 1);
|
||||
len = 16;
|
||||
signed_extend = true;
|
||||
break;
|
||||
}
|
||||
case 0b110: { // SXTW
|
||||
val &= ((1ULL << 32) - 1);
|
||||
len = 32;
|
||||
signed_extend = true;
|
||||
break;
|
||||
}
|
||||
case 0b111: { // SXTX
|
||||
len = 64;
|
||||
signed_extend = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
if (len < bitsize && signed_extend) {
|
||||
extended = SignExtend(val, len, bitsize);
|
||||
} else {
|
||||
extended = val;
|
||||
}
|
||||
|
||||
return extended << shift;
|
||||
}
|
||||
|
||||
u128 InterpreterVisitor::GetVec(Vec v) {
|
||||
return m_fpsimd_regs[static_cast<u32>(v)];
|
||||
}
|
||||
|
||||
u64 InterpreterVisitor::GetReg(Reg r) {
|
||||
return m_regs[static_cast<u32>(r)];
|
||||
}
|
||||
|
||||
u64 InterpreterVisitor::GetSp() {
|
||||
return m_sp;
|
||||
}
|
||||
|
||||
u64 InterpreterVisitor::GetPc() {
|
||||
return m_pc;
|
||||
}
|
||||
|
||||
void InterpreterVisitor::SetVec(Vec v, u128 value) {
|
||||
m_fpsimd_regs[static_cast<u32>(v)] = value;
|
||||
}
|
||||
|
||||
void InterpreterVisitor::SetReg(Reg r, u64 value) {
|
||||
m_regs[static_cast<u32>(r)] = value;
|
||||
}
|
||||
|
||||
void InterpreterVisitor::SetSp(u64 value) {
|
||||
m_sp = value;
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::Ordered(size_t size, bool L, bool o0, Reg Rn, Reg Rt) {
|
||||
const auto memop = L ? MemOp::Load : MemOp::Store;
|
||||
const size_t elsize = 8 << size;
|
||||
const size_t datasize = elsize;
|
||||
|
||||
// Operation
|
||||
const size_t dbytes = datasize / 8;
|
||||
|
||||
u64 address;
|
||||
if (Rn == Reg::SP) {
|
||||
address = this->GetSp();
|
||||
} else {
|
||||
address = this->GetReg(Rn);
|
||||
}
|
||||
|
||||
switch (memop) {
|
||||
case MemOp::Store: {
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
u64 value = this->GetReg(Rt);
|
||||
m_memory.WriteBlock(address, &value, dbytes);
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
break;
|
||||
}
|
||||
case MemOp::Load: {
|
||||
u64 value = 0;
|
||||
m_memory.ReadBlock(address, &value, dbytes);
|
||||
this->SetReg(Rt, value);
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::STLLR(Imm<2> sz, Reg Rn, Reg Rt) {
|
||||
const size_t size = sz.ZeroExtend<size_t>();
|
||||
const bool L = 0;
|
||||
const bool o0 = 0;
|
||||
return this->Ordered(size, L, o0, Rn, Rt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::STLR(Imm<2> sz, Reg Rn, Reg Rt) {
|
||||
const size_t size = sz.ZeroExtend<size_t>();
|
||||
const bool L = 0;
|
||||
const bool o0 = 1;
|
||||
return this->Ordered(size, L, o0, Rn, Rt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::LDLAR(Imm<2> sz, Reg Rn, Reg Rt) {
|
||||
const size_t size = sz.ZeroExtend<size_t>();
|
||||
const bool L = 1;
|
||||
const bool o0 = 0;
|
||||
return this->Ordered(size, L, o0, Rn, Rt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::LDAR(Imm<2> sz, Reg Rn, Reg Rt) {
|
||||
const size_t size = sz.ZeroExtend<size_t>();
|
||||
const bool L = 1;
|
||||
const bool o0 = 1;
|
||||
return this->Ordered(size, L, o0, Rn, Rt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::LDR_lit_gen(bool opc_0, Imm<19> imm19, Reg Rt) {
|
||||
const size_t size = opc_0 == 0 ? 4 : 8;
|
||||
const s64 offset = Dynarmic::concatenate(imm19, Imm<2>{0}).SignExtend<s64>();
|
||||
const u64 address = this->GetPc() + offset;
|
||||
|
||||
u64 data = 0;
|
||||
m_memory.ReadBlock(address, &data, size);
|
||||
|
||||
this->SetReg(Rt, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::LDR_lit_fpsimd(Imm<2> opc, Imm<19> imm19, Vec Vt) {
|
||||
if (opc == 0b11) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
|
||||
// Size in bytes
|
||||
const u64 size = 4 << opc.ZeroExtend();
|
||||
const u64 offset = imm19.SignExtend<u64>() << 2;
|
||||
const u64 address = this->GetPc() + offset;
|
||||
|
||||
u128 data{};
|
||||
m_memory.ReadBlock(address, &data, size);
|
||||
this->SetVec(Vt, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::STP_LDP_gen(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L,
|
||||
Imm<7> imm7, Reg Rt2, Reg Rn, Reg Rt) {
|
||||
if ((L == 0 && opc.Bit<0>() == 1) || opc == 0b11) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto memop = L == 1 ? MemOp::Load : MemOp::Store;
|
||||
if (memop == MemOp::Load && wback && (Rt == Rn || Rt2 == Rn) && Rn != Reg::R31) {
|
||||
// Unpredictable instruction
|
||||
return false;
|
||||
}
|
||||
if (memop == MemOp::Store && wback && (Rt == Rn || Rt2 == Rn) && Rn != Reg::R31) {
|
||||
// Unpredictable instruction
|
||||
return false;
|
||||
}
|
||||
if (memop == MemOp::Load && Rt == Rt2) {
|
||||
// Unpredictable instruction
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 address;
|
||||
if (Rn == Reg::SP) {
|
||||
address = this->GetSp();
|
||||
} else {
|
||||
address = this->GetReg(Rn);
|
||||
}
|
||||
|
||||
const bool postindex = !not_postindex;
|
||||
const bool signed_ = opc.Bit<0>() != 0;
|
||||
const size_t scale = 2 + opc.Bit<1>();
|
||||
const size_t datasize = 8 << scale;
|
||||
const u64 offset = imm7.SignExtend<u64>() << scale;
|
||||
|
||||
if (!postindex) {
|
||||
address += offset;
|
||||
}
|
||||
|
||||
const size_t dbytes = datasize / 8;
|
||||
switch (memop) {
|
||||
case MemOp::Store: {
|
||||
u64 data1 = this->GetReg(Rt);
|
||||
u64 data2 = this->GetReg(Rt2);
|
||||
m_memory.WriteBlock(address, &data1, dbytes);
|
||||
m_memory.WriteBlock(address + dbytes, &data2, dbytes);
|
||||
break;
|
||||
}
|
||||
case MemOp::Load: {
|
||||
u64 data1 = 0, data2 = 0;
|
||||
m_memory.ReadBlock(address, &data1, dbytes);
|
||||
m_memory.ReadBlock(address + dbytes, &data2, dbytes);
|
||||
if (signed_) {
|
||||
this->SetReg(Rt, SignExtend(data1, datasize, 64));
|
||||
this->SetReg(Rt2, SignExtend(data2, datasize, 64));
|
||||
} else {
|
||||
this->SetReg(Rt, data1);
|
||||
this->SetReg(Rt2, data2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
if (wback) {
|
||||
if (postindex) {
|
||||
address += offset;
|
||||
}
|
||||
|
||||
if (Rn == Reg::SP) {
|
||||
this->SetSp(address);
|
||||
} else {
|
||||
this->SetReg(Rn, address);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::STP_LDP_fpsimd(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L,
|
||||
Imm<7> imm7, Vec Vt2, Reg Rn, Vec Vt) {
|
||||
if (opc == 0b11) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto memop = L == 1 ? MemOp::Load : MemOp::Store;
|
||||
if (memop == MemOp::Load && Vt == Vt2) {
|
||||
// Unpredictable instruction
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 address;
|
||||
if (Rn == Reg::SP) {
|
||||
address = this->GetSp();
|
||||
} else {
|
||||
address = this->GetReg(Rn);
|
||||
}
|
||||
|
||||
const bool postindex = !not_postindex;
|
||||
const size_t scale = 2 + opc.ZeroExtend<size_t>();
|
||||
const size_t datasize = 8 << scale;
|
||||
const u64 offset = imm7.SignExtend<u64>() << scale;
|
||||
const size_t dbytes = datasize / 8;
|
||||
|
||||
if (!postindex) {
|
||||
address += offset;
|
||||
}
|
||||
|
||||
switch (memop) {
|
||||
case MemOp::Store: {
|
||||
u128 data1 = VectorGetElement(this->GetVec(Vt), datasize);
|
||||
u128 data2 = VectorGetElement(this->GetVec(Vt2), datasize);
|
||||
m_memory.WriteBlock(address, &data1, dbytes);
|
||||
m_memory.WriteBlock(address + dbytes, &data2, dbytes);
|
||||
break;
|
||||
}
|
||||
case MemOp::Load: {
|
||||
u128 data1{}, data2{};
|
||||
m_memory.ReadBlock(address, &data1, dbytes);
|
||||
m_memory.ReadBlock(address + dbytes, &data2, dbytes);
|
||||
this->SetVec(Vt, data1);
|
||||
this->SetVec(Vt2, data2);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
if (wback) {
|
||||
if (postindex) {
|
||||
address += offset;
|
||||
}
|
||||
|
||||
if (Rn == Reg::SP) {
|
||||
this->SetSp(address);
|
||||
} else {
|
||||
this->SetReg(Rn, address);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::RegisterImmediate(bool wback, bool postindex, size_t scale, u64 offset,
|
||||
Imm<2> size, Imm<2> opc, Reg Rn, Reg Rt) {
|
||||
MemOp memop;
|
||||
bool signed_ = false;
|
||||
size_t regsize = 0;
|
||||
|
||||
if (opc.Bit<1>() == 0) {
|
||||
memop = opc.Bit<0>() ? MemOp::Load : MemOp::Store;
|
||||
regsize = size == 0b11 ? 64 : 32;
|
||||
signed_ = false;
|
||||
} else if (size == 0b11) {
|
||||
memop = MemOp::Prefetch;
|
||||
ASSERT(!opc.Bit<0>());
|
||||
} else {
|
||||
memop = MemOp::Load;
|
||||
ASSERT(!(size == 0b10 && opc.Bit<0>() == 1));
|
||||
regsize = opc.Bit<0>() ? 32 : 64;
|
||||
signed_ = true;
|
||||
}
|
||||
|
||||
if (memop == MemOp::Load && wback && Rn == Rt && Rn != Reg::R31) {
|
||||
// Unpredictable instruction
|
||||
return false;
|
||||
}
|
||||
if (memop == MemOp::Store && wback && Rn == Rt && Rn != Reg::R31) {
|
||||
// Unpredictable instruction
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 address;
|
||||
if (Rn == Reg::SP) {
|
||||
address = this->GetSp();
|
||||
} else {
|
||||
address = this->GetReg(Rn);
|
||||
}
|
||||
if (!postindex) {
|
||||
address += offset;
|
||||
}
|
||||
|
||||
const size_t datasize = 8 << scale;
|
||||
switch (memop) {
|
||||
case MemOp::Store: {
|
||||
u64 data = this->GetReg(Rt);
|
||||
m_memory.WriteBlock(address, &data, datasize / 8);
|
||||
break;
|
||||
}
|
||||
case MemOp::Load: {
|
||||
u64 data = 0;
|
||||
m_memory.ReadBlock(address, &data, datasize / 8);
|
||||
if (signed_) {
|
||||
this->SetReg(Rt, SignExtend(data, datasize, regsize));
|
||||
} else {
|
||||
this->SetReg(Rt, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MemOp::Prefetch:
|
||||
// this->Prefetch(address, Rt)
|
||||
break;
|
||||
}
|
||||
|
||||
if (wback) {
|
||||
if (postindex) {
|
||||
address += offset;
|
||||
}
|
||||
|
||||
if (Rn == Reg::SP) {
|
||||
this->SetSp(address);
|
||||
} else {
|
||||
this->SetReg(Rn, address);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::STRx_LDRx_imm_1(Imm<2> size, Imm<2> opc, Imm<9> imm9, bool not_postindex,
|
||||
Reg Rn, Reg Rt) {
|
||||
const bool wback = true;
|
||||
const bool postindex = !not_postindex;
|
||||
const size_t scale = size.ZeroExtend<size_t>();
|
||||
const u64 offset = imm9.SignExtend<u64>();
|
||||
|
||||
return this->RegisterImmediate(wback, postindex, scale, offset, size, opc, Rn, Rt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::STRx_LDRx_imm_2(Imm<2> size, Imm<2> opc, Imm<12> imm12, Reg Rn, Reg Rt) {
|
||||
const bool wback = false;
|
||||
const bool postindex = false;
|
||||
const size_t scale = size.ZeroExtend<size_t>();
|
||||
const u64 offset = imm12.ZeroExtend<u64>() << scale;
|
||||
|
||||
return this->RegisterImmediate(wback, postindex, scale, offset, size, opc, Rn, Rt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::STURx_LDURx(Imm<2> size, Imm<2> opc, Imm<9> imm9, Reg Rn, Reg Rt) {
|
||||
const bool wback = false;
|
||||
const bool postindex = false;
|
||||
const size_t scale = size.ZeroExtend<size_t>();
|
||||
const u64 offset = imm9.SignExtend<u64>();
|
||||
|
||||
return this->RegisterImmediate(wback, postindex, scale, offset, size, opc, Rn, Rt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::SIMDImmediate(bool wback, bool postindex, size_t scale, u64 offset,
|
||||
MemOp memop, Reg Rn, Vec Vt) {
|
||||
const size_t datasize = 8 << scale;
|
||||
|
||||
u64 address;
|
||||
if (Rn == Reg::SP) {
|
||||
address = this->GetSp();
|
||||
} else {
|
||||
address = this->GetReg(Rn);
|
||||
}
|
||||
|
||||
if (!postindex) {
|
||||
address += offset;
|
||||
}
|
||||
|
||||
switch (memop) {
|
||||
case MemOp::Store: {
|
||||
u128 data = VectorGetElement(this->GetVec(Vt), datasize);
|
||||
m_memory.WriteBlock(address, &data, datasize / 8);
|
||||
break;
|
||||
}
|
||||
case MemOp::Load: {
|
||||
u128 data{};
|
||||
m_memory.ReadBlock(address, &data, datasize / 8);
|
||||
this->SetVec(Vt, data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
if (wback) {
|
||||
if (postindex) {
|
||||
address += offset;
|
||||
}
|
||||
|
||||
if (Rn == Reg::SP) {
|
||||
this->SetSp(address);
|
||||
} else {
|
||||
this->SetReg(Rn, address);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::STR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9,
|
||||
bool not_postindex, Reg Rn, Vec Vt) {
|
||||
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||
if (scale > 4) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool wback = true;
|
||||
const bool postindex = !not_postindex;
|
||||
const u64 offset = imm9.SignExtend<u64>();
|
||||
|
||||
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Store, Rn, Vt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::STR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn,
|
||||
Vec Vt) {
|
||||
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||
if (scale > 4) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool wback = false;
|
||||
const bool postindex = false;
|
||||
const u64 offset = imm12.ZeroExtend<u64>() << scale;
|
||||
|
||||
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Store, Rn, Vt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::LDR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9,
|
||||
bool not_postindex, Reg Rn, Vec Vt) {
|
||||
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||
if (scale > 4) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool wback = true;
|
||||
const bool postindex = !not_postindex;
|
||||
const u64 offset = imm9.SignExtend<u64>();
|
||||
|
||||
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Load, Rn, Vt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::LDR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn,
|
||||
Vec Vt) {
|
||||
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||
if (scale > 4) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool wback = false;
|
||||
const bool postindex = false;
|
||||
const u64 offset = imm12.ZeroExtend<u64>() << scale;
|
||||
|
||||
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Load, Rn, Vt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::STUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) {
|
||||
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||
if (scale > 4) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool wback = false;
|
||||
const bool postindex = false;
|
||||
const u64 offset = imm9.SignExtend<u64>();
|
||||
|
||||
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Store, Rn, Vt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::LDUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) {
|
||||
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||
if (scale > 4) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool wback = false;
|
||||
const bool postindex = false;
|
||||
const u64 offset = imm9.SignExtend<u64>();
|
||||
|
||||
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Load, Rn, Vt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::RegisterOffset(size_t scale, u8 shift, Imm<2> size, Imm<1> opc_1,
|
||||
Imm<1> opc_0, Reg Rm, Imm<3> option, Reg Rn, Reg Rt) {
|
||||
MemOp memop;
|
||||
size_t regsize = 64;
|
||||
bool signed_ = false;
|
||||
|
||||
if (opc_1 == 0) {
|
||||
memop = opc_0 == 1 ? MemOp::Load : MemOp::Store;
|
||||
regsize = size == 0b11 ? 64 : 32;
|
||||
signed_ = false;
|
||||
} else if (size == 0b11) {
|
||||
memop = MemOp::Prefetch;
|
||||
if (opc_0 == 1) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
memop = MemOp::Load;
|
||||
if (size == 0b10 && opc_0 == 1) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
regsize = opc_0 == 1 ? 32 : 64;
|
||||
signed_ = true;
|
||||
}
|
||||
|
||||
const size_t datasize = 8 << scale;
|
||||
|
||||
// Operation
|
||||
const u64 offset = this->ExtendReg(64, Rm, option, shift);
|
||||
|
||||
u64 address;
|
||||
if (Rn == Reg::SP) {
|
||||
address = this->GetSp();
|
||||
} else {
|
||||
address = this->GetReg(Rn);
|
||||
}
|
||||
address += offset;
|
||||
|
||||
switch (memop) {
|
||||
case MemOp::Store: {
|
||||
u64 data = this->GetReg(Rt);
|
||||
m_memory.WriteBlock(address, &data, datasize / 8);
|
||||
break;
|
||||
}
|
||||
case MemOp::Load: {
|
||||
u64 data = 0;
|
||||
m_memory.ReadBlock(address, &data, datasize / 8);
|
||||
if (signed_) {
|
||||
this->SetReg(Rt, SignExtend(data, datasize, regsize));
|
||||
} else {
|
||||
this->SetReg(Rt, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MemOp::Prefetch:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::STRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||
Reg Rt) {
|
||||
const Imm<1> opc_0{0};
|
||||
const size_t scale = size.ZeroExtend<size_t>();
|
||||
const u8 shift = S ? static_cast<u8>(scale) : 0;
|
||||
if (!option.Bit<1>()) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
return this->RegisterOffset(scale, shift, size, opc_1, opc_0, Rm, option, Rn, Rt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::LDRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||
Reg Rt) {
|
||||
const Imm<1> opc_0{1};
|
||||
const size_t scale = size.ZeroExtend<size_t>();
|
||||
const u8 shift = S ? static_cast<u8>(scale) : 0;
|
||||
if (!option.Bit<1>()) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
return this->RegisterOffset(scale, shift, size, opc_1, opc_0, Rm, option, Rn, Rt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::SIMDOffset(size_t scale, u8 shift, Imm<1> opc_0, Reg Rm, Imm<3> option,
|
||||
Reg Rn, Vec Vt) {
|
||||
const auto memop = opc_0 == 1 ? MemOp::Load : MemOp::Store;
|
||||
const size_t datasize = 8 << scale;
|
||||
|
||||
// Operation
|
||||
const u64 offset = this->ExtendReg(64, Rm, option, shift);
|
||||
|
||||
u64 address;
|
||||
if (Rn == Reg::SP) {
|
||||
address = this->GetSp();
|
||||
} else {
|
||||
address = this->GetReg(Rn);
|
||||
}
|
||||
address += offset;
|
||||
|
||||
switch (memop) {
|
||||
case MemOp::Store: {
|
||||
u128 data = VectorGetElement(this->GetVec(Vt), datasize);
|
||||
m_memory.WriteBlock(address, &data, datasize / 8);
|
||||
break;
|
||||
}
|
||||
case MemOp::Load: {
|
||||
u128 data{};
|
||||
m_memory.ReadBlock(address, &data, datasize / 8);
|
||||
this->SetVec(Vt, data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::STR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S,
|
||||
Reg Rn, Vec Vt) {
|
||||
const Imm<1> opc_0{0};
|
||||
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||
if (scale > 4) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
const u8 shift = S ? static_cast<u8>(scale) : 0;
|
||||
if (!option.Bit<1>()) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
return this->SIMDOffset(scale, shift, opc_0, Rm, option, Rn, Vt);
|
||||
}
|
||||
|
||||
bool InterpreterVisitor::LDR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S,
|
||||
Reg Rn, Vec Vt) {
|
||||
const Imm<1> opc_0{1};
|
||||
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||
if (scale > 4) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
const u8 shift = S ? static_cast<u8>(scale) : 0;
|
||||
if (!option.Bit<1>()) {
|
||||
// Unallocated encoding
|
||||
return false;
|
||||
}
|
||||
return this->SIMDOffset(scale, shift, opc_0, Rm, option, Rn, Vt);
|
||||
}
|
||||
|
||||
std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context,
|
||||
fpsimd_context* fpsimd_context) {
|
||||
// Construct the interpreter.
|
||||
std::span<u64, 31> regs(reinterpret_cast<u64*>(context->regs), 31);
|
||||
std::span<u128, 32> vregs(reinterpret_cast<u128*>(fpsimd_context->vregs), 32);
|
||||
u64& sp = *reinterpret_cast<u64*>(&context->sp);
|
||||
const u64& pc = *reinterpret_cast<u64*>(&context->pc);
|
||||
|
||||
InterpreterVisitor visitor(memory, regs, vregs, sp, pc);
|
||||
|
||||
// Read the instruction at the program counter.
|
||||
u32 instruction = memory.Read32(pc);
|
||||
bool was_executed = false;
|
||||
|
||||
// Interpret the instruction.
|
||||
if (auto decoder = Dynarmic::A64::Decode<VisitorBase>(instruction)) {
|
||||
was_executed = decoder->get().call(visitor, instruction);
|
||||
} else {
|
||||
LOG_ERROR(Core_ARM, "Unallocated encoding: {:#x}", instruction);
|
||||
}
|
||||
|
||||
if (was_executed) {
|
||||
return pc + 4;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Core
|
103
src/core/arm/nce/interpreter_visitor.h
Normal file
103
src/core/arm/nce/interpreter_visitor.h
Normal file
@ -0,0 +1,103 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2023 merryhime <https://mary.rs>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "core/arm/nce/visitor_base.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
namespace Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
class InterpreterVisitor final : public VisitorBase {
|
||||
public:
|
||||
explicit InterpreterVisitor(Core::Memory::Memory& memory, std::span<u64, 31> regs,
|
||||
std::span<u128, 32> fpsimd_regs, u64& sp, const u64& pc)
|
||||
: m_memory(memory), m_regs(regs), m_fpsimd_regs(fpsimd_regs), m_sp(sp), m_pc(pc) {}
|
||||
~InterpreterVisitor() override = default;
|
||||
|
||||
enum class MemOp {
|
||||
Load,
|
||||
Store,
|
||||
Prefetch,
|
||||
};
|
||||
|
||||
u128 GetVec(Vec v);
|
||||
u64 GetReg(Reg r);
|
||||
u64 GetSp();
|
||||
u64 GetPc();
|
||||
|
||||
void SetVec(Vec v, u128 value);
|
||||
void SetReg(Reg r, u64 value);
|
||||
void SetSp(u64 value);
|
||||
|
||||
u64 ExtendReg(size_t bitsize, Reg reg, Imm<3> option, u8 shift);
|
||||
|
||||
// Loads and stores - Load/Store Exclusive
|
||||
bool Ordered(size_t size, bool L, bool o0, Reg Rn, Reg Rt);
|
||||
bool STLLR(Imm<2> size, Reg Rn, Reg Rt) override;
|
||||
bool STLR(Imm<2> size, Reg Rn, Reg Rt) override;
|
||||
bool LDLAR(Imm<2> size, Reg Rn, Reg Rt) override;
|
||||
bool LDAR(Imm<2> size, Reg Rn, Reg Rt) override;
|
||||
|
||||
// Loads and stores - Load register (literal)
|
||||
bool LDR_lit_gen(bool opc_0, Imm<19> imm19, Reg Rt) override;
|
||||
bool LDR_lit_fpsimd(Imm<2> opc, Imm<19> imm19, Vec Vt) override;
|
||||
|
||||
// Loads and stores - Load/Store register pair
|
||||
bool STP_LDP_gen(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L, Imm<7> imm7, Reg Rt2,
|
||||
Reg Rn, Reg Rt) override;
|
||||
bool STP_LDP_fpsimd(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L, Imm<7> imm7, Vec Vt2,
|
||||
Reg Rn, Vec Vt) override;
|
||||
|
||||
// Loads and stores - Load/Store register (immediate)
|
||||
bool RegisterImmediate(bool wback, bool postindex, size_t scale, u64 offset, Imm<2> size,
|
||||
Imm<2> opc, Reg Rn, Reg Rt);
|
||||
bool STRx_LDRx_imm_1(Imm<2> size, Imm<2> opc, Imm<9> imm9, bool not_postindex, Reg Rn,
|
||||
Reg Rt) override;
|
||||
bool STRx_LDRx_imm_2(Imm<2> size, Imm<2> opc, Imm<12> imm12, Reg Rn, Reg Rt) override;
|
||||
bool STURx_LDURx(Imm<2> size, Imm<2> opc, Imm<9> imm9, Reg Rn, Reg Rt) override;
|
||||
|
||||
bool SIMDImmediate(bool wback, bool postindex, size_t scale, u64 offset, MemOp memop, Reg Rn,
|
||||
Vec Vt);
|
||||
bool STR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, bool not_postindex, Reg Rn,
|
||||
Vec Vt) override;
|
||||
bool STR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn, Vec Vt) override;
|
||||
bool LDR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, bool not_postindex, Reg Rn,
|
||||
Vec Vt) override;
|
||||
bool LDR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn, Vec Vt) override;
|
||||
bool STUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) override;
|
||||
bool LDUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) override;
|
||||
|
||||
// Loads and stores - Load/Store register (register offset)
|
||||
bool RegisterOffset(size_t scale, u8 shift, Imm<2> size, Imm<1> opc_1, Imm<1> opc_0, Reg Rm,
|
||||
Imm<3> option, Reg Rn, Reg Rt);
|
||||
bool STRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||
Reg Rt) override;
|
||||
bool LDRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||
Reg Rt) override;
|
||||
|
||||
bool SIMDOffset(size_t scale, u8 shift, Imm<1> opc_0, Reg Rm, Imm<3> option, Reg Rn, Vec Vt);
|
||||
bool STR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||
Vec Vt) override;
|
||||
bool LDR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||
Vec Vt) override;
|
||||
|
||||
private:
|
||||
Core::Memory::Memory& m_memory;
|
||||
std::span<u64, 31> m_regs;
|
||||
std::span<u128, 32> m_fpsimd_regs;
|
||||
u64& m_sp;
|
||||
const u64& m_pc;
|
||||
};
|
||||
|
||||
std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context,
|
||||
fpsimd_context* fpsimd_context);
|
||||
|
||||
} // namespace Core
|
2783
src/core/arm/nce/visitor_base.h
Normal file
2783
src/core/arm/nce/visitor_base.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -28,7 +28,6 @@
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/gpu_dirty_memory_manager.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hle/kernel/k_memory_manager.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
@ -36,6 +35,7 @@
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
#include "core/hle/service/apm/apm_controller.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
@ -129,11 +129,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
|
||||
struct System::Impl {
|
||||
explicit Impl(System& system)
|
||||
: kernel{system}, fs_controller{system}, memory{system}, hid_core{}, room_network{},
|
||||
cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system},
|
||||
gpu_dirty_memory_write_manager{} {
|
||||
memory.SetGPUDirtyManagers(gpu_dirty_memory_write_manager);
|
||||
}
|
||||
: kernel{system}, fs_controller{system}, hid_core{}, room_network{}, cpu_manager{system},
|
||||
reporter{system}, applet_manager{system}, profile_manager{}, time_manager{system} {}
|
||||
|
||||
void Initialize(System& system) {
|
||||
device_memory = std::make_unique<Core::DeviceMemory>();
|
||||
@ -240,17 +237,17 @@ struct System::Impl {
|
||||
debugger = std::make_unique<Debugger>(system, port);
|
||||
}
|
||||
|
||||
SystemResultStatus SetupForApplicationProcess(System& system, Frontend::EmuWindow& emu_window) {
|
||||
void InitializeKernel(System& system) {
|
||||
LOG_DEBUG(Core, "initialized OK");
|
||||
|
||||
// Setting changes may require a full system reinitialization (e.g., disabling multicore).
|
||||
ReinitializeIfNecessary(system);
|
||||
|
||||
memory.SetGPUDirtyManagers(gpu_dirty_memory_write_manager);
|
||||
|
||||
kernel.Initialize();
|
||||
cpu_manager.Initialize();
|
||||
}
|
||||
|
||||
SystemResultStatus SetupForApplicationProcess(System& system, Frontend::EmuWindow& emu_window) {
|
||||
/// Reset all glue registrations
|
||||
arp_manager.ResetAll();
|
||||
|
||||
@ -299,17 +296,9 @@ struct System::Impl {
|
||||
return SystemResultStatus::ErrorGetLoader;
|
||||
}
|
||||
|
||||
SystemResultStatus init_result{SetupForApplicationProcess(system, emu_window)};
|
||||
if (init_result != SystemResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
|
||||
static_cast<int>(init_result));
|
||||
ShutdownMainProcess();
|
||||
return init_result;
|
||||
}
|
||||
InitializeKernel(system);
|
||||
|
||||
telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
|
||||
|
||||
// Create the process.
|
||||
// Create the application process.
|
||||
auto main_process = Kernel::KProcess::Create(system.Kernel());
|
||||
Kernel::KProcess::Register(system.Kernel(), main_process);
|
||||
kernel.AppendNewProcess(main_process);
|
||||
@ -322,7 +311,18 @@ struct System::Impl {
|
||||
return static_cast<SystemResultStatus>(
|
||||
static_cast<u32>(SystemResultStatus::ErrorLoader) + static_cast<u32>(load_result));
|
||||
}
|
||||
|
||||
// Set up the rest of the system.
|
||||
SystemResultStatus init_result{SetupForApplicationProcess(system, emu_window)};
|
||||
if (init_result != SystemResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
|
||||
static_cast<int>(init_result));
|
||||
ShutdownMainProcess();
|
||||
return init_result;
|
||||
}
|
||||
|
||||
AddGlueRegistrationForProcess(*app_loader, *main_process);
|
||||
telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
|
||||
|
||||
// Initialize cheat engine
|
||||
if (cheat_engine) {
|
||||
@ -425,7 +425,6 @@ struct System::Impl {
|
||||
cpu_manager.Shutdown();
|
||||
debugger.reset();
|
||||
kernel.Shutdown();
|
||||
memory.Reset();
|
||||
Network::RestartSocketOperations();
|
||||
|
||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
||||
@ -506,7 +505,6 @@ struct System::Impl {
|
||||
std::unique_ptr<Tegra::Host1x::Host1x> host1x_core;
|
||||
std::unique_ptr<Core::DeviceMemory> device_memory;
|
||||
std::unique_ptr<AudioCore::AudioCore> audio_core;
|
||||
Core::Memory::Memory memory;
|
||||
Core::HID::HIDCore hid_core;
|
||||
Network::RoomNetwork room_network;
|
||||
|
||||
@ -532,6 +530,7 @@ struct System::Impl {
|
||||
|
||||
/// Service State
|
||||
Service::Glue::ARPManager arp_manager;
|
||||
Service::Account::ProfileManager profile_manager;
|
||||
Service::Time::TimeManager time_manager;
|
||||
|
||||
/// Service manager
|
||||
@ -565,9 +564,6 @@ struct System::Impl {
|
||||
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
|
||||
std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_cpu{};
|
||||
|
||||
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES>
|
||||
gpu_dirty_memory_write_manager{};
|
||||
|
||||
std::deque<std::vector<u8>> user_channel;
|
||||
};
|
||||
|
||||
@ -650,29 +646,12 @@ void System::PrepareReschedule(const u32 core_index) {
|
||||
impl->kernel.PrepareReschedule(core_index);
|
||||
}
|
||||
|
||||
Core::GPUDirtyMemoryManager& System::CurrentGPUDirtyMemoryManager() {
|
||||
const std::size_t core = impl->kernel.GetCurrentHostThreadID();
|
||||
return impl->gpu_dirty_memory_write_manager[core < Core::Hardware::NUM_CPU_CORES
|
||||
? core
|
||||
: Core::Hardware::NUM_CPU_CORES - 1];
|
||||
}
|
||||
|
||||
/// Provides a constant reference to the current gou dirty memory manager.
|
||||
const Core::GPUDirtyMemoryManager& System::CurrentGPUDirtyMemoryManager() const {
|
||||
const std::size_t core = impl->kernel.GetCurrentHostThreadID();
|
||||
return impl->gpu_dirty_memory_write_manager[core < Core::Hardware::NUM_CPU_CORES
|
||||
? core
|
||||
: Core::Hardware::NUM_CPU_CORES - 1];
|
||||
}
|
||||
|
||||
size_t System::GetCurrentHostThreadID() const {
|
||||
return impl->kernel.GetCurrentHostThreadID();
|
||||
}
|
||||
|
||||
void System::GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback) {
|
||||
for (auto& manager : impl->gpu_dirty_memory_write_manager) {
|
||||
manager.Gather(callback);
|
||||
}
|
||||
return this->ApplicationProcess()->GatherGPUDirtyMemory(callback);
|
||||
}
|
||||
|
||||
PerfStatsResults System::GetAndResetPerfStats() {
|
||||
@ -721,20 +700,12 @@ const Kernel::KProcess* System::ApplicationProcess() const {
|
||||
return impl->kernel.ApplicationProcess();
|
||||
}
|
||||
|
||||
ExclusiveMonitor& System::Monitor() {
|
||||
return impl->kernel.GetExclusiveMonitor();
|
||||
}
|
||||
|
||||
const ExclusiveMonitor& System::Monitor() const {
|
||||
return impl->kernel.GetExclusiveMonitor();
|
||||
}
|
||||
|
||||
Memory::Memory& System::ApplicationMemory() {
|
||||
return impl->memory;
|
||||
return impl->kernel.ApplicationProcess()->GetMemory();
|
||||
}
|
||||
|
||||
const Core::Memory::Memory& System::ApplicationMemory() const {
|
||||
return impl->memory;
|
||||
return impl->kernel.ApplicationProcess()->GetMemory();
|
||||
}
|
||||
|
||||
Tegra::GPU& System::GPU() {
|
||||
@ -921,6 +892,14 @@ const Service::APM::Controller& System::GetAPMController() const {
|
||||
return impl->apm_controller;
|
||||
}
|
||||
|
||||
Service::Account::ProfileManager& System::GetProfileManager() {
|
||||
return impl->profile_manager;
|
||||
}
|
||||
|
||||
const Service::Account::ProfileManager& System::GetProfileManager() const {
|
||||
return impl->profile_manager;
|
||||
}
|
||||
|
||||
Service::Time::TimeManager& System::GetTimeManager() {
|
||||
return impl->time_manager;
|
||||
}
|
||||
|
@ -45,6 +45,10 @@ class Memory;
|
||||
|
||||
namespace Service {
|
||||
|
||||
namespace Account {
|
||||
class ProfileManager;
|
||||
} // namespace Account
|
||||
|
||||
namespace AM::Applets {
|
||||
struct AppletFrontendSet;
|
||||
class AppletManager;
|
||||
@ -112,7 +116,6 @@ class CpuManager;
|
||||
class Debugger;
|
||||
class DeviceMemory;
|
||||
class ExclusiveMonitor;
|
||||
class GPUDirtyMemoryManager;
|
||||
class PerfStats;
|
||||
class Reporter;
|
||||
class SpeedLimiter;
|
||||
@ -221,12 +224,6 @@ public:
|
||||
/// Prepare the core emulation for a reschedule
|
||||
void PrepareReschedule(u32 core_index);
|
||||
|
||||
/// Provides a reference to the gou dirty memory manager.
|
||||
[[nodiscard]] Core::GPUDirtyMemoryManager& CurrentGPUDirtyMemoryManager();
|
||||
|
||||
/// Provides a constant reference to the current gou dirty memory manager.
|
||||
[[nodiscard]] const Core::GPUDirtyMemoryManager& CurrentGPUDirtyMemoryManager() const;
|
||||
|
||||
void GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback);
|
||||
|
||||
[[nodiscard]] size_t GetCurrentHostThreadID() const;
|
||||
@ -246,12 +243,6 @@ public:
|
||||
/// Gets a const reference to the underlying CPU manager
|
||||
[[nodiscard]] const CpuManager& GetCpuManager() const;
|
||||
|
||||
/// Gets a reference to the exclusive monitor
|
||||
[[nodiscard]] ExclusiveMonitor& Monitor();
|
||||
|
||||
/// Gets a constant reference to the exclusive monitor
|
||||
[[nodiscard]] const ExclusiveMonitor& Monitor() const;
|
||||
|
||||
/// Gets a mutable reference to the system memory instance.
|
||||
[[nodiscard]] Core::Memory::Memory& ApplicationMemory();
|
||||
|
||||
@ -383,6 +374,9 @@ public:
|
||||
[[nodiscard]] Service::APM::Controller& GetAPMController();
|
||||
[[nodiscard]] const Service::APM::Controller& GetAPMController() const;
|
||||
|
||||
[[nodiscard]] Service::Account::ProfileManager& GetProfileManager();
|
||||
[[nodiscard]] const Service::Account::ProfileManager& GetProfileManager() const;
|
||||
|
||||
[[nodiscard]] Service::Time::TimeManager& GetTimeManager();
|
||||
[[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const;
|
||||
|
||||
|
@ -29,7 +29,6 @@ std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callbac
|
||||
struct CoreTiming::Event {
|
||||
s64 time;
|
||||
u64 fifo_order;
|
||||
std::uintptr_t user_data;
|
||||
std::weak_ptr<EventType> type;
|
||||
s64 reschedule_time;
|
||||
heap_t::handle_type handle{};
|
||||
@ -67,17 +66,15 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
||||
event_fifo_id = 0;
|
||||
shutting_down = false;
|
||||
cpu_ticks = 0;
|
||||
const auto empty_timed_callback = [](std::uintptr_t, u64, std::chrono::nanoseconds)
|
||||
-> std::optional<std::chrono::nanoseconds> { return std::nullopt; };
|
||||
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
|
||||
if (is_multicore) {
|
||||
timer_thread = std::make_unique<std::jthread>(ThreadEntry, std::ref(*this));
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::ClearPendingEvents() {
|
||||
std::scoped_lock lock{basic_lock};
|
||||
std::scoped_lock lock{advance_lock, basic_lock};
|
||||
event_queue.clear();
|
||||
event.Set();
|
||||
}
|
||||
|
||||
void CoreTiming::Pause(bool is_paused) {
|
||||
@ -119,14 +116,12 @@ bool CoreTiming::HasPendingEvents() const {
|
||||
}
|
||||
|
||||
void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
|
||||
const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data, bool absolute_time) {
|
||||
const std::shared_ptr<EventType>& event_type, bool absolute_time) {
|
||||
{
|
||||
std::scoped_lock scope{basic_lock};
|
||||
const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
|
||||
|
||||
auto h{event_queue.emplace(
|
||||
Event{next_time.count(), event_fifo_id++, user_data, event_type, 0})};
|
||||
auto h{event_queue.emplace(Event{next_time.count(), event_fifo_id++, event_type, 0})};
|
||||
(*h).handle = h;
|
||||
}
|
||||
|
||||
@ -136,13 +131,13 @@ void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
|
||||
void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
|
||||
std::chrono::nanoseconds resched_time,
|
||||
const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data, bool absolute_time) {
|
||||
bool absolute_time) {
|
||||
{
|
||||
std::scoped_lock scope{basic_lock};
|
||||
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
|
||||
|
||||
auto h{event_queue.emplace(Event{next_time.count(), event_fifo_id++, user_data, event_type,
|
||||
resched_time.count()})};
|
||||
auto h{event_queue.emplace(
|
||||
Event{next_time.count(), event_fifo_id++, event_type, resched_time.count()})};
|
||||
(*h).handle = h;
|
||||
}
|
||||
|
||||
@ -150,14 +145,14 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
|
||||
}
|
||||
|
||||
void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data, bool wait) {
|
||||
UnscheduleEventType type) {
|
||||
{
|
||||
std::scoped_lock lk{basic_lock};
|
||||
|
||||
std::vector<heap_t::handle_type> to_remove;
|
||||
for (auto itr = event_queue.begin(); itr != event_queue.end(); itr++) {
|
||||
const Event& e = *itr;
|
||||
if (e.type.lock().get() == event_type.get() && e.user_data == user_data) {
|
||||
if (e.type.lock().get() == event_type.get()) {
|
||||
to_remove.push_back(itr->handle);
|
||||
}
|
||||
}
|
||||
@ -165,10 +160,12 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
|
||||
for (auto h : to_remove) {
|
||||
event_queue.erase(h);
|
||||
}
|
||||
|
||||
event_type->sequence_number++;
|
||||
}
|
||||
|
||||
// Force any in-progress events to finish
|
||||
if (wait) {
|
||||
if (type == UnscheduleEventType::Wait) {
|
||||
std::scoped_lock lk{advance_lock};
|
||||
}
|
||||
}
|
||||
@ -208,28 +205,31 @@ std::optional<s64> CoreTiming::Advance() {
|
||||
const Event& evt = event_queue.top();
|
||||
|
||||
if (const auto event_type{evt.type.lock()}) {
|
||||
if (evt.reschedule_time == 0) {
|
||||
const auto evt_user_data = evt.user_data;
|
||||
const auto evt_time = evt.time;
|
||||
const auto evt_time = evt.time;
|
||||
const auto evt_sequence_num = event_type->sequence_number;
|
||||
|
||||
if (evt.reschedule_time == 0) {
|
||||
event_queue.pop();
|
||||
|
||||
basic_lock.unlock();
|
||||
|
||||
event_type->callback(
|
||||
evt_user_data, evt_time,
|
||||
std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time});
|
||||
evt_time, std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time});
|
||||
|
||||
basic_lock.lock();
|
||||
} else {
|
||||
basic_lock.unlock();
|
||||
|
||||
const auto new_schedule_time{event_type->callback(
|
||||
evt.user_data, evt.time,
|
||||
std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
|
||||
evt_time, std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time})};
|
||||
|
||||
basic_lock.lock();
|
||||
|
||||
if (evt_sequence_num != event_type->sequence_number) {
|
||||
// Heap handle is invalidated after external modification.
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto next_schedule_time{new_schedule_time.has_value()
|
||||
? new_schedule_time.value().count()
|
||||
: evt.reschedule_time};
|
||||
@ -241,8 +241,8 @@ std::optional<s64> CoreTiming::Advance() {
|
||||
next_time = pause_end_time + next_schedule_time;
|
||||
}
|
||||
|
||||
event_queue.update(evt.handle, Event{next_time, event_fifo_id++, evt.user_data,
|
||||
evt.type, next_schedule_time, evt.handle});
|
||||
event_queue.update(evt.handle, Event{next_time, event_fifo_id++, evt.type,
|
||||
next_schedule_time, evt.handle});
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user