Compare commits

..

41 Commits

Author SHA1 Message Date
yuzubot
0adba08441 Android #178 2024-01-01 00:57:34 +00:00
yuzubot
b7bb6fd44c Merge PR 12543 2024-01-01 00:57:33 +00:00
yuzubot
1f99106eb4 Merge PR 12518 2024-01-01 00:57:33 +00:00
yuzubot
a5377a59a1 Merge PR 12513 2024-01-01 00:57:33 +00:00
yuzubot
8c3dfeb0d6 Merge PR 12501 2024-01-01 00:57:33 +00:00
yuzubot
fe1e6a0448 Merge PR 12466 2024-01-01 00:57:33 +00:00
yuzubot
79cfd1f97e Merge PR 12454 2024-01-01 00:57:33 +00:00
liamwhite
09bfc852dc Merge pull request #12509 from liamwhite/ktrace
k_capabilities: simplify KTrace map skip logic
2023-12-30 14:59:09 -05:00
liamwhite
ace74bd066 Merge pull request #12486 from t895/debug-ci
actions: android: Build relWithDebInfo on main repo
2023-12-30 14:59:04 -05:00
liamwhite
f6ee53af14 Merge pull request #12527 from DCNick3/log-more-sdl-errors
Report more SDL errors
2023-12-30 14:58:58 -05:00
liamwhite
6c6cb5745f Merge pull request #12521 from ReillyBrogan/fix-wayland-appid
Fix Wayland appId
2023-12-30 14:58:52 -05:00
liamwhite
3262c0f747 Merge pull request #12520 from t895/settings-tomfoolery
android: Small settings tweaks
2023-12-30 14:58:45 -05:00
Nikita Strygin
9323a1f9b2 yuzu_cmd: Report more SDL errors 2023-12-30 13:32:33 +03:00
Fernando S
f02a8d0ae9 Merge pull request #12479 from GPUCode/linear-bcn
video_core: Fix buffer_row_length for linear compressed textures
2023-12-30 06:56:08 +01:00
Fernando S
8517d7cb44 Merge pull request #12487 from liamwhite/clip3
shader_recompiler: use default value for clip distances array
2023-12-30 06:54:44 +01:00
Reilly Brogan
cb4b4f3d6e Fix Wayland appId
On compliant Wayland compositors windows are matched to their .desktop files by comparing the appId window property to the name of the .desktop file without the .deskop extension.

Qt5/6 by default set this property to the basename of the binary (IE `yuzu`) which does not match the expected value `org.yuzu_emu.yuzu`. We can fix this and fix window associations on compliant compositors (like Plasma) by using the `setDesktopFileName()` function which will set the appId window property. This is a no-op on X11 so is safe to be ran without guards.
2023-12-29 20:32:08 -06:00
t895
21e7f86697 android: Expose anisotropic filtering setting 2023-12-29 17:43:36 -05:00
t895
347b3bd18d android: Center switch setting title when no description is present 2023-12-29 17:28:01 -05:00
t895
755c45777f android: Pair fastmem toggle to CPU Debug toggle
Hides fastmem toggle when CPU Debugging is disabled
2023-12-29 17:27:36 -05:00
t895
d677052e8c actions: android: Build relWithDebInfo on main repo 2023-12-28 20:53:16 -05:00
Liam
95bfc542aa k_capabilities: simplify KTrace map skip logic 2023-12-28 01:22:57 -05:00
Liam
d0c60605ab shader_recompiler: use default value for clip distances array 2023-12-26 19:32:47 -05:00
Liam
6697b665ca shader_recompiler: respect clip distance limits in indexed store 2023-12-26 19:10:25 -05:00
liamwhite
12178c694a Merge pull request #12455 from liamwhite/end-wait
kernel: use simple mutex for object list container
2023-12-26 11:46:19 -05:00
liamwhite
de1e5584b3 Merge pull request #12465 from liamwhite/proper-handle-table
service: fetch objects from the client handle table
2023-12-26 11:46:11 -05:00
liamwhite
1559984f77 Merge pull request #12471 from FearlessTobi/port-7146
Port citra-emu/citra#7146: "assert/logging: Stop the logging thread and flush the backends before crashing"
2023-12-26 11:46:04 -05:00
liamwhite
467ac4fdfe Merge pull request #12472 from FearlessTobi/port-7239
Port citra-emu/citra#7239: "common: Miscellaneous cleanups"
2023-12-26 11:45:57 -05:00
liamwhite
69b7100dac Merge pull request #12449 from liamwhite/debug-utils
renderer_vulkan: skip SetObjectNameEXT on unsupported driver
2023-12-26 11:45:39 -05:00
liamwhite
14dc41d4b3 Merge pull request #12448 from liamwhite/format-assert
renderer_vulkan: demote format assert to error log
2023-12-26 11:45:33 -05:00
liamwhite
ad049f13aa Merge pull request #12415 from ameerj/ogl-draw-auto
gl_rasterizer: Implement DrawTransformFeedback macro
2023-12-26 11:45:25 -05:00
GPUCode
20e0407235 video_core: Fix buffer_row_length computation for linear compressed textures 2023-12-26 16:33:03 +02:00
FearlessTobi
4f569fd568 assert/logging: Stop the logging thread and flush the backends before crashing
Co-Authored-By: SachinVin <26602104+SachinVin@users.noreply.github.com>
2023-12-26 10:35:14 +01:00
FearlessTobi
553dac2ae0 ring_buffer: Use feature macro
Co-Authored-By: GPUCode <47210458+GPUCode@users.noreply.github.com>
2023-12-25 14:10:40 +01:00
FearlessTobi
96abe0d7d3 main: Remove unused enum
Co-Authored-By: GPUCode <47210458+GPUCode@users.noreply.github.com>
2023-12-25 14:10:05 +01:00
Liam
47e44a6693 am/jit: reference memory instance from context 2023-12-24 19:36:42 -05:00
Liam
cf8c7d4ed3 kernel: remove unecessary process member from handle table 2023-12-24 19:23:03 -05:00
Liam
5165ed9efd service: fetch objects from the client handle table 2023-12-24 19:20:43 -05:00
Liam
e3491a9ee8 kernel: use simple mutex for object list container 2023-12-23 16:26:07 -05:00
Liam
6a1ddc5028 renderer_vulkan: skip SetObjectNameEXT on unsupported driver 2023-12-23 11:08:02 -05:00
Liam
b1d4804c07 renderer_vulkan: demote format assert to error log 2023-12-23 11:04:02 -05:00
Ameer J
bbc0ed118d gl_rasterizer: Implement DrawTransformFeedback macro 2023-12-19 19:54:57 -05:00
112 changed files with 2242 additions and 1583 deletions

View File

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

View File

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

View File

@@ -1,9 +1,11 @@
| Pull Request | Commit | Title | Author | Merged? |
|----|----|----|----|----|
| [12448](https://github.com/yuzu-emu/yuzu//pull/12448) | [`b1d4804c0`](https://github.com/yuzu-emu/yuzu//pull/12448/files) | renderer_vulkan: demote format assert to error log | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12449](https://github.com/yuzu-emu/yuzu//pull/12449) | [`6a1ddc502`](https://github.com/yuzu-emu/yuzu//pull/12449/files) | renderer_vulkan: skip SetObjectNameEXT on unsupported driver | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12466](https://github.com/yuzu-emu/yuzu//pull/12466) | [`5f3720138`](https://github.com/yuzu-emu/yuzu//pull/12466/files) | core: track separate heap allocation for linux | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12467](https://github.com/yuzu-emu/yuzu//pull/12467) | [`cfc6c5f8f`](https://github.com/yuzu-emu/yuzu//pull/12467/files) | Revert " shader_recompiler: use minimal clip distance array " | [liamwhite](https://github.com/liamwhite/) | 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 |
| [12543](https://github.com/yuzu-emu/yuzu//pull/12543) | [`737e6e531`](https://github.com/yuzu-emu/yuzu//pull/12543/files) | VideoCore: A few fixes to DMA and swapchain | [FernandoS27](https://github.com/FernandoS27/) | Yes |
End of merge log. You can find the original README.md below the break.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -243,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,
@@ -298,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

View File

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

View File

@@ -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
@@ -141,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)
}
@@ -382,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
}
}
}
@@ -423,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
@@ -496,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
@@ -535,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
}
@@ -564,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())
@@ -577,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)
@@ -592,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
@@ -605,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
}
@@ -667,6 +674,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
}
NativeConfig.saveGlobalConfig()
}
@SuppressLint("SetTextI18n")
@@ -675,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()}%"
@@ -685,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()}%"
@@ -709,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()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 {
/**
@@ -150,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>)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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">
@@ -267,4 +267,21 @@
<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>

View File

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

View File

@@ -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>
@@ -364,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>
@@ -506,6 +509,12 @@
<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>

View File

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

View File

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

View File

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

View File

@@ -1,18 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include <fstream>
#include <vector>
#include "common/heap_tracker.h"
#include "common/logging/log.h"
namespace Common {
namespace {
constexpr size_t MaxResidentMapCount = 0x8000;
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) {}
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,
@@ -27,17 +44,17 @@ void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length,
// We are mapping part of a separate heap.
std::scoped_lock lk{m_lock};
auto* map = new SeparateHeapMap{
auto* const map = new SeparateHeapMap{
.vaddr = virtual_offset,
.paddr = host_offset,
.size = length,
.map_id = m_next_map_id++,
.tick = m_tick++,
.perm = perm,
.is_resident = false,
};
// Insert into mappings.
m_map_count++;
m_mappings.insert(*map);
}
@@ -48,11 +65,10 @@ void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length,
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_rebuild_lock, m_lock};
std::scoped_lock lk{m_lock};
const SeparateHeapMap key{
.vaddr = virtual_offset,
.size = size,
};
// Split at the boundaries of the region we are removing.
@@ -62,19 +78,17 @@ void HeapTracker::Unmap(size_t virtual_offset, size_t size, bool is_separate_hea
// Erase all mappings in range.
auto it = m_mappings.find(key);
while (it != m_mappings.end() && it->vaddr < virtual_offset + size) {
// Get pointer to item.
SeparateHeapMap* const item = std::addressof(*it);
// Get underlying item.
auto* const item = std::addressof(*it);
// If resident, erase from resident map.
if (item->is_resident) {
// Unlink from resident tree.
ASSERT(--m_resident_map_count >= 0);
m_resident_mappings.erase(m_resident_mappings.iterator_to(*item));
// Decrease reference count.
const auto count_it = m_resident_map_counts.find(item->map_id);
this->RemoveReferenceLocked(count_it, 1);
}
// Unlink from mapping tree and advance.
// Erase from map.
ASSERT(--m_map_count >= 0);
it = m_mappings.erase(it);
// Free the item.
@@ -94,8 +108,8 @@ void HeapTracker::Protect(size_t virtual_offset, size_t size, MemoryPermission p
this->SplitHeapMap(virtual_offset, size);
// Declare tracking variables.
const VAddr end = virtual_offset + size;
VAddr cur = virtual_offset;
VAddr end = virtual_offset + size;
while (cur < end) {
VAddr next = cur;
@@ -109,7 +123,7 @@ void HeapTracker::Protect(size_t virtual_offset, size_t size, MemoryPermission p
};
// Try to get the next mapping corresponding to this address.
const auto it = m_mappings.nfind_key(key);
const auto it = m_mappings.nfind(key);
if (it == m_mappings.end()) {
// There are no separate heap mappings remaining.
@@ -152,106 +166,65 @@ bool HeapTracker::DeferredMapSeparateHeap(u8* fault_address) {
}
bool HeapTracker::DeferredMapSeparateHeap(size_t virtual_offset) {
std::scoped_lock lk{m_lock};
bool rebuild_required = false;
while (this->IsEvictRequiredLocked()) {
// Unlock before we rebuild to ensure proper lock ordering.
m_lock.unlock();
{
std::scoped_lock lk{m_lock};
// Evict four maps.
for (size_t i = 0; i < 4; /* ... */) {
i += this->EvictSingleSeparateHeapMap();
// 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;
}
// Lock again.
m_lock.lock();
// 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);
}
// Check to ensure this was a non-resident separate heap mapping.
const auto it = this->GetNearestHeapMapLocked(virtual_offset);
if (it == m_mappings.end()) {
// Not in any separate heap.
return false;
}
if (it->is_resident) {
// Already mapped and shouldn't be considered again.
return false;
if (rebuild_required) {
// A rebuild was required, so perform it now.
this->RebuildSeparateHeapAddressSpace();
}
// Map the area.
m_buffer.Map(it->vaddr, it->paddr, it->size, it->perm, false);
// This map is now resident.
this->AddReferenceLocked(it->map_id, 1);
it->is_resident = true;
it->tick = m_tick++;
// Insert into resident maps.
m_resident_mappings.insert(*it);
// We succeeded.
return true;
}
bool HeapTracker::EvictSingleSeparateHeapMap() {
void HeapTracker::RebuildSeparateHeapAddressSpace() {
std::scoped_lock lk{m_rebuild_lock, m_lock};
ASSERT(!m_resident_mappings.empty());
// Select the item with the lowest tick to evict.
auto* const item = std::addressof(*m_resident_mappings.begin());
auto it = m_mappings.iterator_to(*item);
// 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();
// Track the map ID.
const size_t map_id = it->map_id;
// Walk backwards until we find the first entry.
while (it != m_mappings.begin()) {
// If the previous element does not have the same map ID, stop.
const auto prev = std::prev(it);
if (prev->map_id != map_id) {
break;
}
// Continue.
it = prev;
}
// Track the begin and end address.
const VAddr begin_vaddr = it->vaddr;
VAddr end_vaddr = begin_vaddr;
// Get the count iterator.
const auto count_it = m_resident_map_counts.find(map_id);
// Declare whether we have erased an underlying mapping.
bool was_erased = false;
// Unmark and merge everything in range.
while (it != m_mappings.end() && it->map_id == map_id) {
if (it->is_resident) {
// Remove from resident tree.
m_resident_mappings.erase(m_resident_mappings.iterator_to(*it));
it->is_resident = false;
// Remove reference count.
was_erased |= this->RemoveReferenceLocked(count_it, 1);
}
// Update the end address.
end_vaddr = it->vaddr + it->size;
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.
it = this->MergeHeapMapForEvictLocked(it);
ASSERT(--m_resident_map_count >= 0);
it = m_resident_mappings.erase(it);
}
// Finally, unmap.
ASSERT(end_vaddr >= begin_vaddr);
m_buffer.Unmap(begin_vaddr, end_vaddr - begin_vaddr, false);
// Return whether we actually removed a mapping.
// This will be true if there were no holes, which is likely.
return was_erased;
}
void HeapTracker::SplitHeapMap(VAddr offset, size_t size) {
@@ -268,11 +241,9 @@ void HeapTracker::SplitHeapMapLocked(VAddr offset) {
return;
}
// Get the underlying item as the left.
// Cache the original values.
auto* const left = std::addressof(*it);
// Cache the original size values.
const size_t size = left->size;
const size_t orig_size = left->size;
// Adjust the left map.
const size_t left_size = offset - left->vaddr;
@@ -282,74 +253,21 @@ void HeapTracker::SplitHeapMapLocked(VAddr offset) {
auto* const right = new SeparateHeapMap{
.vaddr = left->vaddr + left_size,
.paddr = left->paddr + left_size,
.size = size - left_size,
.map_id = left->map_id,
.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 the original map was not resident, we are done.
if (!left->is_resident) {
return;
// If resident, also insert into resident map.
if (right->is_resident) {
m_resident_map_count++;
m_resident_mappings.insert(*right);
}
// Update reference count.
this->AddReferenceLocked(left->map_id, 1);
// Insert right into resident map.
m_resident_mappings.insert(*right);
}
HeapTracker::AddrTree::iterator HeapTracker::MergeHeapMapForEvictLocked(AddrTree::iterator it) {
if (it == m_mappings.end()) {
// Not contained.
return it;
}
if (it == m_mappings.begin()) {
// Nothing to merge with.
return std::next(it);
}
// Get the left and right items.
auto* const right = std::addressof(*it);
auto* const left = std::addressof(*std::prev(it));
if (left->vaddr + left->size != right->vaddr) {
// Virtual range not contiguous, cannot merge.
return std::next(it);
}
if (left->paddr + left->size != right->paddr) {
// Physical range not contiguous, cannot merge.
return std::next(it);
}
if (left->perm != right->perm) {
// Permissions mismatch, cannot merge.
return std::next(it);
}
if (left->map_id != right->map_id) {
// Map ID mismatch, cannot merge.
return std::next(it);
}
// Merge size to the left.
left->size += right->size;
// Erase the right element.
const auto next_it = m_mappings.erase(it);
// Free the right element.
delete right;
// Return the iterator to the next position.
return next_it;
}
HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) {
@@ -360,26 +278,4 @@ HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offse
return m_mappings.find(key);
}
void HeapTracker::AddReferenceLocked(size_t map_id, size_t inc) {
m_resident_map_counts[map_id]++;
}
bool HeapTracker::RemoveReferenceLocked(MapCountTree::iterator it, size_t dec) {
ASSERT(it != m_resident_map_counts.end());
const auto new_value = it->second -= dec;
ASSERT(new_value >= 0);
if (new_value <= 0) {
m_resident_map_counts.erase(it);
return true;
}
return false;
}
bool HeapTracker::IsEvictRequiredLocked() {
return m_resident_map_counts.size() > MaxResidentMapCount;
}
} // namespace Common

View File

@@ -3,8 +3,9 @@
#pragma once
#include <map>
#include <atomic>
#include <mutex>
#include <set>
#include <shared_mutex>
#include "common/host_memory.h"
@@ -18,7 +19,6 @@ struct SeparateHeapMap {
VAddr vaddr{};
PAddr paddr{};
size_t size{};
size_t map_id{};
size_t tick{};
MemoryPermission perm{};
bool is_resident{};
@@ -64,14 +64,6 @@ public:
bool DeferredMapSeparateHeap(u8* fault_address);
bool DeferredMapSeparateHeap(size_t virtual_offset);
private:
Common::HostMemory& m_buffer;
std::shared_mutex m_rebuild_lock{};
std::mutex m_lock{};
size_t m_next_map_id{};
size_t m_tick{};
private:
using AddrTreeTraits =
Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&SeparateHeapMap::addr_node>;
@@ -80,9 +72,7 @@ private:
using TickTreeTraits =
Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&SeparateHeapMap::tick_node>;
using TickTree = TickTreeTraits::TreeType<SeparateHeapMapTickComparator>;
using MapCountTree = std::map<size_t, s64>;
MapCountTree m_resident_map_counts{};
AddrTree m_mappings{};
TickTree m_resident_mappings{};
@@ -90,14 +80,19 @@ private:
void SplitHeapMap(VAddr offset, size_t size);
void SplitHeapMapLocked(VAddr offset);
AddrTree::iterator MergeHeapMapForEvictLocked(AddrTree::iterator cur);
AddrTree::iterator GetNearestHeapMapLocked(VAddr offset);
bool EvictSingleSeparateHeapMap();
void RebuildSeparateHeapAddressSpace();
void AddReferenceLocked(size_t map_id, size_t inc);
bool RemoveReferenceLocked(MapCountTree::iterator map_id, size_t dec);
bool IsEvictRequiredLocked();
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

View File

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

View File

@@ -14,6 +14,9 @@ void Initialize();
void Start();
/// Explicitly stops the logger thread and flushes the buffers
void Stop();
void DisableLoggingInTests();
/**

View File

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

View File

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

View File

@@ -18,6 +18,7 @@ enum class Category : u32 {
Cpu,
CpuDebug,
CpuUnsafe,
Overlay,
Renderer,
RendererAdvanced,
RendererDebug,

View File

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

View File

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

View File

@@ -22,17 +22,25 @@ namespace Core::Timing {
/// A callback that may be scheduled for a particular core timing event.
using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>(
std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>;
s64 time, std::chrono::nanoseconds ns_late)>;
/// Contains the characteristics of a particular event.
struct EventType {
explicit EventType(TimedCallback&& callback_, std::string&& name_)
: callback{std::move(callback_)}, name{std::move(name_)} {}
: callback{std::move(callback_)}, name{std::move(name_)}, sequence_number{0} {}
/// The event's callback function.
TimedCallback callback;
/// A pointer to the name of the event.
const std::string name;
/// A monotonic sequence number, incremented when this event is
/// changed externally.
size_t sequence_number;
};
enum class UnscheduleEventType {
Wait,
NoWait,
};
/**
@@ -89,23 +97,17 @@ public:
/// Schedules an event in core timing
void ScheduleEvent(std::chrono::nanoseconds ns_into_future,
const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data = 0,
bool absolute_time = false);
const std::shared_ptr<EventType>& event_type, bool absolute_time = false);
/// Schedules an event which will automatically re-schedule itself with the given time, until
/// unscheduled
void ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
std::chrono::nanoseconds resched_time,
const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data = 0, bool absolute_time = false);
bool absolute_time = false);
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data,
bool wait = true);
void UnscheduleEventWithoutWait(const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data) {
UnscheduleEvent(event_type, user_data, false);
}
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
UnscheduleEventType type = UnscheduleEventType::Wait);
void AddTicks(u64 ticks_to_add);
@@ -158,7 +160,6 @@ private:
heap_t event_queue;
u64 event_fifo_id = 0;
std::shared_ptr<EventType> ev_lost;
Common::Event event{};
Common::Event pause_event{};
mutable std::mutex basic_lock;

View File

@@ -73,6 +73,9 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
return nullptr;
auto in_data = in->ReadAllBytes();
if (in_data.size() == 0) {
return nullptr;
}
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
u64 offset = 5; // After header
@@ -88,6 +91,10 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
else
real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
if (real_offset > in_data.size()) {
return nullptr;
}
u16 data_size{};
if (ips->ReadObject(&data_size, offset) != sizeof(u16))
return nullptr;

View File

@@ -8,19 +8,22 @@
namespace Kernel {
void KAutoObjectWithListContainer::Register(KAutoObjectWithList* obj) {
KScopedLightLock lk(m_lock);
// KScopedInterruptDisable di;
KScopedSpinLock lk(m_lock);
m_object_list.insert_unique(*obj);
}
void KAutoObjectWithListContainer::Unregister(KAutoObjectWithList* obj) {
KScopedLightLock lk(m_lock);
// KScopedInterruptDisable di;
KScopedSpinLock lk(m_lock);
m_object_list.erase(*obj);
}
size_t KAutoObjectWithListContainer::GetOwnedCount(KProcess* owner) {
KScopedLightLock lk(m_lock);
// KScopedInterruptDisable di;
KScopedSpinLock lk(m_lock);
return std::count_if(m_object_list.begin(), m_object_list.end(),
[&](const auto& obj) { return obj.GetOwner() == owner; });

View File

@@ -7,7 +7,7 @@
#include "common/common_funcs.h"
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/k_spin_lock.h"
namespace Kernel {
@@ -21,32 +21,7 @@ public:
using ListType = boost::intrusive::rbtree<KAutoObjectWithList>;
class ListAccessor : public KScopedLightLock {
public:
explicit ListAccessor(KAutoObjectWithListContainer* container)
: KScopedLightLock(container->m_lock), m_list(container->m_object_list) {}
explicit ListAccessor(KAutoObjectWithListContainer& container)
: KScopedLightLock(container.m_lock), m_list(container.m_object_list) {}
typename ListType::iterator begin() const {
return m_list.begin();
}
typename ListType::iterator end() const {
return m_list.end();
}
typename ListType::iterator find(typename ListType::const_reference ref) const {
return m_list.find(ref);
}
private:
ListType& m_list;
};
friend class ListAccessor;
KAutoObjectWithListContainer(KernelCore& kernel) : m_lock(kernel), m_object_list() {}
KAutoObjectWithListContainer(KernelCore& kernel) : m_lock(), m_object_list() {}
void Initialize() {}
void Finalize() {}
@@ -56,7 +31,7 @@ public:
size_t GetOwnedCount(KProcess* owner);
private:
KLightLock m_lock;
KSpinLock m_lock;
ListType m_object_list;
};

View File

@@ -185,6 +185,10 @@ Result KCapabilities::ProcessMapRegionCapability(const u32 cap, F f) {
case RegionType::NoMapping:
break;
case RegionType::KernelTraceBuffer:
if constexpr (!IsKTraceEnabled) {
break;
}
[[fallthrough]];
case RegionType::OnMemoryBootImage:
case RegionType::DTB:
R_TRY(f(MemoryRegions[static_cast<u32>(type)], perm));
@@ -330,8 +334,6 @@ Result KCapabilities::SetCapabilities(std::span<const u32> caps, KProcessPageTab
// Map the range.
R_TRY(this->MapRange_(cap, size_cap, page_table));
} else if (GetCapabilityType(cap) == CapabilityType::MapRegion && !IsKTraceEnabled) {
continue;
} else {
R_TRY(this->SetCapability(cap, set_flags, set_svc, page_table));
}

View File

@@ -30,7 +30,7 @@ public:
public:
explicit KHandleTable(KernelCore& kernel) : m_kernel(kernel) {}
Result Initialize(KProcess* owner, s32 size) {
Result Initialize(s32 size) {
// Check that the table size is valid.
R_UNLESS(size <= static_cast<s32>(MaxTableSize), ResultOutOfMemory);
@@ -44,7 +44,6 @@ public:
m_next_linear_id = MinLinearId;
m_count = 0;
m_free_head_index = -1;
m_owner = owner;
// Free all entries.
for (s32 i = 0; i < static_cast<s32>(m_table_size); ++i) {
@@ -91,8 +90,7 @@ public:
// Handle pseudo-handles.
if constexpr (std::derived_from<KProcess, T>) {
if (handle == Svc::PseudoHandle::CurrentProcess) {
// TODO: this should be the current process
auto* const cur_process = m_owner;
auto* const cur_process = GetCurrentProcessPointer(m_kernel);
ASSERT(cur_process != nullptr);
return cur_process;
}
@@ -302,7 +300,6 @@ private:
private:
KernelCore& m_kernel;
KProcess* m_owner{};
std::array<EntryInfo, MaxTableSize> m_entry_infos{};
std::array<KAutoObject*, MaxTableSize> m_objects{};
mutable KSpinLock m_lock;

View File

@@ -10,15 +10,15 @@ namespace Kernel {
void KHardwareTimer::Initialize() {
// Create the timing callback to register with CoreTiming.
m_event_type = Core::Timing::CreateEvent(
"KHardwareTimer::Callback", [](std::uintptr_t timer_handle, s64, std::chrono::nanoseconds) {
reinterpret_cast<KHardwareTimer*>(timer_handle)->DoTask();
return std::nullopt;
});
m_event_type = Core::Timing::CreateEvent("KHardwareTimer::Callback",
[this](s64, std::chrono::nanoseconds) {
this->DoTask();
return std::nullopt;
});
}
void KHardwareTimer::Finalize() {
m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type, reinterpret_cast<uintptr_t>(this));
m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type);
m_wakeup_time = std::numeric_limits<s64>::max();
m_event_type.reset();
}
@@ -57,13 +57,12 @@ void KHardwareTimer::EnableInterrupt(s64 wakeup_time) {
m_wakeup_time = wakeup_time;
m_kernel.System().CoreTiming().ScheduleEvent(std::chrono::nanoseconds{m_wakeup_time},
m_event_type, reinterpret_cast<uintptr_t>(this),
true);
m_event_type, true);
}
void KHardwareTimer::DisableInterrupt() {
m_kernel.System().CoreTiming().UnscheduleEventWithoutWait(m_event_type,
reinterpret_cast<uintptr_t>(this));
m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type,
Core::Timing::UnscheduleEventType::NoWait);
m_wakeup_time = std::numeric_limits<s64>::max();
}

View File

@@ -552,7 +552,7 @@ private:
Result InitializeHandleTable(s32 size) {
// Try to initialize the handle table.
R_TRY(m_handle_table.Initialize(this, size));
R_TRY(m_handle_table.Initialize(size));
// We succeeded, so note that we did.
m_is_handle_table_initialized = true;

View File

@@ -1147,8 +1147,7 @@ Result KServerSession::ReceiveRequest(uintptr_t server_message, uintptr_t server
*out_context =
std::make_shared<Service::HLERequestContext>(m_kernel, memory, this, client_thread);
(*out_context)->SetSessionRequestManager(manager);
(*out_context)
->PopulateFromIncomingCommandBuffer(*client_thread->GetOwnerProcess(), cmd_buf);
(*out_context)->PopulateFromIncomingCommandBuffer(cmd_buf);
// We succeeded.
R_SUCCEED();
} else {

View File

@@ -5,6 +5,7 @@
#include <optional>
#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/k_page_group.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/kernel/svc_types.h"

View File

@@ -238,7 +238,7 @@ struct KernelCore::Impl {
void InitializePreemption(KernelCore& kernel) {
preemption_event = Core::Timing::CreateEvent(
"PreemptionCallback",
[this, &kernel](std::uintptr_t, s64 time,
[this, &kernel](s64 time,
std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
{
KScopedSchedulerLock lock(kernel);

View File

@@ -1513,8 +1513,7 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(HLERequestContext& ctx)
return;
}
auto transfer_mem =
system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(handle);
auto transfer_mem = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(handle);
if (transfer_mem.IsNull()) {
LOG_ERROR(Service_AM, "transfer_mem is a nullptr for handle={:08X}", handle);
@@ -1524,8 +1523,7 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(HLERequestContext& ctx)
}
std::vector<u8> memory(transfer_mem->GetSize());
system.ApplicationMemory().ReadBlock(transfer_mem->GetSourceAddress(), memory.data(),
memory.size());
ctx.GetMemory().ReadBlock(transfer_mem->GetSourceAddress(), memory.data(), memory.size());
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
@@ -1547,8 +1545,7 @@ void ILibraryAppletCreator::CreateHandleStorage(HLERequestContext& ctx) {
return;
}
auto transfer_mem =
system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(handle);
auto transfer_mem = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(handle);
if (transfer_mem.IsNull()) {
LOG_ERROR(Service_AM, "transfer_mem is a nullptr for handle={:08X}", handle);
@@ -1558,8 +1555,7 @@ void ILibraryAppletCreator::CreateHandleStorage(HLERequestContext& ctx) {
}
std::vector<u8> memory(transfer_mem->GetSize());
system.ApplicationMemory().ReadBlock(transfer_mem->GetSourceAddress(), memory.data(),
memory.size());
ctx.GetMemory().ReadBlock(transfer_mem->GetSourceAddress(), memory.data(), memory.size());
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);

View File

@@ -454,10 +454,8 @@ void AudRenU::OpenAudioRenderer(HLERequestContext& ctx) {
return;
}
const auto& handle_table{system.ApplicationProcess()->GetHandleTable()};
auto process{handle_table.GetObject<Kernel::KProcess>(process_handle)};
auto transfer_memory{
process->GetHandleTable().GetObject<Kernel::KTransferMemory>(transfer_memory_handle)};
auto process{ctx.GetObjectFromHandle<Kernel::KProcess>(process_handle)};
auto transfer_memory{ctx.GetObjectFromHandle<Kernel::KTransferMemory>(transfer_memory_handle)};
const auto session_id{impl->GetSessionId()};
if (session_id == -1) {

View File

@@ -278,9 +278,7 @@ void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) {
auto params = rp.PopRaw<OpusParameters>();
auto transfer_memory_size{rp.Pop<u32>()};
auto transfer_memory_handle{ctx.GetCopyHandle(0)};
auto transfer_memory{
system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
transfer_memory_handle)};
auto transfer_memory{ctx.GetObjectFromHandle<Kernel::KTransferMemory>(transfer_memory_handle)};
LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}",
params.sample_rate, params.channel_count, transfer_memory_size);
@@ -323,9 +321,7 @@ void HwOpus::OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx) {
auto transfer_memory_size{rp.Pop<u32>()};
auto transfer_memory_handle{ctx.GetCopyHandle(0)};
auto transfer_memory{
system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
transfer_memory_handle)};
auto transfer_memory{ctx.GetObjectFromHandle<Kernel::KTransferMemory>(transfer_memory_handle)};
LOG_DEBUG(Service_Audio,
"sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
@@ -374,9 +370,7 @@ void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) {
auto params = rp.PopRaw<OpusParametersEx>();
auto transfer_memory_size{rp.Pop<u32>()};
auto transfer_memory_handle{ctx.GetCopyHandle(0)};
auto transfer_memory{
system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
transfer_memory_handle)};
auto transfer_memory{ctx.GetObjectFromHandle<Kernel::KTransferMemory>(transfer_memory_handle)};
LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}",
params.sample_rate, params.channel_count, transfer_memory_size);
@@ -414,9 +408,7 @@ void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) {
auto transfer_memory_size{rp.Pop<u32>()};
auto transfer_memory_handle{ctx.GetCopyHandle(0)};
auto transfer_memory{
system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
transfer_memory_handle)};
auto transfer_memory{ctx.GetObjectFromHandle<Kernel::KTransferMemory>(transfer_memory_handle)};
LOG_DEBUG(Service_Audio,
"sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "

View File

@@ -1850,8 +1850,7 @@ void IHidServer::InitializeSevenSixAxisSensor(HLERequestContext& ctx) {
ASSERT_MSG(t_mem_1_size == 0x1000, "t_mem_1_size is not 0x1000 bytes");
ASSERT_MSG(t_mem_2_size == 0x7F000, "t_mem_2_size is not 0x7F000 bytes");
auto t_mem_1 = system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
t_mem_1_handle);
auto t_mem_1 = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(t_mem_1_handle);
if (t_mem_1.IsNull()) {
LOG_ERROR(Service_HID, "t_mem_1 is a nullptr for handle=0x{:08X}", t_mem_1_handle);
@@ -1860,8 +1859,7 @@ void IHidServer::InitializeSevenSixAxisSensor(HLERequestContext& ctx) {
return;
}
auto t_mem_2 = system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
t_mem_2_handle);
auto t_mem_2 = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(t_mem_2_handle);
if (t_mem_2.IsNull()) {
LOG_ERROR(Service_HID, "t_mem_2 is a nullptr for handle=0x{:08X}", t_mem_2_handle);
@@ -2142,8 +2140,7 @@ void IHidServer::WritePalmaWaveEntry(HLERequestContext& ctx) {
ASSERT_MSG(t_mem_size == 0x3000, "t_mem_size is not 0x3000 bytes");
auto t_mem = system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
t_mem_handle);
auto t_mem = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(t_mem_handle);
if (t_mem.IsNull()) {
LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);

View File

@@ -49,10 +49,10 @@ HidBus::HidBus(Core::System& system_)
// Register update callbacks
hidbus_update_event = Core::Timing::CreateEvent(
"Hidbus::UpdateCallback",
[this](std::uintptr_t user_data, s64 time,
[this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
UpdateHidbus(user_data, ns_late);
UpdateHidbus(ns_late);
return std::nullopt;
});
@@ -61,10 +61,10 @@ HidBus::HidBus(Core::System& system_)
}
HidBus::~HidBus() {
system.CoreTiming().UnscheduleEvent(hidbus_update_event, 0);
system.CoreTiming().UnscheduleEvent(hidbus_update_event);
}
void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
void HidBus::UpdateHidbus(std::chrono::nanoseconds ns_late) {
if (is_hidbus_enabled) {
for (std::size_t i = 0; i < devices.size(); ++i) {
if (!devices[i].is_device_initializated) {
@@ -448,8 +448,7 @@ void HidBus::EnableJoyPollingReceiveMode(HLERequestContext& ctx) {
ASSERT_MSG(t_mem_size == 0x1000, "t_mem_size is not 0x1000 bytes");
auto t_mem = system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
t_mem_handle);
auto t_mem = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(t_mem_handle);
if (t_mem.IsNull()) {
LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);

View File

@@ -108,7 +108,7 @@ private:
void DisableJoyPollingReceiveMode(HLERequestContext& ctx);
void SetStatusManagerType(HLERequestContext& ctx);
void UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
void UpdateHidbus(std::chrono::nanoseconds ns_late);
std::optional<std::size_t> GetDeviceIndexFromHandle(BusHandle handle) const;
template <typename T>

View File

@@ -197,8 +197,7 @@ void IRS::RunImageTransferProcessor(HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
const auto t_mem_handle{ctx.GetCopyHandle(0)};
auto t_mem = system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
t_mem_handle);
auto t_mem = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(t_mem_handle);
if (t_mem.IsNull()) {
LOG_ERROR(Service_IRS, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
@@ -444,8 +443,7 @@ void IRS::RunImageTransferExProcessor(HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
const auto t_mem_handle{ctx.GetCopyHandle(0)};
auto t_mem = system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
t_mem_handle);
auto t_mem = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(t_mem_handle);
LOG_INFO(Service_IRS,
"called, npad_type={}, npad_id={}, transfer_memory_size={}, "

View File

@@ -227,8 +227,7 @@ void ResourceManager::EnableTouchScreen(u64 aruid, bool is_enabled) {
applet_resource->EnableTouchScreen(aruid, is_enabled);
}
void ResourceManager::UpdateControllers(std::uintptr_t user_data,
std::chrono::nanoseconds ns_late) {
void ResourceManager::UpdateControllers(std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
debug_pad->OnUpdate(core_timing);
digitizer->OnUpdate(core_timing);
@@ -241,20 +240,19 @@ void ResourceManager::UpdateControllers(std::uintptr_t user_data,
capture_button->OnUpdate(core_timing);
}
void ResourceManager::UpdateNpad(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
void ResourceManager::UpdateNpad(std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
npad->OnUpdate(core_timing);
}
void ResourceManager::UpdateMouseKeyboard(std::uintptr_t user_data,
std::chrono::nanoseconds ns_late) {
void ResourceManager::UpdateMouseKeyboard(std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
mouse->OnUpdate(core_timing);
debug_mouse->OnUpdate(core_timing);
keyboard->OnUpdate(core_timing);
}
void ResourceManager::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
void ResourceManager::UpdateMotion(std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
six_axis->OnUpdate(core_timing);
seven_six_axis->OnUpdate(core_timing);
@@ -273,34 +271,34 @@ IAppletResource::IAppletResource(Core::System& system_, std::shared_ptr<Resource
// Register update callbacks
npad_update_event = Core::Timing::CreateEvent(
"HID::UpdatePadCallback",
[this, resource](std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)
-> std::optional<std::chrono::nanoseconds> {
[this, resource](
s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
resource->UpdateNpad(user_data, ns_late);
resource->UpdateNpad(ns_late);
return std::nullopt;
});
default_update_event = Core::Timing::CreateEvent(
"HID::UpdateDefaultCallback",
[this, resource](std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)
-> std::optional<std::chrono::nanoseconds> {
[this, resource](
s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
resource->UpdateControllers(user_data, ns_late);
resource->UpdateControllers(ns_late);
return std::nullopt;
});
mouse_keyboard_update_event = Core::Timing::CreateEvent(
"HID::UpdateMouseKeyboardCallback",
[this, resource](std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)
-> std::optional<std::chrono::nanoseconds> {
[this, resource](
s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
resource->UpdateMouseKeyboard(user_data, ns_late);
resource->UpdateMouseKeyboard(ns_late);
return std::nullopt;
});
motion_update_event = Core::Timing::CreateEvent(
"HID::UpdateMotionCallback",
[this, resource](std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)
-> std::optional<std::chrono::nanoseconds> {
[this, resource](
s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
resource->UpdateMotion(user_data, ns_late);
resource->UpdateMotion(ns_late);
return std::nullopt;
});
@@ -314,10 +312,10 @@ IAppletResource::IAppletResource(Core::System& system_, std::shared_ptr<Resource
}
IAppletResource::~IAppletResource() {
system.CoreTiming().UnscheduleEvent(npad_update_event, 0);
system.CoreTiming().UnscheduleEvent(default_update_event, 0);
system.CoreTiming().UnscheduleEvent(mouse_keyboard_update_event, 0);
system.CoreTiming().UnscheduleEvent(motion_update_event, 0);
system.CoreTiming().UnscheduleEvent(npad_update_event);
system.CoreTiming().UnscheduleEvent(default_update_event);
system.CoreTiming().UnscheduleEvent(mouse_keyboard_update_event);
system.CoreTiming().UnscheduleEvent(motion_update_event);
resource_manager->FreeAppletResourceId(aruid);
}

View File

@@ -81,10 +81,10 @@ public:
void EnablePadInput(u64 aruid, bool is_enabled);
void EnableTouchScreen(u64 aruid, bool is_enabled);
void UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
void UpdateNpad(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
void UpdateMouseKeyboard(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
void UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
void UpdateControllers(std::chrono::nanoseconds ns_late);
void UpdateNpad(std::chrono::nanoseconds ns_late);
void UpdateMouseKeyboard(std::chrono::nanoseconds ns_late);
void UpdateMotion(std::chrono::nanoseconds ns_late);
private:
Result CreateAppletResourceImpl(u64 aruid);

View File

@@ -146,10 +146,7 @@ HLERequestContext::HLERequestContext(Kernel::KernelCore& kernel_, Core::Memory::
HLERequestContext::~HLERequestContext() = default;
void HLERequestContext::ParseCommandBuffer(Kernel::KProcess& process, u32_le* src_cmdbuf,
bool incoming) {
client_handle_table = &process.GetHandleTable();
void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
IPC::RequestParser rp(src_cmdbuf);
command_header = rp.PopRaw<IPC::CommandHeader>();
@@ -162,7 +159,7 @@ void HLERequestContext::ParseCommandBuffer(Kernel::KProcess& process, u32_le* sr
if (command_header->enable_handle_descriptor) {
handle_descriptor_header = rp.PopRaw<IPC::HandleDescriptorHeader>();
if (handle_descriptor_header->send_current_pid) {
pid = process.GetProcessId();
pid = thread->GetOwnerProcess()->GetProcessId();
rp.Skip(2, false);
}
if (incoming) {
@@ -270,9 +267,10 @@ void HLERequestContext::ParseCommandBuffer(Kernel::KProcess& process, u32_le* sr
rp.Skip(1, false); // The command is actually an u64, but we don't use the high part.
}
Result HLERequestContext::PopulateFromIncomingCommandBuffer(Kernel::KProcess& process,
u32_le* src_cmdbuf) {
ParseCommandBuffer(process, src_cmdbuf, true);
Result HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf) {
client_handle_table = &thread->GetOwnerProcess()->GetHandleTable();
ParseCommandBuffer(src_cmdbuf, true);
if (command_header->IsCloseCommand()) {
// Close does not populate the rest of the IPC header
@@ -284,9 +282,9 @@ Result HLERequestContext::PopulateFromIncomingCommandBuffer(Kernel::KProcess& pr
return ResultSuccess;
}
Result HLERequestContext::WriteToOutgoingCommandBuffer(Kernel::KThread& requesting_thread) {
Result HLERequestContext::WriteToOutgoingCommandBuffer() {
auto current_offset = handles_offset;
auto& owner_process = *requesting_thread.GetOwnerProcess();
auto& owner_process = *thread->GetOwnerProcess();
auto& handle_table = owner_process.GetHandleTable();
for (auto& object : outgoing_copy_objects) {
@@ -319,7 +317,7 @@ Result HLERequestContext::WriteToOutgoingCommandBuffer(Kernel::KThread& requesti
}
// Copy the translated command buffer back into the thread's command buffer area.
memory.WriteBlock(requesting_thread.GetTlsAddress(), cmd_buf.data(), write_size * sizeof(u32));
memory.WriteBlock(thread->GetTlsAddress(), cmd_buf.data(), write_size * sizeof(u32));
return ResultSuccess;
}

View File

@@ -17,6 +17,7 @@
#include "common/concepts.h"
#include "common/swap.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/svc_common.h"
union Result;
@@ -196,10 +197,10 @@ public:
}
/// Populates this context with data from the requesting process/thread.
Result PopulateFromIncomingCommandBuffer(Kernel::KProcess& process, u32_le* src_cmdbuf);
Result PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf);
/// Writes data from this context back to the requesting process/thread.
Result WriteToOutgoingCommandBuffer(Kernel::KThread& requesting_thread);
Result WriteToOutgoingCommandBuffer();
[[nodiscard]] u32_le GetHipcCommand() const {
return command;
@@ -359,8 +360,17 @@ public:
return *thread;
}
Kernel::KHandleTable& GetClientHandleTable() {
return *client_handle_table;
[[nodiscard]] Core::Memory::Memory& GetMemory() const {
return memory;
}
template <typename T>
Kernel::KScopedAutoObject<T> GetObjectFromHandle(u32 handle) {
auto obj = client_handle_table->GetObjectForIpc(handle, thread);
if (obj.IsNotNull()) {
return obj->DynamicCast<T*>();
}
return nullptr;
}
[[nodiscard]] std::shared_ptr<SessionRequestManager> GetManager() const {
@@ -378,7 +388,7 @@ public:
private:
friend class IPC::ResponseBuilder;
void ParseCommandBuffer(Kernel::KProcess& process, u32_le* src_cmdbuf, bool incoming);
void ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming);
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
Kernel::KServerSession* server_session{};

View File

@@ -4,11 +4,11 @@
#include "core/arm/debug.h"
#include "core/arm/symbols.h"
#include "core/core.h"
#include "core/hle/kernel/k_code_memory.h"
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/result.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/jit/jit.h"
#include "core/hle/service/jit/jit_code_memory.h"
#include "core/hle/service/jit/jit_context.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
@@ -23,9 +23,11 @@ struct CodeRange {
class IJitEnvironment final : public ServiceFramework<IJitEnvironment> {
public:
explicit IJitEnvironment(Core::System& system_, Kernel::KProcess& process_, CodeRange user_rx,
CodeRange user_ro)
: ServiceFramework{system_, "IJitEnvironment"}, process{&process_},
explicit IJitEnvironment(Core::System& system_,
Kernel::KScopedAutoObject<Kernel::KProcess>&& process_,
CodeMemory&& user_rx_, CodeMemory&& user_ro_)
: ServiceFramework{system_, "IJitEnvironment"}, process{std::move(process_)},
user_rx{std::move(user_rx_)}, user_ro{std::move(user_ro_)},
context{system_.ApplicationMemory()} {
// clang-format off
static const FunctionInfo functions[] = {
@@ -39,10 +41,13 @@ public:
RegisterHandlers(functions);
// Identity map user code range into sysmodule context
configuration.user_ro_memory = user_ro;
configuration.user_rx_memory = user_rx;
configuration.sys_ro_memory = user_ro;
configuration.sys_rx_memory = user_rx;
configuration.user_rx_memory.size = user_rx.GetSize();
configuration.user_rx_memory.offset = user_rx.GetAddress();
configuration.user_ro_memory.size = user_ro.GetSize();
configuration.user_ro_memory.offset = user_ro.GetAddress();
configuration.sys_rx_memory = configuration.user_rx_memory;
configuration.sys_ro_memory = configuration.user_ro_memory;
}
void GenerateCode(HLERequestContext& ctx) {
@@ -188,7 +193,7 @@ public:
return;
}
auto tmem{process->GetHandleTable().GetObject<Kernel::KTransferMemory>(tmem_handle)};
auto tmem{ctx.GetObjectFromHandle<Kernel::KTransferMemory>(tmem_handle)};
if (tmem.IsNull()) {
LOG_ERROR(Service_JIT, "attempted to load plugin with invalid transfer memory handle");
IPC::ResponseBuilder rb{ctx, 2};
@@ -318,6 +323,8 @@ private:
}
Kernel::KScopedAutoObject<Kernel::KProcess> process;
CodeMemory user_rx;
CodeMemory user_ro;
GuestCallbacks callbacks;
JITConfiguration configuration;
JITContext context;
@@ -335,6 +342,7 @@ public:
RegisterHandlers(functions);
}
private:
void CreateJitEnvironment(HLERequestContext& ctx) {
LOG_DEBUG(Service_JIT, "called");
@@ -356,11 +364,7 @@ public:
return;
}
// Fetch using the handle table for the application process here,
// since we are not multiprocess yet.
const auto& handle_table{system.ApplicationProcess()->GetHandleTable()};
auto process{handle_table.GetObject<Kernel::KProcess>(process_handle)};
auto process{ctx.GetObjectFromHandle<Kernel::KProcess>(process_handle)};
if (process.IsNull()) {
LOG_ERROR(Service_JIT, "process is null for handle=0x{:08X}", process_handle);
IPC::ResponseBuilder rb{ctx, 2};
@@ -368,7 +372,7 @@ public:
return;
}
auto rx_mem{handle_table.GetObject<Kernel::KCodeMemory>(rx_mem_handle)};
auto rx_mem{ctx.GetObjectFromHandle<Kernel::KCodeMemory>(rx_mem_handle)};
if (rx_mem.IsNull()) {
LOG_ERROR(Service_JIT, "rx_mem is null for handle=0x{:08X}", rx_mem_handle);
IPC::ResponseBuilder rb{ctx, 2};
@@ -376,7 +380,7 @@ public:
return;
}
auto ro_mem{handle_table.GetObject<Kernel::KCodeMemory>(ro_mem_handle)};
auto ro_mem{ctx.GetObjectFromHandle<Kernel::KCodeMemory>(ro_mem_handle)};
if (ro_mem.IsNull()) {
LOG_ERROR(Service_JIT, "ro_mem is null for handle=0x{:08X}", ro_mem_handle);
IPC::ResponseBuilder rb{ctx, 2};
@@ -384,20 +388,35 @@ public:
return;
}
const CodeRange user_rx{
.offset = GetInteger(rx_mem->GetSourceAddress()),
.size = parameters.rx_size,
};
CodeMemory rx, ro;
Result res;
const CodeRange user_ro{
.offset = GetInteger(ro_mem->GetSourceAddress()),
.size = parameters.ro_size,
};
res = rx.Initialize(*process, *rx_mem, parameters.rx_size,
Kernel::Svc::MemoryPermission::ReadExecute, generate_random);
if (R_FAILED(res)) {
LOG_ERROR(Service_JIT, "rx_mem could not be mapped for handle=0x{:08X}", rx_mem_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(res);
return;
}
res = ro.Initialize(*process, *ro_mem, parameters.ro_size,
Kernel::Svc::MemoryPermission::Read, generate_random);
if (R_FAILED(res)) {
LOG_ERROR(Service_JIT, "ro_mem could not be mapped for handle=0x{:08X}", ro_mem_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(res);
return;
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IJitEnvironment>(system, *process, user_rx, user_ro);
rb.PushIpcInterface<IJitEnvironment>(system, std::move(process), std::move(rx),
std::move(ro));
}
private:
std::mt19937_64 generate_random{};
};
void LoopProcess(Core::System& system) {

View File

@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/jit/jit_code_memory.h"
namespace Service::JIT {
Result CodeMemory::Initialize(Kernel::KProcess& process, Kernel::KCodeMemory& code_memory,
size_t size, Kernel::Svc::MemoryPermission perm,
std::mt19937_64& generate_random) {
auto& page_table = process.GetPageTable();
const u64 alias_code_start =
GetInteger(page_table.GetAliasCodeRegionStart()) / Kernel::PageSize;
const u64 alias_code_size = page_table.GetAliasCodeRegionSize() / Kernel::PageSize;
// NOTE: This will retry indefinitely until mapping the code memory succeeds.
while (true) {
// Generate a new trial address.
const u64 mapped_address =
(alias_code_start + (generate_random() % alias_code_size)) * Kernel::PageSize;
// Try to map the address
R_TRY_CATCH(code_memory.MapToOwner(mapped_address, size, perm)) {
R_CATCH(Kernel::ResultInvalidMemoryRegion) {
// If we could not map here, retry.
continue;
}
}
R_END_TRY_CATCH;
// Set members.
m_code_memory = std::addressof(code_memory);
m_size = size;
m_address = mapped_address;
m_perm = perm;
// Open a new reference to the code memory.
m_code_memory->Open();
// We succeeded.
R_SUCCEED();
}
}
void CodeMemory::Finalize() {
if (m_code_memory) {
R_ASSERT(m_code_memory->UnmapFromOwner(m_address, m_size));
m_code_memory->Close();
}
m_code_memory = nullptr;
}
} // namespace Service::JIT

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <random>
#include "core/hle/kernel/k_code_memory.h"
namespace Service::JIT {
class CodeMemory {
public:
YUZU_NON_COPYABLE(CodeMemory);
explicit CodeMemory() = default;
CodeMemory(CodeMemory&& rhs) {
std::swap(m_code_memory, rhs.m_code_memory);
std::swap(m_size, rhs.m_size);
std::swap(m_address, rhs.m_address);
std::swap(m_perm, rhs.m_perm);
}
~CodeMemory() {
this->Finalize();
}
public:
Result Initialize(Kernel::KProcess& process, Kernel::KCodeMemory& code_memory, size_t size,
Kernel::Svc::MemoryPermission perm, std::mt19937_64& generate_random);
void Finalize();
size_t GetSize() const {
return m_size;
}
u64 GetAddress() const {
return m_address;
}
private:
Kernel::KCodeMemory* m_code_memory{};
size_t m_size{};
u64 m_address{};
Kernel::Svc::MemoryPermission m_perm{};
};
} // namespace Service::JIT

View File

@@ -67,7 +67,7 @@ Nvnflinger::Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_
// Schedule the screen composition events
multi_composition_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](std::uintptr_t, s64 time,
[this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
vsync_signal.Set();
return std::chrono::nanoseconds(GetNextTicks());
@@ -75,7 +75,7 @@ Nvnflinger::Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_
single_composition_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](std::uintptr_t, s64 time,
[this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto lock_guard = Lock();
Compose();
@@ -93,11 +93,11 @@ Nvnflinger::Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_
Nvnflinger::~Nvnflinger() {
if (system.IsMulticore()) {
system.CoreTiming().UnscheduleEvent(multi_composition_event, {});
system.CoreTiming().UnscheduleEvent(multi_composition_event);
vsync_thread.request_stop();
vsync_signal.Set();
} else {
system.CoreTiming().UnscheduleEvent(single_composition_event, {});
system.CoreTiming().UnscheduleEvent(single_composition_event);
}
ShutdownLayers();

View File

@@ -651,10 +651,9 @@ private:
void RegisterProcessHandle(HLERequestContext& ctx) {
LOG_DEBUG(Service_LDR, "(called)");
auto process_h = ctx.GetClientHandleTable().GetObject(ctx.GetCopyHandle(0));
auto process = ctx.GetObjectFromHandle<Kernel::KProcess>(ctx.GetCopyHandle(0));
auto client_pid = ctx.GetPID();
auto result = interface.RegisterProcessHandle(client_pid,
process_h->DynamicCast<Kernel::KProcess*>());
auto result = interface.RegisterProcessHandle(client_pid, process.GetPointerUnsafe());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
@@ -671,12 +670,11 @@ private:
IPC::RequestParser rp{ctx};
auto params = rp.PopRaw<InputParameters>();
auto process_h = ctx.GetClientHandleTable().GetObject(ctx.GetCopyHandle(0));
auto process = ctx.GetObjectFromHandle<Kernel::KProcess>(ctx.GetCopyHandle(0));
auto client_pid = ctx.GetPID();
auto result =
interface.RegisterProcessModuleInfo(client_pid, params.nrr_address, params.nrr_size,
process_h->DynamicCast<Kernel::KProcess*>());
auto result = interface.RegisterProcessModuleInfo(
client_pid, params.nrr_address, params.nrr_size, process.GetPointerUnsafe());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);

View File

@@ -203,7 +203,7 @@ Result ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& session,
// If emulation was shutdown, we are closing service threads, do not write the response back to
// memory that may be shutting down as well.
if (system.IsPoweredOn()) {
ctx.WriteToOutgoingCommandBuffer(ctx.GetThread());
ctx.WriteToOutgoingCommandBuffer();
}
return result;

View File

@@ -730,17 +730,6 @@ struct Memory::Impl {
GetInteger(vaddr), []() {}, []() {});
}
void FixPageProtection(u64 vaddr) {
vaddr = Common::AlignDown(vaddr, YUZU_PAGESIZE);
if (!AddressSpaceContains(*current_page_table, vaddr, 1)) [[unlikely]] {
return;
}
ProtectRegion(*current_page_table, vaddr, YUZU_PAGESIZE,
Common::MemoryPermission::ReadWrite);
}
/**
* Reads a particular data type out of memory at the given virtual address.
*
@@ -1094,18 +1083,13 @@ bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
rasterizer = true;
});
const bool mapping_exists = mapped && ptr != nullptr;
#ifdef __linux__
if (mapping_exists && !rasterizer) {
if (!impl->buffer->DeferredMapSeparateHeap(GetInteger(vaddr))) {
// GPU may have raced reprotecting this page, try to fix it.
impl->FixPageProtection(GetInteger(vaddr));
}
if (!rasterizer && mapped) {
impl->buffer->DeferredMapSeparateHeap(GetInteger(vaddr));
}
#endif
return mapping_exists;
return mapped && ptr != nullptr;
}
bool Memory::InvalidateSeparateHeap(void* fault_address) {

View File

@@ -686,7 +686,8 @@ public:
} else {
this->m_memory.WriteBlockUnsafe(this->m_addr, this->data(), this->size_bytes());
}
} else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
} else if constexpr ((FLAGS & GuestMemoryFlags::Safe) ||
(FLAGS & GuestMemoryFlags::Cached)) {
this->m_memory.InvalidateRegion(this->m_addr, this->size_bytes());
}
}

View File

@@ -190,15 +190,15 @@ CheatEngine::CheatEngine(System& system_, std::vector<CheatEntry> cheats_,
}
CheatEngine::~CheatEngine() {
core_timing.UnscheduleEvent(event, 0);
core_timing.UnscheduleEvent(event);
}
void CheatEngine::Initialize() {
event = Core::Timing::CreateEvent(
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
[this](std::uintptr_t user_data, s64 time,
[this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
FrameCallback(user_data, ns_late);
FrameCallback(ns_late);
return std::nullopt;
});
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, event);
@@ -239,7 +239,7 @@ void CheatEngine::Reload(std::vector<CheatEntry> reload_cheats) {
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
void CheatEngine::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late) {
void CheatEngine::FrameCallback(std::chrono::nanoseconds ns_late) {
if (is_pending_reload.exchange(false)) {
vm.LoadProgram(cheats);
}

View File

@@ -70,7 +70,7 @@ public:
void Reload(std::vector<CheatEntry> reload_cheats);
private:
void FrameCallback(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
void FrameCallback(std::chrono::nanoseconds ns_late);
DmntCheatVm vm;
CheatProcessMetadata metadata;

View File

@@ -51,18 +51,17 @@ void MemoryWriteWidth(Core::Memory::Memory& memory, u32 width, VAddr addr, u64 v
Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_)
: core_timing{core_timing_}, memory{memory_} {
event = Core::Timing::CreateEvent(
"MemoryFreezer::FrameCallback",
[this](std::uintptr_t user_data, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
FrameCallback(user_data, ns_late);
return std::nullopt;
});
event = Core::Timing::CreateEvent("MemoryFreezer::FrameCallback",
[this](s64 time, std::chrono::nanoseconds ns_late)
-> std::optional<std::chrono::nanoseconds> {
FrameCallback(ns_late);
return std::nullopt;
});
core_timing.ScheduleEvent(memory_freezer_ns, event);
}
Freezer::~Freezer() {
core_timing.UnscheduleEvent(event, 0);
core_timing.UnscheduleEvent(event);
}
void Freezer::SetActive(bool is_active) {
@@ -159,7 +158,7 @@ Freezer::Entries::const_iterator Freezer::FindEntry(VAddr address) const {
[address](const Entry& entry) { return entry.address == address; });
}
void Freezer::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late) {
void Freezer::FrameCallback(std::chrono::nanoseconds ns_late) {
if (!IsActive()) {
LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events.");
return;

View File

@@ -77,7 +77,7 @@ private:
Entries::iterator FindEntry(VAddr address);
Entries::const_iterator FindEntry(VAddr address) const;
void FrameCallback(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
void FrameCallback(std::chrono::nanoseconds ns_late);
void FillEntryReads();
std::atomic_bool active{false};

View File

@@ -403,59 +403,63 @@ void Config::SavePlayerValues(const std::size_t player_index) {
// No custom profile selected
return;
}
WriteSetting(std::string(player_prefix).append("profile_name"), player.profile_name,
std::make_optional(std::string("")));
WriteStringSetting(std::string(player_prefix).append("profile_name"), player.profile_name,
std::make_optional(std::string("")));
}
WriteSetting(std::string(player_prefix).append("type"), static_cast<u8>(player.controller_type),
std::make_optional(static_cast<u8>(Settings::ControllerType::ProController)));
WriteIntegerSetting(
std::string(player_prefix).append("type"), static_cast<u8>(player.controller_type),
std::make_optional(static_cast<u8>(Settings::ControllerType::ProController)));
if (!player_prefix.empty() || !Settings::IsConfiguringGlobal()) {
WriteSetting(std::string(player_prefix).append("connected"), player.connected,
std::make_optional(player_index == 0));
WriteSetting(std::string(player_prefix).append("vibration_enabled"),
player.vibration_enabled, std::make_optional(true));
WriteSetting(std::string(player_prefix).append("vibration_strength"),
player.vibration_strength, std::make_optional(100));
WriteSetting(std::string(player_prefix).append("body_color_left"), player.body_color_left,
std::make_optional(Settings::JOYCON_BODY_NEON_BLUE));
WriteSetting(std::string(player_prefix).append("body_color_right"), player.body_color_right,
std::make_optional(Settings::JOYCON_BODY_NEON_RED));
WriteSetting(std::string(player_prefix).append("button_color_left"),
player.button_color_left,
std::make_optional(Settings::JOYCON_BUTTONS_NEON_BLUE));
WriteSetting(std::string(player_prefix).append("button_color_right"),
player.button_color_right,
std::make_optional(Settings::JOYCON_BUTTONS_NEON_RED));
WriteBooleanSetting(std::string(player_prefix).append("connected"), player.connected,
std::make_optional(player_index == 0));
WriteIntegerSetting(std::string(player_prefix).append("vibration_enabled"),
player.vibration_enabled, std::make_optional(true));
WriteIntegerSetting(std::string(player_prefix).append("vibration_strength"),
player.vibration_strength, std::make_optional(100));
WriteIntegerSetting(std::string(player_prefix).append("body_color_left"),
player.body_color_left,
std::make_optional(Settings::JOYCON_BODY_NEON_BLUE));
WriteIntegerSetting(std::string(player_prefix).append("body_color_right"),
player.body_color_right,
std::make_optional(Settings::JOYCON_BODY_NEON_RED));
WriteIntegerSetting(std::string(player_prefix).append("button_color_left"),
player.button_color_left,
std::make_optional(Settings::JOYCON_BUTTONS_NEON_BLUE));
WriteIntegerSetting(std::string(player_prefix).append("button_color_right"),
player.button_color_right,
std::make_optional(Settings::JOYCON_BUTTONS_NEON_RED));
}
}
void Config::SaveTouchscreenValues() {
const auto& touchscreen = Settings::values.touchscreen;
WriteSetting(std::string("touchscreen_enabled"), touchscreen.enabled, std::make_optional(true));
WriteBooleanSetting(std::string("touchscreen_enabled"), touchscreen.enabled,
std::make_optional(true));
WriteSetting(std::string("touchscreen_angle"), touchscreen.rotation_angle,
std::make_optional(static_cast<u32>(0)));
WriteSetting(std::string("touchscreen_diameter_x"), touchscreen.diameter_x,
std::make_optional(static_cast<u32>(15)));
WriteSetting(std::string("touchscreen_diameter_y"), touchscreen.diameter_y,
std::make_optional(static_cast<u32>(15)));
WriteIntegerSetting(std::string("touchscreen_angle"), touchscreen.rotation_angle,
std::make_optional(static_cast<u32>(0)));
WriteIntegerSetting(std::string("touchscreen_diameter_x"), touchscreen.diameter_x,
std::make_optional(static_cast<u32>(15)));
WriteIntegerSetting(std::string("touchscreen_diameter_y"), touchscreen.diameter_y,
std::make_optional(static_cast<u32>(15)));
}
void Config::SaveMotionTouchValues() {
BeginArray(std::string("touch_from_button_maps"));
for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
SetArrayIndex(static_cast<int>(p));
WriteSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name,
std::make_optional(std::string("default")));
WriteStringSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name,
std::make_optional(std::string("default")));
BeginArray(std::string("entries"));
for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size();
++q) {
SetArrayIndex(static_cast<int>(q));
WriteSetting(std::string("bind"),
Settings::values.touch_from_button_maps[p].buttons[q]);
WriteStringSetting(std::string("bind"),
Settings::values.touch_from_button_maps[p].buttons[q]);
}
EndArray(); // entries
}
@@ -520,16 +524,16 @@ void Config::SaveCoreValues() {
void Config::SaveDataStorageValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
WriteSetting(std::string("nand_directory"), FS::GetYuzuPathString(FS::YuzuPath::NANDDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
WriteSetting(std::string("sdmc_directory"), FS::GetYuzuPathString(FS::YuzuPath::SDMCDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
WriteSetting(std::string("load_directory"), FS::GetYuzuPathString(FS::YuzuPath::LoadDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
WriteSetting(std::string("dump_directory"), FS::GetYuzuPathString(FS::YuzuPath::DumpDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
WriteSetting(std::string("tas_directory"), FS::GetYuzuPathString(FS::YuzuPath::TASDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::TASDir)));
WriteStringSetting(std::string("nand_directory"), FS::GetYuzuPathString(FS::YuzuPath::NANDDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
WriteStringSetting(std::string("sdmc_directory"), FS::GetYuzuPathString(FS::YuzuPath::SDMCDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
WriteStringSetting(std::string("load_directory"), FS::GetYuzuPathString(FS::YuzuPath::LoadDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
WriteStringSetting(std::string("dump_directory"), FS::GetYuzuPathString(FS::YuzuPath::DumpDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
WriteStringSetting(std::string("tas_directory"), FS::GetYuzuPathString(FS::YuzuPath::TASDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::TASDir)));
WriteCategory(Settings::Category::DataStorage);
@@ -540,7 +544,7 @@ void Config::SaveDebuggingValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging));
// Intentionally not using the QT default setting as this is intended to be changed in the ini
WriteSetting(std::string("record_frame_times"), Settings::values.record_frame_times);
WriteBooleanSetting(std::string("record_frame_times"), Settings::values.record_frame_times);
WriteCategory(Settings::Category::Debugging);
WriteCategory(Settings::Category::DebuggingGraphics);
@@ -564,11 +568,13 @@ void Config::SaveDisabledAddOnValues() {
BeginArray(std::string(""));
for (const auto& elem : Settings::values.disabled_addons) {
SetArrayIndex(i);
WriteSetting(std::string("title_id"), elem.first, std::make_optional(static_cast<u64>(0)));
WriteIntegerSetting(std::string("title_id"), elem.first,
std::make_optional(static_cast<u64>(0)));
BeginArray(std::string("disabled"));
for (std::size_t j = 0; j < elem.second.size(); ++j) {
SetArrayIndex(static_cast<int>(j));
WriteSetting(std::string("d"), elem.second[j], std::make_optional(std::string("")));
WriteStringSetting(std::string("d"), elem.second[j],
std::make_optional(std::string("")));
}
EndArray(); // disabled
++i;
@@ -609,8 +615,8 @@ void Config::SaveRendererValues() {
void Config::SaveScreenshotValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots));
WriteSetting(std::string("screenshot_path"),
FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir));
WriteStringSetting(std::string("screenshot_path"),
FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir));
WriteCategory(Settings::Category::Screenshots);
EndGroup();
@@ -746,46 +752,70 @@ bool Config::Exists(const std::string& section, const std::string& key) const {
return !value.empty();
}
template <typename Type>
void Config::WriteSetting(const std::string& key, const Type& value,
const std::optional<Type>& default_value,
const std::optional<bool>& use_global) {
std::string full_key = GetFullKey(key, false);
std::string saved_value;
std::string string_default;
if constexpr (std::is_same_v<Type, std::string>) {
saved_value.append(AdjustOutputString(value));
if (default_value.has_value()) {
string_default.append(AdjustOutputString(default_value.value()));
}
} else {
saved_value.append(AdjustOutputString(ToString(value)));
if (default_value.has_value()) {
string_default.append(ToString(default_value.value()));
}
void Config::WriteBooleanSetting(const std::string& key, const bool& value,
const std::optional<bool>& default_value,
const std::optional<bool>& use_global) {
std::optional<std::string> string_default = std::nullopt;
if (default_value.has_value()) {
string_default = std::make_optional(ToString(default_value.value()));
}
WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
}
if (default_value.has_value() && use_global.has_value()) {
template <typename T>
std::enable_if_t<std::is_integral_v<T>> Config::WriteIntegerSetting(
const std::string& key, const T& value, const std::optional<T>& default_value,
const std::optional<bool>& use_global) {
std::optional<std::string> string_default = std::nullopt;
if (default_value.has_value()) {
string_default = std::make_optional(ToString(default_value.value()));
}
WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
}
void Config::WriteDoubleSetting(const std::string& key, const double& value,
const std::optional<double>& default_value,
const std::optional<bool>& use_global) {
std::optional<std::string> string_default = std::nullopt;
if (default_value.has_value()) {
string_default = std::make_optional(ToString(default_value.value()));
}
WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
}
void Config::WriteStringSetting(const std::string& key, const std::string& value,
const std::optional<std::string>& default_value,
const std::optional<bool>& use_global) {
std::optional string_default = default_value;
if (default_value.has_value()) {
string_default.value().append(AdjustOutputString(default_value.value()));
}
WritePreparedSetting(key, AdjustOutputString(value), string_default, use_global);
}
void Config::WritePreparedSetting(const std::string& key, const std::string& adjusted_value,
const std::optional<std::string>& adjusted_default_value,
const std::optional<bool>& use_global) {
std::string full_key = GetFullKey(key, false);
if (adjusted_default_value.has_value() && use_global.has_value()) {
if (!global) {
WriteSettingInternal(std::string(full_key).append("\\global"),
ToString(use_global.value()));
WriteString(std::string(full_key).append("\\global"), ToString(use_global.value()));
}
if (global || use_global.value() == false) {
WriteSettingInternal(std::string(full_key).append("\\default"),
ToString(string_default == saved_value));
WriteSettingInternal(full_key, saved_value);
WriteString(std::string(full_key).append("\\default"),
ToString(adjusted_default_value == adjusted_value));
WriteString(full_key, adjusted_value);
}
} else if (default_value.has_value() && !use_global.has_value()) {
WriteSettingInternal(std::string(full_key).append("\\default"),
ToString(string_default == saved_value));
WriteSettingInternal(full_key, saved_value);
} else if (adjusted_default_value.has_value() && !use_global.has_value()) {
WriteString(std::string(full_key).append("\\default"),
ToString(adjusted_default_value == adjusted_value));
WriteString(full_key, adjusted_value);
} else {
WriteSettingInternal(full_key, saved_value);
WriteString(full_key, adjusted_value);
}
}
void Config::WriteSettingInternal(const std::string& key, const std::string& value) {
void Config::WriteString(const std::string& key, const std::string& value) {
config->SetValue(GetSection().c_str(), key.c_str(), value.c_str());
}
@@ -861,17 +891,17 @@ void Config::WriteSettingGeneric(const Settings::BasicSetting* const setting) {
std::string key = AdjustKey(setting->GetLabel());
if (setting->Switchable()) {
if (!global) {
WriteSetting(std::string(key).append("\\use_global"), setting->UsingGlobal());
WriteBooleanSetting(std::string(key).append("\\use_global"), setting->UsingGlobal());
}
if (global || !setting->UsingGlobal()) {
WriteSetting(std::string(key).append("\\default"),
setting->ToString() == setting->DefaultToString());
WriteSetting(key, setting->ToString());
WriteBooleanSetting(std::string(key).append("\\default"),
setting->ToString() == setting->DefaultToString());
WriteStringSetting(key, setting->ToString());
}
} else if (global) {
WriteSetting(std::string(key).append("\\default"),
setting->ToString() == setting->DefaultToString());
WriteSetting(key, setting->ToString());
WriteBooleanSetting(std::string(key).append("\\default"),
setting->ToString() == setting->DefaultToString());
WriteStringSetting(key, setting->ToString());
}
}

View File

@@ -154,11 +154,20 @@ protected:
* @param use_global Specifies if the custom or global config should be in use, for custom
* configs
*/
template <typename Type = int>
void WriteSetting(const std::string& key, const Type& value,
const std::optional<Type>& default_value = std::nullopt,
const std::optional<bool>& use_global = std::nullopt);
void WriteSettingInternal(const std::string& key, const std::string& value);
void WriteBooleanSetting(const std::string& key, const bool& value,
const std::optional<bool>& default_value = std::nullopt,
const std::optional<bool>& use_global = std::nullopt);
template <typename T>
std::enable_if_t<std::is_integral_v<T>> WriteIntegerSetting(
const std::string& key, const T& value,
const std::optional<T>& default_value = std::nullopt,
const std::optional<bool>& use_global = std::nullopt);
void WriteDoubleSetting(const std::string& key, const double& value,
const std::optional<double>& default_value = std::nullopt,
const std::optional<bool>& use_global = std::nullopt);
void WriteStringSetting(const std::string& key, const std::string& value,
const std::optional<std::string>& default_value = std::nullopt,
const std::optional<bool>& use_global = std::nullopt);
void ReadCategory(Settings::Category category);
void WriteCategory(Settings::Category category);
@@ -175,8 +184,10 @@ protected:
return value_ ? "true" : "false";
} else if constexpr (std::is_same_v<T, u64>) {
return std::to_string(static_cast<u64>(value_));
} else {
} else if constexpr (std::is_same_v<T, s64>) {
return std::to_string(static_cast<s64>(value_));
} else {
return std::to_string(value_);
}
}
@@ -197,9 +208,13 @@ protected:
const bool global;
private:
inline static std::array<char, 19> special_characters = {'!', '#', '$', '%', '^', '&', '*',
'|', ';', '\'', '\"', ',', '<', '.',
'>', '?', '`', '~', '='};
void WritePreparedSetting(const std::string& key, const std::string& adjusted_value,
const std::optional<std::string>& adjusted_default_value,
const std::optional<bool>& use_global);
void WriteString(const std::string& key, const std::string& value);
inline static std::array<char, 18> special_characters = {
'!', '#', '$', '%', '^', '&', '*', '|', ';', '\'', '\"', ',', '<', '>', '?', '`', '~', '='};
struct ConfigArray {
std::string name;

View File

@@ -74,6 +74,11 @@ std::optional<OutAttr> OutputAttrPointer(EmitContext& ctx, IR::Attribute attr) {
case IR::Attribute::ClipDistance7: {
const u32 base{static_cast<u32>(IR::Attribute::ClipDistance0)};
const u32 index{static_cast<u32>(attr) - base};
if (index >= ctx.profile.max_user_clip_distances) {
LOG_WARNING(Shader, "Ignoring clip distance store {} >= {} supported", index,
ctx.profile.max_user_clip_distances);
return std::nullopt;
}
const Id clip_num{ctx.Const(index)};
return OutputAccessChain(ctx, ctx.output_f32, ctx.clip_distances, clip_num);
}

View File

@@ -96,9 +96,9 @@ Id ImageType(EmitContext& ctx, const ImageDescriptor& desc, Id sampled_type) {
}
Id DefineVariable(EmitContext& ctx, Id type, std::optional<spv::BuiltIn> builtin,
spv::StorageClass storage_class) {
spv::StorageClass storage_class, std::optional<Id> initializer = std::nullopt) {
const Id pointer_type{ctx.TypePointer(storage_class, type)};
const Id id{ctx.AddGlobalVariable(pointer_type, storage_class)};
const Id id{ctx.AddGlobalVariable(pointer_type, storage_class, initializer)};
if (builtin) {
ctx.Decorate(id, spv::Decoration::BuiltIn, *builtin);
}
@@ -144,11 +144,12 @@ Id DefineInput(EmitContext& ctx, Id type, bool per_invocation,
}
Id DefineOutput(EmitContext& ctx, Id type, std::optional<u32> invocations,
std::optional<spv::BuiltIn> builtin = std::nullopt) {
std::optional<spv::BuiltIn> builtin = std::nullopt,
std::optional<Id> initializer = std::nullopt) {
if (invocations && ctx.stage == Stage::TessellationControl) {
type = ctx.TypeArray(type, ctx.Const(*invocations));
}
return DefineVariable(ctx, type, builtin, spv::StorageClass::Output);
return DefineVariable(ctx, type, builtin, spv::StorageClass::Output, initializer);
}
void DefineGenericOutput(EmitContext& ctx, size_t index, std::optional<u32> invocations) {
@@ -811,10 +812,14 @@ void EmitContext::DefineAttributeMemAccess(const Info& info) {
labels.push_back(OpLabel());
}
if (info.stores.ClipDistances()) {
literals.push_back(static_cast<u32>(IR::Attribute::ClipDistance0) >> 2);
labels.push_back(OpLabel());
literals.push_back(static_cast<u32>(IR::Attribute::ClipDistance4) >> 2);
labels.push_back(OpLabel());
if (profile.max_user_clip_distances >= 4) {
literals.push_back(static_cast<u32>(IR::Attribute::ClipDistance0) >> 2);
labels.push_back(OpLabel());
}
if (profile.max_user_clip_distances >= 8) {
literals.push_back(static_cast<u32>(IR::Attribute::ClipDistance4) >> 2);
labels.push_back(OpLabel());
}
}
OpSelectionMerge(end_block, spv::SelectionControlMask::MaskNone);
OpSwitch(compare_index, default_label, literals, labels);
@@ -843,17 +848,21 @@ void EmitContext::DefineAttributeMemAccess(const Info& info) {
++label_index;
}
if (info.stores.ClipDistances()) {
AddLabel(labels[label_index]);
const Id pointer{OpAccessChain(output_f32, clip_distances, masked_index)};
OpStore(pointer, store_value);
OpReturn();
++label_index;
AddLabel(labels[label_index]);
const Id fixed_index{OpIAdd(U32[1], masked_index, Const(4U))};
const Id pointer2{OpAccessChain(output_f32, clip_distances, fixed_index)};
OpStore(pointer2, store_value);
OpReturn();
++label_index;
if (profile.max_user_clip_distances >= 4) {
AddLabel(labels[label_index]);
const Id pointer{OpAccessChain(output_f32, clip_distances, masked_index)};
OpStore(pointer, store_value);
OpReturn();
++label_index;
}
if (profile.max_user_clip_distances >= 8) {
AddLabel(labels[label_index]);
const Id fixed_index{OpIAdd(U32[1], masked_index, Const(4U))};
const Id pointer{OpAccessChain(output_f32, clip_distances, fixed_index)};
OpStore(pointer, store_value);
OpReturn();
++label_index;
}
}
AddLabel(end_block);
OpUnreachable();
@@ -1532,8 +1541,16 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
if (stage == Stage::Fragment) {
throw NotImplementedException("Storing ClipDistance in fragment stage");
}
const Id type{TypeArray(F32[1], Const(8U))};
clip_distances = DefineOutput(*this, type, invocations, spv::BuiltIn::ClipDistance);
if (profile.max_user_clip_distances > 0) {
const u32 used{std::min(profile.max_user_clip_distances, 8u)};
const std::array<Id, 8> zero{f32_zero_value, f32_zero_value, f32_zero_value,
f32_zero_value, f32_zero_value, f32_zero_value,
f32_zero_value, f32_zero_value};
const Id type{TypeArray(F32[1], Const(used))};
const Id initializer{ConstantComposite(type, std::span(zero).subspan(0, used))};
clip_distances =
DefineOutput(*this, type, invocations, spv::BuiltIn::ClipDistance, initializer);
}
}
if (info.stores[IR::Attribute::Layer] &&
(profile.support_viewport_index_layer_non_geometry || stage == Stage::Geometry)) {

View File

@@ -913,7 +913,11 @@ void GatherInfoFromHeader(Environment& env, Info& info) {
}
for (size_t index = 0; index < 8; ++index) {
const u16 mask{header.vtg.omap_systemc.clip_distances};
info.stores.Set(IR::Attribute::ClipDistance0 + index, ((mask >> index) & 1) != 0);
const bool used{((mask >> index) & 1) != 0};
info.stores.Set(IR::Attribute::ClipDistance0 + index, used);
if (used) {
info.used_clip_distances = static_cast<u32>(index) + 1;
}
}
info.stores.Set(IR::Attribute::PrimitiveId,
header.vtg.omap_systemb.primitive_array_id != 0);

View File

@@ -87,6 +87,8 @@ struct Profile {
bool has_broken_robust{};
u64 min_ssbo_alignment{};
u32 max_user_clip_distances{};
};
} // namespace Shader

View File

@@ -324,6 +324,8 @@ struct Info {
bool requires_layer_emulation{};
IR::Attribute emulated_layer{};
u32 used_clip_distances{};
boost::container::static_vector<ConstantBufferDescriptor, MAX_CBUFS>
constant_buffer_descriptors;
boost::container::static_vector<StorageBufferDescriptor, MAX_SSBOS> storage_buffers_descriptors;

View File

@@ -16,20 +16,16 @@
namespace {
// Numbers are chosen randomly to make sure the correct one is given.
constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}};
constexpr std::array<u64, 5> calls_order{{2, 0, 1, 4, 3}};
std::array<s64, 5> delays{};
std::bitset<CB_IDS.size()> callbacks_ran_flags;
std::bitset<5> callbacks_ran_flags;
u64 expected_callback = 0;
template <unsigned int IDX>
std::optional<std::chrono::nanoseconds> HostCallbackTemplate(std::uintptr_t user_data, s64 time,
std::optional<std::chrono::nanoseconds> HostCallbackTemplate(s64 time,
std::chrono::nanoseconds ns_late) {
static_assert(IDX < CB_IDS.size(), "IDX out of range");
static_assert(IDX < callbacks_ran_flags.size(), "IDX out of range");
callbacks_ran_flags.set(IDX);
REQUIRE(CB_IDS[IDX] == user_data);
REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]);
delays[IDX] = ns_late.count();
++expected_callback;
return std::nullopt;
@@ -76,7 +72,7 @@ TEST_CASE("CoreTiming[BasicOrder]", "[core]") {
const u64 order = calls_order[i];
const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};
core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]);
core_timing.ScheduleEvent(future_ns, events[order]);
}
/// test pause
REQUIRE(callbacks_ran_flags.none());
@@ -118,7 +114,7 @@ TEST_CASE("CoreTiming[BasicOrderNoPausing]", "[core]") {
for (std::size_t i = 0; i < events.size(); i++) {
const u64 order = calls_order[i];
const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};
core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]);
core_timing.ScheduleEvent(future_ns, events[order]);
}
const u64 end = core_timing.GetGlobalTimeNs().count();

View File

@@ -228,7 +228,7 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
Core::Memory::GpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead> tmp_read_buffer(
memory_manager, src_operand.address, src_size, &read_buffer);
Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::SafeReadCachedWrite>
Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::UnsafeReadCachedWrite>
tmp_write_buffer(memory_manager, dst_operand.address, dst_size, &write_buffer);
UnswizzleSubrect(tmp_write_buffer, tmp_read_buffer, bytes_per_pixel, width, height, depth,
@@ -292,7 +292,7 @@ void MaxwellDMA::CopyPitchToBlockLinear() {
GPUVAddr dst_addr = regs.offset_out;
Core::Memory::GpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead> tmp_read_buffer(
memory_manager, src_addr, src_size, &read_buffer);
Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::SafeReadCachedWrite>
Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::UnsafeReadCachedWrite>
tmp_write_buffer(memory_manager, dst_addr, dst_size, &write_buffer);
// If the input is linear and the output is tiled, swizzle the input and copy it over.

View File

@@ -327,12 +327,13 @@ public:
explicit HLE_DrawIndirectByteCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
const bool force = maxwell3d.Rasterizer().HasDrawTransformFeedback();
auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0] & 0xFFFFU);
if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) {
if (!force && (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology))) {
Fallback(parameters);
return;
}
auto& params = maxwell3d.draw_manager->GetIndirectParams();
params.is_byte_count = true;
params.is_indexed = false;
@@ -503,6 +504,8 @@ public:
maxwell3d.CallMethod(static_cast<size_t>(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true);
maxwell3d.CallMethod(static_cast<size_t>(MAXWELL3D_REG_INDEX(inline_data)),
regs.transform_feedback.controls[0].stride, true);
maxwell3d.Rasterizer().RegisterTransformFeedback(regs.upload.dest.Address());
}
};

View File

@@ -173,5 +173,13 @@ public:
virtual void BindChannel(Tegra::Control::ChannelState& channel) {}
virtual void ReleaseChannel(s32 channel_id) {}
/// Register the address as a Transform Feedback Object
virtual void RegisterTransformFeedback(GPUVAddr tfb_object_addr) {}
/// Returns true when the rasterizer has Draw Transform Feedback capabilities
virtual bool HasDrawTransformFeedback() {
return false;
}
};
} // namespace VideoCore

View File

@@ -376,4 +376,15 @@ void BufferCacheRuntime::BindImageBuffer(Buffer& buffer, u32 offset, u32 size, P
*image_handles++ = buffer.View(offset, size, format);
}
void BufferCacheRuntime::BindTransformFeedbackObject(GPUVAddr tfb_object_addr) {
OGLTransformFeedback& tfb_object = tfb_objects[tfb_object_addr];
tfb_object.Create();
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tfb_object.handle);
}
GLuint BufferCacheRuntime::GetTransformFeedbackObject(GPUVAddr tfb_object_addr) {
ASSERT(tfb_objects.contains(tfb_object_addr));
return tfb_objects[tfb_object_addr].handle;
}
} // namespace OpenGL

View File

@@ -5,6 +5,7 @@
#include <array>
#include <span>
#include <unordered_map>
#include "common/common_types.h"
#include "video_core/buffer_cache/buffer_cache_base.h"
@@ -121,6 +122,9 @@ public:
void BindImageBuffer(Buffer& buffer, u32 offset, u32 size,
VideoCore::Surface::PixelFormat format);
void BindTransformFeedbackObject(GPUVAddr tfb_object_addr);
GLuint GetTransformFeedbackObject(GPUVAddr tfb_object_addr);
u64 GetDeviceMemoryUsage() const;
void BindFastUniformBuffer(size_t stage, u32 binding_index, u32 size) {
@@ -233,6 +237,7 @@ private:
u32 index_buffer_offset = 0;
u64 device_access_memory;
std::unordered_map<GPUVAddr, OGLTransformFeedback> tfb_objects;
};
struct BufferCacheParams {

View File

@@ -309,6 +309,13 @@ void RasterizerOpenGL::DrawIndirect() {
const auto& params = maxwell3d->draw_manager->GetIndirectParams();
buffer_cache.SetDrawIndirect(&params);
PrepareDraw(params.is_indexed, [this, &params](GLenum primitive_mode) {
if (params.is_byte_count) {
const GPUVAddr tfb_object_base_addr = params.indirect_start_address - 4U;
const GLuint tfb_object =
buffer_cache_runtime.GetTransformFeedbackObject(tfb_object_base_addr);
glDrawTransformFeedback(primitive_mode, tfb_object);
return;
}
const auto [buffer, offset] = buffer_cache.GetDrawIndirectBuffer();
const GLvoid* const gl_offset =
reinterpret_cast<const GLvoid*>(static_cast<uintptr_t>(offset));
@@ -1371,6 +1378,10 @@ void RasterizerOpenGL::ReleaseChannel(s32 channel_id) {
query_cache.EraseChannel(channel_id);
}
void RasterizerOpenGL::RegisterTransformFeedback(GPUVAddr tfb_object_addr) {
buffer_cache_runtime.BindTransformFeedbackObject(tfb_object_addr);
}
AccelerateDMA::AccelerateDMA(BufferCache& buffer_cache_, TextureCache& texture_cache_)
: buffer_cache{buffer_cache_}, texture_cache{texture_cache_} {}

View File

@@ -139,6 +139,12 @@ public:
void ReleaseChannel(s32 channel_id) override;
void RegisterTransformFeedback(GPUVAddr tfb_object_addr) override;
bool HasDrawTransformFeedback() override {
return true;
}
private:
static constexpr size_t MAX_TEXTURES = 192;
static constexpr size_t MAX_IMAGES = 48;

View File

@@ -207,4 +207,21 @@ void OGLQuery::Release() {
handle = 0;
}
void OGLTransformFeedback::Create() {
if (handle != 0)
return;
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
glCreateTransformFeedbacks(1, &handle);
}
void OGLTransformFeedback::Release() {
if (handle == 0)
return;
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
glDeleteTransformFeedbacks(1, &handle);
handle = 0;
}
} // namespace OpenGL

View File

@@ -323,4 +323,31 @@ public:
GLuint handle = 0;
};
class OGLTransformFeedback final {
public:
YUZU_NON_COPYABLE(OGLTransformFeedback);
OGLTransformFeedback() = default;
OGLTransformFeedback(OGLTransformFeedback&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
~OGLTransformFeedback() {
Release();
}
OGLTransformFeedback& operator=(OGLTransformFeedback&& o) noexcept {
Release();
handle = std::exchange(o.handle, 0);
return *this;
}
/// Creates a new internal OpenGL resource and stores the handle
void Create();
/// Deletes the internal OpenGL resource
void Release();
GLuint handle = 0;
};
} // namespace OpenGL

View File

@@ -233,6 +233,7 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo
.ignore_nan_fp_comparisons = true,
.gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(),
.min_ssbo_alignment = device.GetShaderStorageBufferAlignment(),
.max_user_clip_distances = 8,
},
host_info{
.support_float64 = true,

Some files were not shown because too many files have changed in this diff Show More