Compare commits

..

12 Commits

Author SHA1 Message Date
CamilleLaVey 53060c9a91 Small experiment 2026-04-08 23:47:15 +02:00
CamilleLaVey 8200a7564c [texture_cache] Removal of lowmemorydevice dead code 2026-04-08 23:47:15 +02:00
CamilleLaVey 1637a2cb30 fix license headers+ 2026-04-08 23:47:15 +02:00
CamilleLaVey cb851cf091 [texture_cache] Adjusted GC logic for the iterations with older or obsolete textures 2026-04-08 23:47:15 +02:00
CamilleLaVey 90fd089e58 [texture_cache] Reduce garbage collection logic by simplifying conditions and thresholds 2026-04-08 23:47:15 +02:00
CamilleLaVey 29e77b5e2d small fix for the softlock after lru cache removal 2026-04-08 23:47:15 +02:00
CamilleLaVey c515a6bf83 Gido MEOW 2026-04-08 23:47:15 +02:00
CamilleLaVey 2e90822686 [texture_cache] Replace LRU index with frame tick in ImageBase + update garbage collection logic 2026-04-08 23:47:15 +02:00
CamilleLaVey 9bef0b35ba I got meowed by Gidoly 2026-04-08 23:47:15 +02:00
CamilleLaVey ba1e137bb6 [buffer_cache] Removal of LRU inside buffer cache and replaced with tick operations inside frames. 2026-04-08 23:47:15 +02:00
CamilleLaVey febddbd00c [maxwell] Removed prefetching for ProcessCommands 2026-04-08 23:47:15 +02:00
CamilleLaVey ad2225b5d0 [maxwell] Refactor execution mask initialization to use fill() instead of reset() 2026-04-08 23:47:15 +02:00
147 changed files with 10375 additions and 15471 deletions
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -35,17 +35,17 @@ set(GIT_DESC ${BUILD_VERSION})
# Generate cpp with Git revision from template # Generate cpp with Git revision from template
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well # Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
# Auto-updater metadata! Must somewhat mirror GitHub/Forgejo API endpoint # Auto-updater metadata! Must somewhat mirror GitHub API endpoint
set(BUILD_AUTO_UPDATE_API_PATH "/latest/release.json")
if (NIGHTLY_BUILD) if (NIGHTLY_BUILD)
set(BUILD_AUTO_UPDATE_WEBSITE "https://git.eden-emu.dev") set(BUILD_AUTO_UPDATE_WEBSITE "https://git.eden-emu.dev")
set(BUILD_AUTO_UPDATE_API "nightly.eden-emu.dev") set(BUILD_AUTO_UPDATE_API "git.eden-emu.dev")
set(BUILD_AUTO_UPDATE_API_PATH "/api/v1/repos/")
set(BUILD_AUTO_UPDATE_REPO "eden-ci/nightly") set(BUILD_AUTO_UPDATE_REPO "eden-ci/nightly")
set(REPO_NAME "Eden Nightly") set(REPO_NAME "Eden Nightly")
else() else()
set(BUILD_AUTO_UPDATE_WEBSITE "https://git.eden-emu.dev") set(BUILD_AUTO_UPDATE_WEBSITE "https://git.eden-emu.dev")
set(BUILD_AUTO_UPDATE_API "stable.eden-emu.dev") set(BUILD_AUTO_UPDATE_API "git.eden-emu.dev")
set(BUILD_AUTO_UPDATE_API_PATH "/api/v1/repos/")
set(BUILD_AUTO_UPDATE_REPO "eden-emu/eden") set(BUILD_AUTO_UPDATE_REPO "eden-emu/eden")
set(REPO_NAME "Eden") set(REPO_NAME "Eden")
endif() endif()
+337 -334
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+334 -331
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+308 -304
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+396 -393
View File
File diff suppressed because it is too large Load Diff
+308 -304
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+307 -303
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+308 -304
View File
File diff suppressed because it is too large Load Diff
+471 -495
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+308 -304
View File
File diff suppressed because it is too large Load Diff
+308 -305
View File
File diff suppressed because it is too large Load Diff
+308 -304
View File
File diff suppressed because it is too large Load Diff
+308 -305
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+308 -304
View File
File diff suppressed because it is too large Load Diff
+306 -302
View File
File diff suppressed because it is too large Load Diff
+2 -3
View File
@@ -79,7 +79,7 @@ Certain other dependencies will be fetched by CPM regardless. System packages *c
On amd64: On amd64:
* [xbyak](https://github.com/herumi/xbyak) - 7.35.2+ * [xbyak](https://github.com/herumi/xbyak) - 7.22 or earlier is recommended
On aarch64 OR if `DYNARMIC_TESTS` is on: On aarch64 OR if `DYNARMIC_TESTS` is on:
@@ -161,12 +161,11 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl
<summary>Ubuntu, Debian, Mint Linux</summary> <summary>Ubuntu, Debian, Mint Linux</summary>
```sh ```sh
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl2-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl2-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev
``` ```
* Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required. * Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required.
* To enable QT Web Engine, add `-DYUZU_USE_QT_WEB_ENGINE=ON` when running CMake. * To enable QT Web Engine, add `-DYUZU_USE_QT_WEB_ENGINE=ON` when running CMake.
* `libxbyak-dev` is optional for x86_64 targets
</details> </details>
+2 -7
View File
@@ -58,13 +58,8 @@
"package": "xbyak", "package": "xbyak",
"repo": "herumi/xbyak", "repo": "herumi/xbyak",
"tag": "v%VERSION%", "tag": "v%VERSION%",
"hash": "d93971cc8f17f20818e36099aa862d15d30b60f17c7dff0cc8b7ac89ad6dc6453256dd47a454904666a85cea6ca27f9867f782c7b2c6d0c16039af8e3e28e6cc", "hash": "b6475276b2faaeb315734ea8f4f8bd87ededcee768961b39679bee547e7f3e98884d8b7851e176d861dab30a80a76e6ea302f8c111483607dde969b4797ea95a",
"git_version": "7.35.4", "git_version": "7.35.2"
"bundled": true,
"skip_updates": true,
"patches": [
"0001-rvalue-optimize.patch"
]
}, },
"oaknut": { "oaknut": {
"repo": "eden-emulator/oaknut", "repo": "eden-emulator/oaknut",
@@ -15,7 +15,6 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
@@ -101,7 +100,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
private var romSwapGeneration = 0 private var romSwapGeneration = 0
private var hasEmulationSession = processHasEmulationSession private var hasEmulationSession = processHasEmulationSession
private val romSwapStopTimeoutRunnable = Runnable { onRomSwapStopTimeout() } private val romSwapStopTimeoutRunnable = Runnable { onRomSwapStopTimeout() }
private val pictureInPictureFailureActions: MutableSet<String> = mutableSetOf()
private fun onRomSwapStopTimeout() { private fun onRomSwapStopTimeout() {
if (!isWaitingForRomSwapStop) { if (!isWaitingForRomSwapStop) {
@@ -268,18 +266,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
} }
override fun onUserLeaveHint() { override fun onUserLeaveHint() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S || if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
!isPictureInPictureSupported() || if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) {
!BooleanSetting.PICTURE_IN_PICTURE.getBoolean() || val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
isInPictureInPictureMode .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
) { enterPictureInPictureMode(pictureInPictureParamsBuilder.build())
return }
}
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
runPictureInPictureAction("enter picture-in-picture mode") {
enterPictureInPictureMode(pictureInPictureParamsBuilder.build())
} }
} }
@@ -659,29 +651,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
return this.apply { setActions(pictureInPictureActions) } return this.apply { setActions(pictureInPictureActions) }
} }
private fun isPictureInPictureSupported() =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
private fun runPictureInPictureAction(actionName: String, action: () -> Unit) {
try {
action()
} catch (e: IllegalStateException) {
if (pictureInPictureFailureActions.add(actionName)) {
Log.warning("[PiP] Failed to $actionName: ${e.message}")
}
} catch (e: UnsupportedOperationException) {
if (pictureInPictureFailureActions.add(actionName)) {
Log.warning("[PiP] Failed to $actionName: ${e.message}")
}
}
}
fun buildPictureInPictureParams() { fun buildPictureInPictureParams() {
if (!isPictureInPictureSupported()) {
return
}
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -691,9 +661,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && isEmulationActive BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && isEmulationActive
) )
} }
runPictureInPictureAction("set picture-in-picture params") { setPictureInPictureParams(pictureInPictureParamsBuilder.build())
setPictureInPictureParams(pictureInPictureParamsBuilder.build())
}
} }
fun displayMultiplayerDialog() { fun displayMultiplayerDialog() {
@@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -37,10 +33,8 @@ object SoftwareKeyboard {
val emulationActivity = NativeLibrary.sEmulationActivity.get() val emulationActivity = NativeLibrary.sEmulationActivity.get()
val overlayView = emulationActivity!!.findViewById<View>(R.id.surface_input_overlay) val overlayView = emulationActivity!!.findViewById<View>(R.id.surface_input_overlay)
overlayView.requestFocus()
val im = val im =
overlayView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager overlayView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
im.restartInput(overlayView)
im.showSoftInput(overlayView, InputMethodManager.SHOW_FORCED) im.showSoftInput(overlayView, InputMethodManager.SHOW_FORCED)
// There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result. // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result.
@@ -17,7 +17,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"), USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"),
SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"), SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"),
FIX_BLOOM_EFFECTS("fix_bloom_effects"), FIX_BLOOM_EFFECTS("fix_bloom_effects"),
EMULATE_BGR565("emulate_bgr565"),
CPUOPT_UNSAFE_HOST_MMU("cpuopt_unsafe_host_mmu"), CPUOPT_UNSAFE_HOST_MMU("cpuopt_unsafe_host_mmu"),
USE_DOCKED_MODE("use_docked_mode"), USE_DOCKED_MODE("use_docked_mode"),
USE_AUTO_STUB("use_auto_stub"), USE_AUTO_STUB("use_auto_stub"),
@@ -756,13 +756,6 @@ abstract class SettingsItem(
descriptionId = R.string.fix_bloom_effects_description descriptionId = R.string.fix_bloom_effects_description
) )
) )
put(
SwitchSetting(
BooleanSetting.EMULATE_BGR565,
titleId = R.string.emulate_bgr565,
descriptionId = R.string.emulate_bgr565_description
)
)
put( put(
SwitchSetting( SwitchSetting(
BooleanSetting.CPUOPT_UNSAFE_HOST_MMU, BooleanSetting.CPUOPT_UNSAFE_HOST_MMU,
@@ -284,6 +284,8 @@ class SettingsFragmentPresenter(
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key) add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key) add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key) add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key) add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
add(BooleanSetting.ENABLE_BUFFER_HISTORY.key) add(BooleanSetting.ENABLE_BUFFER_HISTORY.key)
add(BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS.key) add(BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS.key)
@@ -293,10 +295,7 @@ class SettingsFragmentPresenter(
add(IntSetting.FAST_GPU_TIME.key) add(IntSetting.FAST_GPU_TIME.key)
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key) add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
add(BooleanSetting.FIX_BLOOM_EFFECTS.key) add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
add(BooleanSetting.EMULATE_BGR565.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key) add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
add(SettingsItem.GPU_UNSWIZZLE_COMBINED) add(SettingsItem.GPU_UNSWIZZLE_COMBINED)
add(HeaderSetting(R.string.extensions)) add(HeaderSetting(R.string.extensions))
@@ -291,23 +291,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
// Game launched via intent (check for existing custom config) // Game launched via intent (check for existing custom config)
intentGame != null -> { intentGame != null -> {
game?.let { gameInstance -> game?.let { gameInstance ->
runCatching { GameHelper.restoreContentForGame(gameInstance) }
.onFailure {
Log.warning(
"[EmulationFragment] Failed to restore content for intent launch: ${it.message}"
)
}
val customConfigFile = SettingsFile.getCustomSettingsFile(gameInstance) val customConfigFile = SettingsFile.getCustomSettingsFile(gameInstance)
if (customConfigFile.exists()) { if (customConfigFile.exists()) {
shouldUseCustom = true
Log.info( Log.info(
"[EmulationFragment] Found existing custom settings for ${gameInstance.title}, loading them" "[EmulationFragment] Found existing custom settings for ${gameInstance.title}, loading them"
) )
SettingsFile.loadCustomConfig(gameInstance) SettingsFile.loadCustomConfig(gameInstance)
NativeConfig.unloadPerGameConfig()
} else { } else {
shouldUseCustom = false
Log.info( Log.info(
"[EmulationFragment] No custom settings found for ${gameInstance.title}, using global settings" "[EmulationFragment] No custom settings found for ${gameInstance.title}, using global settings"
) )
@@ -881,7 +871,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (drawerView == binding.quickSettingsSheet) { if (drawerView == binding.quickSettingsSheet) {
isQuickSettingsMenuOpen = true isQuickSettingsMenuOpen = true
if (shouldUseCustom) { if (shouldUseCustom) {
SettingsFile.loadCustomConfig(game!!) SettingsFile.loadCustomConfig(args.game!!)
} }
} }
} }
@@ -2509,6 +2499,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
fun toggleOverlay(enable: Boolean) { fun toggleOverlay(enable: Boolean) {
if (!isAdded || _binding == null) return if (!isAdded || _binding == null) return
if (enable && hasPhysicalControllerConnected && !args.overlayGamelessEditMode) return
if (enable == !BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) { if (enable == !BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) {
// Reset controller input flag so controller can hide overlay again // Reset controller input flag so controller can hide overlay again
if (!enable) { if (!enable) {
@@ -2546,8 +2537,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (binding.surfaceInputOverlay.isGamelessMode()) return if (binding.surfaceInputOverlay.isGamelessMode()) return
if (hasConnectedControllers) { if (hasConnectedControllers) {
if (BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() && if (BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) {
BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) {
overlayHiddenByPhysicalController = true overlayHiddenByPhysicalController = true
toggleOverlay(false) toggleOverlay(false)
} }
@@ -17,23 +17,16 @@ import android.graphics.drawable.VectorDrawable
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.Editable
import android.text.InputType
import android.util.AttributeSet import android.util.AttributeSet
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.View.OnTouchListener import android.view.View.OnTouchListener
import android.view.WindowInsets import android.view.WindowInsets
import android.view.inputmethod.BaseInputConnection
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.window.layout.WindowMetricsCalculator import androidx.window.layout.WindowMetricsCalculator
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.features.input.NativeInput import org.yuzu.yuzu_emu.features.input.NativeInput
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
@@ -56,7 +49,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet() private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet() private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
private val overlayJoysticks: MutableSet<InputOverlayDrawableJoystick> = HashSet() private val overlayJoysticks: MutableSet<InputOverlayDrawableJoystick> = HashSet()
private val imeEditable = Editable.Factory.getInstance().newEditable("")
private var inEditMode = false private var inEditMode = false
private var gamelessMode = false private var gamelessMode = false
@@ -83,63 +75,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
// External listener for EmulationFragment joypad overlay auto-hide // External listener for EmulationFragment joypad overlay auto-hide
var touchEventListener: ((MotionEvent) -> Unit)? = null var touchEventListener: ((MotionEvent) -> Unit)? = null
override fun onCheckIsTextEditor(): Boolean = true
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
imeEditable.clear()
outAttrs.inputType =
InputType.TYPE_CLASS_TEXT or
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS or
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI or EditorInfo.IME_ACTION_DONE
outAttrs.initialSelStart = 0
outAttrs.initialSelEnd = 0
return object : BaseInputConnection(this, true) {
override fun getEditable(): Editable = imeEditable
override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean {
if (!text.isNullOrEmpty()) {
forwardCommittedText(text)
}
return super.commitText(text, newCursorPosition)
}
override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
repeat(beforeLength.coerceAtLeast(0)) {
NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_DEL)
}
return super.deleteSurroundingText(beforeLength, afterLength)
}
override fun sendKeyEvent(event: KeyEvent): Boolean {
if (event.action != KeyEvent.ACTION_DOWN) {
return true
}
when (event.keyCode) {
KeyEvent.KEYCODE_BACK,
KeyEvent.KEYCODE_DEL,
KeyEvent.KEYCODE_ENTER -> {
NativeLibrary.submitInlineKeyboardInput(event.keyCode)
}
else -> {
val textChar = event.unicodeChar
if (textChar != 0) {
NativeLibrary.submitInlineKeyboardText(textChar.toChar().toString())
}
}
}
return true
}
override fun performEditorAction(actionCode: Int): Boolean {
NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER)
return true
}
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom) super.onLayout(changed, left, top, right, bottom)
@@ -184,25 +119,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
} }
} }
private fun forwardCommittedText(text: CharSequence) {
val builder = StringBuilder()
text.forEach { character ->
when (character) {
'\n' -> {
if (builder.isNotEmpty()) {
NativeLibrary.submitInlineKeyboardText(builder.toString())
builder.clear()
}
NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER)
}
else -> builder.append(character)
}
}
if (builder.isNotEmpty()) {
NativeLibrary.submitInlineKeyboardText(builder.toString())
}
}
private fun drawGrid(canvas: Canvas) { private fun drawGrid(canvas: Canvas) {
val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt() val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt()
val width = canvas.width val width = canvas.width
@@ -95,7 +95,7 @@ namespace AndroidSettings {
Settings::Setting<u32> input_overlay_auto_hide{linkage, 5, "input_overlay_auto_hide", Settings::Setting<u32> input_overlay_auto_hide{linkage, 5, "input_overlay_auto_hide",
Settings::Category::Overlay, Settings::Category::Overlay,
Settings::Specialization::Default, true, true, &enable_input_overlay_auto_hide}; Settings::Specialization::Default, true, true, &enable_input_overlay_auto_hide};
Settings::Setting<bool> hide_overlay_on_controller_input{linkage, true, Settings::Setting<bool> hide_overlay_on_controller_input{linkage, false,
"hide_overlay_on_controller_input", "hide_overlay_on_controller_input",
Settings::Category::Overlay, Settings::Category::Overlay,
Settings::Specialization::Default, true, Settings::Specialization::Default, true,
+8 -6
View File
@@ -1726,8 +1726,9 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
jobject obj, jobject obj,
jstring version) { jstring version) {
const char* version_str = env->GetStringUTFChars(version, nullptr); const char* version_str = env->GetStringUTFChars(version, nullptr);
const std::string url = fmt::format("{}/{}", const std::string url = fmt::format("{}/{}/releases/tag/{}",
std::string{Common::g_build_auto_update_api}, std::string{Common::g_build_auto_update_website},
std::string{Common::g_build_auto_update_repo},
version_str); version_str);
env->ReleaseStringUTFChars(version, version_str); env->ReleaseStringUTFChars(version, version_str);
return env->NewStringUTF(url.c_str()); return env->NewStringUTF(url.c_str());
@@ -1759,10 +1760,11 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
} }
const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", artifact_str, variant); const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", artifact_str, variant);
const std::string url = fmt::format("{}/{}/releases/download/{}/{}",
const std::string url = fmt::format("{}/{}/{}", std::string{Common::g_build_auto_update_website},
std::string{Common::g_build_auto_update_api}, std::string{Common::g_build_auto_update_repo},
version_str, apk_filename); version_str,
apk_filename);
env->ReleaseStringUTFChars(tag, version_str); env->ReleaseStringUTFChars(tag, version_str);
env->ReleaseStringUTFChars(artifact, artifact_str); env->ReleaseStringUTFChars(artifact, artifact_str);
@@ -32,7 +32,7 @@
<string name="hide_overlay_on_controller_input">إخفاء الطبقة عند إدخال ذراع التحكم</string> <string name="hide_overlay_on_controller_input">إخفاء الطبقة عند إدخال ذراع التحكم</string>
<string name="hide_overlay_on_controller_input_description">إخفاء تلقائي لطبقة عناصر التحكم باللمس عند استخدام ذراع تحكم فعلية. تظهر الطبقة مرة أخرى عند فصل ذراع التحكم.</string> <string name="hide_overlay_on_controller_input_description">إخفاء تلقائي لطبقة عناصر التحكم باللمس عند استخدام ذراع تحكم فعلية. تظهر الطبقة مرة أخرى عند فصل ذراع التحكم.</string>
<string name="invert_confirm_back_controller_buttons">عكس أزرار التأكيد/الرجوع في وحدة التحكم</string> <string name="invert_confirm_back_controller_buttons">عكس أزرار التأكيد/الرجوع في وحدة التحكم</string>
<string name="invert_confirm_back_controller_buttons_description">تبديل طريقة التعامل مع زري «تأكيد» و«رجوع» في نظام Android لتتوافق مع أنماط أجهزة Switch وXbox أثناء استخدام واجهة المستخدم الخاصة بالتطبيق.</string> <string name="invert_confirm_back_controller_buttons_description">قم بتبديل طريقة عمل زري التأكيد والرجوع في نظام Android لتتوافق مع أنماط Switch و Xbox أثناء استخدام واجهة المستخدم الخاصة بالتطبيق.</string>
<string name="input_overlay_options">طبقة الإدخال</string> <string name="input_overlay_options">طبقة الإدخال</string>
<string name="input_overlay_options_description">ضبط ذراع التحكم على الشاشة</string> <string name="input_overlay_options_description">ضبط ذراع التحكم على الشاشة</string>
@@ -268,7 +268,7 @@
<string name="product">المنتج</string> <string name="product">المنتج</string>
<string name="android_version">إصدار Android</string> <string name="android_version">إصدار Android</string>
<string name="android_security_patch">تصحيح الأمان</string> <string name="android_security_patch">تصحيح الأمان</string>
<string name="build_id">معرف الإصدار</string> <string name="build_id">معرف البناء</string>
<string name="general_information">معلومات عامة</string> <string name="general_information">معلومات عامة</string>
<string name="hardware">الأجهزة</string> <string name="hardware">الأجهزة</string>
<string name="supported_abis">واجهات برمجة التطبيقات المدعومة</string> <string name="supported_abis">واجهات برمجة التطبيقات المدعومة</string>
@@ -287,7 +287,7 @@
<string name="install_amiibo_keys">تثبيت مفاتيح أميبو</string> <string name="install_amiibo_keys">تثبيت مفاتيح أميبو</string>
<string name="install_amiibo_keys_description">مطلوب لاستخدام أميبو في اللعبة</string> <string name="install_amiibo_keys_description">مطلوب لاستخدام أميبو في اللعبة</string>
<string name="gpu_driver_fetcher">أداة جلب برامج تشغيل وحدة المعالجة الرسومية</string> <string name="gpu_driver_fetcher">أداة جلب برامج تشغيل وحدة المعالجة الرسومية</string>
<string name="gpu_driver_manager">إدارة برامج تشغيل وحدة معالجة الرسومات</string> <string name="gpu_driver_manager">مدير برامج تشغيل وحدة معالجة الرسومات</string>
<string name="install_gpu_driver_description">تثبيت برامج تشغيل بديلة لأداء أو دقة أفضل</string> <string name="install_gpu_driver_description">تثبيت برامج تشغيل بديلة لأداء أو دقة أفضل</string>
<string name="advanced_settings">الإعدادات المتقدمة</string> <string name="advanced_settings">الإعدادات المتقدمة</string>
<string name="settings_description">ضبط إعدادات المحاكي</string> <string name="settings_description">ضبط إعدادات المحاكي</string>
@@ -403,7 +403,7 @@
<string name="contributors">المساهمين</string> <string name="contributors">المساهمين</string>
<string name="contributors_description">الأشخاص الذين جعلوا تطبيق Eden لنظام Android ممكنًا</string> <string name="contributors_description">الأشخاص الذين جعلوا تطبيق Eden لنظام Android ممكنًا</string>
<string name="licenses_description">المشاريع التي تجعل Eden لأجهزة Android ممكنة</string> <string name="licenses_description">المشاريع التي تجعل Eden لأجهزة Android ممكنة</string>
<string name="build">الإصدار</string> <string name="build">البناء</string>
<string name="user_data">بيانات المستخدم</string> <string name="user_data">بيانات المستخدم</string>
<string name="user_data_description">استيراد/تصدير جميع بيانات التطبيق.\nعند استيراد بيانات المستخدم، سيتم حذف جميع بيانات المستخدم الموجودة!\nقد يُسبب استيراد البيانات من Citron بعض المشاكل. يُنصح باستيراد جميع البيانات المطلوبة يدويًا.</string> <string name="user_data_description">استيراد/تصدير جميع بيانات التطبيق.\nعند استيراد بيانات المستخدم، سيتم حذف جميع بيانات المستخدم الموجودة!\nقد يُسبب استيراد البيانات من Citron بعض المشاكل. يُنصح باستيراد جميع البيانات المطلوبة يدويًا.</string>
<string name="exporting_user_data">جارٍ تصدير بيانات المستخدم...</string> <string name="exporting_user_data">جارٍ تصدير بيانات المستخدم...</string>
@@ -486,10 +486,6 @@
<string name="use_disk_shader_cache_description">يقلل من التأتأة عن طريق تخزين وتحميل التظليلات التي تم إنشاؤها محليًا.</string> <string name="use_disk_shader_cache_description">يقلل من التأتأة عن طريق تخزين وتحميل التظليلات التي تم إنشاؤها محليًا.</string>
<string name="renderer_force_max_clock">إجبار السرعة القصوى (لأجهزة Adreno فقط)</string> <string name="renderer_force_max_clock">إجبار السرعة القصوى (لأجهزة Adreno فقط)</string>
<string name="renderer_force_max_clock_description">يجبر وحدة معالجة الرسومات على العمل بأقصى سرعة ممكنة (سيظل يتم تطبيق القيود الحرارية).</string> <string name="renderer_force_max_clock_description">يجبر وحدة معالجة الرسومات على العمل بأقصى سرعة ممكنة (سيظل يتم تطبيق القيود الحرارية).</string>
<string name="renderer_asynchronous_gpu_emulation">محاكاة غير متزامنة لوحدة معالجة الرسومات</string>
<string name="renderer_asynchronous_gpu_emulation_description">يُشغّل هذا الخيار محاكاة وحدة معالجة الرسومات بشكل غير متزامن لتقليل توقف وحدة المعالجة المركزية وتحسين الإنتاجية. عطّل هذا الخيار فقط في حال واجهت مشاكل متعلقة بالتوقيت.</string>
<string name="renderer_async_presentation">عرض غير متزامن</string>
<string name="renderer_async_presentation_description">يحسّن الأداء بشكل طفيف عن طريق نقل عملية العرض إلى خيط معالجة منفصل لوحدة المعالجة المركزية.</string>
<string name="renderer_reactive_flushing">استخدم التنظيف التفاعلي</string> <string name="renderer_reactive_flushing">استخدم التنظيف التفاعلي</string>
<string name="renderer_reactive_flushing_description">يحسن دقة العرض في بعض الألعاب على حساب الأداء.</string> <string name="renderer_reactive_flushing_description">يحسن دقة العرض في بعض الألعاب على حساب الأداء.</string>
<string name="enable_buffer_history">تمكين سجل التخزين المؤقت</string> <string name="enable_buffer_history">تمكين سجل التخزين المؤقت</string>
@@ -631,10 +627,10 @@
<string name="gamecube_controller">ذراع تحكم GameCube</string> <string name="gamecube_controller">ذراع تحكم GameCube</string>
<string name="invert_axis">عكس المحور</string> <string name="invert_axis">عكس المحور</string>
<string name="invert_button">عكس الزر</string> <string name="invert_button">عكس الزر</string>
<string name="toggle_button">زر تشغيل/إيقاف</string> <string name="toggle_button">زر التبديل</string>
<string name="turbo_button">زر التوربو</string> <string name="turbo_button">زر التوربو</string>
<string name="set_threshold">تعيين الحد الفاصل</string> <string name="set_threshold">تعيين الحد الفاصل</string>
<string name="toggle_axis">تشغيل/إيقاف المحور</string> <string name="toggle_axis">تبديل المحور</string>
<string name="connected">متصل</string> <string name="connected">متصل</string>
<string name="use_system_vibrator">استخدم هزاز النظام</string> <string name="use_system_vibrator">استخدم هزاز النظام</string>
<string name="input_overlay">طبقة الإدخال</string> <string name="input_overlay">طبقة الإدخال</string>
@@ -660,7 +656,7 @@
<!-- Miscellaneous --> <!-- Miscellaneous -->
<string name="slider_default">افتراضي</string> <string name="slider_default">افتراضي</string>
<string name="default_string">افتراضي</string> <string name="default_string">الافتراضي</string>
<string name="loading">جارٍ التحميل…</string> <string name="loading">جارٍ التحميل…</string>
<string name="shutting_down">جارٍ إيقاف التشغيل...</string> <string name="shutting_down">جارٍ إيقاف التشغيل...</string>
<string name="reset_setting_confirmation">هل تريد إعادة تعيين هذا الإعداد إلى قيمته الافتراضية؟</string> <string name="reset_setting_confirmation">هل تريد إعادة تعيين هذا الإعداد إلى قيمته الافتراضية؟</string>
@@ -686,8 +682,8 @@
<string name="import_success">تم الاستيراد بنجاح</string> <string name="import_success">تم الاستيراد بنجاح</string>
<string name="export_success">تم التصدير بنجاح</string> <string name="export_success">تم التصدير بنجاح</string>
<string name="start">بدء</string> <string name="start">بدء</string>
<string name="global">الإعدادات العامة</string> <string name="global">المتغيرات العامة</string>
<string name="custom">الإعدادات المخصصة للعبة</string> <string name="custom">مخصص</string>
<string name="import_complete">اكتمل الاستيراد</string> <string name="import_complete">اكتمل الاستيراد</string>
<string name="use_global_setting">استخدام الإعداد العام</string> <string name="use_global_setting">استخدام الإعداد العام</string>
<string name="operation_completed_successfully">اكتملت العملية بنجاح</string> <string name="operation_completed_successfully">اكتملت العملية بنجاح</string>
@@ -766,7 +762,7 @@
<string name="info_description">معرف العنوان، المطور، الإصدار</string> <string name="info_description">معرف العنوان، المطور، الإصدار</string>
<string name="per_game_settings">الإعدادات الخاصة للعبة</string> <string name="per_game_settings">الإعدادات الخاصة للعبة</string>
<string name="per_game_settings_description">تعديل الإعدادات الخاصة بهذه اللعبة</string> <string name="per_game_settings_description">تعديل الإعدادات الخاصة بهذه اللعبة</string>
<string name="launch_options">إعدادات التشغيل</string> <string name="launch_options">تشغيل الإعدادات</string>
<string name="path">المسار</string> <string name="path">المسار</string>
<string name="program_id">معرف العنوان</string> <string name="program_id">معرف العنوان</string>
<string name="developer">المطور</string> <string name="developer">المطور</string>
@@ -878,13 +874,13 @@
<!-- Emulation Menu --> <!-- Emulation Menu -->
<string name="emulation_exit">خروج من المحاكاة</string> <string name="emulation_exit">خروج من المحاكاة</string>
<string name="emulation_done">إنهاء</string> <string name="emulation_done">إنهاء</string>
<string name="emulation_toggle_controls">تشغيل/إيقاف أزرار التحكم</string> <string name="emulation_toggle_controls">تبديل أزرار التحكم</string>
<string name="emulation_rel_stick_center">مركز العصا النسبي</string> <string name="emulation_rel_stick_center">مركز العصا النسبي</string>
<string name="emulation_dpad_slide">انزلاق الأسهم</string> <string name="emulation_dpad_slide">انزلاق الأسهم</string>
<string name="emulation_haptics">الاهتزازات الديناميكية</string> <string name="emulation_haptics">الاهتزازات الديناميكية</string>
<string name="emulation_show_overlay">عرض ذراع التحكم</string> <string name="emulation_show_overlay">عرض ذراع التحكم</string>
<string name="emulation_hide_overlay">إخفاء ذراع التحكم</string> <string name="emulation_hide_overlay">إخفاء ذراع التحكم</string>
<string name="emulation_toggle_all">تشغيل/إيقاف الكل</string> <string name="emulation_toggle_all">تبديل الكل</string>
<string name="emulation_control_adjust">ضبط الطبقة</string> <string name="emulation_control_adjust">ضبط الطبقة</string>
<string name="emulation_control_scale">الحجم</string> <string name="emulation_control_scale">الحجم</string>
<string name="emulation_control_opacity">الشفافية</string> <string name="emulation_control_opacity">الشفافية</string>
@@ -1126,7 +1122,7 @@
<string name="enable_overlay_description">تمكين التطبيق الصغير المدمج في Horizon. اضغط مع الاستمرار على زر الشاشة الرئيسية لمدة 1 ثانية لإظهاره.</string> <string name="enable_overlay_description">تمكين التطبيق الصغير المدمج في Horizon. اضغط مع الاستمرار على زر الشاشة الرئيسية لمدة 1 ثانية لإظهاره.</string>
<!-- Profile Management --> <!-- Profile Management -->
<string name="profile_manager">إدارة ملف التعريف</string> <string name="profile_manager">مدير الملف الشخصي</string>
<string name="profile_manager_description">إدارة ملفات تعريف المستخدمين</string> <string name="profile_manager_description">إدارة ملفات تعريف المستخدمين</string>
<string name="profile_add_user">إضافة مستخدم</string> <string name="profile_add_user">إضافة مستخدم</string>
<string name="profile_new_user">مستخدم جديد</string> <string name="profile_new_user">مستخدم جديد</string>
@@ -272,7 +272,7 @@
<string name="general_information">Información general</string> <string name="general_information">Información general</string>
<string name="hardware">Hardware</string> <string name="hardware">Hardware</string>
<string name="supported_abis">ABIs soportadas</string> <string name="supported_abis">ABIs soportadas</string>
<string name="cpu_info">Información del procesador</string> <string name="cpu_info">Información de la CPU</string>
<string name="gpu_information">Información de la GPU</string> <string name="gpu_information">Información de la GPU</string>
<string name="vulkan_driver_version">Versión del controlador de Vulkan</string> <string name="vulkan_driver_version">Versión del controlador de Vulkan</string>
<string name="error_getting_emulator_info">Error al obtener la información del emulador</string> <string name="error_getting_emulator_info">Error al obtener la información del emulador</string>
@@ -351,7 +351,7 @@
<item quantity="other">%d archivos de guardado importados con éxito.</item> <item quantity="other">%d archivos de guardado importados con éxito.</item>
</plurals> </plurals>
<string name="no_save_data_found">No se encontraron datos de guardado</string> <string name="no_save_data_found">No se encontraron datos de guardado</string>
<string name="verify_installed_content">Verificar los contenidos instalados</string> <string name="verify_installed_content">Verificar contenido instalado</string>
<string name="verify_installed_content_description">Comprueba todo el contenido instalado por si hubiese alguno corrupto</string> <string name="verify_installed_content_description">Comprueba todo el contenido instalado por si hubiese alguno corrupto</string>
<string name="keys_missing">Faltan las claves de encriptación</string> <string name="keys_missing">Faltan las claves de encriptación</string>
@@ -416,8 +416,8 @@
<string name="turbo_speed_limit_description">Cuando el modo turbo esté activado, la emulación se ejecutará a esta velocidad.</string> <string name="turbo_speed_limit_description">Cuando el modo turbo esté activado, la emulación se ejecutará a esta velocidad.</string>
<string name="slow_speed_limit">Velocidad lenta</string> <string name="slow_speed_limit">Velocidad lenta</string>
<string name="slow_speed_limit_description">Cuando el modo lento esté activado, la emulación se ejecutará a esta velocidad.</string> <string name="slow_speed_limit_description">Cuando el modo lento esté activado, la emulación se ejecutará a esta velocidad.</string>
<string name="cpu_backend">Motor del procesador</string> <string name="cpu_backend">Motor de la CPU</string>
<string name="cpu_accuracy">Precisión del procesador</string> <string name="cpu_accuracy">Precisión de la CPU</string>
<string name="value_with_units">%1$s%2$s</string> <string name="value_with_units">%1$s%2$s</string>
<!-- System settings strings --> <!-- System settings strings -->
@@ -433,7 +433,7 @@
<string name="set_custom_rtc">Configurar RTC personalizado</string> <string name="set_custom_rtc">Configurar RTC personalizado</string>
<!-- CPU --> <!-- CPU -->
<string name="fast_cpu_time">Overclock del procesador</string> <string name="fast_cpu_time">Overclock de la CPU</string>
<string name="fast_cpu_time_description">Fuerza a la CPU emulada a ejecutarser a una velocidad de reloj más alta, lo cual reduce ciertos limitadores de fotogramas por segundo. Usa Boost (1700 MHz) para ejecutar a la velocidad de reloj nativa más alta de la Switch, o Fast (2000 MHz) para ejecutar a una velocidad doble de reloj.</string> <string name="fast_cpu_time_description">Fuerza a la CPU emulada a ejecutarser a una velocidad de reloj más alta, lo cual reduce ciertos limitadores de fotogramas por segundo. Usa Boost (1700 MHz) para ejecutar a la velocidad de reloj nativa más alta de la Switch, o Fast (2000 MHz) para ejecutar a una velocidad doble de reloj.</string>
<string name="custom_cpu_ticks">Ticks de CPU personalizados</string> <string name="custom_cpu_ticks">Ticks de CPU personalizados</string>
<string name="custom_cpu_ticks_description">Establezca un valor personalizado de los ciclos de la CPU. Los valores más altos pueden aumentar el rendimiento, pero también pueden hacer que el juego se congele. Se recomienda un rango de 7721000.</string> <string name="custom_cpu_ticks_description">Establezca un valor personalizado de los ciclos de la CPU. Los valores más altos pueden aumentar el rendimiento, pero también pueden hacer que el juego se congele. Se recomienda un rango de 7721000.</string>
@@ -480,10 +480,6 @@
<string name="use_disk_shader_cache_description">Reduce los tirones almacenando y cargando los sombreadores generados.</string> <string name="use_disk_shader_cache_description">Reduce los tirones almacenando y cargando los sombreadores generados.</string>
<string name="renderer_force_max_clock">Forzar velocidad al máximo (solo Adreno)</string> <string name="renderer_force_max_clock">Forzar velocidad al máximo (solo Adreno)</string>
<string name="renderer_force_max_clock_description">Fuerza a la GPU a ejecutarse a la velocidad máxima de reloj posible (se seguirán aplicando restricciones térmicas).</string> <string name="renderer_force_max_clock_description">Fuerza a la GPU a ejecutarse a la velocidad máxima de reloj posible (se seguirán aplicando restricciones térmicas).</string>
<string name="renderer_asynchronous_gpu_emulation">Emulación de GPU asíncrona</string>
<string name="renderer_asynchronous_gpu_emulation_description">Ejecuta la emulación de la GPU de forma asíncrona para reducir los bloqueos de la CPU y mejorar el rendimiento. Desactiva esta opción solo si experimentas problemas de sincronización.</string>
<string name="renderer_async_presentation">Presentación asíncrona</string>
<string name="renderer_async_presentation_description">Mejora ligeramente el rendimiento al mover la presentación a un hilo independiente de la CPU.</string>
<string name="renderer_reactive_flushing">Usar limpieza reactiva</string> <string name="renderer_reactive_flushing">Usar limpieza reactiva</string>
<string name="renderer_reactive_flushing_description">Mejora la precisión de renderizado en algunos juegos, pero reduce el rendimiento.</string> <string name="renderer_reactive_flushing_description">Mejora la precisión de renderizado en algunos juegos, pero reduce el rendimiento.</string>
<string name="enable_buffer_history">Activar el historial del búfer</string> <string name="enable_buffer_history">Activar el historial del búfer</string>
@@ -536,7 +532,7 @@
<string name="warning_resolution">Escalar la resolución a 2x o más puede causar problemas y ralentizar significativamente su dispositivo.</string> <string name="warning_resolution">Escalar la resolución a 2x o más puede causar problemas y ralentizar significativamente su dispositivo.</string>
<!-- Debug settings strings --> <!-- Debug settings strings -->
<string name="cpu">Procesador</string> <string name="cpu">CPU</string>
<string name="use_auto_stub">Usar Auto Stub</string> <string name="use_auto_stub">Usar Auto Stub</string>
<string name="use_auto_stub_description">Rellena automáticamente servicios y funciones ausentes. Puede mejorar la compatibilidad pero puede causar cierres inesperados.</string> <string name="use_auto_stub_description">Rellena automáticamente servicios y funciones ausentes. Puede mejorar la compatibilidad pero puede causar cierres inesperados.</string>
@@ -482,10 +482,6 @@
<string name="use_disk_shader_cache_description">Уменьшение зависаний за счет хранения и загрузки сгенерированных шейдеров.</string> <string name="use_disk_shader_cache_description">Уменьшение зависаний за счет хранения и загрузки сгенерированных шейдеров.</string>
<string name="renderer_force_max_clock">Принудительная максимальная тактовая частота (только для Adreno)</string> <string name="renderer_force_max_clock">Принудительная максимальная тактовая частота (только для Adreno)</string>
<string name="renderer_force_max_clock_description">Заставляет ГПУ работать на максимально возможных тактовых частотах (тепловые ограничения все равно будут применяться).</string> <string name="renderer_force_max_clock_description">Заставляет ГПУ работать на максимально возможных тактовых частотах (тепловые ограничения все равно будут применяться).</string>
<string name="renderer_asynchronous_gpu_emulation">Асинхронная эмуляция ГПУ</string>
<string name="renderer_asynchronous_gpu_emulation_description">Выполняет эмуляцию ГПУ асинхронно для снижения задержек ЦП и увеличения производительности. Отключайте только при возникновении проблем с таймингами.</string>
<string name="renderer_async_presentation">Асинхронная презентация</string>
<string name="renderer_async_presentation_description">Немного улучшает производительность, перемещая презентацию в отдельный поток ЦП.</string>
<string name="renderer_reactive_flushing">Реактивная очистка</string> <string name="renderer_reactive_flushing">Реактивная очистка</string>
<string name="renderer_reactive_flushing_description">Повышение точности рендеринга в некоторых играх за счет снижения производительности.</string> <string name="renderer_reactive_flushing_description">Повышение точности рендеринга в некоторых играх за счет снижения производительности.</string>
<string name="enable_buffer_history">Включить историю буфера</string> <string name="enable_buffer_history">Включить историю буфера</string>
@@ -1084,7 +1080,6 @@
<string name="app_language_system">Следовать системе</string> <string name="app_language_system">Следовать системе</string>
<!-- Static Themes --> <!-- Static Themes -->
<string name="static_theme_color">Цвет темы</string> <string name="static_theme_color">Цвет темы</string>
<string name="eden_theme">Eden</string>
<string name="violet">Фиолетовый </string> <string name="violet">Фиолетовый </string>
<string name="blue">Синий</string> <string name="blue">Синий</string>
<string name="cyan">Циановый</string> <string name="cyan">Циановый</string>
@@ -482,10 +482,6 @@
<string name="use_disk_shader_cache_description">Зменшує затримки шляхом збереження шейдерів.</string> <string name="use_disk_shader_cache_description">Зменшує затримки шляхом збереження шейдерів.</string>
<string name="renderer_force_max_clock">Максимальна тактова частота (тільки Adreno)</string> <string name="renderer_force_max_clock">Максимальна тактова частота (тільки Adreno)</string>
<string name="renderer_force_max_clock_description">Змушує GPU працювати на максимальній тактовій частоті.</string> <string name="renderer_force_max_clock_description">Змушує GPU працювати на максимальній тактовій частоті.</string>
<string name="renderer_asynchronous_gpu_emulation">Асинхронна емуляція ГП</string>
<string name="renderer_asynchronous_gpu_emulation_description">Емуляція ГП виконується асинхронно для зменшення затримок ЦП й покращення пропускної здатності. Вимкніть лише у випадку виникнення проблем із таймінгами.</string>
<string name="renderer_async_presentation">Асинхронне подання</string>
<string name="renderer_async_presentation_description">Трохи покращує продуктивність завдяки переміщенню подання на окремий потік ЦП.</string>
<string name="renderer_reactive_flushing">Реактивне очищення</string> <string name="renderer_reactive_flushing">Реактивне очищення</string>
<string name="renderer_reactive_flushing_description">Покращує точність рендерингу в деяких іграх.</string> <string name="renderer_reactive_flushing_description">Покращує точність рендерингу в деяких іграх.</string>
<string name="enable_buffer_history">Увімкнути історію буфера</string> <string name="enable_buffer_history">Увімкнути історію буфера</string>
@@ -55,7 +55,7 @@
<string name="stats_overlay_options_description">配置性能统计叠加层中显示的信息</string> <string name="stats_overlay_options_description">配置性能统计叠加层中显示的信息</string>
<string name="show_fps">显示帧率</string> <string name="show_fps">显示帧率</string>
<string name="show_fps_description">显示当前帧率</string> <string name="show_fps_description">显示当前帧率</string>
<string name="show_frametime">显示 Frametime</string> <string name="show_frametime">显示帧时间</string>
<string name="show_app_ram_usage">显示应用内存使用情况</string> <string name="show_app_ram_usage">显示应用内存使用情况</string>
<string name="show_app_ram_usage_description">显示模拟器内存用量</string> <string name="show_app_ram_usage_description">显示模拟器内存用量</string>
<string name="show_system_ram_usage">显示系统内存使用情况</string> <string name="show_system_ram_usage">显示系统内存使用情况</string>
@@ -476,25 +476,21 @@
<string name="use_disk_shader_cache_description">将生成的着色器缓存于磁盘中并进行读取,以减少卡顿。</string> <string name="use_disk_shader_cache_description">将生成的着色器缓存于磁盘中并进行读取,以减少卡顿。</string>
<string name="renderer_force_max_clock">强制最大时钟 (仅限 Adreno)</string> <string name="renderer_force_max_clock">强制最大时钟 (仅限 Adreno)</string>
<string name="renderer_force_max_clock_description">强制 GPU 以最大时钟运行 (仍被温控限制)。</string> <string name="renderer_force_max_clock_description">强制 GPU 以最大时钟运行 (仍被温控限制)。</string>
<string name="renderer_asynchronous_gpu_emulation">GPU 异步模拟</string>
<string name="renderer_asynchronous_gpu_emulation_description">异步运行 GPU 模拟,以减少 CPU 停顿并提高吞吐量。仅当遇到与时序相关的问题时才应禁用此功能。</string>
<string name="renderer_async_presentation">异步呈现</string>
<string name="renderer_async_presentation_description">通过将呈现操作移至单独的 CPU 线程来略微提升性能。</string>
<string name="renderer_reactive_flushing">启用反应性刷新</string> <string name="renderer_reactive_flushing">启用反应性刷新</string>
<string name="renderer_reactive_flushing_description">通过牺牲性能提高某些游戏的渲染精度。</string> <string name="renderer_reactive_flushing_description">牺牲性能提高某些游戏的渲染精度。</string>
<string name="enable_buffer_history">启用缓冲区历史</string> <string name="enable_buffer_history">启用缓冲区历史</string>
<string name="enable_buffer_history_description">启用对先前缓冲状态的访问。此选项可在某些游戏中提升渲染质量并保持性能一致性。</string> <string name="enable_buffer_history_description">允许访问之前的缓冲状态。\n这个选项可能会提升某些游戏渲染质量性能一致性。</string>
<string name="use_optimized_vertex_buffers">优化顶点缓冲区</string> <string name="use_optimized_vertex_buffers">优化顶点缓冲区</string>
<string name="use_optimized_vertex_buffers_description">启用经过优化顶点缓冲区绑定以提升性能。需要 Mesa 26.0 及以上版本的 Turnip 驱动程序。在旧版驱动程序上会导致程序崩溃。</string> <string name="use_optimized_vertex_buffers_description">实现优化顶点缓冲区绑定以提升性能。需要 Mesa 26.0+ Turnip 驱动。老驱动会崩溃。</string>
<string name="hacks">Hacks</string> <string name="hacks">Hacks</string>
<string name="fast_gpu_time">GPU 超频频率</string> <string name="fast_gpu_time">GPU 超频频率</string>
<string name="fast_gpu_time_description">强制大多数游戏以其最高原生分辨率运行。设置为 256 获得最性能,设置为 512 获得最佳画面保真度。</string> <string name="fast_gpu_time_description">强制大多数游戏以其最高原生分辨率运行。使用 256 获得最性能,使用 512 获得最大图形保真度。</string>
<string name="skip_cpu_inner_invalidation">跳过CPU内部无效化</string> <string name="skip_cpu_inner_invalidation">跳过CPU内部无效化</string>
<string name="skip_cpu_inner_invalidation_description">在内存更新期间跳过某些CPU端缓存无效化,减少CPU使用率并提高其性能。可能会导致某些游戏出现故障或崩溃。</string> <string name="skip_cpu_inner_invalidation_description">在内存更新期间跳过某些CPU端缓存无效化,减少CPU使用率并提高其性能。可能会导致某些游戏出现故障或崩溃。</string>
<string name="fix_bloom_effects">修复 Bloom 效果</string> <string name="fix_bloom_effects">修复泛光效果</string>
<string name="fix_bloom_effects_description">减少《塞尔达传说:智慧的再现》Adreno 700)中的 bloom 模糊,并移除《Burnout》中的 bloom 效果。警告:可能会导致其他游戏出现画面显示问题</string> <string name="fix_bloom_effects_description">减少《塞尔达传说:智慧的再现》(LA/EOW) 在 Adreno 700 系列 GPU 上的模糊,并移除《横冲直撞》(Burnout) 中的模糊特效。警告:可能会导致其他游戏出现画面花屏或异常</string>
<string name="renderer_asynchronous_shaders">使用异步着色器</string> <string name="renderer_asynchronous_shaders">使用异步着色器</string>
<string name="renderer_asynchronous_shaders_description">异步编译着色器。这可能会减少卡顿,但也可能会导致图形错误。</string> <string name="renderer_asynchronous_shaders_description">异步编译着色器。这可能会减少卡顿,但也可能会导致图形错误。</string>
<string name="gpu_unswizzle_settings">GPU 还原设置</string> <string name="gpu_unswizzle_settings">GPU 还原设置</string>
@@ -501,7 +501,7 @@
<string name="enable_buffer_history">Enable buffer history</string> <string name="enable_buffer_history">Enable buffer history</string>
<string name="enable_buffer_history_description">Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games.</string> <string name="enable_buffer_history_description">Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games.</string>
<string name="use_optimized_vertex_buffers">Optimized Vertex Buffers</string> <string name="use_optimized_vertex_buffers">Optimized Vertex Buffers</string>
<string name="use_optimized_vertex_buffers_description">Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers.</string> <string name="use_optimized_vertex_buffers_description">Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers. Will crash on older drivers.</string>
<string name="hacks">Hacks</string> <string name="hacks">Hacks</string>
@@ -510,9 +510,7 @@
<string name="skip_cpu_inner_invalidation">Skip CPU Inner Invalidation</string> <string name="skip_cpu_inner_invalidation">Skip CPU Inner Invalidation</string>
<string name="skip_cpu_inner_invalidation_description">Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games.</string> <string name="skip_cpu_inner_invalidation_description">Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games.</string>
<string name="fix_bloom_effects">Fix Bloom Effects</string> <string name="fix_bloom_effects">Fix Bloom Effects</string>
<string name="fix_bloom_effects_description">Reduces bloom blur in LA/EOW (Adreno A6XX - A7XX/ Turnip), removes bloom in Burnout. Warning: may cause graphical artifacts in other games.</string> <string name="fix_bloom_effects_description">Reduces bloom blur in LA/EOW (Adreno 700), removes bloom in Burnout. Warning: may cause graphical artifacts in other games.</string>
<string name="emulate_bgr565">Emulate BGR565</string>
<string name="emulate_bgr565_description">Fixes problems with inverted colors in games or strange artifacts or strange shadows.</string>
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string> <string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string> <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string> <string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
+6
View File
@@ -183,6 +183,8 @@ if(ARCHITECTURE_x86_64)
x64/cpu_detect.h x64/cpu_detect.h
x64/cpu_wait.cpp x64/cpu_wait.cpp
x64/cpu_wait.h x64/cpu_wait.h
x64/native_clock.cpp
x64/native_clock.h
x64/rdtsc.cpp x64/rdtsc.cpp
x64/rdtsc.h x64/rdtsc.h
x64/xbyak_abi.h x64/xbyak_abi.h
@@ -190,6 +192,10 @@ if(ARCHITECTURE_x86_64)
target_link_libraries(common PRIVATE xbyak::xbyak) target_link_libraries(common PRIVATE xbyak::xbyak)
endif() endif()
if(HAS_NCE)
target_sources(common PRIVATE arm64/native_clock.cpp arm64/native_clock.h)
endif()
if(MSVC) if(MSVC)
target_compile_definitions( target_compile_definitions(
common common
@@ -243,9 +243,6 @@ void AndroidKeyboard::SubmitInlineKeyboardInput(int key_code) {
static_cast<s32>(m_current_text.size())); static_cast<s32>(m_current_text.size()));
break; break;
case KEYCODE_DEL: case KEYCODE_DEL:
if (m_current_text.empty()) {
return;
}
m_current_text.pop_back(); m_current_text.pop_back();
submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, m_current_text, submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, m_current_text,
static_cast<int>(m_current_text.size())); static_cast<int>(m_current_text.size()));
+87
View File
@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef ANDROID
#include <sys/system_properties.h>
#endif
#include "common/arm64/native_clock.h"
namespace Common::Arm64 {
namespace {
NativeClock::FactorType GetFixedPointFactor(u64 num, u64 den) {
return (static_cast<NativeClock::FactorType>(num) << 64) / den;
}
u64 MultiplyHigh(u64 m, NativeClock::FactorType factor) {
return static_cast<u64>((m * factor) >> 64);
}
} // namespace
NativeClock::NativeClock() {
const u64 host_cntfrq = GetHostCNTFRQ();
ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
}
std::chrono::nanoseconds NativeClock::GetTimeNS() const {
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_cntfrq_factor)};
}
std::chrono::microseconds NativeClock::GetTimeUS() const {
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_cntfrq_factor)};
}
std::chrono::milliseconds NativeClock::GetTimeMS() const {
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_cntfrq_factor)};
}
s64 NativeClock::GetCNTPCT() const {
return MultiplyHigh(GetUptime(), guest_cntfrq_factor);
}
s64 NativeClock::GetGPUTick() const {
return MultiplyHigh(GetUptime(), gputick_cntfrq_factor);
}
s64 NativeClock::GetUptime() const {
s64 cntvct_el0 = 0;
asm volatile("dsb ish\n\t"
"mrs %[cntvct_el0], cntvct_el0\n\t"
"dsb ish\n\t"
: [cntvct_el0] "=r"(cntvct_el0));
return cntvct_el0;
}
bool NativeClock::IsNative() const {
return true;
}
s64 NativeClock::GetHostCNTFRQ() {
u64 cntfrq_el0 = 0;
std::string_view board{""};
#ifdef ANDROID
char buffer[PROP_VALUE_MAX];
int len{__system_property_get("ro.product.board", buffer)};
board = std::string_view(buffer, static_cast<size_t>(len));
#endif
if (board == "s5e9925") { // Exynos 2200
cntfrq_el0 = 25600000;
} else if (board == "exynos2100") { // Exynos 2100
cntfrq_el0 = 26000000;
} else if (board == "exynos9810") { // Exynos 9810
cntfrq_el0 = 26000000;
} else if (board == "s5e8825") { // Exynos 1280
cntfrq_el0 = 26000000;
} else {
asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
}
return cntfrq_el0;
}
} // namespace Common::Arm64
+45
View File
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/wall_clock.h"
namespace Common::Arm64 {
class NativeClock final : public WallClock {
public:
explicit NativeClock();
std::chrono::nanoseconds GetTimeNS() const override;
std::chrono::microseconds GetTimeUS() const override;
std::chrono::milliseconds GetTimeMS() const override;
s64 GetCNTPCT() const override;
s64 GetGPUTick() const override;
s64 GetUptime() const override;
bool IsNative() const override;
static s64 GetHostCNTFRQ();
public:
using FactorType = unsigned __int128;
FactorType GetGuestCNTFRQFactor() const {
return guest_cntfrq_factor;
}
private:
FactorType ns_cntfrq_factor;
FactorType us_cntfrq_factor;
FactorType ms_cntfrq_factor;
FactorType guest_cntfrq_factor;
FactorType gputick_cntfrq_factor;
};
} // namespace Common::Arm64
-139
View File
@@ -1,139 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <deque>
#include <memory>
#include <type_traits>
#include "common/common_types.h"
namespace Common {
template <class Traits>
class LeastRecentlyUsedCache {
using ObjectType = typename Traits::ObjectType;
using TickType = typename Traits::TickType;
struct Item {
ObjectType obj;
TickType tick;
Item* next{};
Item* prev{};
};
public:
LeastRecentlyUsedCache() : first_item{}, last_item{} {}
~LeastRecentlyUsedCache() = default;
size_t Insert(ObjectType obj, TickType tick) {
const auto new_id = Build();
auto& item = item_pool[new_id];
item.obj = obj;
item.tick = tick;
Attach(item);
return new_id;
}
void Touch(size_t id, TickType tick) {
auto& item = item_pool[id];
if (item.tick >= tick) {
return;
}
item.tick = tick;
if (&item == last_item) {
return;
}
Detach(item);
Attach(item);
}
void Free(size_t id) {
auto& item = item_pool[id];
Detach(item);
item.prev = nullptr;
item.next = nullptr;
free_items.push_back(id);
}
template <typename Func>
void ForEachItemBelow(TickType tick, Func&& func) {
static constexpr bool RETURNS_BOOL =
std::is_same_v<std::invoke_result<Func, ObjectType>, bool>;
Item* iterator = first_item;
while (iterator) {
if (static_cast<s64>(tick) - static_cast<s64>(iterator->tick) < 0) {
return;
}
Item* next = iterator->next;
if constexpr (RETURNS_BOOL) {
if (func(iterator->obj)) {
return;
}
} else {
func(iterator->obj);
}
iterator = next;
}
}
private:
size_t Build() {
if (free_items.empty()) {
const size_t item_id = item_pool.size();
auto& item = item_pool.emplace_back();
item.next = nullptr;
item.prev = nullptr;
return item_id;
}
const size_t item_id = free_items.front();
free_items.pop_front();
auto& item = item_pool[item_id];
item.next = nullptr;
item.prev = nullptr;
return item_id;
}
void Attach(Item& item) {
if (!first_item) {
first_item = &item;
}
if (!last_item) {
last_item = &item;
} else {
item.prev = last_item;
last_item->next = &item;
item.next = nullptr;
last_item = &item;
}
}
void Detach(Item& item) {
if (item.prev) {
item.prev->next = item.next;
}
if (item.next) {
item.next->prev = item.prev;
}
if (&item == first_item) {
first_item = item.next;
if (first_item) {
first_item->prev = nullptr;
}
}
if (&item == last_item) {
last_item = item.prev;
if (last_item) {
last_item->next = nullptr;
}
}
}
std::deque<Item> item_pool;
std::deque<size_t> free_items;
Item* first_item{};
Item* last_item{};
};
} // namespace Common
+26 -15
View File
@@ -13,13 +13,13 @@
namespace Common { namespace Common {
template <typename BaseAddr> template <typename BaseAddr>
MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_, std::size_t first_level_bits_, std::size_t page_bits_) MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_,
: address_space_bits{address_space_bits_} std::size_t first_level_bits_,
, first_level_bits{first_level_bits_} std::size_t page_bits_)
, page_bits{page_bits_} : address_space_bits{address_space_bits_},
{ first_level_bits{first_level_bits_}, page_bits{page_bits_} {
if (page_bits == 0) { if (page_bits == 0) {
return; return;
} }
first_level_shift = address_space_bits - first_level_bits; first_level_shift = address_space_bits - first_level_bits;
first_level_chunk_size = (1ULL << (first_level_shift - page_bits)) * sizeof(BaseAddr); first_level_chunk_size = (1ULL << (first_level_shift - page_bits)) * sizeof(BaseAddr);
@@ -30,9 +30,12 @@ MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bit
void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)}; void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)};
#else #else
void* base{mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)}; void* base{mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)};
if (base == MAP_FAILED)
if (base == MAP_FAILED) {
base = nullptr; base = nullptr;
}
#endif #endif
ASSERT(base); ASSERT(base);
base_ptr = reinterpret_cast<BaseAddr*>(base); base_ptr = reinterpret_cast<BaseAddr*>(base);
} }
@@ -53,21 +56,29 @@ template <typename BaseAddr>
void MultiLevelPageTable<BaseAddr>::ReserveRange(u64 start, std::size_t size) { void MultiLevelPageTable<BaseAddr>::ReserveRange(u64 start, std::size_t size) {
const u64 new_start = start >> first_level_shift; const u64 new_start = start >> first_level_shift;
const u64 new_end = (start + size) >> first_level_shift; const u64 new_end = (start + size) >> first_level_shift;
for (u64 i = new_start; i <= new_end; i++) for (u64 i = new_start; i <= new_end; i++) {
if (!first_level_map[i]) if (!first_level_map[i]) {
AllocateLevel(i); AllocateLevel(i);
}
}
} }
template <typename BaseAddr> template <typename BaseAddr>
void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 index) { void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 level) {
void* ptr = reinterpret_cast<char *>(base_ptr) + index * first_level_chunk_size; void* ptr = reinterpret_cast<char *>(base_ptr) + level * first_level_chunk_size;
#ifdef _WIN32 #ifdef _WIN32
void* base = VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE); void* base{VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE)};
ASSERT(base);
#else #else
void* base = ptr; void* base{mmap(ptr, first_level_chunk_size, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)};
if (base == MAP_FAILED) {
base = nullptr;
}
#endif #endif
first_level_map[index] = base; ASSERT(base);
first_level_map[level] = base;
} }
} // namespace Common } // namespace Common
+4 -7
View File
@@ -555,9 +555,6 @@ struct Values {
SwitchableSetting<bool> fix_bloom_effects{linkage, false, "fix_bloom_effects", SwitchableSetting<bool> fix_bloom_effects{linkage, false, "fix_bloom_effects",
Category::RendererHacks}; Category::RendererHacks};
SwitchableSetting<bool> emulate_bgr565{linkage, false, "emulate_bgr565",
Category::RendererHacks};
SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack", SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack",
Category::RendererHacks}; Category::RendererHacks};
@@ -587,7 +584,7 @@ struct Values {
SwitchableSetting<ExtendedDynamicState> dyna_state{linkage, SwitchableSetting<ExtendedDynamicState> dyna_state{linkage,
#if defined(ANDROID) #if defined(ANDROID)
ExtendedDynamicState::Disabled, ExtendedDynamicState::EDS1,
#elif defined(__APPLE__) #elif defined(__APPLE__)
ExtendedDynamicState::Disabled, ExtendedDynamicState::Disabled,
#else #else
@@ -639,8 +636,8 @@ struct Values {
Category::System}; Category::System};
SwitchableSetting<Region, true> region_index{linkage, Region::Usa, "region_index", Category::System}; SwitchableSetting<Region, true> region_index{linkage, Region::Usa, "region_index", Category::System};
SwitchableSetting<TimeZone, true> time_zone_index{linkage, TimeZone::Auto, "time_zone_index", Category::System}; SwitchableSetting<TimeZone, true> time_zone_index{linkage, TimeZone::Auto, "time_zone_index", Category::System};
Setting<u32> serial_battery{linkage, 0, "serial_battery", Category::Debugging}; Setting<u32> serial_battery{linkage, 0, "serial_battery", Category::System};
Setting<u32> serial_unit{linkage, 0, "serial_unit", Category::Debugging}; Setting<u32> serial_unit{linkage, 0, "serial_unit", Category::System};
// Measured in seconds since epoch // Measured in seconds since epoch
SwitchableSetting<bool> custom_rtc_enabled{linkage, false, "custom_rtc_enabled", Category::System, Specialization::Paired, true, true}; SwitchableSetting<bool> custom_rtc_enabled{linkage, false, "custom_rtc_enabled", Category::System, Specialization::Paired, true, true};
SwitchableSetting<s64> custom_rtc{ SwitchableSetting<s64> custom_rtc{
@@ -810,7 +807,7 @@ struct Values {
0, 0,
65535, 65535,
"debug_knobs", "debug_knobs",
Category::Debugging, Category::Core,
Specialization::Countable, Specialization::Countable,
true, true,
true}; true};
+47 -166
View File
@@ -1,196 +1,77 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/steady_clock.h" #include "common/steady_clock.h"
#include "common/uint128.h"
#include "common/wall_clock.h" #include "common/wall_clock.h"
#ifdef __ANDROID__
#include <sys/system_properties.h>
#endif
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h" #include "common/x64/cpu_detect.h"
#include "common/x64/native_clock.h"
#include "common/x64/rdtsc.h" #include "common/x64/rdtsc.h"
#endif #endif
#ifdef HAS_NCE
#include "common/arm64/native_clock.h"
#endif
namespace Common { namespace Common {
#if defined(ARCHITECTURE_x86_64) class StandardWallClock final : public WallClock {
WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept public:
: invariant{invariant_} explicit StandardWallClock() {}
, rdtsc_frequency{rdtsc_frequency_}
, ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency_)}
, us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency_)}
, ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency_)}
, cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency_)}
, gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency_)}
{}
std::chrono::nanoseconds WallClock::GetTimeNS() const { std::chrono::nanoseconds GetTimeNS() const override {
if (invariant) return std::chrono::duration_cast<std::chrono::nanoseconds>(
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()); std::chrono::system_clock::now().time_since_epoch());
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_rdtsc_factor)};
}
std::chrono::microseconds WallClock::GetTimeUS() const {
if (invariant)
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_rdtsc_factor)};
}
std::chrono::milliseconds WallClock::GetTimeMS() const {
if (invariant)
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_rdtsc_factor)};
}
s64 WallClock::GetCNTPCT() const {
if (invariant)
return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor);
}
s64 WallClock::GetGPUTick() const {
if (invariant)
return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
return MultiplyHigh(GetUptime(), gputick_rdtsc_factor);
}
s64 WallClock::GetUptime() const {
if (invariant)
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
return s64(Common::X64::FencedRDTSC());
}
bool WallClock::IsNative() const {
if (invariant)
return false;
return true;
}
#elif defined(HAS_NCE)
namespace {
[[nodiscard]] WallClock::FactorType GetFixedPointFactor(u64 num, u64 den) noexcept {
return (WallClock::FactorType(num) << 64) / den;
}
[[nodiscard]] u64 MultiplyHigh(u64 m, WallClock::FactorType factor) noexcept {
return static_cast<u64>((m * factor) >> 64);
}
[[nodiscard]] s64 GetHostCNTFRQ() noexcept {
u64 cntfrq_el0 = 0;
#ifdef ANDROID
std::string_view board{""};
char buffer[PROP_VALUE_MAX];
int len{__system_property_get("ro.product.board", buffer)};
board = std::string_view(buffer, static_cast<size_t>(len));
if (board == "s5e9925") { // Exynos 2200
cntfrq_el0 = 25600000;
} else if (board == "exynos2100") { // Exynos 2100
cntfrq_el0 = 26000000;
} else if (board == "exynos9810") { // Exynos 9810
cntfrq_el0 = 26000000;
} else if (board == "s5e8825") { // Exynos 1280
cntfrq_el0 = 26000000;
} else {
asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
} }
return cntfrq_el0;
#else
asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
return cntfrq_el0;
#endif
}
} // namespace std::chrono::microseconds GetTimeUS() const override {
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch());
}
WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept { std::chrono::milliseconds GetTimeMS() const override {
const u64 host_cntfrq = std::max<u64>(GetHostCNTFRQ(), 1); return std::chrono::duration_cast<std::chrono::milliseconds>(
ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq); std::chrono::system_clock::now().time_since_epoch());
us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq); }
ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
}
std::chrono::nanoseconds WallClock::GetTimeNS() const { s64 GetCNTPCT() const override {
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_cntfrq_factor)}; return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
} }
std::chrono::microseconds WallClock::GetTimeUS() const { s64 GetGPUTick() const override {
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_cntfrq_factor)}; return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
} }
std::chrono::milliseconds WallClock::GetTimeMS() const { s64 GetUptime() const override {
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_cntfrq_factor)}; return std::chrono::duration_cast<std::chrono::nanoseconds>(
} std::chrono::steady_clock::now().time_since_epoch())
.count();
}
s64 WallClock::GetCNTPCT() const { bool IsNative() const override {
return MultiplyHigh(GetUptime(), guest_cntfrq_factor); return false;
} }
};
s64 WallClock::GetGPUTick() const { std::unique_ptr<WallClock> CreateOptimalClock() {
return MultiplyHigh(GetUptime(), gputick_cntfrq_factor);
}
s64 WallClock::GetUptime() const {
s64 cntvct_el0 = 0;
asm volatile(
"dsb ish\n\t"
"mrs %[cntvct_el0], cntvct_el0\n\t"
"dsb ish\n\t"
: [cntvct_el0] "=r"(cntvct_el0)
);
return cntvct_el0;
}
bool WallClock::IsNative() const {
return true;
}
#else
WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept {}
std::chrono::nanoseconds WallClock::GetTimeNS() const {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch());
}
std::chrono::microseconds WallClock::GetTimeUS() const {
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());
}
std::chrono::milliseconds WallClock::GetTimeMS() const {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
}
s64 WallClock::GetCNTPCT() const {
return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
}
s64 WallClock::GetGPUTick() const {
return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
}
s64 WallClock::GetUptime() const {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
}
bool WallClock::IsNative() const {
return false;
}
#endif
WallClock CreateOptimalClock() noexcept {
#if defined(ARCHITECTURE_x86_64) #if defined(ARCHITECTURE_x86_64)
auto const& caps = GetCPUCaps(); const auto& caps = GetCPUCaps();
return WallClock(!(caps.invariant_tsc && caps.tsc_frequency >= std::nano::den), std::max<u64>(caps.tsc_frequency, 1));
if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) {
return std::make_unique<X64::NativeClock>(caps.tsc_frequency);
} else {
// Fallback to StandardWallClock if the hardware TSC
// - Is not invariant
// - Is not more precise than 1 GHz (1ns resolution)
return std::make_unique<StandardWallClock>();
}
#elif defined(HAS_NCE) #elif defined(HAS_NCE)
return WallClock(false, 1); return std::make_unique<Arm64::NativeClock>();
#else #else
return WallClock(true, 1); return std::make_unique<StandardWallClock>();
#endif #endif
} }
+10 -35
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -20,28 +20,28 @@ public:
static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz
static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz
explicit WallClock(bool invariant, u64 rdtsc_frequency_) noexcept; virtual ~WallClock() = default;
/// @returns The time in nanoseconds since the construction of this clock. /// @returns The time in nanoseconds since the construction of this clock.
std::chrono::nanoseconds GetTimeNS() const; virtual std::chrono::nanoseconds GetTimeNS() const = 0;
/// @returns The time in microseconds since the construction of this clock. /// @returns The time in microseconds since the construction of this clock.
std::chrono::microseconds GetTimeUS() const; virtual std::chrono::microseconds GetTimeUS() const = 0;
/// @returns The time in milliseconds since the construction of this clock. /// @returns The time in milliseconds since the construction of this clock.
std::chrono::milliseconds GetTimeMS() const; virtual std::chrono::milliseconds GetTimeMS() const = 0;
/// @returns The guest CNTPCT ticks since the construction of this clock. /// @returns The guest CNTPCT ticks since the construction of this clock.
s64 GetCNTPCT() const; virtual s64 GetCNTPCT() const = 0;
/// @returns The guest GPU ticks since the construction of this clock. /// @returns The guest GPU ticks since the construction of this clock.
s64 GetGPUTick() const; virtual s64 GetGPUTick() const = 0;
/// @returns The raw host timer ticks since an indeterminate epoch. /// @returns The raw host timer ticks since an indeterminate epoch.
s64 GetUptime() const; virtual s64 GetUptime() const = 0;
/// @returns Whether the clock directly uses the host's hardware clock. /// @returns Whether the clock directly uses the host's hardware clock.
bool IsNative() const; virtual bool IsNative() const = 0;
static inline u64 NSToCNTPCT(u64 ns) { static inline u64 NSToCNTPCT(u64 ns) {
return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
@@ -85,33 +85,8 @@ protected:
using CPUTickToUsRatio = std::ratio<std::micro::den, CPUTickFreq>; using CPUTickToUsRatio = std::ratio<std::micro::den, CPUTickFreq>;
using CPUTickToCNTPCTRatio = std::ratio<CNTFRQ, CPUTickFreq>; using CPUTickToCNTPCTRatio = std::ratio<CNTFRQ, CPUTickFreq>;
using CPUTickToGPUTickRatio = std::ratio<GPUTickFreq, CPUTickFreq>; using CPUTickToGPUTickRatio = std::ratio<GPUTickFreq, CPUTickFreq>;
#if defined(ARCHITECTURE_x86_64)
bool invariant;
u64 rdtsc_frequency;
u64 ns_rdtsc_factor;
u64 us_rdtsc_factor;
u64 ms_rdtsc_factor;
u64 cntpct_rdtsc_factor;
u64 gputick_rdtsc_factor;
#elif defined(HAS_NCE)
public:
using FactorType = unsigned __int128;
FactorType GetGuestCNTFRQFactor() const {
return guest_cntfrq_factor;
}
protected:
FactorType ns_cntfrq_factor;
FactorType us_cntfrq_factor;
FactorType ms_cntfrq_factor;
FactorType guest_cntfrq_factor;
FactorType gputick_cntfrq_factor;
#else
#endif
}; };
[[nodiscard]] WallClock CreateOptimalClock() noexcept; [[nodiscard]] std::unique_ptr<WallClock> CreateOptimalClock();
} // namespace Common } // namespace Common
+46
View File
@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/uint128.h"
#include "common/x64/native_clock.h"
#include "common/x64/rdtsc.h"
namespace Common::X64 {
NativeClock::NativeClock(u64 rdtsc_frequency_)
: rdtsc_frequency{rdtsc_frequency_}, ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den,
rdtsc_frequency)},
us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency)},
ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency)},
cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency)},
gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency)} {}
std::chrono::nanoseconds NativeClock::GetTimeNS() const {
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_rdtsc_factor)};
}
std::chrono::microseconds NativeClock::GetTimeUS() const {
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_rdtsc_factor)};
}
std::chrono::milliseconds NativeClock::GetTimeMS() const {
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_rdtsc_factor)};
}
s64 NativeClock::GetCNTPCT() const {
return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor);
}
s64 NativeClock::GetGPUTick() const {
return MultiplyHigh(GetUptime(), gputick_rdtsc_factor);
}
s64 NativeClock::GetUptime() const {
return static_cast<s64>(FencedRDTSC());
}
bool NativeClock::IsNative() const {
return true;
}
} // namespace Common::X64
+38
View File
@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/wall_clock.h"
namespace Common::X64 {
class NativeClock final : public WallClock {
public:
explicit NativeClock(u64 rdtsc_frequency_);
std::chrono::nanoseconds GetTimeNS() const override;
std::chrono::microseconds GetTimeUS() const override;
std::chrono::milliseconds GetTimeMS() const override;
s64 GetCNTPCT() const override;
s64 GetGPUTick() const override;
s64 GetUptime() const override;
bool IsNative() const override;
private:
u64 rdtsc_frequency;
u64 ns_rdtsc_factor;
u64 us_rdtsc_factor;
u64 ms_rdtsc_factor;
u64 cntpct_rdtsc_factor;
u64 gputick_rdtsc_factor;
};
} // namespace Common::X64
+7 -3
View File
@@ -113,7 +113,8 @@ void DynarmicCallbacks32::CallSVC(u32 swi) {
} }
void DynarmicCallbacks32::AddTicks(u64 ticks) { void DynarmicCallbacks32::AddTicks(u64 ticks) {
ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled"); ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off // rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should // if not all cores are doing a similar amount of work. Instead of doing this, we should
@@ -122,12 +123,14 @@ void DynarmicCallbacks32::AddTicks(u64 ticks) {
u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES; u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES;
// Always execute at least one tick. // Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1); amortized_ticks = std::max<u64>(amortized_ticks, 1);
m_parent.m_system.CoreTiming().AddTicks(amortized_ticks); m_parent.m_system.CoreTiming().AddTicks(amortized_ticks);
} }
u64 DynarmicCallbacks32::GetTicksRemaining() { u64 DynarmicCallbacks32::GetTicksRemaining() {
ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled"); ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
return std::max<s64>(m_parent.m_system.CoreTiming().downcount, 0);
return std::max<s64>(m_parent.m_system.CoreTiming().GetDowncount(), 0);
} }
bool DynarmicCallbacks32::CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) { bool DynarmicCallbacks32::CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) {
@@ -286,6 +289,7 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
break; break;
// Paranoia mode for debugging optimizations // Paranoia mode for debugging optimizations
case Settings::CpuAccuracy::Paranoid: case Settings::CpuAccuracy::Paranoid:
+5 -2
View File
@@ -150,7 +150,8 @@ void DynarmicCallbacks64::CallSVC(u32 svc) {
} }
void DynarmicCallbacks64::AddTicks(u64 ticks) { void DynarmicCallbacks64::AddTicks(u64 ticks) {
ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled"); ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off // rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should // if not all cores are doing a similar amount of work. Instead of doing this, we should
@@ -159,12 +160,13 @@ void DynarmicCallbacks64::AddTicks(u64 ticks) {
u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES; u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES;
// Always execute at least one tick. // Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1); amortized_ticks = std::max<u64>(amortized_ticks, 1);
m_parent.m_system.CoreTiming().AddTicks(amortized_ticks); m_parent.m_system.CoreTiming().AddTicks(amortized_ticks);
} }
u64 DynarmicCallbacks64::GetTicksRemaining() { u64 DynarmicCallbacks64::GetTicksRemaining() {
ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled"); ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled");
return std::max<s64>(m_parent.m_system.CoreTiming().downcount, 0); return std::max<s64>(m_parent.m_system.CoreTiming().GetDowncount(), 0);
} }
u64 DynarmicCallbacks64::GetCNTPCT() { u64 DynarmicCallbacks64::GetCNTPCT() {
@@ -338,6 +340,7 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s
config.unsafe_optimizations = true; config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.fastmem_address_space_bits = 64; config.fastmem_address_space_bits = 64;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
break; break;
// Paranoia mode for debugging optimizations // Paranoia mode for debugging optimizations
case Settings::CpuAccuracy::Paranoid: case Settings::CpuAccuracy::Paranoid:
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -10,30 +7,28 @@
namespace Core { namespace Core {
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::Memory& memory_, std::size_t core_count_) DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::Memory& memory_, std::size_t core_count_)
: monitor{} : monitor{core_count_}, memory{memory_} {}
, memory{memory_}
{}
DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default; DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;
u8 DynarmicExclusiveMonitor::ExclusiveRead8(std::size_t core_index, VAddr addr) { u8 DynarmicExclusiveMonitor::ExclusiveRead8(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u8>(core_index, addr, [=]() -> u8 { return memory.Read8(addr); }); return monitor.ReadAndMark<u8>(core_index, addr, [&]() -> u8 { return memory.Read8(addr); });
} }
u16 DynarmicExclusiveMonitor::ExclusiveRead16(std::size_t core_index, VAddr addr) { u16 DynarmicExclusiveMonitor::ExclusiveRead16(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u16>(core_index, addr, [=]() -> u16 { return memory.Read16(addr); }); return monitor.ReadAndMark<u16>(core_index, addr, [&]() -> u16 { return memory.Read16(addr); });
} }
u32 DynarmicExclusiveMonitor::ExclusiveRead32(std::size_t core_index, VAddr addr) { u32 DynarmicExclusiveMonitor::ExclusiveRead32(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u32>(core_index, addr, [=]() -> u32 { return memory.Read32(addr); }); return monitor.ReadAndMark<u32>(core_index, addr, [&]() -> u32 { return memory.Read32(addr); });
} }
u64 DynarmicExclusiveMonitor::ExclusiveRead64(std::size_t core_index, VAddr addr) { u64 DynarmicExclusiveMonitor::ExclusiveRead64(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u64>(core_index, addr, [=]() -> u64 { return memory.Read64(addr); }); return monitor.ReadAndMark<u64>(core_index, addr, [&]() -> u64 { return memory.Read64(addr); });
} }
u128 DynarmicExclusiveMonitor::ExclusiveRead128(std::size_t core_index, VAddr addr) { u128 DynarmicExclusiveMonitor::ExclusiveRead128(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u128>(core_index, addr, [=]() -> u128 { return monitor.ReadAndMark<u128>(core_index, addr, [&]() -> u128 {
u128 result; u128 result;
result[0] = memory.Read64(addr); result[0] = memory.Read64(addr);
result[1] = memory.Read64(addr + 8); result[1] = memory.Read64(addr + 8);
@@ -46,31 +41,31 @@ void DynarmicExclusiveMonitor::ClearExclusive(std::size_t core_index) {
} }
bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) { bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) {
return monitor.DoExclusiveOperation<u8>(core_index, vaddr, [=](u8 expected) -> bool { return monitor.DoExclusiveOperation<u8>(core_index, vaddr, [&](u8 expected) -> bool {
return memory.WriteExclusive8(vaddr, value, expected); return memory.WriteExclusive8(vaddr, value, expected);
}); });
} }
bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) { bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) {
return monitor.DoExclusiveOperation<u16>(core_index, vaddr, [=](u16 expected) -> bool { return monitor.DoExclusiveOperation<u16>(core_index, vaddr, [&](u16 expected) -> bool {
return memory.WriteExclusive16(vaddr, value, expected); return memory.WriteExclusive16(vaddr, value, expected);
}); });
} }
bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) { bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) {
return monitor.DoExclusiveOperation<u32>(core_index, vaddr, [=](u32 expected) -> bool { return monitor.DoExclusiveOperation<u32>(core_index, vaddr, [&](u32 expected) -> bool {
return memory.WriteExclusive32(vaddr, value, expected); return memory.WriteExclusive32(vaddr, value, expected);
}); });
} }
bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) { bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) {
return monitor.DoExclusiveOperation<u64>(core_index, vaddr, [=](u64 expected) -> bool { return monitor.DoExclusiveOperation<u64>(core_index, vaddr, [&](u64 expected) -> bool {
return memory.WriteExclusive64(vaddr, value, expected); return memory.WriteExclusive64(vaddr, value, expected);
}); });
} }
bool DynarmicExclusiveMonitor::ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) { bool DynarmicExclusiveMonitor::ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) {
return monitor.DoExclusiveOperation<u128>(core_index, vaddr, [=](u128 expected) -> bool { return monitor.DoExclusiveOperation<u128>(core_index, vaddr, [&](u128 expected) -> bool {
return memory.WriteExclusive128(vaddr, value, expected); return memory.WriteExclusive128(vaddr, value, expected);
}); });
} }
+2 -6
View File
@@ -3,7 +3,7 @@
#include <numeric> #include <numeric>
#include <bit> #include <bit>
#include "common/wall_clock.h" #include "common/arm64/native_clock.h"
#include "common/alignment.h" #include "common/alignment.h"
#include "common/literals.h" #include "common/literals.h"
#include "core/arm/nce/arm_nce.h" #include "core/arm/nce/arm_nce.h"
@@ -578,11 +578,7 @@ void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg,
} }
void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) { void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) {
#if defined(HAS_NCE) static Common::Arm64::NativeClock clock{};
static Common::WallClock clock(false, 1);
#else
static Common::WallClock clock(true, 1);
#endif
const auto factor = clock.GetGuestCNTFRQFactor(); const auto factor = clock.GetGuestCNTFRQFactor();
const auto raw_factor = std::bit_cast<std::array<u64, 2>>(factor); const auto raw_factor = std::bit_cast<std::array<u64, 2>>(factor);
+88 -67
View File
@@ -57,51 +57,15 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
Reset(); Reset();
on_thread_init = std::move(on_thread_init_); on_thread_init = std::move(on_thread_init_);
event_fifo_id = 0; event_fifo_id = 0;
shutting_down = false;
cpu_ticks = 0; cpu_ticks = 0;
if (is_multicore) { if (is_multicore) {
timer_thread = std::jthread([this](std::stop_token stop_token) { timer_thread.emplace([](CoreTiming& instance) {
Common::SetCurrentThreadName("HostTiming"); Common::SetCurrentThreadName("HostTiming");
Common::SetCurrentThreadPriority(Common::ThreadPriority::High); Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
on_thread_init(); instance.on_thread_init();
has_started = true; instance.ThreadLoop();
while (!stop_token.stop_requested()) { }, std::ref(*this));
while (!paused && !stop_token.stop_requested()) {
paused_set = false;
if (auto const next_time = Advance(); next_time) {
// There are more events left in the queue, wait until the next event.
auto wait_time = *next_time - GetGlobalTimeNs().count();
if (wait_time > 0) {
#ifdef _WIN32
while (!paused && !event.IsSet() && wait_time > 0) {
wait_time = *next_time - GetGlobalTimeNs().count();
if (wait_time >= timer_resolution_ns) {
Common::Windows::SleepForOneTick();
} else {
#ifdef ARCHITECTURE_x86_64
Common::X64::MicroSleep();
#else
std::this_thread::yield();
#endif
}
}
if (event.IsSet())
event.Reset();
#else
event.WaitFor(std::chrono::nanoseconds(wait_time));
#endif
}
} else {
// Queue is empty, wait until another event is scheduled and signals us to
// continue.
wait_set = true;
event.Wait();
}
wait_set = false;
}
paused_set = true;
pause_event.Wait();
}
});
} }
} }
@@ -126,7 +90,7 @@ void CoreTiming::SyncPause(bool is_paused) {
} }
Pause(is_paused); Pause(is_paused);
if (timer_thread.joinable()) { if (timer_thread) {
if (!is_paused) { if (!is_paused) {
pause_event.Set(); pause_event.Set();
} }
@@ -226,22 +190,33 @@ void CoreTiming::ResetTicks() {
} }
u64 CoreTiming::GetClockTicks() const { u64 CoreTiming::GetClockTicks() const {
u64 fres = is_multicore ? clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks); u64 fres;
if (auto const overclock = Settings::values.fast_cpu_time.GetValue(); overclock != Settings::CpuClock::Off) { if (is_multicore) [[likely]] {
fres = u64(f64(fres) * (1.7 + 0.3 * u32(overclock))); fres = clock->GetCNTPCT();
} else {
fres = Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
} }
if (::Settings::values.sync_core_speed.GetValue()) {
auto const ticks = f64(fres); const auto overclock = Settings::values.fast_cpu_time.GetValue();
auto const speed_limit = f64(Settings::SpeedLimit()) * 0.01;
return u64(ticks / speed_limit); if (overclock != Settings::CpuClock::Off) {
} fres = (u64) ((double) fres * (1.7 + 0.3 * u32(overclock)));
return fres; }
if (Settings::values.sync_core_speed.GetValue()) {
const auto ticks = double(fres);
const auto speed_limit = double(Settings::SpeedLimit())*0.01;
return u64(ticks/speed_limit);
} else {
return fres;
}
} }
u64 CoreTiming::GetGPUTicks() const { u64 CoreTiming::GetGPUTicks() const {
return is_multicore if (is_multicore) [[likely]] {
? clock.GetGPUTick() return clock->GetGPUTick();
: Common::WallClock::CPUTickToGPUTick(cpu_ticks); }
return Common::WallClock::CPUTickToGPUTick(cpu_ticks);
} }
std::optional<s64> CoreTiming::Advance() { std::optional<s64> CoreTiming::Advance() {
@@ -303,29 +278,75 @@ std::optional<s64> CoreTiming::Advance() {
} }
} }
void CoreTiming::ThreadLoop() {
has_started = true;
while (!shutting_down) {
while (!paused) {
paused_set = false;
const auto next_time = Advance();
if (next_time) {
// There are more events left in the queue, wait until the next event.
auto wait_time = *next_time - GetGlobalTimeNs().count();
if (wait_time > 0) {
#ifdef _WIN32
while (!paused && !event.IsSet() && wait_time > 0) {
wait_time = *next_time - GetGlobalTimeNs().count();
if (wait_time >= timer_resolution_ns) {
Common::Windows::SleepForOneTick();
} else {
#ifdef ARCHITECTURE_x86_64
Common::X64::MicroSleep();
#else
std::this_thread::yield();
#endif
}
}
if (event.IsSet()) {
event.Reset();
}
#else
event.WaitFor(std::chrono::nanoseconds(wait_time));
#endif
}
} else {
// Queue is empty, wait until another event is scheduled and signals us to
// continue.
wait_set = true;
event.Wait();
}
wait_set = false;
}
paused_set = true;
pause_event.Wait();
}
}
void CoreTiming::Reset() { void CoreTiming::Reset() {
paused = true; paused = true;
shutting_down = true;
pause_event.Set(); pause_event.Set();
event.Set(); event.Set();
if (timer_thread.joinable()) { if (timer_thread) {
timer_thread.request_stop(); timer_thread->join();
timer_thread.join();
} }
timer_thread.reset();
has_started = false; has_started = false;
} }
/// @brief Returns current time in nanoseconds. std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const {
std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const noexcept { if (is_multicore) [[likely]] {
return is_multicore return clock->GetTimeNS();
? clock.GetTimeNS() }
: std::chrono::nanoseconds{Common::WallClock::CPUTickToNS(cpu_ticks)}; return std::chrono::nanoseconds{Common::WallClock::CPUTickToNS(cpu_ticks)};
} }
/// @brief Returns current time in microseconds. std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const {
std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const noexcept { if (is_multicore) [[likely]] {
return is_multicore return clock->GetTimeUS();
? clock.GetTimeUS() }
: std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)}; return std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)};
} }
#ifdef _WIN32 #ifdef _WIN32
+12 -6
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -118,7 +118,7 @@ public:
void Idle(); void Idle();
s64 GetDowncount() const noexcept { s64 GetDowncount() const {
return downcount; return downcount;
} }
@@ -128,8 +128,11 @@ public:
/// Returns the current GPU tick value. /// Returns the current GPU tick value.
u64 GetGPUTicks() const; u64 GetGPUTicks() const;
[[nodiscard]] std::chrono::microseconds GetGlobalTimeUs() const noexcept; /// Returns current time in microseconds.
[[nodiscard]] std::chrono::nanoseconds GetGlobalTimeNs() const noexcept; std::chrono::microseconds GetGlobalTimeUs() const;
/// Returns current time in nanoseconds.
std::chrono::nanoseconds GetGlobalTimeNs() const;
/// Checks for events manually and returns time in nanoseconds for next event, threadsafe. /// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
std::optional<s64> Advance(); std::optional<s64> Advance();
@@ -138,11 +141,13 @@ public:
void SetTimerResolutionNs(std::chrono::nanoseconds ns); void SetTimerResolutionNs(std::chrono::nanoseconds ns);
#endif #endif
private:
struct Event; struct Event;
void ThreadLoop();
void Reset(); void Reset();
Common::WallClock clock; std::unique_ptr<Common::WallClock> clock;
s64 global_timer = 0; s64 global_timer = 0;
@@ -160,10 +165,11 @@ public:
Common::Event pause_event{}; Common::Event pause_event{};
mutable std::mutex basic_lock; mutable std::mutex basic_lock;
std::mutex advance_lock; std::mutex advance_lock;
std::jthread timer_thread; std::optional<std::jthread> timer_thread;
std::atomic<bool> paused{}; std::atomic<bool> paused{};
std::atomic<bool> paused_set{}; std::atomic<bool> paused_set{};
std::atomic<bool> wait_set{}; std::atomic<bool> wait_set{};
std::atomic<bool> shutting_down{};
std::atomic<bool> has_started{}; std::atomic<bool> has_started{};
std::function<void()> on_thread_init{}; std::function<void()> on_thread_init{};
@@ -15,76 +15,86 @@
namespace FileSys::SystemArchive { namespace FileSys::SystemArchive {
constexpr u64 SYSTEM_ARCHIVE_BASE_TITLE_ID = 0x0100000000000800;
constexpr std::size_t SYSTEM_ARCHIVE_COUNT = 0x28;
using SystemArchiveSupplier = VirtualDir (*)();
struct SystemArchiveDescriptor { struct SystemArchiveDescriptor {
u64 title_id;
const char* name; const char* name;
VirtualDir (*supplier)(); SystemArchiveSupplier supplier;
}; };
constexpr inline SystemArchiveDescriptor GetSystemArchive(u64 title_id) { constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{
switch (title_id) { {0x0100000000000800, "CertStore", nullptr},
case 0x0100000000000800: return {"CertStore", nullptr}; {0x0100000000000801, "ErrorMessage", nullptr},
case 0x0100000000000801: return {"ErrorMessage", nullptr}; {0x0100000000000802, "MiiModel", &MiiModel},
case 0x0100000000000802: return {"MiiModel", &MiiModel}; {0x0100000000000803, "BrowserDll", nullptr},
case 0x0100000000000803: return {"BrowserDll", nullptr}; {0x0100000000000804, "Help", nullptr},
case 0x0100000000000804: return {"Help", nullptr}; {0x0100000000000805, "SharedFont", nullptr},
case 0x0100000000000805: return {"SharedFont", nullptr}; {0x0100000000000806, "NgWord", &NgWord1},
case 0x0100000000000806: return {"NgWord", &NgWord1}; {0x0100000000000807, "SsidList", nullptr},
case 0x0100000000000807: return {"SsidList", nullptr}; {0x0100000000000808, "Dictionary", nullptr},
case 0x0100000000000808: return {"Dictionary", nullptr}; {0x0100000000000809, "SystemVersion", &SystemVersion},
case 0x0100000000000809: return {"SystemVersion", &SystemVersion}; {0x010000000000080A, "AvatarImage", nullptr},
case 0x010000000000080A: return {"AvatarImage", nullptr}; {0x010000000000080B, "LocalNews", nullptr},
case 0x010000000000080B: return {"LocalNews", nullptr}; {0x010000000000080C, "Eula", nullptr},
case 0x010000000000080C: return {"Eula", nullptr}; {0x010000000000080D, "UrlBlackList", nullptr},
case 0x010000000000080D: return {"UrlBlackList", nullptr}; {0x010000000000080E, "TimeZoneBinary", &TimeZoneBinary},
case 0x010000000000080E: return {"TimeZoneBinary", &TimeZoneBinary}; {0x010000000000080F, "CertStoreCruiser", nullptr},
case 0x010000000000080F: return {"CertStoreCruiser", nullptr}; {0x0100000000000810, "FontNintendoExtension", &FontNintendoExtension},
case 0x0100000000000810: return {"FontNintendoExtension", &FontNintendoExtension}; {0x0100000000000811, "FontStandard", &FontStandard},
case 0x0100000000000811: return {"FontStandard", &FontStandard}; {0x0100000000000812, "FontKorean", &FontKorean},
case 0x0100000000000812: return {"FontKorean", &FontKorean}; {0x0100000000000813, "FontChineseTraditional", &FontChineseTraditional},
case 0x0100000000000813: return {"FontChineseTraditional", &FontChineseTraditional}; {0x0100000000000814, "FontChineseSimple", &FontChineseSimple},
case 0x0100000000000814: return {"FontChineseSimple", &FontChineseSimple}; {0x0100000000000815, "FontBfcpx", nullptr},
case 0x0100000000000815: return {"FontBfcpx", nullptr}; {0x0100000000000816, "SystemUpdate", nullptr},
case 0x0100000000000816: return {"SystemUpdate", nullptr}; {0x0100000000000817, "0100000000000817", nullptr},
case 0x0100000000000817: return {"0100000000000817", nullptr}; {0x0100000000000818, "FirmwareDebugSettings", nullptr},
case 0x0100000000000818: return {"FirmwareDebugSettings", nullptr}; {0x0100000000000819, "BootImagePackage", nullptr},
case 0x0100000000000819: return {"BootImagePackage", nullptr}; {0x010000000000081A, "BootImagePackageSafe", nullptr},
case 0x010000000000081A: return {"BootImagePackageSafe", nullptr}; {0x010000000000081B, "BootImagePackageExFat", nullptr},
case 0x010000000000081B: return {"BootImagePackageExFat", nullptr}; {0x010000000000081C, "BootImagePackageExFatSafe", nullptr},
case 0x010000000000081C: return {"BootImagePackageExFatSafe", nullptr}; {0x010000000000081D, "FatalMessage", nullptr},
case 0x010000000000081D: return {"FatalMessage", nullptr}; {0x010000000000081E, "ControllerIcon", nullptr},
case 0x010000000000081E: return {"ControllerIcon", nullptr}; {0x010000000000081F, "PlatformConfigIcosa", nullptr},
case 0x010000000000081F: return {"PlatformConfigIcosa", nullptr}; {0x0100000000000820, "PlatformConfigCopper", nullptr},
case 0x0100000000000820: return {"PlatformConfigCopper", nullptr}; {0x0100000000000821, "PlatformConfigHoag", nullptr},
case 0x0100000000000821: return {"PlatformConfigHoag", nullptr}; {0x0100000000000822, "ControllerFirmware", nullptr},
case 0x0100000000000822: return {"ControllerFirmware", nullptr}; {0x0100000000000823, "NgWord2", &NgWord2},
case 0x0100000000000823: return {"NgWord2", &NgWord2}; {0x0100000000000824, "PlatformConfigIcosaMariko", nullptr},
case 0x0100000000000824: return {"PlatformConfigIcosaMariko", nullptr}; {0x0100000000000825, "ApplicationBlackList", nullptr},
case 0x0100000000000825: return {"ApplicationBlackList", nullptr}; {0x0100000000000826, "RebootlessSystemUpdateVersion", nullptr},
case 0x0100000000000826: return {"RebootlessSystemUpdateVersion", nullptr}; {0x0100000000000827, "ContentActionTable", nullptr},
case 0x0100000000000827: return {"ContentActionTable", nullptr}; }};
case 0x0100000000000828: return {"FunctionBlackList", nullptr};
case 0x0100000000000829: return {"PlatformConfigCalcio", nullptr};
case 0x0100000000000830: return {"NgWordT", nullptr};
case 0x0100000000000831: return {"PlatformConfigAula", nullptr};
case 0x0100000000000832: return {"CradleFirmware", nullptr};
case 0x0100000000000835: return {"ErrorMessageUtf8", nullptr};
case 0x0100000000000859: return {"ClientCertData", nullptr};
case 0x010000000000085C: return {"GameCardConfigurationData", nullptr};
default: return {nullptr, nullptr};
}
}
VirtualFile SynthesizeSystemArchive(const u64 title_id) { VirtualFile SynthesizeSystemArchive(const u64 title_id) {
auto const desc = GetSystemArchive(title_id); if (title_id < SYSTEM_ARCHIVES.front().title_id || title_id > SYSTEM_ARCHIVES.back().title_id) {
LOG_INFO(Service_FS, "Synthesizing system archive '{}' (0x{:016X}).", desc.name, title_id); return nullptr;
if (desc.supplier != nullptr) {
if (auto const dir = desc.supplier(); dir != nullptr) {
if (auto const romfs = CreateRomFS(dir); romfs != nullptr) {
LOG_INFO(Service_FS, " - System archive generation successful!");
return romfs;
}
}
} }
return nullptr;
const auto& desc = SYSTEM_ARCHIVES[title_id - SYSTEM_ARCHIVE_BASE_TITLE_ID];
LOG_INFO(Service_FS, "Synthesizing system archive '{}' (0x{:016X}).", desc.name, desc.title_id);
if (desc.supplier == nullptr) {
return nullptr;
}
const auto dir = desc.supplier();
if (dir == nullptr) {
return nullptr;
}
const auto romfs = CreateRomFS(dir);
if (romfs == nullptr) {
return nullptr;
}
LOG_INFO(Service_FS, " - System archive generation successful!");
return romfs;
} }
} // namespace FileSys::SystemArchive } // namespace FileSys::SystemArchive
+59 -43
View File
@@ -6,8 +6,6 @@
#include <condition_variable> #include <condition_variable>
#include <mutex> #include <mutex>
#include <optional>
#include <stop_token>
#include <thread> #include <thread>
#include "core/core.h" #include "core/core.h"
@@ -20,53 +18,59 @@ namespace Kernel::Svc {
constexpr auto MAX_MSG_TIME = std::chrono::milliseconds(250); constexpr auto MAX_MSG_TIME = std::chrono::milliseconds(250);
const auto MAX_MSG_SIZE = 0x1000; const auto MAX_MSG_SIZE = 0x1000;
static std::string msg_buffer;
static std::mutex msg_mutex;
static std::condition_variable msg_cv;
static std::chrono::steady_clock::time_point last_msg_time;
static bool worker_running = true;
static std::unique_ptr<std::thread> flush_thread;
static std::once_flag start_flag;
static void FlushDbgLoop() {
while (true) {
std::unique_lock lock(msg_mutex);
msg_cv.wait(lock, [] { return !msg_buffer.empty() || !worker_running; });
if (!worker_running && msg_buffer.empty()) break;
auto timeout = last_msg_time + MAX_MSG_TIME;
bool woke_early = msg_cv.wait_until(lock, timeout, [] {
return msg_buffer.size() >= MAX_MSG_SIZE || !worker_running;
});
if (!woke_early || msg_buffer.size() >= MAX_MSG_SIZE || !worker_running) {
if (!msg_buffer.empty()) {
// Remove trailing newline as LOG_INFO adds that anyways
if (msg_buffer.back() == '\n')
msg_buffer.pop_back();
LOG_INFO(Debug_Emulated, "\n{}", msg_buffer);
msg_buffer.clear();
}
if (!worker_running) break;
}
}
}
/// Used to output a message on a debug hardware unit - does nothing on a retail unit /// Used to output a message on a debug hardware unit - does nothing on a retail unit
Result OutputDebugString(Core::System& system, u64 address, u64 len) { Result OutputDebugString(Core::System& system, u64 address, u64 len) {
static struct DebugFlusher {
std::string msg_buffer;
std::mutex msg_mutex;
std::condition_variable msg_cv;
std::chrono::steady_clock::time_point last_msg_time;
std::optional<std::jthread> thread;
} flusher_data;
R_SUCCEED_IF(len == 0); R_SUCCEED_IF(len == 0);
// Only start the thread the very first time this function is called
if (!flusher_data.thread) {
flusher_data.thread.emplace([](std::stop_token stop_token) {
while (!stop_token.stop_requested()) {
std::unique_lock lock(flusher_data.msg_mutex);
flusher_data.msg_cv.wait(lock, [&stop_token] {
return !flusher_data.msg_buffer.empty() || stop_token.stop_requested();
});
if (stop_token.stop_requested() && flusher_data.msg_buffer.empty())
break;
auto timeout = flusher_data.last_msg_time + MAX_MSG_TIME;
bool woke_early = flusher_data.msg_cv.wait_until(lock, timeout, [&stop_token] {
return flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested();
});
if (!woke_early || flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested()) {
if (!flusher_data.msg_buffer.empty()) {
// Remove trailing newline as LOG_INFO adds that anyways
if (flusher_data.msg_buffer.back() == '\n')
flusher_data.msg_buffer.pop_back();
LOG_INFO(Debug_Emulated, "\n{}", flusher_data.msg_buffer); // Only start the thread the very first time this function is called
flusher_data.msg_buffer.clear(); std::call_once(start_flag, [] {
} flush_thread = std::make_unique<std::thread>(FlushDbgLoop);
if (stop_token.stop_requested()) break; });
}
}
flusher_data.msg_cv.notify_all();
});
}
{ {
std::lock_guard lock(flusher_data.msg_mutex); std::lock_guard lock(msg_mutex);
const auto old_size = flusher_data.msg_buffer.size(); const auto old_size = msg_buffer.size();
flusher_data.msg_buffer.resize(old_size + len); msg_buffer.resize(old_size + len);
GetCurrentMemory(system.Kernel()).ReadBlock(address, flusher_data.msg_buffer.data() + old_size, len); GetCurrentMemory(system.Kernel()).ReadBlock(address, msg_buffer.data() + old_size, len);
flusher_data.last_msg_time = std::chrono::steady_clock::now();
last_msg_time = std::chrono::steady_clock::now();
} }
flusher_data.msg_cv.notify_one();
msg_cv.notify_one();
R_SUCCEED(); R_SUCCEED();
} }
@@ -78,4 +82,16 @@ Result OutputDebugString64From32(Core::System& system, uint32_t debug_str, uint3
R_RETURN(OutputDebugString(system, debug_str, len)); R_RETURN(OutputDebugString(system, debug_str, len));
} }
struct BufferAutoFlush {
~BufferAutoFlush() {
{
std::lock_guard lock(msg_mutex);
worker_running = false;
}
msg_cv.notify_all();
if (flush_thread && flush_thread->joinable()) flush_thread->join();
}
};
static BufferAutoFlush auto_flusher;
} // namespace Kernel::Svc } // namespace Kernel::Svc
@@ -4,7 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "applet_web_browser_types.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/fs/file.h" #include "common/fs/file.h"
#include "common/fs/fs.h" #include "common/fs/fs.h"
@@ -371,13 +370,16 @@ void WebBrowser::ExtractOfflineRomFS() {
void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) { void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) {
const bool use_tlv_output = const bool use_tlv_output =
(web_arg_header.shim_kind == ShimKind::Share && web_applet_version >= WebAppletVersion::Version196608) (web_arg_header.shim_kind == ShimKind::Share &&
|| (web_arg_header.shim_kind == ShimKind::Web && web_applet_version >= WebAppletVersion::Version524288) web_applet_version >= WebAppletVersion::Version196608) ||
|| (web_arg_header.shim_kind == ShimKind::Lhub); (web_arg_header.shim_kind == ShimKind::Web &&
web_applet_version >= WebAppletVersion::Version524288) ||
(web_arg_header.shim_kind == ShimKind::Lhub);
// https://switchbrew.org/wiki/Internet_Browser#TLVs // https://switchbrew.org/wiki/Internet_Browser#TLVs
if (use_tlv_output) { if (use_tlv_output) {
LOG_DEBUG(Service_AM, "Using TLV output: exit_reason={}, last_url={}, last_url_size={}", exit_reason, last_url, last_url.size()); LOG_DEBUG(Service_AM, "Using TLV output: exit_reason={}, last_url={}, last_url_size={}",
exit_reason, last_url, last_url.size());
// storage size for TLVs is 0x2000 bytes (as per switchbrew documentation) // storage size for TLVs is 0x2000 bytes (as per switchbrew documentation)
constexpr size_t TLV_STORAGE_SIZE = 0x2000; constexpr size_t TLV_STORAGE_SIZE = 0x2000;
@@ -598,14 +600,11 @@ void WebBrowser::ExecuteShare() {
void WebBrowser::ExecuteWeb() { void WebBrowser::ExecuteWeb() {
LOG_INFO(Service_AM, "Opening external URL at {}", external_url); LOG_INFO(Service_AM, "Opening external URL at {}", external_url);
frontend.OpenExternalWebPage(external_url, [this](WebExitReason exit_reason, std::string last_url) {
// Offline and web applets must be explicitly exited from because they respect exit state frontend.OpenExternalWebPage(external_url,
// Unlike the other web stuffs [this](WebExitReason exit_reason, std::string last_url) {
if (exit_reason == WebExitReason::ExitRequested || exit_reason == WebExitReason::EndButtonPressed) WebBrowserExit(exit_reason, last_url);
exit_reason = (web_arg_header.shim_kind == ShimKind::Web || web_arg_header.shim_kind == ShimKind::Offline) });
? WebExitReason::ExitRequested : WebExitReason::EndButtonPressed;
WebBrowserExit(exit_reason, last_url);
});
} }
void WebBrowser::ExecuteWifi() { void WebBrowser::ExecuteWifi() {
@@ -10,7 +10,6 @@
#include "common/alignment.h" #include "common/alignment.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/common_types.h"
#include "common/logging.h" #include "common/logging.h"
#include "core/core.h" #include "core/core.h"
#include "core/hle/service/nvdrv/core/container.h" #include "core/hle/service/nvdrv/core/container.h"
@@ -131,12 +130,12 @@ NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) {
const auto start_pages{static_cast<u32>(vm.va_range_start >> VM::PAGE_SIZE_BITS)}; const auto start_pages{static_cast<u32>(vm.va_range_start >> VM::PAGE_SIZE_BITS)};
const auto end_pages{static_cast<u32>(vm.va_range_split >> VM::PAGE_SIZE_BITS)}; const auto end_pages{static_cast<u32>(vm.va_range_split >> VM::PAGE_SIZE_BITS)};
vm.small_page_allocator.emplace(start_pages, end_pages); vm.small_page_allocator = std::make_shared<VM::Allocator>(start_pages, end_pages);
const auto start_big_pages{static_cast<u32>(vm.va_range_split >> vm.big_page_size_bits)}; const auto start_big_pages{static_cast<u32>(vm.va_range_split >> vm.big_page_size_bits)};
const auto end_big_pages{ const auto end_big_pages{
static_cast<u32>((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)}; static_cast<u32>((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)};
vm.big_page_allocator.emplace(start_big_pages, end_big_pages); vm.big_page_allocator = std::make_unique<VM::Allocator>(start_big_pages, end_big_pages);
gmmu = std::make_shared<Tegra::MemoryManager>(system, max_big_page_bits, vm.va_range_split, gmmu = std::make_shared<Tegra::MemoryManager>(system, max_big_page_bits, vm.va_range_split,
vm.big_page_size_bits, VM::PAGE_SIZE_BITS); vm.big_page_size_bits, VM::PAGE_SIZE_BITS);
@@ -189,8 +188,8 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
} }
allocation_map[params.offset] = { allocation_map[params.offset] = {
.mappings{},
.size = size, .size = size,
.mappings{},
.page_size = params.page_size, .page_size = params.page_size,
.sparse = (params.flags & MappingFlags::Sparse) != MappingFlags::None, .sparse = (params.flags & MappingFlags::Sparse) != MappingFlags::None,
.big_pages = params.page_size != VM::YUZU_PAGESIZE, .big_pages = params.page_size != VM::YUZU_PAGESIZE,
@@ -200,52 +199,71 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
} }
void nvhost_as_gpu::FreeMappingLocked(u64 offset) { void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
auto const it = mapping_map.find(offset); auto mapping{mapping_map.at(offset)};
auto const mapping = it->second;
if (!mapping.fixed) { if (!mapping->fixed) {
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
u32 page_size_bits{mapping.big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS}; u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
u32 page_size{mapping.big_page ? vm.big_page_size : VM::YUZU_PAGESIZE}; u32 page_size{mapping->big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
u64 aligned_size{Common::AlignUp(mapping.size, page_size)}; u64 aligned_size{Common::AlignUp(mapping->size, page_size)};
allocator.Free(u32(mapping.offset >> page_size_bits), u32(aligned_size >> page_size_bits));
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
static_cast<u32>(aligned_size >> page_size_bits));
} }
nvmap.UnpinHandle(mapping.handle);
nvmap.UnpinHandle(mapping->handle);
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
// Only FreeSpace can unmap them fully // Only FreeSpace can unmap them fully
if (mapping.sparse_alloc) { if (mapping->sparse_alloc) {
gmmu->MapSparse(offset, mapping.size, mapping.big_page); gmmu->MapSparse(offset, mapping->size, mapping->big_page);
} else { } else {
gmmu->Unmap(offset, mapping.size); gmmu->Unmap(offset, mapping->size);
} }
mapping_map.erase(it);
mapping_map.erase(offset);
} }
NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) { NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) {
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset, params.pages, params.page_size); LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
params.pages, params.page_size);
std::scoped_lock lock(mutex); std::scoped_lock lock(mutex);
if (!vm.initialised) { if (!vm.initialised) {
return NvResult::BadValue; return NvResult::BadValue;
} }
if (auto const it = allocation_map.find(params.offset); it != allocation_map.end()) {
auto const allocation = it->second;
if (allocation.page_size != params.page_size || allocation.size != (u64(params.pages) * params.page_size))
return NvResult::BadValue;
for (const auto mapping_offset : allocation.mappings) try {
FreeMappingLocked(mapping_offset); auto allocation{allocation_map[params.offset]};
if (allocation.page_size != params.page_size ||
allocation.size != (static_cast<u64>(params.pages) * params.page_size)) {
return NvResult::BadValue;
}
for (const auto& mapping : allocation.mappings) {
FreeMappingLocked(mapping->offset);
}
// Unset sparse flag if required // Unset sparse flag if required
if (allocation.sparse) if (allocation.sparse) {
gmmu->Unmap(params.offset, allocation.size); gmmu->Unmap(params.offset, allocation.size);
}
auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator : *vm.big_page_allocator}; auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS : vm.big_page_size_bits}; : *vm.big_page_allocator};
u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
: vm.big_page_size_bits};
allocator.Free(u32(params.offset >> page_size_bits), u32(allocation.size >> page_size_bits)); allocator.Free(static_cast<u32>(params.offset >> page_size_bits),
static_cast<u32>(allocation.size >> page_size_bits));
allocation_map.erase(params.offset); allocation_map.erase(params.offset);
return NvResult::Success; } catch (const std::out_of_range&) {
return NvResult::BadValue;
} }
return NvResult::BadValue;
return NvResult::Success;
} }
NvResult nvhost_as_gpu::Remap(std::span<IoctlRemapEntry> entries) { NvResult nvhost_as_gpu::Remap(std::span<IoctlRemapEntry> entries) {
@@ -309,18 +327,26 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
// Remaps a subregion of an existing mapping to a different PA // Remaps a subregion of an existing mapping to a different PA
if ((params.flags & MappingFlags::Remap) != MappingFlags::None) { if ((params.flags & MappingFlags::Remap) != MappingFlags::None) {
if (auto const it = mapping_map.find(params.offset); it != mapping_map.end()) { try {
auto const mapping = it->second; auto mapping{mapping_map.at(params.offset)};
if (mapping.size < params.mapping_size) {
LOG_WARNING(Service_NVDRV, "Cannot remap a partially mapped GPU address space region: {:#X}", params.offset); if (mapping->size < params.mapping_size) {
LOG_WARNING(Service_NVDRV,
"Cannot remap a partially mapped GPU address space region: {:#X}",
params.offset);
return NvResult::BadValue; return NvResult::BadValue;
} }
u64 gpu_address = u64(params.offset + params.buffer_offset);
VAddr device_address{mapping.ptr + params.buffer_offset}; u64 gpu_address{static_cast<u64>(params.offset + params.buffer_offset)};
gmmu->Map(gpu_address, device_address, params.mapping_size, Tegra::PTEKind(params.kind), mapping.big_page); VAddr device_address{mapping->ptr + params.buffer_offset};
gmmu->Map(gpu_address, device_address, params.mapping_size,
static_cast<Tegra::PTEKind>(params.kind), mapping->big_page);
return NvResult::Success; return NvResult::Success;
} else { } catch (const std::out_of_range&) {
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}", params.offset); LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}",
params.offset);
return NvResult::BadValue; return NvResult::BadValue;
} }
} }
@@ -330,7 +356,8 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
return NvResult::BadValue; return NvResult::BadValue;
} }
DAddr device_address = DAddr(nvmap.PinHandle(params.handle, false) + params.buffer_offset); DAddr device_address{
static_cast<DAddr>(nvmap.PinHandle(params.handle, false) + params.buffer_offset)};
u64 size{params.mapping_size ? params.mapping_size : handle->orig_size}; u64 size{params.mapping_size ? params.mapping_size : handle->orig_size};
bool big_page{[&]() { bool big_page{[&]() {
@@ -354,22 +381,32 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
} }
const bool use_big_pages = alloc->second.big_pages && big_page; const bool use_big_pages = alloc->second.big_pages && big_page;
gmmu->Map(params.offset, device_address, size, static_cast<Tegra::PTEKind>(params.kind), use_big_pages); gmmu->Map(params.offset, device_address, size, static_cast<Tegra::PTEKind>(params.kind),
use_big_pages);
alloc->second.mappings.push_back(params.offset); auto mapping{std::make_shared<Mapping>(params.handle, device_address, params.offset, size,
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, true, use_big_pages, alloc->second.sparse)); true, use_big_pages, alloc->second.sparse)};
alloc->second.mappings.push_back(mapping);
mapping_map[params.offset] = mapping;
} else { } else {
auto& allocator{big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; auto& allocator{big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
u32 page_size{big_page ? vm.big_page_size : VM::YUZU_PAGESIZE}; u32 page_size{big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
u32 page_size_bits{big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS}; u32 page_size_bits{big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
params.offset = u64(allocator.Allocate(u32(Common::AlignUp(size, page_size) >> page_size_bits))) << page_size_bits; params.offset = static_cast<u64>(allocator.Allocate(
static_cast<u32>(Common::AlignUp(size, page_size) >> page_size_bits)))
<< page_size_bits;
if (!params.offset) { if (!params.offset) {
ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!"); ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!");
return NvResult::InsufficientMemory; return NvResult::InsufficientMemory;
} }
gmmu->Map(params.offset, device_address, Common::AlignUp(size, page_size), Tegra::PTEKind(params.kind), big_page);
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, false, big_page, false)); gmmu->Map(params.offset, device_address, Common::AlignUp(size, page_size),
static_cast<Tegra::PTEKind>(params.kind), big_page);
auto mapping{std::make_shared<Mapping>(params.handle, device_address, params.offset, size,
false, big_page, false)};
mapping_map[params.offset] = mapping;
} }
map_buffer_offsets.insert(params.offset); map_buffer_offsets.insert(params.offset);
@@ -378,32 +415,37 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
} }
NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) { NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
std::scoped_lock lock(mutex); if (map_buffer_offsets.find(params.offset) != map_buffer_offsets.end()) {
if (auto const offset_it = map_buffer_offsets.find(params.offset); offset_it != map_buffer_offsets.end()) {
LOG_DEBUG(Service_NVDRV, "called, offset={:#X}", params.offset); LOG_DEBUG(Service_NVDRV, "called, offset={:#X}", params.offset);
std::scoped_lock lock(mutex);
if (!vm.initialised) { if (!vm.initialised) {
return NvResult::BadValue; return NvResult::BadValue;
} }
auto const it = mapping_map.find(params.offset); auto mapping{mapping_map.at(params.offset)};
auto const mapping = it->second;
if (!mapping.fixed) { if (!mapping->fixed) {
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
u32 page_size_bits{mapping.big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS}; u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
allocator.Free(u32(mapping.offset >> page_size_bits), u32(mapping.size >> page_size_bits));
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
static_cast<u32>(mapping->size >> page_size_bits));
} }
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
// Only FreeSpace can unmap them fully // Only FreeSpace can unmap them fully
if (mapping.sparse_alloc) { if (mapping->sparse_alloc) {
gmmu->MapSparse(params.offset, mapping.size, mapping.big_page); gmmu->MapSparse(params.offset, mapping->size, mapping->big_page);
} else { } else {
gmmu->Unmap(params.offset, mapping.size); gmmu->Unmap(params.offset, mapping->size);
} }
nvmap.UnpinHandle(mapping.handle); nvmap.UnpinHandle(mapping->handle);
mapping_map.erase(it);
map_buffer_offsets.erase(offset_it); mapping_map.erase(params.offset);
map_buffer_offsets.erase(params.offset);
} }
return NvResult::Success; return NvResult::Success;
} }
@@ -436,7 +478,8 @@ void nvhost_as_gpu::GetVARegionsImpl(IoctlGetVaRegions& params) {
} }
NvResult nvhost_as_gpu::GetVARegions1(IoctlGetVaRegions& params) { NvResult nvhost_as_gpu::GetVARegions1(IoctlGetVaRegions& params) {
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr, params.buf_size); LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
params.buf_size);
std::scoped_lock lock(mutex); std::scoped_lock lock(mutex);
@@ -165,36 +165,35 @@ private:
NvCore::NvMap& nvmap; NvCore::NvMap& nvmap;
struct Mapping { struct Mapping {
NvCore::NvMap::Handle::Id handle;
DAddr ptr; DAddr ptr;
u64 offset; u64 offset;
u64 size; u64 size;
NvCore::NvMap::Handle::Id handle; bool fixed;
bool fixed : 1; bool big_page; // Only valid if fixed == false
bool big_page : 1; // Only valid if fixed == false bool sparse_alloc;
bool sparse_alloc : 1;
Mapping(NvCore::NvMap::Handle::Id handle_, DAddr ptr_, u64 offset_, u64 size_, bool fixed_, bool big_page_, bool sparse_alloc_) Mapping(NvCore::NvMap::Handle::Id handle_, DAddr ptr_, u64 offset_, u64 size_, bool fixed_,
: ptr(ptr_), offset(offset_), size(size_), handle(handle_) bool big_page_, bool sparse_alloc_)
, fixed(fixed_), big_page(big_page_), sparse_alloc(sparse_alloc_) : handle(handle_), ptr(ptr_), offset(offset_), size(size_), fixed(fixed_),
{} big_page(big_page_), sparse_alloc(sparse_alloc_) {}
}; };
struct Allocation { struct Allocation {
std::vector<u64> mappings;
u64 size; u64 size;
std::list<std::shared_ptr<Mapping>> mappings;
u32 page_size; u32 page_size;
bool sparse; bool sparse;
bool big_pages; bool big_pages;
}; };
//!< This maps the base addresses of mapped buffers to their total sizes and std::map<u64, std::shared_ptr<Mapping>>
//!< mapping type, this is needed as what was originally a single buffer may mapping_map; //!< This maps the base addresses of mapped buffers to their total sizes and
//!< have been split into multiple GPU side buffers with the remap flag. //!< mapping type, this is needed as what was originally a single buffer may
std::map<u64, Mapping> mapping_map; //!< have been split into multiple GPU side buffers with the remap flag.
//!< Holds allocations created by AllocSpace from std::map<u64, Allocation> allocation_map; //!< Holds allocations created by AllocSpace from
//!< which fixed buffers can be mapped into //!< which fixed buffers can be mapped into
std::map<u64, Allocation> allocation_map; std::mutex mutex; //!< Locks all AS operations
std::mutex mutex; //!< Locks all AS operations
struct VM { struct VM {
static constexpr u32 YUZU_PAGESIZE{0x1000}; static constexpr u32 YUZU_PAGESIZE{0x1000};
@@ -214,8 +213,9 @@ private:
using Allocator = Common::FlatAllocator<u32, 0, 32>; using Allocator = Common::FlatAllocator<u32, 0, 32>;
std::optional<Allocator> big_page_allocator; std::unique_ptr<Allocator> big_page_allocator;
std::optional<Allocator> small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel std::shared_ptr<Allocator>
small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
bool initialised{}; bool initialised{};
} vm; } vm;
@@ -26,11 +26,8 @@ namespace Service::android {
BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_, BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
std::shared_ptr<BufferQueueCore> buffer_queue_core_, std::shared_ptr<BufferQueueCore> buffer_queue_core_,
Service::Nvidia::NvCore::NvMap& nvmap_) Service::Nvidia::NvCore::NvMap& nvmap_)
: service_context{service_context_}, core{std::move(buffer_queue_core_)} : service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots),
, slots(core->slots) clock{Common::CreateOptimalClock()}, nvmap(nvmap_) {
, clock{Common::CreateOptimalClock()}
, nvmap(nvmap_)
{
buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent"); buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
} }
@@ -488,7 +485,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
slots[slot].buffer_state = BufferState::Queued; slots[slot].buffer_state = BufferState::Queued;
slots[slot].frame_number = core->frame_counter; slots[slot].frame_number = core->frame_counter;
slots[slot].queue_time = timestamp; slots[slot].queue_time = timestamp;
slots[slot].presentation_time = clock.GetTimeNS().count(); slots[slot].presentation_time = clock->GetTimeNS().count();
slots[slot].fence = fence; slots[slot].fence = fence;
item.slot = slot; item.slot = slot;
@@ -89,7 +89,8 @@ private:
s32 next_callback_ticket{}; s32 next_callback_ticket{};
s32 current_callback_ticket{}; s32 current_callback_ticket{};
std::condition_variable_any callback_condition; std::condition_variable_any callback_condition;
Common::WallClock clock; std::unique_ptr<Common::WallClock> clock;
Service::Nvidia::NvCore::NvMap& nvmap; Service::Nvidia::NvCore::NvMap& nvmap;
}; };
+4 -5
View File
@@ -54,14 +54,13 @@ enum class NetDbError : s32 {
}; };
static const constexpr std::array blockedDomains = { static const constexpr std::array blockedDomains = {
"srv.nintendo.net", // Obvious "srv.nintendo.net", //obvious
"phoenix-api.wbagora.com", // Hogwarts legacy "phoenix-api.wbagora.com", //hogwarts legacy
"battle.net", "battle.net",
"microsoft.com", // Minecraft dungeons + other games "microsoft.com", //minecraft dungeons + other games
"mojang.com", "mojang.com",
"xboxlive.com", "xboxlive.com",
"minecraftservices.com", "minecraftservices.com"
"508223012e5a5ff19f30a391b2bdadc0.my.2k.com", // Civilization 5
}; };
static bool IsBlockedHost(const std::string& host) { static bool IsBlockedHost(const std::string& host) {
@@ -228,8 +228,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
code_size += patch_ctx.GetTotalPatchSize(); code_size += patch_ctx.GetTotalPatchSize();
// TODO: this is bad form of ASLR, it sucks // TODO: this is bad form of ASLR, it sucks
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue() size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000; ? ::Settings::values.rng_seed.GetValue()
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
// Setup the process code layout // Setup the process code layout
if (process.LoadFromMetadata(metadata, code_size, fastmem_base, aslr_offset, is_hbl).IsError()) { if (process.LoadFromMetadata(metadata, code_size, fastmem_base, aslr_offset, is_hbl).IsError()) {
+3 -2
View File
@@ -89,8 +89,9 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
codeset.DataSegment().size += kip->GetBSSSize(); codeset.DataSegment().size += kip->GetBSSSize();
// TODO: this is bad form of ASLR, it sucks // TODO: this is bad form of ASLR, it sucks
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue() size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000; ? ::Settings::values.rng_seed.GetValue()
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
// Setup the process code layout // Setup the process code layout
if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), codeset.memory.size(), 0, aslr_offset, false).IsError()) { if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), codeset.memory.size(), 0, aslr_offset, false).IsError()) {
+3 -2
View File
@@ -242,8 +242,9 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
}(); }();
// TODO: this is bad form of ASLR, it sucks // TODO: this is bad form of ASLR, it sucks
std::uintptr_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue() size_t aslr_offset = ((::Settings::values.rng_seed_enabled.GetValue()
? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000; ? ::Settings::values.rng_seed.GetValue()
: Common::Random::Random64(0)) * 0x734287f27) & 0xfff000;
// Setup the process code layout // Setup the process code layout
if (process if (process
+5 -3
View File
@@ -169,6 +169,8 @@ if ("x86_64" IN_LIST ARCHITECTURE)
backend/x64/emit_x64_vector.cpp backend/x64/emit_x64_vector.cpp
backend/x64/emit_x64_vector_floating_point.cpp backend/x64/emit_x64_vector_floating_point.cpp
backend/x64/emit_x64_vector_saturation.cpp backend/x64/emit_x64_vector_saturation.cpp
backend/x64/exclusive_monitor.cpp
backend/x64/exclusive_monitor_friend.h
backend/x64/host_feature.h backend/x64/host_feature.h
backend/x64/hostloc.h backend/x64/hostloc.h
backend/x64/jitstate_info.h backend/x64/jitstate_info.h
@@ -229,6 +231,7 @@ if ("arm64" IN_LIST ARCHITECTURE)
backend/arm64/emit_arm64_vector_floating_point.cpp backend/arm64/emit_arm64_vector_floating_point.cpp
backend/arm64/emit_arm64_vector_saturation.cpp backend/arm64/emit_arm64_vector_saturation.cpp
backend/arm64/emit_context.h backend/arm64/emit_context.h
backend/arm64/exclusive_monitor.cpp
backend/arm64/fastmem.h backend/arm64/fastmem.h
backend/arm64/fpsr_manager.cpp backend/arm64/fpsr_manager.cpp
backend/arm64/fpsr_manager.h backend/arm64/fpsr_manager.h
@@ -275,6 +278,7 @@ if ("riscv64" IN_LIST ARCHITECTURE)
backend/riscv64/emit_riscv64_vector.cpp backend/riscv64/emit_riscv64_vector.cpp
backend/riscv64/emit_riscv64.cpp backend/riscv64/emit_riscv64.cpp
backend/riscv64/emit_riscv64.h backend/riscv64/emit_riscv64.h
backend/riscv64/exclusive_monitor.cpp
backend/riscv64/reg_alloc.cpp backend/riscv64/reg_alloc.cpp
backend/riscv64/reg_alloc.h backend/riscv64/reg_alloc.h
backend/riscv64/stack_layout.h backend/riscv64/stack_layout.h
@@ -363,6 +367,7 @@ if (BOOST_NO_HEADERS)
else() else()
target_link_libraries(dynarmic PRIVATE Boost::headers) target_link_libraries(dynarmic PRIVATE Boost::headers)
endif() endif()
if (DYNARMIC_USE_LLVM) if (DYNARMIC_USE_LLVM)
target_include_directories(dynarmic PRIVATE ${LLVM_INCLUDE_DIRS}) target_include_directories(dynarmic PRIVATE ${LLVM_INCLUDE_DIRS})
target_compile_definitions(dynarmic PRIVATE DYNARMIC_USE_LLVM=1 ${LLVM_DEFINITIONS}) target_compile_definitions(dynarmic PRIVATE DYNARMIC_USE_LLVM=1 ${LLVM_DEFINITIONS})
@@ -380,7 +385,4 @@ endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Windows") if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
target_compile_definitions(dynarmic PRIVATE FMT_USE_WINDOWS_H=0) target_compile_definitions(dynarmic PRIVATE FMT_USE_WINDOWS_H=0)
endif() endif()
if (NOT DEFINED xbyak_ADDED)
target_compile_definitions(dynarmic PRIVATE XBYAK_BUNDLED=1)
endif()
target_compile_definitions(dynarmic PRIVATE FMT_USE_USER_DEFINED_LITERALS=1) target_compile_definitions(dynarmic PRIVATE FMT_USE_USER_DEFINED_LITERALS=1)
@@ -135,9 +135,12 @@ static void* EmitExclusiveWriteCallTrampoline(oaknut::CodeGenerator& code, const
oaknut::Label l_addr, l_this; oaknut::Label l_addr, l_this;
auto fn = [](const A64::UserConfig& conf, A64::VAddr vaddr, T value) -> u32 { auto fn = [](const A64::UserConfig& conf, A64::VAddr vaddr, T value) -> u32 {
return conf.global_monitor->DoExclusiveOperation<T>(conf.processor_id, vaddr, [&](T expected) -> bool { return conf.global_monitor->DoExclusiveOperation<T>(conf.processor_id, vaddr,
return (conf.callbacks->*callback)(vaddr, value, expected); [&](T expected) -> bool {
}) ? 0 : 1; return (conf.callbacks->*callback)(vaddr, value, expected);
})
? 0
: 1;
}; };
void* target = code.xptr<void*>(); void* target = code.xptr<void*>();
@@ -297,9 +300,12 @@ static void* EmitExclusiveWrite128CallTrampoline(oaknut::CodeGenerator& code, co
oaknut::Label l_addr, l_this; oaknut::Label l_addr, l_this;
auto fn = [](const A64::UserConfig& conf, A64::VAddr vaddr, Vector value) -> u32 { auto fn = [](const A64::UserConfig& conf, A64::VAddr vaddr, Vector value) -> u32 {
return conf.global_monitor->DoExclusiveOperation<Vector>(conf.processor_id, vaddr, [&](Vector expected) -> bool { return conf.global_monitor->DoExclusiveOperation<Vector>(conf.processor_id, vaddr,
return conf.callbacks->MemoryWriteExclusive128(vaddr, value, expected); [&](Vector expected) -> bool {
}) ? 0 : 1; return conf.callbacks->MemoryWriteExclusive128(vaddr, value, expected);
})
? 0
: 1;
}; };
void* target = code.xptr<void*>(); void* target = code.xptr<void*>();
@@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
* Copyright (c) 2022 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include "dynarmic/interface/exclusive_monitor.h"
#include <algorithm>
#include "common/assert.h"
namespace Dynarmic {
ExclusiveMonitor::ExclusiveMonitor(std::size_t processor_count)
: exclusive_addresses(processor_count, INVALID_EXCLUSIVE_ADDRESS), exclusive_values(processor_count) {}
size_t ExclusiveMonitor::GetProcessorCount() const {
return exclusive_addresses.size();
}
void ExclusiveMonitor::Lock() {
lock.Lock();
}
void ExclusiveMonitor::Unlock() {
lock.Unlock();
}
bool ExclusiveMonitor::CheckAndClear(std::size_t processor_id, VAddr address) {
const VAddr masked_address = address & RESERVATION_GRANULE_MASK;
Lock();
if (exclusive_addresses[processor_id] != masked_address) {
Unlock();
return false;
}
for (VAddr& other_address : exclusive_addresses) {
if (other_address == masked_address) {
other_address = INVALID_EXCLUSIVE_ADDRESS;
}
}
return true;
}
void ExclusiveMonitor::Clear() {
Lock();
std::fill(exclusive_addresses.begin(), exclusive_addresses.end(), INVALID_EXCLUSIVE_ADDRESS);
Unlock();
}
void ExclusiveMonitor::ClearProcessor(std::size_t processor_id) {
Lock();
exclusive_addresses[processor_id] = INVALID_EXCLUSIVE_ADDRESS;
Unlock();
}
} // namespace Dynarmic
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "dynarmic/interface/exclusive_monitor.h"
#include <algorithm>
namespace Dynarmic {
ExclusiveMonitor::ExclusiveMonitor(std::size_t processor_count)
: exclusive_addresses(processor_count, INVALID_EXCLUSIVE_ADDRESS), exclusive_values(processor_count) {}
size_t ExclusiveMonitor::GetProcessorCount() const {
return exclusive_addresses.size();
}
void ExclusiveMonitor::Lock() {
lock.Lock();
}
void ExclusiveMonitor::Unlock() {
lock.Unlock();
}
bool ExclusiveMonitor::CheckAndClear(size_t processor_id, VAddr address) {
const VAddr masked_address = address & RESERVATION_GRANULE_MASK;
Lock();
if (exclusive_addresses[processor_id] != masked_address) {
Unlock();
return false;
}
for (VAddr& other_address : exclusive_addresses) {
if (other_address == masked_address) {
other_address = INVALID_EXCLUSIVE_ADDRESS;
}
}
return true;
}
void ExclusiveMonitor::Clear() {
Lock();
std::fill(exclusive_addresses.begin(), exclusive_addresses.end(), INVALID_EXCLUSIVE_ADDRESS);
Unlock();
}
void ExclusiveMonitor::ClearProcessor(size_t processor_id) {
Lock();
exclusive_addresses[processor_id] = INVALID_EXCLUSIVE_ADDRESS;
Unlock();
}
} // namespace Dynarmic
@@ -20,7 +20,7 @@
#include "dynarmic/backend/x64/abi.h" #include "dynarmic/backend/x64/abi.h"
#include "dynarmic/backend/x64/devirtualize.h" #include "dynarmic/backend/x64/devirtualize.h"
#include "dynarmic/backend/x64/emit_x64_memory.h" #include "dynarmic/backend/x64/emit_x64_memory.h"
#include "dynarmic/interface/exclusive_monitor.h" #include "dynarmic/backend/x64/exclusive_monitor_friend.h"
#include "dynarmic/backend/x64/perf_map.h" #include "dynarmic/backend/x64/perf_map.h"
#include "dynarmic/interface/exclusive_monitor.h" #include "dynarmic/interface/exclusive_monitor.h"
@@ -174,35 +174,67 @@ void A32EmitX64::EmitA32ClearExclusive(A32EmitContext&, IR::Inst*) {
} }
void A32EmitX64::EmitA32ExclusiveReadMemory8(A32EmitContext& ctx, IR::Inst* inst) { void A32EmitX64::EmitA32ExclusiveReadMemory8(A32EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveReadMemoryInline<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveReadMemoryInline<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst);
} else {
EmitExclusiveReadMemory<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst);
}
} }
void A32EmitX64::EmitA32ExclusiveReadMemory16(A32EmitContext& ctx, IR::Inst* inst) { void A32EmitX64::EmitA32ExclusiveReadMemory16(A32EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveReadMemoryInline<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveReadMemoryInline<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst);
} else {
EmitExclusiveReadMemory<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst);
}
} }
void A32EmitX64::EmitA32ExclusiveReadMemory32(A32EmitContext& ctx, IR::Inst* inst) { void A32EmitX64::EmitA32ExclusiveReadMemory32(A32EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveReadMemoryInline<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveReadMemoryInline<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst);
} else {
EmitExclusiveReadMemory<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst);
}
} }
void A32EmitX64::EmitA32ExclusiveReadMemory64(A32EmitContext& ctx, IR::Inst* inst) { void A32EmitX64::EmitA32ExclusiveReadMemory64(A32EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveReadMemoryInline<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveReadMemoryInline<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst);
} else {
EmitExclusiveReadMemory<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst);
}
} }
void A32EmitX64::EmitA32ExclusiveWriteMemory8(A32EmitContext& ctx, IR::Inst* inst) { void A32EmitX64::EmitA32ExclusiveWriteMemory8(A32EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveWriteMemoryInline<8, &A32::UserCallbacks::MemoryWriteExclusive8>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveWriteMemoryInline<8, &A32::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
} else {
EmitExclusiveWriteMemory<8, &A32::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
}
} }
void A32EmitX64::EmitA32ExclusiveWriteMemory16(A32EmitContext& ctx, IR::Inst* inst) { void A32EmitX64::EmitA32ExclusiveWriteMemory16(A32EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveWriteMemoryInline<16, &A32::UserCallbacks::MemoryWriteExclusive16>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveWriteMemoryInline<16, &A32::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
} else {
EmitExclusiveWriteMemory<16, &A32::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
}
} }
void A32EmitX64::EmitA32ExclusiveWriteMemory32(A32EmitContext& ctx, IR::Inst* inst) { void A32EmitX64::EmitA32ExclusiveWriteMemory32(A32EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveWriteMemoryInline<32, &A32::UserCallbacks::MemoryWriteExclusive32>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveWriteMemoryInline<32, &A32::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
} else {
EmitExclusiveWriteMemory<32, &A32::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
}
} }
void A32EmitX64::EmitA32ExclusiveWriteMemory64(A32EmitContext& ctx, IR::Inst* inst) { void A32EmitX64::EmitA32ExclusiveWriteMemory64(A32EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveWriteMemoryInline<64, &A32::UserCallbacks::MemoryWriteExclusive64>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveWriteMemoryInline<64, &A32::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
} else {
EmitExclusiveWriteMemory<64, &A32::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
}
} }
void A32EmitX64::EmitCheckMemoryAbort(A32EmitContext& ctx, IR::Inst* inst, Xbyak::Label* end) { void A32EmitX64::EmitCheckMemoryAbort(A32EmitContext& ctx, IR::Inst* inst, Xbyak::Label* end) {
@@ -20,7 +20,7 @@
#include "dynarmic/backend/x64/abi.h" #include "dynarmic/backend/x64/abi.h"
#include "dynarmic/backend/x64/devirtualize.h" #include "dynarmic/backend/x64/devirtualize.h"
#include "dynarmic/backend/x64/emit_x64_memory.h" #include "dynarmic/backend/x64/emit_x64_memory.h"
#include "dynarmic/interface/exclusive_monitor.h" #include "dynarmic/backend/x64/exclusive_monitor_friend.h"
#include "dynarmic/backend/x64/perf_map.h" #include "dynarmic/backend/x64/perf_map.h"
#include "dynarmic/common/spin_lock_x64.h" #include "dynarmic/common/spin_lock_x64.h"
#include "dynarmic/interface/exclusive_monitor.h" #include "dynarmic/interface/exclusive_monitor.h"
@@ -330,43 +330,83 @@ void A64EmitX64::EmitA64ClearExclusive(A64EmitContext&, IR::Inst*) {
} }
void A64EmitX64::EmitA64ExclusiveReadMemory8(A64EmitContext& ctx, IR::Inst* inst) { void A64EmitX64::EmitA64ExclusiveReadMemory8(A64EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveReadMemoryInline<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveReadMemoryInline<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst);
} else {
EmitExclusiveReadMemory<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst);
}
} }
void A64EmitX64::EmitA64ExclusiveReadMemory16(A64EmitContext& ctx, IR::Inst* inst) { void A64EmitX64::EmitA64ExclusiveReadMemory16(A64EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveReadMemoryInline<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveReadMemoryInline<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst);
} else {
EmitExclusiveReadMemory<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst);
}
} }
void A64EmitX64::EmitA64ExclusiveReadMemory32(A64EmitContext& ctx, IR::Inst* inst) { void A64EmitX64::EmitA64ExclusiveReadMemory32(A64EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveReadMemoryInline<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveReadMemoryInline<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst);
} else {
EmitExclusiveReadMemory<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst);
}
} }
void A64EmitX64::EmitA64ExclusiveReadMemory64(A64EmitContext& ctx, IR::Inst* inst) { void A64EmitX64::EmitA64ExclusiveReadMemory64(A64EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveReadMemoryInline<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveReadMemoryInline<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst);
} else {
EmitExclusiveReadMemory<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst);
}
} }
void A64EmitX64::EmitA64ExclusiveReadMemory128(A64EmitContext& ctx, IR::Inst* inst) { void A64EmitX64::EmitA64ExclusiveReadMemory128(A64EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveReadMemoryInline<128, &A64::UserCallbacks::MemoryRead128>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveReadMemoryInline<128, &A64::UserCallbacks::MemoryRead128>(ctx, inst);
} else {
EmitExclusiveReadMemory<128, &A64::UserCallbacks::MemoryRead128>(ctx, inst);
}
} }
void A64EmitX64::EmitA64ExclusiveWriteMemory8(A64EmitContext& ctx, IR::Inst* inst) { void A64EmitX64::EmitA64ExclusiveWriteMemory8(A64EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveWriteMemoryInline<8, &A64::UserCallbacks::MemoryWriteExclusive8>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveWriteMemoryInline<8, &A64::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
} else {
EmitExclusiveWriteMemory<8, &A64::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
}
} }
void A64EmitX64::EmitA64ExclusiveWriteMemory16(A64EmitContext& ctx, IR::Inst* inst) { void A64EmitX64::EmitA64ExclusiveWriteMemory16(A64EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveWriteMemoryInline<16, &A64::UserCallbacks::MemoryWriteExclusive16>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveWriteMemoryInline<16, &A64::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
} else {
EmitExclusiveWriteMemory<16, &A64::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
}
} }
void A64EmitX64::EmitA64ExclusiveWriteMemory32(A64EmitContext& ctx, IR::Inst* inst) { void A64EmitX64::EmitA64ExclusiveWriteMemory32(A64EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveWriteMemoryInline<32, &A64::UserCallbacks::MemoryWriteExclusive32>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveWriteMemoryInline<32, &A64::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
} else {
EmitExclusiveWriteMemory<32, &A64::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
}
} }
void A64EmitX64::EmitA64ExclusiveWriteMemory64(A64EmitContext& ctx, IR::Inst* inst) { void A64EmitX64::EmitA64ExclusiveWriteMemory64(A64EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveWriteMemoryInline<64, &A64::UserCallbacks::MemoryWriteExclusive64>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveWriteMemoryInline<64, &A64::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
} else {
EmitExclusiveWriteMemory<64, &A64::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
}
} }
void A64EmitX64::EmitA64ExclusiveWriteMemory128(A64EmitContext& ctx, IR::Inst* inst) { void A64EmitX64::EmitA64ExclusiveWriteMemory128(A64EmitContext& ctx, IR::Inst* inst) {
EmitExclusiveWriteMemoryInline<128, &A64::UserCallbacks::MemoryWriteExclusive128>(ctx, inst); if (conf.fastmem_exclusive_access) {
EmitExclusiveWriteMemoryInline<128, &A64::UserCallbacks::MemoryWriteExclusive128>(ctx, inst);
} else {
EmitExclusiveWriteMemory<128, &A64::UserCallbacks::MemoryWriteExclusive128>(ctx, inst);
}
} }
void A64EmitX64::EmitCheckMemoryAbort(A64EmitContext&, IR::Inst* inst, Xbyak::Label* end) { void A64EmitX64::EmitCheckMemoryAbort(A64EmitContext&, IR::Inst* inst, Xbyak::Label* end) {
@@ -519,6 +519,10 @@ void BlockOfCode::LoadRequiredFlagsForCondFromRax(IR::Cond cond) {
} }
} }
Xbyak::Address BlockOfCode::Const(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) {
return constant_pool.GetConstant(frame, lower, upper);
}
CodePtr BlockOfCode::GetCodeBegin() const { CodePtr BlockOfCode::GetCodeBegin() const {
return code_begin; return code_begin;
} }
@@ -24,7 +24,6 @@
#include "dynarmic/common/cast_util.h" #include "dynarmic/common/cast_util.h"
#include "dynarmic/interface/halt_reason.h" #include "dynarmic/interface/halt_reason.h"
#include "dynarmic/ir/cond.h" #include "dynarmic/ir/cond.h"
#include "xbyak/xbyak.h"
namespace Dynarmic::Backend::X64 { namespace Dynarmic::Backend::X64 {
@@ -102,8 +101,8 @@ public:
} }
/// Code emitter: Calls the lambda. Lambda must not have any captures. /// Code emitter: Calls the lambda. Lambda must not have any captures.
template<typename F> template<typename Lambda>
void CallLambda(F l) { void CallLambda(Lambda l) {
CallFunction(Common::FptrCast(l)); CallFunction(Common::FptrCast(l));
} }
@@ -125,35 +124,22 @@ public:
} }
} }
// Xbyak benefits slightly from not having & references on some of its ctors, however one main Xbyak::Address Const(const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0);
// disadvantage is that this breaks ABI slightly because we need to hijack the main privated ctor, hence
// we define two paths, one for gentoo (non bundled) and one for our custom bundled set with patch
#ifdef XBYAK_BUNDLED
[[nodiscard]] Xbyak::Address Const(const Xbyak::AddressFrame frame, u64 lower, u64 upper = 0) {
return constant_pool.GetConstant(Xbyak::AddressFrame(frame.bit_, frame.broadcast_), lower, upper);
}
template<size_t esize> template<size_t esize>
[[nodiscard]] Xbyak::Address BConst(const Xbyak::AddressFrame frame, u64 value) { Xbyak::Address BConst(const Xbyak::AddressFrame& frame, u64 value) {
return Const(Xbyak::AddressFrame(frame.bit_, frame.broadcast_), mcl::bit::replicate_element<u64>(esize, value), mcl::bit::replicate_element<u64>(esize, value)); return Const(frame, mcl::bit::replicate_element<u64>(esize, value),
mcl::bit::replicate_element<u64>(esize, value));
} }
#else
[[nodiscard]] Xbyak::Address Const(const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0) {
return constant_pool.GetConstant(frame, lower, upper);
}
template<size_t esize>
[[nodiscard]] Xbyak::Address BConst(const Xbyak::AddressFrame& frame, u64 value) {
return Const(frame, mcl::bit::replicate_element<u64>(esize, value), mcl::bit::replicate_element<u64>(esize, value));
}
#endif
CodePtr GetCodeBegin() const; CodePtr GetCodeBegin() const;
size_t GetTotalCodeSize() const; size_t GetTotalCodeSize() const;
[[nodiscard]] const void* GetReturnFromRunCodeAddress() const { const void* GetReturnFromRunCodeAddress() const {
return return_from_run_code[0]; return return_from_run_code[0];
} }
[[nodiscard]] const void* GetForceReturnFromRunCodeAddress() const { const void* GetForceReturnFromRunCodeAddress() const {
return return_from_run_code[FORCE_RETURN]; return return_from_run_code[FORCE_RETURN];
} }
@@ -25,11 +25,7 @@ ConstantPool::ConstantPool(BlockOfCode& code, size_t size)
reinterpret_cast<ConstantT*>(code.AllocateFromCodeSpace(size)), size / align_size); reinterpret_cast<ConstantT*>(code.AllocateFromCodeSpace(size)), size / align_size);
} }
#ifdef XBYAK_BUNDLED
Xbyak::Address ConstantPool::GetConstant(const Xbyak::AddressFrame frame, u64 lower, u64 upper) {
#else
Xbyak::Address ConstantPool::GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) { Xbyak::Address ConstantPool::GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) {
#endif
const auto constant = ConstantT(lower, upper); const auto constant = ConstantT(lower, upper);
auto iter = constant_info.find(constant); auto iter = constant_info.find(constant);
if (iter == constant_info.end()) { if (iter == constant_info.end()) {
@@ -29,11 +29,7 @@ class ConstantPool final {
public: public:
ConstantPool(BlockOfCode& code, size_t size); ConstantPool(BlockOfCode& code, size_t size);
#ifdef XBYAK_BUNDLED
Xbyak::Address GetConstant(const Xbyak::AddressFrame frame, u64 lower, u64 upper = 0);
#else
Xbyak::Address GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0); Xbyak::Address GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0);
#endif
private: private:
static constexpr size_t align_size = 16; // bytes static constexpr size_t align_size = 16; // bytes
@@ -230,11 +230,12 @@ void AxxEmitX64::EmitExclusiveReadMemory(AxxEmitContext& ctx, IR::Inst* inst) {
if (ordered) { if (ordered) {
code.mfence(); code.mfence();
} }
code.CallLambda([](AxxUserConfig& conf, Axx::VAddr vaddr) -> T { code.CallLambda(
return conf.global_monitor->ReadAndMark<T>(conf.processor_id, vaddr, [&]() -> T { [](AxxUserConfig& conf, Axx::VAddr vaddr) -> T {
return (conf.callbacks->*callback)(vaddr); return conf.global_monitor->ReadAndMark<T>(conf.processor_id, vaddr, [&]() -> T {
return (conf.callbacks->*callback)(vaddr);
});
}); });
});
code.ZeroExtendFrom(bitsize, code.ABI_RETURN); code.ZeroExtendFrom(bitsize, code.ABI_RETURN);
} else { } else {
const Xbyak::Xmm result = ctx.reg_alloc.ScratchXmm(code); const Xbyak::Xmm result = ctx.reg_alloc.ScratchXmm(code);
@@ -249,11 +250,12 @@ void AxxEmitX64::EmitExclusiveReadMemory(AxxEmitContext& ctx, IR::Inst* inst) {
if (ordered) { if (ordered) {
code.mfence(); code.mfence();
} }
code.CallLambda([](AxxUserConfig& conf, Axx::VAddr vaddr, Vector& ret) { code.CallLambda(
ret = conf.global_monitor->ReadAndMark<Vector>(conf.processor_id, vaddr, [&]() -> Vector { [](AxxUserConfig& conf, Axx::VAddr vaddr, Vector& ret) {
return (conf.callbacks->*callback)(vaddr); ret = conf.global_monitor->ReadAndMark<Vector>(conf.processor_id, vaddr, [&]() -> Vector {
return (conf.callbacks->*callback)(vaddr);
});
}); });
});
code.movups(result, xword[rsp + ABI_SHADOW_SPACE]); code.movups(result, xword[rsp + ABI_SHADOW_SPACE]);
ctx.reg_alloc.ReleaseStackSpace(code, 16 + ABI_SHADOW_SPACE); ctx.reg_alloc.ReleaseStackSpace(code, 16 + ABI_SHADOW_SPACE);
@@ -318,12 +320,11 @@ void AxxEmitX64::EmitExclusiveWriteMemory(AxxEmitContext& ctx, IR::Inst* inst) {
template<std::size_t bitsize, auto callback> template<std::size_t bitsize, auto callback>
void AxxEmitX64::EmitExclusiveReadMemoryInline(AxxEmitContext& ctx, IR::Inst* inst) { void AxxEmitX64::EmitExclusiveReadMemoryInline(AxxEmitContext& ctx, IR::Inst* inst) {
ASSERT(conf.global_monitor); ASSERT(conf.global_monitor && conf.fastmem_pointer);
if (!conf.fastmem_exclusive_access || !exception_handler.SupportsFastmem()) { if (!exception_handler.SupportsFastmem()) {
EmitExclusiveReadMemory<bitsize, callback>(ctx, inst); EmitExclusiveReadMemory<bitsize, callback>(ctx, inst);
return; return;
} }
ASSERT(conf.fastmem_pointer);
auto args = ctx.reg_alloc.GetArgumentInfo(inst); auto args = ctx.reg_alloc.GetArgumentInfo(inst);
constexpr bool ordered = true; constexpr bool ordered = true;
@@ -343,10 +344,10 @@ void AxxEmitX64::EmitExclusiveReadMemoryInline(AxxEmitContext& ctx, IR::Inst* in
const auto wrapped_fn = read_fallbacks[std::make_tuple(ordered, bitsize, vaddr.getIdx(), value_idx)]; const auto wrapped_fn = read_fallbacks[std::make_tuple(ordered, bitsize, vaddr.getIdx(), value_idx)];
EmitExclusiveLock(code, conf, tmp2.cvt32()); EmitExclusiveLock(code, conf, tmp, tmp2.cvt32());
code.mov(code.byte[code.ABI_JIT_PTR + offsetof(AxxJitState, exclusive_state)], u8(1)); code.mov(code.byte[code.ABI_JIT_PTR + offsetof(AxxJitState, exclusive_state)], u8(1));
code.mov(tmp, std::bit_cast<u64>(conf.global_monitor->exclusive_addresses.data() + conf.processor_id)); code.mov(tmp, std::bit_cast<u64>(GetExclusiveMonitorAddressPointer(conf.global_monitor, conf.processor_id)));
code.mov(qword[tmp], vaddr); code.mov(qword[tmp], vaddr);
const auto fastmem_marker = ShouldFastmem(ctx, inst); const auto fastmem_marker = ShouldFastmem(ctx, inst);
@@ -380,10 +381,10 @@ void AxxEmitX64::EmitExclusiveReadMemoryInline(AxxEmitContext& ctx, IR::Inst* in
code.call(wrapped_fn); code.call(wrapped_fn);
} }
code.mov(tmp, std::bit_cast<u64>(conf.global_monitor->exclusive_values.data() + conf.processor_id)); code.mov(tmp, std::bit_cast<u64>(GetExclusiveMonitorValuePointer(conf.global_monitor, conf.processor_id)));
EmitWriteMemoryMov<bitsize>(code, tmp, value_idx, false); EmitWriteMemoryMov<bitsize>(code, tmp, value_idx, false);
EmitExclusiveUnlock(code, conf, tmp2.cvt32()); EmitExclusiveUnlock(code, conf, tmp, tmp2.cvt32());
if constexpr (bitsize == 128) { if constexpr (bitsize == 128) {
ctx.reg_alloc.DefineValue(code, inst, Xbyak::Xmm{value_idx}); ctx.reg_alloc.DefineValue(code, inst, Xbyak::Xmm{value_idx});
@@ -396,12 +397,11 @@ void AxxEmitX64::EmitExclusiveReadMemoryInline(AxxEmitContext& ctx, IR::Inst* in
template<std::size_t bitsize, auto callback> template<std::size_t bitsize, auto callback>
void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* inst) { void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* inst) {
ASSERT(conf.global_monitor); ASSERT(conf.global_monitor && conf.fastmem_pointer);
if (!conf.fastmem_exclusive_access || !exception_handler.SupportsFastmem()) { if (!exception_handler.SupportsFastmem()) {
EmitExclusiveWriteMemory<bitsize, callback>(ctx, inst); EmitExclusiveWriteMemory<bitsize, callback>(ctx, inst);
return; return;
} }
ASSERT(conf.fastmem_pointer);
auto args = ctx.reg_alloc.GetArgumentInfo(inst); auto args = ctx.reg_alloc.GetArgumentInfo(inst);
constexpr bool ordered = true; constexpr bool ordered = true;
@@ -425,7 +425,7 @@ void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* i
const auto wrapped_fn = exclusive_write_fallbacks[std::make_tuple(ordered, bitsize, vaddr.getIdx(), value.getIdx())]; const auto wrapped_fn = exclusive_write_fallbacks[std::make_tuple(ordered, bitsize, vaddr.getIdx(), value.getIdx())];
EmitExclusiveLock(code, conf, tmp2.cvt32()); EmitExclusiveLock(code, conf, tmp, tmp2.cvt32());
SharedLabel end = ctx.GenSharedLabel(); SharedLabel end = ctx.GenSharedLabel();
@@ -433,14 +433,14 @@ void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* i
code.movzx(tmp.cvt32(), code.byte[code.ABI_JIT_PTR + offsetof(AxxJitState, exclusive_state)]); code.movzx(tmp.cvt32(), code.byte[code.ABI_JIT_PTR + offsetof(AxxJitState, exclusive_state)]);
code.test(tmp.cvt8(), tmp.cvt8()); code.test(tmp.cvt8(), tmp.cvt8());
code.je(*end, code.T_NEAR); code.je(*end, code.T_NEAR);
code.mov(tmp, std::bit_cast<u64>(conf.global_monitor->exclusive_addresses.data() + conf.processor_id)); code.mov(tmp, std::bit_cast<u64>(GetExclusiveMonitorAddressPointer(conf.global_monitor, conf.processor_id)));
code.cmp(qword[tmp], vaddr); code.cmp(qword[tmp], vaddr);
code.jne(*end, code.T_NEAR); code.jne(*end, code.T_NEAR);
EmitExclusiveTestAndClear(code, conf, vaddr, tmp, rax); EmitExclusiveTestAndClear(code, conf, vaddr, tmp, rax);
code.mov(code.byte[code.ABI_JIT_PTR + offsetof(AxxJitState, exclusive_state)], u8(0)); code.mov(code.byte[code.ABI_JIT_PTR + offsetof(AxxJitState, exclusive_state)], u8(0));
code.mov(tmp, std::bit_cast<u64>(conf.global_monitor->exclusive_values.data() + conf.processor_id)); code.mov(tmp, std::bit_cast<u64>(GetExclusiveMonitorValuePointer(conf.global_monitor, conf.processor_id)));
if constexpr (bitsize == 128) { if constexpr (bitsize == 128) {
code.mov(rax, qword[tmp + 0]); code.mov(rax, qword[tmp + 0]);
@@ -519,7 +519,7 @@ void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* i
} }
code.L(*end); code.L(*end);
EmitExclusiveUnlock(code, conf, eax); EmitExclusiveUnlock(code, conf, tmp, eax);
ctx.reg_alloc.DefineValue(code, inst, status); ctx.reg_alloc.DefineValue(code, inst, status);
EmitCheckMemoryAbort(ctx, inst); EmitCheckMemoryAbort(ctx, inst);
} }
@@ -13,7 +13,7 @@
#include "dynarmic/backend/x64/a32_emit_x64.h" #include "dynarmic/backend/x64/a32_emit_x64.h"
#include "dynarmic/backend/x64/a64_emit_x64.h" #include "dynarmic/backend/x64/a64_emit_x64.h"
#include "dynarmic/interface/exclusive_monitor.h" #include "dynarmic/backend/x64/exclusive_monitor_friend.h"
#include "dynarmic/common/spin_lock_x64.h" #include "dynarmic/common/spin_lock_x64.h"
#include "dynarmic/interface/exclusive_monitor.h" #include "dynarmic/interface/exclusive_monitor.h"
#include "dynarmic/ir/acc_type.h" #include "dynarmic/ir/acc_type.h"
@@ -344,36 +344,43 @@ const void* EmitWriteMemoryMov(BlockOfCode& code, const Xbyak::RegExp& addr, int
} }
template<typename UserConfig> template<typename UserConfig>
void EmitExclusiveLock(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg32 tmp) { void EmitExclusiveLock(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg64 pointer, Xbyak::Reg32 tmp) {
if (!conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) { if (conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
u64 const slp = std::bit_cast<u64>(std::addressof(conf.global_monitor->lock.storage)); return;
EmitSpinLockLock(code, dword[slp], tmp, code.HasHostFeature(HostFeature::WAITPKG));
} }
code.mov(pointer, std::bit_cast<u64>(GetExclusiveMonitorLockPointer(conf.global_monitor)));
EmitSpinLockLock(code, pointer, tmp, code.HasHostFeature(HostFeature::WAITPKG));
} }
template<typename UserConfig> template<typename UserConfig>
void EmitExclusiveUnlock(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg32 tmp) { void EmitExclusiveUnlock(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg64 pointer, Xbyak::Reg32 tmp) {
if (!conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) { if (conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
u64 const slp = std::bit_cast<u64>(std::addressof(conf.global_monitor->lock.storage)); return;
EmitSpinLockUnlock(code, dword[slp], tmp);
} }
code.mov(pointer, std::bit_cast<u64>(GetExclusiveMonitorLockPointer(conf.global_monitor)));
EmitSpinLockUnlock(code, pointer, tmp);
} }
template<typename UserConfig> template<typename UserConfig>
void EmitExclusiveTestAndClear(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg64 vaddr, Xbyak::Reg64 pointer, Xbyak::Reg64 tmp) { void EmitExclusiveTestAndClear(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg64 vaddr, Xbyak::Reg64 pointer, Xbyak::Reg64 tmp) {
if (!conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) { if (conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
code.mov(tmp, 0xDEAD'DEAD'DEAD'DEAD); return;
static_assert(ExclusiveMonitor::MAX_NUM_CPU_CORES == 4); }
for (size_t i = 0; i < ExclusiveMonitor::MAX_NUM_CPU_CORES; i++) {
if (i != conf.processor_id) { code.mov(tmp, 0xDEAD'DEAD'DEAD'DEAD);
Xbyak::Label ok; const size_t processor_count = GetExclusiveMonitorProcessorCount(conf.global_monitor);
code.mov(pointer, std::bit_cast<u64>(conf.global_monitor->exclusive_addresses.data() + i)); for (size_t processor_index = 0; processor_index < processor_count; processor_index++) {
code.cmp(qword[pointer], vaddr); if (processor_index == conf.processor_id) {
code.jne(ok, code.T_NEAR); continue;
code.mov(qword[pointer], tmp);
code.L(ok);
}
} }
Xbyak::Label ok;
code.mov(pointer, std::bit_cast<u64>(GetExclusiveMonitorAddressPointer(conf.global_monitor, processor_index)));
code.cmp(qword[pointer], vaddr);
code.jne(ok, code.T_NEAR);
code.mov(qword[pointer], tmp);
code.L(ok);
} }
} }
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project. /* This file is part of the dynarmic project.
@@ -595,8 +595,7 @@ void EmitX64::EmitPackedHalvingSubAddS16(EmitContext& ctx, IR::Inst* inst) {
EmitPackedSubAdd(code, ctx, inst, false, true, true); EmitPackedSubAdd(code, ctx, inst, false, true, true);
} }
template<typename F> static void EmitPackedOperation(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, void (Xbyak::CodeGenerator::*fn)(const Xbyak::Mmx& mmx, const Xbyak::Operand&)) {
static void EmitPackedOperation(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, F&& fn) {
auto args = ctx.reg_alloc.GetArgumentInfo(inst); auto args = ctx.reg_alloc.GetArgumentInfo(inst);
const Xbyak::Xmm xmm_a = ctx.reg_alloc.UseScratchXmm(code, args[0]); const Xbyak::Xmm xmm_a = ctx.reg_alloc.UseScratchXmm(code, args[0]);
@@ -23,8 +23,7 @@ using namespace Xbyak::util;
namespace { namespace {
template<typename F1, typename F2, typename F3> void EmitVectorSaturatedNative(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, void (Xbyak::CodeGenerator::*saturated_fn)(const Xbyak::Mmx& mmx, const Xbyak::Operand&), void (Xbyak::CodeGenerator::*unsaturated_fn)(const Xbyak::Mmx& mmx, const Xbyak::Operand&), void (Xbyak::CodeGenerator::*sub_fn)(const Xbyak::Mmx& mmx, const Xbyak::Operand&)) {
static void EmitVectorSaturatedNative(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, F1&& saturated_fn, F2&& unsaturated_fn, F3&& sub_fn) {
auto args = ctx.reg_alloc.GetArgumentInfo(inst); auto args = ctx.reg_alloc.GetArgumentInfo(inst);
const Xbyak::Xmm result = ctx.reg_alloc.UseScratchXmm(code, args[0]); const Xbyak::Xmm result = ctx.reg_alloc.UseScratchXmm(code, args[0]);
@@ -8,6 +8,8 @@
#pragma once #pragma once
#include <bitset> #include <bitset>
#include <xbyak/xbyak.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "dynarmic/backend/x64/xbyak.h" #include "dynarmic/backend/x64/xbyak.h"
@@ -23,18 +23,19 @@ static const auto default_cg_mode = nullptr; //Allow RWE
namespace Dynarmic { namespace Dynarmic {
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp, bool waitpkg) { void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp, bool waitpkg) {
// TODO: this is because we lack regalloc - so better to be safe :(
if (waitpkg) {
code.push(Xbyak::util::eax);
code.push(Xbyak::util::ebx);
code.push(Xbyak::util::edx);
}
Xbyak::Label start, loop; Xbyak::Label start, loop;
code.jmp(start, code.T_NEAR); code.jmp(start, code.T_NEAR);
code.L(loop); code.L(loop);
if (waitpkg) { if (waitpkg) {
// TODO: this is because we lack regalloc - so better to be safe :(
code.push(Xbyak::util::rax);
code.push(Xbyak::util::rbx);
code.push(Xbyak::util::rdx);
// TODO: This clobbers EAX and EDX did we tell the regalloc? // TODO: This clobbers EAX and EDX did we tell the regalloc?
// ARM ptr for address-monitoring // ARM ptr for address-monitoring
code.mov(Xbyak::util::eax, ptr); code.umonitor(ptr);
code.umonitor(Xbyak::util::eax);
// tmp.bit[0] = 0: C0.1 | Slow Wakup | Better Savings // tmp.bit[0] = 0: C0.1 | Slow Wakup | Better Savings
// tmp.bit[0] = 1: C0.2 | Fast Wakup | Lesser Savings // tmp.bit[0] = 1: C0.2 | Fast Wakup | Lesser Savings
// edx:eax is implicitly used as a 64-bit deadline timestamp // edx:eax is implicitly used as a 64-bit deadline timestamp
@@ -48,9 +49,6 @@ void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32
code.umwait(Xbyak::util::ebx); code.umwait(Xbyak::util::ebx);
// CF == 1 if we hit the OS-timeout in IA32_UMWAIT_CONTROL without a write // CF == 1 if we hit the OS-timeout in IA32_UMWAIT_CONTROL without a write
// CF == 0 if we exited the wait for any other reason // CF == 0 if we exited the wait for any other reason
code.pop(Xbyak::util::rdx);
code.pop(Xbyak::util::rbx);
code.pop(Xbyak::util::rax);
} else { } else {
code.pause(); code.pause();
} }
@@ -59,12 +57,16 @@ void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32
/*code.lock();*/ code.xchg(code.dword[ptr], tmp); /*code.lock();*/ code.xchg(code.dword[ptr], tmp);
code.test(tmp, tmp); code.test(tmp, tmp);
code.jnz(loop, code.T_NEAR); code.jnz(loop, code.T_NEAR);
if (waitpkg) {
code.pop(Xbyak::util::edx);
code.pop(Xbyak::util::ebx);
code.pop(Xbyak::util::eax);
}
} }
// ptr operand must be a dword[ptr] void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp) {
void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Address ptr, Xbyak::Reg32 tmp) {
code.xor_(tmp, tmp); code.xor_(tmp, tmp);
code.xchg(ptr, tmp); code.xchg(code.dword[ptr], tmp);
code.mfence(); code.mfence();
} }
@@ -87,12 +89,11 @@ void SpinLockImpl::Initialize() noexcept {
Xbyak::Reg64 const ABI_PARAM1 = Backend::X64::HostLocToReg64(Backend::X64::ABI_PARAM1); Xbyak::Reg64 const ABI_PARAM1 = Backend::X64::HostLocToReg64(Backend::X64::ABI_PARAM1);
code.align(); code.align();
lock = code.getCurr<void (*)(volatile int*)>(); lock = code.getCurr<void (*)(volatile int*)>();
EmitSpinLockLock(code, code.dword[ABI_PARAM1], code.eax, false); EmitSpinLockLock(code, ABI_PARAM1, code.eax, false);
code.ret(); code.ret();
code.align(); code.align();
unlock = code.getCurr<void (*)(volatile int*)>(); unlock = code.getCurr<void (*)(volatile int*)>();
EmitSpinLockUnlock(code, code.dword[ABI_PARAM1], code.eax); EmitSpinLockUnlock(code, ABI_PARAM1, code.eax);
code.ret(); code.ret();
} }
@@ -12,7 +12,7 @@
namespace Dynarmic { namespace Dynarmic {
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Address ptr, Xbyak::Reg32 tmp, bool waitpkg); void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp, bool waitpkg);
void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Address ptr, Xbyak::Reg32 tmp); void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp);
} // namespace Dynarmic } // namespace Dynarmic
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project. /* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage * Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD * SPDX-License-Identifier: 0BSD
@@ -23,71 +20,68 @@ using Vector = std::array<std::uint64_t, 2>;
class ExclusiveMonitor { class ExclusiveMonitor {
public: public:
explicit ExclusiveMonitor() noexcept { /// @param processor_count Maximum number of processors using this global
std::fill(exclusive_addresses.begin(), exclusive_addresses.end(), INVALID_EXCLUSIVE_ADDRESS); /// exclusive monitor. Each processor must have a
} /// unique id.
explicit ExclusiveMonitor(size_t processor_count);
size_t GetProcessorCount() const;
/// Marks a region containing [address, address+size) to be exclusive to /// Marks a region containing [address, address+size) to be exclusive to
/// processor index. /// processor processor_id.
template<typename T, typename F> template<typename T, typename Function>
[[nodiscard]] inline T ReadAndMark(std::size_t index, VAddr address, F f) { T ReadAndMark(size_t processor_id, VAddr address, Function op) {
static_assert(std::is_trivially_copyable_v<T>); static_assert(std::is_trivially_copyable_v<T>);
const VAddr masked_address = address & RESERVATION_GRANULE_MASK; const VAddr masked_address = address & RESERVATION_GRANULE_MASK;
lock.Lock();
exclusive_addresses[index] = masked_address; Lock();
T const value = f(); exclusive_addresses[processor_id] = masked_address;
std::memcpy(exclusive_values[index].data(), std::addressof(value), sizeof(T)); const T value = op();
lock.Unlock(); std::memcpy(exclusive_values[processor_id].data(), &value, sizeof(T));
Unlock();
return value; return value;
} }
[[nodiscard]] inline bool CheckAndClear(std::size_t index, VAddr address) { /// Checks to see if processor processor_id has exclusive access to the
const VAddr masked_address = address & RESERVATION_GRANULE_MASK;
if (exclusive_addresses[index] != masked_address)
return false;
for (VAddr& other_address : exclusive_addresses)
if (other_address == masked_address)
other_address = INVALID_EXCLUSIVE_ADDRESS;
return true;
}
/// Checks to see if processor index has exclusive access to the
/// specified region. If it does, executes the operation then clears /// specified region. If it does, executes the operation then clears
/// the exclusive state for processors if their exclusive region(s) /// the exclusive state for processors if their exclusive region(s)
/// contain [address, address+size). /// contain [address, address+size).
template<typename T, typename F> template<typename T, typename Function>
[[nodiscard]] inline bool DoExclusiveOperation(std::size_t index, VAddr address, F&& f) { bool DoExclusiveOperation(size_t processor_id, VAddr address, Function op) {
static_assert(std::is_trivially_copyable_v<T>); static_assert(std::is_trivially_copyable_v<T>);
bool result = false; if (!CheckAndClear(processor_id, address)) {
lock.Lock(); return false;
if (CheckAndClear(index, address)) {
T saved_value{};
std::memcpy(std::addressof(saved_value), exclusive_values[index].data(), sizeof(T));
result = f(saved_value);
} }
lock.Unlock();
T saved_value;
std::memcpy(&saved_value, exclusive_values[processor_id].data(), sizeof(T));
const bool result = op(saved_value);
Unlock();
return result; return result;
} }
/// Unmark everything. /// Unmark everything.
inline void Clear() { void Clear();
lock.Lock();
std::fill(exclusive_addresses.begin(), exclusive_addresses.end(), INVALID_EXCLUSIVE_ADDRESS);
lock.Unlock();
}
/// Unmark processor id /// Unmark processor id
inline void ClearProcessor(size_t index) { void ClearProcessor(size_t processor_id);
lock.Lock();
exclusive_addresses[index] = INVALID_EXCLUSIVE_ADDRESS; private:
lock.Unlock(); bool CheckAndClear(size_t processor_id, VAddr address);
}
void Lock();
void Unlock();
friend volatile int* GetExclusiveMonitorLockPointer(ExclusiveMonitor*);
friend size_t GetExclusiveMonitorProcessorCount(ExclusiveMonitor*);
friend VAddr* GetExclusiveMonitorAddressPointer(ExclusiveMonitor*, size_t index);
friend Vector* GetExclusiveMonitorValuePointer(ExclusiveMonitor*, size_t index);
static constexpr VAddr RESERVATION_GRANULE_MASK = 0xFFFF'FFFF'FFFF'FFFFull; static constexpr VAddr RESERVATION_GRANULE_MASK = 0xFFFF'FFFF'FFFF'FFFFull;
static constexpr VAddr INVALID_EXCLUSIVE_ADDRESS = 0xDEAD'DEAD'DEAD'DEADull; static constexpr VAddr INVALID_EXCLUSIVE_ADDRESS = 0xDEAD'DEAD'DEAD'DEADull;
static constexpr size_t MAX_NUM_CPU_CORES = 4; // Sync with src/core/hardware_properties static constexpr size_t MAX_NUM_CPU_CORES = 4; // Sync with src/core/hardware_properties
std::array<VAddr, MAX_NUM_CPU_CORES> exclusive_addresses; boost::container::static_vector<VAddr, MAX_NUM_CPU_CORES> exclusive_addresses;
std::array<Vector, MAX_NUM_CPU_CORES> exclusive_values; boost::container::static_vector<Vector, MAX_NUM_CPU_CORES> exclusive_values;
SpinLock lock; SpinLock lock;
}; };
+44 -9
View File
@@ -80,24 +80,55 @@ std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::stri
} }
} }
std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease() { std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease(bool include_prereleases) {
#ifdef YUZU_BUNDLED_OPENSSL #ifdef YUZU_BUNDLED_OPENSSL
const auto update_check_url = fmt::format("https://{}", Common::g_build_auto_update_api); const auto update_check_url = fmt::format("https://{}", Common::g_build_auto_update_api);
#else #else
const auto update_check_url = std::string{Common::g_build_auto_update_api}; const auto update_check_url = std::string{Common::g_build_auto_update_api};
#endif #endif
auto update_check_path = std::string{Common::g_build_auto_update_api_path}; auto update_check_path = fmt::format("{}{}", std::string{Common::g_build_auto_update_api_path},
std::string{Common::g_build_auto_update_repo});
try { try {
const auto response = UpdateChecker::GetResponse(update_check_url, update_check_path); if (include_prereleases) { // This can return either a prerelease or a stable release,
// whichever is more recent.
const auto update_check_tags_path = update_check_path + "/tags";
const auto update_check_releases_path = update_check_path + "/releases";
if (!response) const auto tags_response = UpdateChecker::GetResponse(update_check_url, update_check_tags_path);
return {}; const auto releases_response = UpdateChecker::GetResponse(update_check_url, update_check_releases_path);
const std::string latest_tag = nlohmann::json::parse(response.value()).at("tag_name"); if (!tags_response || !releases_response)
const std::string latest_name = nlohmann::json::parse(response.value()).at("name"); return {};
const std::string latest_tag
= nlohmann::json::parse(tags_response.value()).at(0).at("name");
const std::string latest_name =
nlohmann::json::parse(releases_response.value()).at(0).at("name");
const bool latest_tag_has_release = releases_response.value().find(
fmt::format("\"{}\"", latest_tag))
!= std::string::npos;
// If there is a newer tag, but that tag has no associated release, don't prompt the
// user to update.
if (!latest_tag_has_release)
return {};
return Update{latest_tag, latest_name};
} else { // This is a stable release, only check for other stable releases.
update_check_path += "/releases/latest";
const auto response = UpdateChecker::GetResponse(update_check_url, update_check_path);
if (!response)
return {};
const std::string latest_tag = nlohmann::json::parse(response.value()).at("tag_name");
const std::string latest_name = nlohmann::json::parse(response.value()).at("name");
return Update{latest_tag, latest_name};
}
return Update{latest_tag, latest_name};
} catch (nlohmann::detail::out_of_range&) { } catch (nlohmann::detail::out_of_range&) {
LOG_ERROR(Frontend, LOG_ERROR(Frontend,
"Parsing JSON response from {}{} failed during update check: " "Parsing JSON response from {}{} failed during update check: "
@@ -116,8 +147,12 @@ std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease() {
} }
std::optional<UpdateChecker::Update> UpdateChecker::GetUpdate() { std::optional<UpdateChecker::Update> UpdateChecker::GetUpdate() {
const bool is_prerelease = ((strstr(Common::g_build_version, "pre-alpha") != NULL) ||
(strstr(Common::g_build_version, "alpha") != NULL) ||
(strstr(Common::g_build_version, "beta") != NULL) ||
(strstr(Common::g_build_version, "rc") != NULL));
const std::optional<UpdateChecker::Update> latest_release_tag = const std::optional<UpdateChecker::Update> latest_release_tag =
UpdateChecker::GetLatestRelease(); UpdateChecker::GetLatestRelease(is_prerelease);
if (!latest_release_tag) if (!latest_release_tag)
goto empty; goto empty;
+1 -1
View File
@@ -18,6 +18,6 @@ typedef struct {
} Update; } Update;
std::optional<std::string> GetResponse(std::string url, std::string path); std::optional<std::string> GetResponse(std::string url, std::string path);
std::optional<Update> GetLatestRelease(); std::optional<Update> GetLatestRelease(bool include_prereleases);
std::optional<Update> GetUpdate(); std::optional<Update> GetUpdate();
} // namespace UpdateChecker } // namespace UpdateChecker
+131 -47
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -108,10 +108,10 @@ void EmulatedController::ReloadFromSettings() {
// Other or debug controller should always be a pro controller // Other or debug controller should always be a pro controller
if (npad_id_type != NpadIdType::Other) { if (npad_id_type != NpadIdType::Other) {
SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type)); SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
original_npad_type = npad_type.load(); original_npad_type = npad_type;
} else { } else {
SetNpadStyleIndex(NpadStyleIndex::Fullkey); SetNpadStyleIndex(NpadStyleIndex::Fullkey);
original_npad_type = npad_type.load(); original_npad_type = npad_type;
} }
// Disable special features before disconnecting // Disable special features before disconnecting
@@ -577,9 +577,10 @@ void EmulatedController::UnloadInput() {
} }
void EmulatedController::EnableConfiguration() { void EmulatedController::EnableConfiguration() {
is_configuring.store(true); std::unique_lock lock1{connect_mutex}, lock2{npad_mutex};
tmp_is_connected.store(is_connected); is_configuring = true;
tmp_npad_type.store(npad_type); tmp_is_connected = is_connected;
tmp_npad_type = npad_type;
} }
void EmulatedController::DisableConfiguration() { void EmulatedController::DisableConfiguration() {
@@ -599,7 +600,7 @@ void EmulatedController::DisableConfiguration() {
Disconnect(); Disconnect();
} }
SetNpadStyleIndex(tmp_npad_type); SetNpadStyleIndex(tmp_npad_type);
original_npad_type.store(tmp_npad_type); original_npad_type = tmp_npad_type;
} }
// Apply temporary connected status to the real controller // Apply temporary connected status to the real controller
@@ -613,16 +614,19 @@ void EmulatedController::DisableConfiguration() {
} }
void EmulatedController::EnableSystemButtons() { void EmulatedController::EnableSystemButtons() {
std::unique_lock lock{mutex};
system_buttons_enabled = true; system_buttons_enabled = true;
} }
void EmulatedController::DisableSystemButtons() { void EmulatedController::DisableSystemButtons() {
std::unique_lock lock{mutex};
system_buttons_enabled = false; system_buttons_enabled = false;
controller.home_button_state.raw = 0; controller.home_button_state.raw = 0;
controller.capture_button_state.raw = 0; controller.capture_button_state.raw = 0;
} }
void EmulatedController::ResetSystemButtons() { void EmulatedController::ResetSystemButtons() {
std::unique_lock lock{mutex};
controller.home_button_state.home.Assign(false); controller.home_button_state.home.Assign(false);
controller.capture_button_state.capture.Assign(false); controller.capture_button_state.capture.Assign(false);
} }
@@ -759,7 +763,8 @@ void EmulatedController::StartMotionCalibration() {
} }
} }
void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index, Common::UUID uuid) { void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index,
Common::UUID uuid) {
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type); const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
const auto& player = Settings::values.players.GetValue()[player_index]; const auto& player = Settings::values.players.GetValue()[player_index];
@@ -767,6 +772,7 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
return; return;
} }
std::unique_lock lock{mutex};
bool value_changed = false; bool value_changed = false;
const auto new_status = TransformToButton(callback); const auto new_status = TransformToButton(callback);
auto& current_status = controller.button_values[index]; auto& current_status = controller.button_values[index];
@@ -812,6 +818,7 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
controller.debug_pad_button_state.raw = 0; controller.debug_pad_button_state.raw = 0;
controller.home_button_state.raw = 0; controller.home_button_state.raw = 0;
controller.capture_button_state.raw = 0; controller.capture_button_state.raw = 0;
lock.unlock();
TriggerOnChange(ControllerTriggerType::Button, false); TriggerOnChange(ControllerTriggerType::Button, false);
return; return;
} }
@@ -915,6 +922,8 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
break; break;
} }
lock.unlock();
if (!is_connected) { if (!is_connected) {
if (npad_type == NpadStyleIndex::Handheld) { if (npad_type == NpadStyleIndex::Handheld) {
if (npad_id_type == NpadIdType::Handheld) { if (npad_id_type == NpadIdType::Handheld) {
@@ -943,6 +952,7 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
auto trigger_guard = SCOPE_GUARD { auto trigger_guard = SCOPE_GUARD {
TriggerOnChange(ControllerTriggerType::Stick, !is_configuring); TriggerOnChange(ControllerTriggerType::Stick, !is_configuring);
}; };
std::unique_lock lock{mutex};
const auto stick_value = TransformToStick(callback); const auto stick_value = TransformToStick(callback);
// Only read stick values that have the same uuid or are over the threshold to avoid flapping // Only read stick values that have the same uuid or are over the threshold to avoid flapping
@@ -999,6 +1009,7 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
auto trigger_guard = SCOPE_GUARD { auto trigger_guard = SCOPE_GUARD {
TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring); TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring);
}; };
std::unique_lock lock{mutex};
const auto trigger_value = TransformToTrigger(callback); const auto trigger_value = TransformToTrigger(callback);
// Only read trigger values that have the same uuid or are pressed once // Only read trigger values that have the same uuid or are pressed once
@@ -1046,6 +1057,7 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
SCOPE_EXIT { SCOPE_EXIT {
TriggerOnChange(ControllerTriggerType::Motion, !is_configuring); TriggerOnChange(ControllerTriggerType::Motion, !is_configuring);
}; };
std::unique_lock lock{mutex};
auto& raw_status = controller.motion_values[index].raw_status; auto& raw_status = controller.motion_values[index].raw_status;
auto& emulated = controller.motion_values[index].emulated; auto& emulated = controller.motion_values[index].emulated;
@@ -1081,6 +1093,7 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback
auto trigger_guard = SCOPE_GUARD { auto trigger_guard = SCOPE_GUARD {
TriggerOnChange(ControllerTriggerType::Color, !is_configuring); TriggerOnChange(ControllerTriggerType::Color, !is_configuring);
}; };
std::unique_lock lock{mutex};
controller.color_values[index] = TransformToColor(callback); controller.color_values[index] = TransformToColor(callback);
if (is_configuring) { if (is_configuring) {
@@ -1123,13 +1136,15 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback
} }
} }
void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index) { void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback,
std::size_t index) {
if (index >= controller.battery_values.size()) { if (index >= controller.battery_values.size()) {
return; return;
} }
SCOPE_EXIT { SCOPE_EXIT {
TriggerOnChange(ControllerTriggerType::Battery, !is_configuring); TriggerOnChange(ControllerTriggerType::Battery, !is_configuring);
}; };
std::unique_lock lock{mutex};
controller.battery_values[index] = TransformToBattery(callback); controller.battery_values[index] = TransformToBattery(callback);
if (is_configuring) { if (is_configuring) {
@@ -1194,33 +1209,47 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback
SCOPE_EXIT { SCOPE_EXIT {
TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring); TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring);
}; };
std::unique_lock lock{mutex};
controller.camera_values = TransformToCamera(callback); controller.camera_values = TransformToCamera(callback);
if (!is_configuring) {
controller.camera_state.sample++; if (is_configuring) {
controller.camera_state.format = Core::IrSensor::ImageTransferProcessorFormat(controller.camera_values.format); return;
controller.camera_state.data = controller.camera_values.data;
} }
controller.camera_state.sample++;
controller.camera_state.format =
static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format);
controller.camera_state.data = controller.camera_values.data;
} }
void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) { void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
SCOPE_EXIT { SCOPE_EXIT {
TriggerOnChange(ControllerTriggerType::RingController, !is_configuring); TriggerOnChange(ControllerTriggerType::RingController, !is_configuring);
}; };
std::unique_lock lock{mutex};
const auto force_value = TransformToStick(callback); const auto force_value = TransformToStick(callback);
controller.ring_analog_value = force_value.x; controller.ring_analog_value = force_value.x;
if (!is_configuring) {
controller.ring_analog_state.force = force_value.x.value; if (is_configuring) {
return;
} }
controller.ring_analog_state.force = force_value.x.value;
} }
void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) { void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
SCOPE_EXIT { SCOPE_EXIT {
TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring); TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring);
}; };
std::unique_lock lock{mutex};
controller.nfc_values = TransformToNfc(callback); controller.nfc_values = TransformToNfc(callback);
if (!is_configuring) {
controller.nfc_state = controller.nfc_values; if (is_configuring) {
return;
} }
controller.nfc_state = controller.nfc_values;
} }
bool EmulatedController::SetVibration(bool should_vibrate) { bool EmulatedController::SetVibration(bool should_vibrate) {
@@ -1229,6 +1258,7 @@ bool EmulatedController::SetVibration(bool should_vibrate) {
vibration_value.high_amplitude = 1.0f; vibration_value.high_amplitude = 1.0f;
vibration_value.low_amplitude = 1.0f; vibration_value.low_amplitude = 1.0f;
} }
return SetVibration(DeviceIndex::Left, vibration_value); return SetVibration(DeviceIndex::Left, vibration_value);
} }
@@ -1238,6 +1268,7 @@ bool EmulatedController::SetVibration(u32 slot, Core::HID::VibrationGcErmCommand
vibration_value.high_amplitude = 1.0f; vibration_value.high_amplitude = 1.0f;
vibration_value.low_amplitude = 1.0f; vibration_value.low_amplitude = 1.0f;
} }
return SetVibration(DeviceIndex::Left, vibration_value); return SetVibration(DeviceIndex::Left, vibration_value);
} }
@@ -1601,7 +1632,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
// Attempt to reconnect with the original type // Attempt to reconnect with the original type
if (npad_type != original_npad_type) { if (npad_type != original_npad_type) {
Disconnect(); Disconnect();
const auto current_npad_type = npad_type.load(); const auto current_npad_type = npad_type;
SetNpadStyleIndex(original_npad_type); SetNpadStyleIndex(original_npad_type);
if (IsControllerSupported()) { if (IsControllerSupported()) {
Connect(); Connect();
@@ -1619,7 +1650,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
// Fallback Fullkey controllers to Pro controllers // Fallback Fullkey controllers to Pro controllers
if (IsControllerFullkey() && supported_style_tag.fullkey) { if (IsControllerFullkey() && supported_style_tag.fullkey) {
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type.load()); LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
SetNpadStyleIndex(NpadStyleIndex::Fullkey); SetNpadStyleIndex(NpadStyleIndex::Fullkey);
Connect(); Connect();
return; return;
@@ -1627,7 +1658,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
// Fallback Dual joycon controllers to Pro controllers // Fallback Dual joycon controllers to Pro controllers
if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) { if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) {
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type.load()); LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
SetNpadStyleIndex(NpadStyleIndex::Fullkey); SetNpadStyleIndex(NpadStyleIndex::Fullkey);
Connect(); Connect();
return; return;
@@ -1635,16 +1666,19 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
// Fallback Pro controllers to Dual joycon // Fallback Pro controllers to Dual joycon
if (npad_type == NpadStyleIndex::Fullkey && supported_style_tag.joycon_dual) { if (npad_type == NpadStyleIndex::Fullkey && supported_style_tag.joycon_dual) {
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type.load()); LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type);
SetNpadStyleIndex(NpadStyleIndex::JoyconDual); SetNpadStyleIndex(NpadStyleIndex::JoyconDual);
Connect(); Connect();
return; return;
} }
LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller", npad_type.load());
LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller",
npad_type);
} }
bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const { bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const {
const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load(); std::unique_lock lock{mutex};
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
switch (type) { switch (type) {
case NpadStyleIndex::Fullkey: case NpadStyleIndex::Fullkey:
case NpadStyleIndex::GameCube: case NpadStyleIndex::GameCube:
@@ -1659,26 +1693,39 @@ bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const {
} }
bool EmulatedController::IsControllerSupported(bool use_temporary_value) const { bool EmulatedController::IsControllerSupported(bool use_temporary_value) const {
const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load(); std::unique_lock lock{mutex};
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
switch (type) { switch (type) {
case NpadStyleIndex::Fullkey: return supported_style_tag.fullkey.As<bool>(); case NpadStyleIndex::Fullkey:
case NpadStyleIndex::Handheld: return supported_style_tag.handheld.As<bool>(); return supported_style_tag.fullkey.As<bool>();
case NpadStyleIndex::JoyconDual: return supported_style_tag.joycon_dual.As<bool>(); case NpadStyleIndex::Handheld:
case NpadStyleIndex::JoyconLeft: return supported_style_tag.joycon_left.As<bool>(); return supported_style_tag.handheld.As<bool>();
case NpadStyleIndex::JoyconRight: return supported_style_tag.joycon_right.As<bool>(); case NpadStyleIndex::JoyconDual:
case NpadStyleIndex::GameCube: return supported_style_tag.gamecube.As<bool>(); return supported_style_tag.joycon_dual.As<bool>();
case NpadStyleIndex::Pokeball: return supported_style_tag.palma.As<bool>(); case NpadStyleIndex::JoyconLeft:
case NpadStyleIndex::NES: return supported_style_tag.lark.As<bool>(); return supported_style_tag.joycon_left.As<bool>();
case NpadStyleIndex::SNES: return supported_style_tag.lucia.As<bool>(); case NpadStyleIndex::JoyconRight:
case NpadStyleIndex::N64: return supported_style_tag.lagoon.As<bool>(); return supported_style_tag.joycon_right.As<bool>();
case NpadStyleIndex::SegaGenesis: return supported_style_tag.lager.As<bool>(); case NpadStyleIndex::GameCube:
default: return false; return supported_style_tag.gamecube.As<bool>();
case NpadStyleIndex::Pokeball:
return supported_style_tag.palma.As<bool>();
case NpadStyleIndex::NES:
return supported_style_tag.lark.As<bool>();
case NpadStyleIndex::SNES:
return supported_style_tag.lucia.As<bool>();
case NpadStyleIndex::N64:
return supported_style_tag.lagoon.As<bool>();
case NpadStyleIndex::SegaGenesis:
return supported_style_tag.lager.As<bool>();
default:
return false;
} }
} }
void EmulatedController::Connect(bool use_temporary_value) { void EmulatedController::Connect(bool use_temporary_value) {
if (!IsControllerSupported(use_temporary_value)) { if (!IsControllerSupported(use_temporary_value)) {
const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load(); const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
LOG_ERROR(Service_HID, "Controller type {} is not supported", type); LOG_ERROR(Service_HID, "Controller type {} is not supported", type);
return; return;
} }
@@ -1686,10 +1733,12 @@ void EmulatedController::Connect(bool use_temporary_value) {
auto trigger_guard = SCOPE_GUARD { auto trigger_guard = SCOPE_GUARD {
TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); TriggerOnChange(ControllerTriggerType::Connected, !is_configuring);
}; };
std::unique_lock lock1{connect_mutex}, lock2{mutex};
if (is_configuring) { if (is_configuring) {
tmp_is_connected = true; tmp_is_connected = true;
return; return;
} }
if (is_connected) { if (is_connected) {
trigger_guard.Cancel(); trigger_guard.Cancel();
return; return;
@@ -1701,10 +1750,12 @@ void EmulatedController::Disconnect() {
auto trigger_guard = SCOPE_GUARD { auto trigger_guard = SCOPE_GUARD {
TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring);
}; };
std::unique_lock lock1{connect_mutex}, lock2{mutex};
if (is_configuring) { if (is_configuring) {
tmp_is_connected = false; tmp_is_connected = false;
return; return;
} }
if (!is_connected) { if (!is_connected) {
trigger_guard.Cancel(); trigger_guard.Cancel();
return; return;
@@ -1713,16 +1764,19 @@ void EmulatedController::Disconnect() {
} }
bool EmulatedController::IsConnected(bool get_temporary_value) const { bool EmulatedController::IsConnected(bool get_temporary_value) const {
std::shared_lock lock{connect_mutex};
if (get_temporary_value && is_configuring) if (get_temporary_value && is_configuring)
return tmp_is_connected; return tmp_is_connected;
return is_connected; return is_connected;
} }
NpadIdType EmulatedController::GetNpadIdType() const { NpadIdType EmulatedController::GetNpadIdType() const {
std::shared_lock lock{mutex};
return npad_id_type; return npad_id_type;
} }
NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const { NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const {
std::shared_lock lock{npad_mutex};
if (get_temporary_value && is_configuring) if (get_temporary_value && is_configuring)
return tmp_npad_type; return tmp_npad_type;
return npad_type; return npad_type;
@@ -1732,6 +1786,8 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
auto trigger_guard = SCOPE_GUARD { auto trigger_guard = SCOPE_GUARD {
TriggerOnChange(ControllerTriggerType::Type, !is_configuring); TriggerOnChange(ControllerTriggerType::Type, !is_configuring);
}; };
std::unique_lock lock1{mutex}, lock2{npad_mutex};
if (is_configuring) { if (is_configuring) {
if (tmp_npad_type == npad_type_) { if (tmp_npad_type == npad_type_) {
trigger_guard.Cancel(); trigger_guard.Cancel();
@@ -1740,12 +1796,14 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
tmp_npad_type = npad_type_; tmp_npad_type = npad_type_;
return; return;
} }
if (npad_type == npad_type_) { if (npad_type == npad_type_) {
trigger_guard.Cancel(); trigger_guard.Cancel();
return; return;
} }
if (is_connected) { if (is_connected) {
LOG_WARNING(Service_HID, "Controller {} type changed while it's connected", Service::HID::NpadIdTypeToIndex(npad_id_type)); LOG_WARNING(Service_HID, "Controller {} type changed while it's connected",
Service::HID::NpadIdTypeToIndex(npad_id_type));
} }
npad_type = npad_type_; npad_type = npad_type_;
} }
@@ -1774,30 +1832,37 @@ LedPattern EmulatedController::GetLedPattern() const {
} }
ButtonValues EmulatedController::GetButtonsValues() const { ButtonValues EmulatedController::GetButtonsValues() const {
std::unique_lock lock{mutex};
return controller.button_values; return controller.button_values;
} }
SticksValues EmulatedController::GetSticksValues() const { SticksValues EmulatedController::GetSticksValues() const {
std::unique_lock lock{mutex};
return controller.stick_values; return controller.stick_values;
} }
TriggerValues EmulatedController::GetTriggersValues() const { TriggerValues EmulatedController::GetTriggersValues() const {
std::unique_lock lock{mutex};
return controller.trigger_values; return controller.trigger_values;
} }
ControllerMotionValues EmulatedController::GetMotionValues() const { ControllerMotionValues EmulatedController::GetMotionValues() const {
std::unique_lock lock{mutex};
return controller.motion_values; return controller.motion_values;
} }
ColorValues EmulatedController::GetColorsValues() const { ColorValues EmulatedController::GetColorsValues() const {
std::unique_lock lock{mutex};
return controller.color_values; return controller.color_values;
} }
BatteryValues EmulatedController::GetBatteryValues() const { BatteryValues EmulatedController::GetBatteryValues() const {
std::unique_lock lock{mutex};
return controller.battery_values; return controller.battery_values;
} }
CameraValues EmulatedController::GetCameraValues() const { CameraValues EmulatedController::GetCameraValues() const {
std::unique_lock lock{mutex};
return controller.camera_values; return controller.camera_values;
} }
@@ -1806,54 +1871,72 @@ RingAnalogValue EmulatedController::GetRingSensorValues() const {
} }
HomeButtonState EmulatedController::GetHomeButtons() const { HomeButtonState EmulatedController::GetHomeButtons() const {
if (is_configuring) std::unique_lock lock{mutex};
if (is_configuring) {
return {}; return {};
}
return controller.home_button_state; return controller.home_button_state;
} }
CaptureButtonState EmulatedController::GetCaptureButtons() const { CaptureButtonState EmulatedController::GetCaptureButtons() const {
if (is_configuring) std::unique_lock lock{mutex};
if (is_configuring) {
return {}; return {};
}
return controller.capture_button_state; return controller.capture_button_state;
} }
NpadButtonState EmulatedController::GetNpadButtons() const { NpadButtonState EmulatedController::GetNpadButtons() const {
if (is_configuring) std::unique_lock lock{mutex};
if (is_configuring) {
return {}; return {};
}
return {controller.npad_button_state.raw & GetTurboButtonMask()}; return {controller.npad_button_state.raw & GetTurboButtonMask()};
} }
DebugPadButton EmulatedController::GetDebugPadButtons() const { DebugPadButton EmulatedController::GetDebugPadButtons() const {
if (is_configuring) std::unique_lock lock{mutex};
if (is_configuring) {
return {}; return {};
}
return controller.debug_pad_button_state; return controller.debug_pad_button_state;
} }
AnalogSticks EmulatedController::GetSticks() const { AnalogSticks EmulatedController::GetSticks() const {
if (is_configuring) std::unique_lock lock{mutex};
if (is_configuring) {
return {}; return {};
}
return controller.analog_stick_state; return controller.analog_stick_state;
} }
NpadGcTriggerState EmulatedController::GetTriggers() const { NpadGcTriggerState EmulatedController::GetTriggers() const {
if (is_configuring) std::unique_lock lock{mutex};
if (is_configuring) {
return {}; return {};
}
return controller.gc_trigger_state; return controller.gc_trigger_state;
} }
MotionState EmulatedController::GetMotions() const { MotionState EmulatedController::GetMotions() const {
std::unique_lock lock{mutex};
return controller.motion_state; return controller.motion_state;
} }
ControllerColors EmulatedController::GetColors() const { ControllerColors EmulatedController::GetColors() const {
std::unique_lock lock{mutex};
return controller.colors_state; return controller.colors_state;
} }
BatteryLevelState EmulatedController::GetBattery() const { BatteryLevelState EmulatedController::GetBattery() const {
std::unique_lock lock{mutex};
return controller.battery_state; return controller.battery_state;
} }
const CameraState& EmulatedController::GetCamera() const { const CameraState& EmulatedController::GetCamera() const {
std::unique_lock lock{mutex};
return controller.camera_state; return controller.camera_state;
} }
@@ -1862,14 +1945,15 @@ RingSensorForce EmulatedController::GetRingSensorForce() const {
} }
const NfcState& EmulatedController::GetNfc() const { const NfcState& EmulatedController::GetNfc() const {
std::unique_lock lock{mutex};
return controller.nfc_state; return controller.nfc_state;
} }
NpadColor EmulatedController::GetNpadColor(u32 color) { NpadColor EmulatedController::GetNpadColor(u32 color) {
return { return {
.r = u8((color >> 16) & 0xFF), .r = static_cast<u8>((color >> 16) & 0xFF),
.g = u8((color >> 8) & 0xFF), .g = static_cast<u8>((color >> 8) & 0xFF),
.b = u8(color & 0xFF), .b = static_cast<u8>(color & 0xFF),
.a = 0xff, .a = 0xff,
}; };
} }
+13 -13
View File
@@ -11,10 +11,8 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <shared_mutex> #include <shared_mutex>
#include <vector>
#include <atomic>
#include <ankerl/unordered_dense.h> #include <ankerl/unordered_dense.h>
#include <vector>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/input.h" #include "common/input.h"
@@ -578,7 +576,13 @@ private:
NpadButton GetTurboButtonMask() const; NpadButton GetTurboButtonMask() const;
const NpadIdType npad_id_type; const NpadIdType npad_id_type;
NpadStyleIndex npad_type{NpadStyleIndex::None};
NpadStyleIndex original_npad_type{NpadStyleIndex::None};
NpadStyleTag supported_style_tag{NpadStyleSet::All}; NpadStyleTag supported_style_tag{NpadStyleSet::All};
bool is_connected{false};
bool is_configuring{false};
bool is_initialized{false};
bool system_buttons_enabled{true};
f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard}; f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
u32 turbo_button_state{0}; u32 turbo_button_state{0};
std::size_t nfc_handles{0}; std::size_t nfc_handles{0};
@@ -587,16 +591,9 @@ private:
std::array<std::chrono::steady_clock::time_point, 2> last_vibration_timepoint{}; std::array<std::chrono::steady_clock::time_point, 2> last_vibration_timepoint{};
std::array<bool, HIDCore::available_controllers> controller_connected{}; std::array<bool, HIDCore::available_controllers> controller_connected{};
// Atomically synched values
std::atomic<HID::NpadStyleIndex> npad_type{HID::NpadStyleIndex::None};
std::atomic<HID::NpadStyleIndex> original_npad_type{HID::NpadStyleIndex::None};
// Temporary values to avoid doing changes while the controller is in configuring mode // Temporary values to avoid doing changes while the controller is in configuring mode
std::atomic<HID::NpadStyleIndex> tmp_npad_type{HID::NpadStyleIndex::None}; NpadStyleIndex tmp_npad_type{NpadStyleIndex::None};
std::atomic<bool> tmp_is_connected{false}; bool tmp_is_connected{false};
std::atomic<bool> is_connected{false};
std::atomic<bool> is_configuring{false};
std::atomic<bool> is_initialized{false};
std::atomic<bool> system_buttons_enabled{true};
ButtonParams button_params; ButtonParams button_params;
StickParams stick_params; StickParams stick_params;
@@ -635,7 +632,10 @@ private:
StickDevices virtual_stick_devices; StickDevices virtual_stick_devices;
ControllerMotionDevices virtual_motion_devices; ControllerMotionDevices virtual_motion_devices;
mutable std::mutex callback_mutex; mutable std::shared_mutex mutex;
mutable std::shared_mutex callback_mutex;
mutable std::shared_mutex npad_mutex;
mutable std::shared_mutex connect_mutex;
ankerl::unordered_dense::map<int, ControllerUpdateCallback> callback_list; ankerl::unordered_dense::map<int, ControllerUpdateCallback> callback_list;
int last_callback_key = 0; int last_callback_key = 0;
+9 -15
View File
@@ -157,14 +157,11 @@ void NPad::ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t c
if (!controller.device->IsConnected()) { if (!controller.device->IsConnected()) {
return; return;
} }
if (auto* shared_memory = controller.shared_memory; shared_memory) { auto* shared_memory = controller.shared_memory;
const auto& battery_level = controller.device->GetBattery(); const auto& battery_level = controller.device->GetBattery();
shared_memory->battery_level_dual = battery_level.dual.battery_level; shared_memory->battery_level_dual = battery_level.dual.battery_level;
shared_memory->battery_level_left = battery_level.left.battery_level; shared_memory->battery_level_left = battery_level.left.battery_level;
shared_memory->battery_level_right = battery_level.right.battery_level; shared_memory->battery_level_right = battery_level.right.battery_level;
} else {
LOG_WARNING(Service_HID, "shared_memory is null {}", controller_idx);
}
break; break;
} }
default: default:
@@ -183,10 +180,6 @@ void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) {
const auto& body_colors = controller.device->GetColors(); const auto& body_colors = controller.device->GetColors();
const auto& battery_level = controller.device->GetBattery(); const auto& battery_level = controller.device->GetBattery();
auto* shared_memory = controller.shared_memory; auto* shared_memory = controller.shared_memory;
if (!shared_memory) {
LOG_WARNING(Service_HID, "shared_memory is null for npad_id={}", npad_id);
return;
}
if (controller_type == Core::HID::NpadStyleIndex::None) { if (controller_type == Core::HID::NpadStyleIndex::None) {
npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id); npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id);
return; return;
@@ -808,7 +801,7 @@ Result NPad::DisconnectNpad(u64 aruid, Core::HID::NpadIdType npad_id) {
auto* shared_memory = controller.shared_memory; auto* shared_memory = controller.shared_memory;
if (!shared_memory) { if (!shared_memory) {
LOG_WARNING(Service_HID, "shared_memory is null for npad_id={}", npad_id); LOG_WARNING(Service_HID, "DisconnectNpad: shared_memory is null for npad_id={}", npad_id);
return ResultSuccess; return ResultSuccess;
} }
// Don't reset shared_memory->assignment_mode this value is persistent // Don't reset shared_memory->assignment_mode this value is persistent
@@ -1202,10 +1195,11 @@ const Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties(
AppletDetailedUiType NPad::GetAppletDetailedUiType(Core::HID::NpadIdType npad_id) { AppletDetailedUiType NPad::GetAppletDetailedUiType(Core::HID::NpadIdType npad_id) {
const auto aruid = applet_resource_holder.applet_resource->GetActiveAruid(); const auto aruid = applet_resource_holder.applet_resource->GetActiveAruid();
const auto* shared_memory = GetControllerFromNpadIdType(aruid, npad_id).shared_memory; const auto& shared_memory = GetControllerFromNpadIdType(aruid, npad_id).shared_memory;
return { return {
.ui_variant = 0, .ui_variant = 0,
.footer = shared_memory ? shared_memory->applet_footer_type : AppletFooterUiType::None, .footer = shared_memory->applet_footer_type,
}; };
} }
+3 -2
View File
@@ -169,8 +169,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
tr("Runs an additional optimization pass over generated SPIRV shaders.\n" tr("Runs an additional optimization pass over generated SPIRV shaders.\n"
"Will increase time required for shader compilation.\nMay slightly improve " "Will increase time required for shader compilation.\nMay slightly improve "
"performance.\nThis feature is experimental.")); "performance.\nThis feature is experimental."));
INSERT(Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"),
tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled."));
INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"), INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"),
tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for " tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for "
"decoding, or perform no decoding at all (black screen on videos).\n" "decoding, or perform no decoding at all (black screen on videos).\n"
@@ -317,6 +315,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
"their resolution, details and supported controllers and depending on this setting.\n" "their resolution, details and supported controllers and depending on this setting.\n"
"Setting to Handheld can help improve performance for low end systems.")); "Setting to Handheld can help improve performance for low end systems."));
INSERT(Settings, current_user, QString(), QString()); INSERT(Settings, current_user, QString(), QString());
INSERT(Settings, serial_unit, tr("Unit Serial"), QString());
INSERT(Settings, serial_battery, tr("Battery Serial"), QString());
INSERT(Settings, debug_knobs, tr("Debug knobs"), QString());
// Controls // Controls
@@ -178,9 +178,6 @@ void DefineGenericOutput(EmitContext& ctx, size_t index, std::optional<u32> invo
ctx.Decorate(id, spv::Decoration::XfbBuffer, xfb_varying->buffer); ctx.Decorate(id, spv::Decoration::XfbBuffer, xfb_varying->buffer);
ctx.Decorate(id, spv::Decoration::XfbStride, xfb_varying->stride); ctx.Decorate(id, spv::Decoration::XfbStride, xfb_varying->stride);
ctx.Decorate(id, spv::Decoration::Offset, xfb_varying->offset); ctx.Decorate(id, spv::Decoration::Offset, xfb_varying->offset);
if (ctx.stage == Stage::Geometry && xfb_varying->stream != 0) {
ctx.Decorate(id, spv::Decoration::Stream, xfb_varying->stream);
}
} }
if (num_components < 4 || element > 0) { if (num_components < 4 || element > 0) {
const std::string_view subswizzle{swizzle.substr(element, num_components)}; const std::string_view subswizzle{swizzle.substr(element, num_components)};
-1
View File
@@ -76,7 +76,6 @@ enum class TessSpacing {
struct TransformFeedbackVarying { struct TransformFeedbackVarying {
u32 buffer{}; u32 buffer{};
u32 stream{};
u32 stride{}; u32 stride{};
u32 offset{}; u32 offset{};
u32 components{}; u32 components{};

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