Compare commits

..

15 commits

Author SHA1 Message Date
Allison Cunha
9e33f71dbc Initial a9 (minsdk=28) support
All checks were successful
eden-license / license-header (pull_request) Successful in 23s
Fixed WindowInsetsCompat.Type.systemBars() to CarouselRecyclerView
2025-09-30 00:58:01 +02:00
03c7d6ce4a
[android] input over(lay)haul 1: Auto-hide input overlay setting (#493)
This is step 1 of #47 which was the easiest to implement. How was this not implemented on yuzu already?
Would prefer if more people tested this than the usual amount.

Reviewed-on: #493
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: nyx-ynx <contact@nyxynx.dev>
Co-committed-by: nyx-ynx <contact@nyxynx.dev>
2025-09-29 22:40:03 +02:00
nyx
824dc6948e
[android] input over(lay)haul 2: Individual scaling of buttons (#2562)
### (Needs testing)

This PR makes it possible to adjust the scale of each touch input overlay button independently from the global scale
This individual value always goes on top of the global scale.

Reviewed-on: #2562
Co-authored-by: nyx <contact@innix.space>
Co-committed-by: nyx <contact@innix.space>
2025-09-29 22:38:26 +02:00
85b5e650cc
[dist, docs] Clearer wording for settings, guidelines for new settings (#2570)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: #2570
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-09-29 19:41:01 +02:00
324ace3cd6
[macos] associate .XCI/NSP file extensions (#2617)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: #2617
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-09-29 18:43:13 +02:00
33f93ad003
[macos, qt] workaround upstream rendering bug (#2616)
See https://bugreports.qt.io/browse/QTBUG-138942

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: #2616
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-09-29 18:42:51 +02:00
9f423a24b8
[linux] fix aarch64 builds (again) + fix with slightly outdated qt (#2612)
Fixes issues building on aarch64 linux with a slightly outdated system qt; also fixes linker selection process

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: #2612
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-09-29 18:42:28 +02:00
50ceb9a43a
[.ci] install-msvc: fix installation on MSVC (#2611)
* changed from Build Tools to Community (congrats Microsoft very cool)
* add spining to show it didnt stopped installing

Signed-off-by: Caio Oliveira <caiooliveirafarias0@gmail.com>

Reviewed-on: #2611
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Co-committed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
2025-09-29 18:42:04 +02:00
ecb811ad04
[qt] move addons row to rightmost side (#2610)
This is because the rightmost row is "extended" to the rest of the table, and add-ons have long names, play time doesn't need that much space.

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: #2610
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-09-29 18:41:28 +02:00
bf302d7917
[common] No need to specify min/max for settings; fix crash when OOB value is given for some settings (#2609)
This fixes issues when migrating settings that refer to invalid filters/scales. For example if we had 5 filters, but we set filter=6, the program would crash.
This also makes so specifying min/max manually isn't needed (but can still be set for cases like NCE).

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: #2609
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-09-29 18:40:29 +02:00
d19a7c3782
[service] unstub process winding (#2590)
It's used on FW19+ and FW20+ but since all 20+ applets stuck on HID, you still can't boot into applets.
Should fix: Bioshock Infinite on FW19

Reviewed-on: #2590
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: unknown <sahyno1996@gmail.com>
Co-committed-by: unknown <sahyno1996@gmail.com>
2025-09-28 18:43:01 +02:00
c725641f13
[video_core] Fix fast buffers without performance loss (#2605)
Fixes games that have some elements flickering on the screen, such as Kirby Star Allies and others, without impacting performance.

Reviewed-on: #2605
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2025-09-28 07:29:19 +02:00
nyx
02016697d6
[cmake, macos] Suppress warnings for unused private members (#2583)
Co-authored-by: crueter <crueter@eden-emu.dev>
Reviewed-on: #2583
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: nyx <contact@innix.space>
Co-committed-by: nyx <contact@innix.space>
2025-09-27 22:40:18 +02:00
c77ad128b9
[cmake] whole-program LTO, prefer lld on clang (#2581)
Reviewed-on: #2581
2025-09-27 22:40:08 +02:00
cc50571275
[dynarmic] fix tests builds (#2601)
This fixes tests for dynarmic, print_info and test_generator.

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: #2601
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-09-27 20:41:52 +02:00
69 changed files with 1343 additions and 438 deletions

View file

@ -10,7 +10,7 @@ if (-not ([bool](net session 2>$null))) {
} }
$VSVer = "17" $VSVer = "17"
$ExeFile = "vs_BuildTools.exe" $ExeFile = "vs_community.exe"
$Uri = "https://aka.ms/vs/$VSVer/release/$ExeFile" $Uri = "https://aka.ms/vs/$VSVer/release/$ExeFile"
$Destination = "./$ExeFile" $Destination = "./$ExeFile"
@ -19,21 +19,39 @@ $WebClient = New-Object System.Net.WebClient
$WebClient.DownloadFile($Uri, $Destination) $WebClient.DownloadFile($Uri, $Destination)
Write-Host "Finished downloading $ExeFile" Write-Host "Finished downloading $ExeFile"
$VSROOT = "C:/VSBuildTools/$VSVer"
$Arguments = @( $Arguments = @(
"--installPath `"$VSROOT`"", # set custom installation path "--quiet", # Suppress installer UI
"--quiet", # suppress UI "--wait", # Wait for installation to complete
"--wait", # wait for installation to complete "--norestart", # Prevent automatic restart
"--norestart", # prevent automatic restart "--force", # Force installation even if components are already installed
"--add Microsoft.VisualStudio.Workload.VCTools", # add C++ build tools workload "--add Microsoft.VisualStudio.Workload.NativeDesktop", # Desktop development with C++
"--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64", # add core x86/x64 C++ tools "--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64", # Core C++ compiler/tools for x86/x64
"--add Microsoft.VisualStudio.Component.Windows10SDK.19041" # add specific Windows SDK "--add Microsoft.VisualStudio.Component.Windows11SDK.26100",# Windows 11 SDK (26100)
"--add Microsoft.VisualStudio.Component.Windows10SDK.19041",# Windows 10 SDK (19041)
"--add Microsoft.VisualStudio.Component.VC.Llvm.Clang", # LLVM Clang compiler
"--add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset", # LLVM Clang integration toolset
"--add Microsoft.VisualStudio.Component.Windows11SDK.22621",# Windows 11 SDK (22621)
"--add Microsoft.VisualStudio.Component.VC.CMake.Project", # CMake project support
"--add Microsoft.VisualStudio.ComponentGroup.VC.Tools.142.x86.x64", # VC++ 14.2 toolset
"--add Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Llvm.Clang" # LLVM Clang for native desktop
) )
Write-Host "Installing Visual Studio Build Tools" Write-Host "Installing Visual Studio Build Tools"
$InstallProcess = Start-Process -FilePath $Destination -NoNewWindow -PassThru -Wait -ArgumentList $Arguments $InstallProcess = Start-Process -FilePath $Destination -NoNewWindow -PassThru -ArgumentList $Arguments
$ExitCode = $InstallProcess.ExitCode
# Spinner while installing
$Spinner = "|/-\"
$i = 0
while (-not $InstallProcess.HasExited) {
Write-Host -NoNewline ("`rInstalling... " + $Spinner[$i % $Spinner.Length])
Start-Sleep -Milliseconds 250
$i++
}
# Clear spinner line
Write-Host "`rSetup completed! "
$ExitCode = $InstallProcess.ExitCode
if ($ExitCode -ne 0) { if ($ExitCode -ne 0) {
Write-Host "Error installing Visual Studio Build Tools (Error: $ExitCode)" Write-Host "Error installing Visual Studio Build Tools (Error: $ExitCode)"
Exit $ExitCode Exit $ExitCode

View file

@ -147,7 +147,11 @@ if (MSVC OR ANDROID)
set(EXT_DEFAULT ON) set(EXT_DEFAULT ON)
endif() endif()
CMAKE_DEPENDENT_OPTION(YUZU_USE_EXTERNAL_SDL2 "Compile external SDL2" ${EXT_DEFAULT} "ENABLE_SDL2;NOT MSVC" OFF) if (ENABLE_SDL2)
# TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system
CMAKE_DEPENDENT_OPTION(YUZU_USE_EXTERNAL_SDL2 "Compile external SDL2" OFF "NOT MSVC" OFF)
option(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${MSVC}")
endif()
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "NOT ANDROID" OFF) cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "NOT ANDROID" OFF)
@ -185,8 +189,6 @@ option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ${EXT_DEFAULT})
# TODO(crueter): CI this? # TODO(crueter): CI this?
option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON) option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON)
# TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system
CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${MSVC}" "ENABLE_SDL2" OFF)
CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF) CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF)
@ -197,6 +199,14 @@ CMAKE_DEPENDENT_OPTION(YUZU_CMD "Compile the eden-cli executable" ON "ENABLE_SDL
CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF) CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF)
option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF) option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF)
if(YUZU_ENABLE_LTO)
include(CheckIPOSupported)
check_ipo_supported(RESULT COMPILER_SUPPORTS_LTO)
if(NOT COMPILER_SUPPORTS_LTO)
message(FATAL_ERROR "Your compiler does not support interprocedural optimization (IPO). Re-run CMake with -DYUZU_ENABLE_LTO=OFF.")
endif()
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ${COMPILER_SUPPORTS_LTO})
endif()
option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" ON) option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" ON)
@ -884,19 +894,24 @@ if (MSVC AND CXX_CLANG)
link_libraries(llvm-mingw-runtime) link_libraries(llvm-mingw-runtime)
endif() endif()
if (YUZU_USE_FASTER_LD AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if (YUZU_USE_FASTER_LD)
# We will assume that if the compiler is GCC, it will attempt to use ld.bfd by default. # fallback if everything fails (bfd)
# Try to pick a faster linker. set(LINKER bfd)
# clang should always use lld
find_program(LLD lld) find_program(LLD lld)
find_program(MOLD mold) if (LLD)
set(LINKER lld)
if (MOLD AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.1")
message(NOTICE "Selecting mold as linker")
add_link_options("-fuse-ld=mold")
elseif (LLD)
message(NOTICE "Selecting lld as linker")
add_link_options("-fuse-ld=lld")
endif() endif()
# GNU appears to work better with mold
# TODO: mold has been slow lately, see if better options exist (search for gold?)
if (CXX_GCC)
find_program(MOLD mold)
if (MOLD AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.1")
set(LINKER mold)
endif()
endif()
message(NOTICE "Selecting ${LINKER} as linker")
add_link_options("-fuse-ld=${LINKER}")
endif() endif()
# Set runtime library to MD/MDd for all configurations # Set runtime library to MD/MDd for all configurations

View file

@ -4,8 +4,8 @@ To build Eden, you MUST have a C++ compiler.
* On Linux, this is usually [GCC](https://gcc.gnu.org/) 11+ or [Clang](https://clang.llvm.org/) v14+ * On Linux, this is usually [GCC](https://gcc.gnu.org/) 11+ or [Clang](https://clang.llvm.org/) v14+
- GCC 12 also requires Clang 14+ - GCC 12 also requires Clang 14+
* On Windows, this is either: * On Windows, this is either:
- **[MSVC](https://visualstudio.microsoft.com/downloads/)**, - **[MSVC](https://visualstudio.microsoft.com/downloads/)** (you should select *Community* option),
* *A convenience script to install the **minimal** version (Visual Build Tools) is provided in `.ci/windows/install-msvc.ps1`* * *A convenience script to install the Visual Community Studio 2022 with necessary tools is provided in `.ci/windows/install-msvc.ps1`*
- clang-cl - can be downloaded from the MSVC installer, - clang-cl - can be downloaded from the MSVC installer,
- or **[MSYS2](https://www.msys2.org)** - or **[MSYS2](https://www.msys2.org)**
* On macOS, this is Apple Clang * On macOS, this is Apple Clang
@ -211,4 +211,4 @@ Then install the libraries: `sudo pkg install qt6 boost glslang libzip library/l
## All Done ## All Done
You may now return to the **[root build guide](Build.md)**. You may now return to the **[root build guide](Build.md)**.

View file

@ -36,6 +36,28 @@ Pull requests are only to be merged by core developers when properly tested and
- Maintainers are permitted to change namespaces at will. - Maintainers are permitted to change namespaces at will.
- Commits within PRs are not required to be namespaced, but it is highly recommended. - Commits within PRs are not required to be namespaced, but it is highly recommended.
## Adding new settings
When adding new settings, use `tr("Setting:")` if the setting is meant to be a field, otherwise use `tr("Setting")` if the setting is meant to be a Yes/No or checkmark type of setting, see [this short style guide](https://learn.microsoft.com/en-us/style-guide/punctuation/colons#in-ui).
- The majority of software must work with the default option selected for such setting. Unless the setting significantly degrades performance.
- Debug settings must never be turned on by default.
- Provide reasonable bounds (for example, a setting controlling the amount of VRAM should never be 0).
- The description of the setting must be short and concise, if the setting "does a lot of things" consider splitting the setting into multiple if possible.
- Try to avoid excessive/redundant explainations "recommended for most users and games" can just be "(recommended)".
- Try to not write "slow/fast" options unless it clearly degrades/increases performance for a given case, as most options may modify behaviour that result in different metrics accross different systems. If for example the option is an "accuracy" option, writing "High" is sufficient to imply "Slow". No need to write "High (Slow)".
Some examples:
- "[...] negatively affecting image quality", "[...] degrading image quality": Same wording but with less filler.
- "[...] this may cause some glitches or crashes in some games", "[...] this may cause soft-crashes": Crashes implies there may be glitches (as crashes are technically a form of a fatal glitch). The entire sentence is structured as "may cause [...] on some games", which is redundant, because "may cause [...] in games" has the same semantic meaning ("may" is a chance that it will occur on "some" given set).
- "FIFO Relaxed is similar to FIFO [...]", "FIFO Relaxed [...]": The name already implies similarity.
- "[...] but may also reduce performance in some cases", "[...] but may degrade performance": Again, "some cases" and "may" implies there is a probability.
- "[...] it can [...] in some cases", "[...] it can [...]": Implied probability.
Before adding a new setting, consider:
- Does the piece of code that the setting pertains to, make a significant difference if it's on/off?
- Can it be auto-detected?
# IDE setup # IDE setup
## VSCode ## VSCode

View file

@ -141,7 +141,7 @@ else()
-Wno-missing-field-initializers -Wno-missing-field-initializers
) )
if (CXX_CLANG OR CXX_ICC) # Clang or AppleClang if (CXX_CLANG OR CXX_ICC OR CXX_APPLE) # Clang, AppleClang, or Intel C++
if (NOT MSVC) if (NOT MSVC)
add_compile_options( add_compile_options(
-Werror=shadow-uncaptured-local -Werror=shadow-uncaptured-local

View file

@ -175,12 +175,10 @@ android {
"-DYUZU_USE_CPM=ON", "-DYUZU_USE_CPM=ON",
"-DCPMUTIL_FORCE_BUNDLED=ON", "-DCPMUTIL_FORCE_BUNDLED=ON",
"-DYUZU_USE_BUNDLED_FFMPEG=ON", "-DYUZU_USE_BUNDLED_FFMPEG=ON",
"-DYUZU_ENABLE_LTO=ON",
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
"-DBUILD_TESTING=OFF", "-DBUILD_TESTING=OFF",
"-DYUZU_TESTS=OFF", "-DYUZU_TESTS=OFF",
"-DDYNARMIC_TESTS=OFF", "-DDYNARMIC_TESTS=OFF"
"-DDYNARMIC_ENABLE_LTO=ON"
) )
abiFilters("arm64-v8a") abiFilters("arm64-v8a")

View file

@ -68,6 +68,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
var isActivityRecreated = false var isActivityRecreated = false
private lateinit var nfcReader: NfcReader private lateinit var nfcReader: NfcReader
private var touchDownTime: Long = 0
private val maxTapDuration = 500L
private val gyro = FloatArray(3) private val gyro = FloatArray(3)
private val accel = FloatArray(3) private val accel = FloatArray(3)
private var motionTimestamp: Long = 0 private var motionTimestamp: Long = 0
@ -489,6 +492,38 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
} }
} }
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
val emulationFragment = navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
emulationFragment?.let { fragment ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
touchDownTime = System.currentTimeMillis()
// show overlay immediately on touch and cancel timer
if (!emulationViewModel.drawerOpen.value) {
fragment.handler.removeCallbacksAndMessages(null)
fragment.showOverlay()
}
}
MotionEvent.ACTION_UP -> {
if (!emulationViewModel.drawerOpen.value) {
val touchDuration = System.currentTimeMillis() - touchDownTime
if (touchDuration <= maxTapDuration) {
fragment.handleScreenTap(false)
} else {
// just start the auto-hide timer without toggling visibility
fragment.handleScreenTap(true)
}
}
}
}
}
return super.dispatchTouchEvent(event)
}
fun onEmulationStarted() { fun onEmulationStarted() {
emulationViewModel.setEmulationStarted(true) emulationViewModel.setEmulationStarted(true)
} }

View file

@ -55,6 +55,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
FRAME_INTERPOLATION("frame_interpolation"), FRAME_INTERPOLATION("frame_interpolation"),
// FRAME_SKIPPING("frame_skipping"), // FRAME_SKIPPING("frame_skipping"),
ENABLE_INPUT_OVERLAY_AUTO_HIDE("enable_input_overlay_auto_hide"),
PERF_OVERLAY_BACKGROUND("perf_overlay_background"), PERF_OVERLAY_BACKGROUND("perf_overlay_background"),
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"), SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),

View file

@ -59,7 +59,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
OFFLINE_WEB_APPLET("offline_web_applet_mode"), OFFLINE_WEB_APPLET("offline_web_applet_mode"),
LOGIN_SHARE_APPLET("login_share_applet_mode"), LOGIN_SHARE_APPLET("login_share_applet_mode"),
WIFI_WEB_AUTH_APPLET("wifi_web_auth_applet_mode"), WIFI_WEB_AUTH_APPLET("wifi_web_auth_applet_mode"),
MY_PAGE_APPLET("my_page_applet_mode") MY_PAGE_APPLET("my_page_applet_mode"),
INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide")
; ;
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)

View file

@ -12,6 +12,7 @@ object Settings {
SECTION_SYSTEM(R.string.preferences_system), SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics), SECTION_RENDERER(R.string.preferences_graphics),
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options), SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
SECTION_SOC_OVERLAY(R.string.soc_overlay_options), SECTION_SOC_OVERLAY(R.string.soc_overlay_options),
SECTION_AUDIO(R.string.preferences_audio), SECTION_AUDIO(R.string.preferences_audio),
SECTION_INPUT(R.string.preferences_controls), SECTION_INPUT(R.string.preferences_controls),

View file

@ -96,6 +96,7 @@ abstract class SettingsItem(
const val TYPE_INT_SINGLE_CHOICE = 9 const val TYPE_INT_SINGLE_CHOICE = 9
const val TYPE_INPUT_PROFILE = 10 const val TYPE_INPUT_PROFILE = 10
const val TYPE_STRING_INPUT = 11 const val TYPE_STRING_INPUT = 11
const val TYPE_SPINBOX = 12
const val FASTMEM_COMBINED = "fastmem_combined" const val FASTMEM_COMBINED = "fastmem_combined"
@ -385,6 +386,22 @@ abstract class SettingsItem(
warningMessage = R.string.warning_resolution warningMessage = R.string.warning_resolution
) )
) )
put(
SwitchSetting(
BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE,
titleId = R.string.enable_input_overlay_auto_hide,
)
)
put(
SpinBoxSetting(
IntSetting.INPUT_OVERLAY_AUTO_HIDE,
titleId = R.string.overlay_auto_hide,
descriptionId = R.string.overlay_auto_hide_description,
min = 1,
max = 999,
valueHint = R.string.seconds
)
)
put( put(
SwitchSetting( SwitchSetting(

View file

@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.model.view
import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
class SpinBoxSetting(
setting: AbstractSetting,
@StringRes titleId: Int = 0,
titleString: String = "",
@StringRes descriptionId: Int = 0,
descriptionString: String = "",
val valueHint: Int,
val min: Int,
val max: Int
) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
override val type = TYPE_SPINBOX
fun getSelectedValue(needsGlobal: Boolean = false) =
when (setting) {
is AbstractByteSetting -> setting.getByte(needsGlobal).toInt()
is AbstractShortSetting -> setting.getShort(needsGlobal).toInt()
is AbstractIntSetting -> setting.getInt(needsGlobal)
is AbstractFloatSetting -> setting.getFloat(needsGlobal).toInt()
else -> 0
}
fun setSelectedValue(value: Int) =
when (setting) {
is AbstractByteSetting -> setting.setByte(value.toByte())
is AbstractShortSetting -> setting.setShort(value.toShort())
is AbstractFloatSetting -> setting.setFloat(value.toFloat())
else -> (setting as AbstractIntSetting).setInt(value)
}
}

View file

@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui package org.yuzu.yuzu_emu.features.settings.ui
@ -61,6 +61,10 @@ class SettingsAdapter(
SliderViewHolder(ListItemSettingBinding.inflate(inflater), this) SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
} }
SettingsItem.TYPE_SPINBOX -> {
SpinBoxViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
SettingsItem.TYPE_SUBMENU -> { SettingsItem.TYPE_SUBMENU -> {
SubmenuViewHolder(ListItemSettingBinding.inflate(inflater), this) SubmenuViewHolder(ListItemSettingBinding.inflate(inflater), this)
} }
@ -191,6 +195,14 @@ class SettingsAdapter(
position position
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
} }
fun onSpinBoxClick(item: SpinBoxSetting, position: Int) {
SettingsDialogFragment.newInstance(
settingsViewModel,
item,
SettingsItem.TYPE_SPINBOX,
position
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
}
fun onSubmenuClick(item: SubmenuSetting) { fun onSubmenuClick(item: SubmenuSetting) {
val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null) val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)

View file

@ -14,6 +14,7 @@ import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
@ -22,6 +23,7 @@ import com.google.android.material.slider.Slider
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
import org.yuzu.yuzu_emu.databinding.DialogSpinboxBinding
import org.yuzu.yuzu_emu.features.input.NativeInput import org.yuzu.yuzu_emu.features.input.NativeInput
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting
@ -30,6 +32,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.IntSingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SpinBoxSetting
import org.yuzu.yuzu_emu.features.settings.model.view.StringInputSetting import org.yuzu.yuzu_emu.features.settings.model.view.StringInputSetting
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
import org.yuzu.yuzu_emu.utils.ParamPackage import org.yuzu.yuzu_emu.utils.ParamPackage
@ -46,6 +49,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
private lateinit var sliderBinding: DialogSliderBinding private lateinit var sliderBinding: DialogSliderBinding
private lateinit var stringInputBinding: DialogEditTextBinding private lateinit var stringInputBinding: DialogEditTextBinding
private lateinit var spinboxBinding: DialogSpinboxBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -142,6 +146,76 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
.create() .create()
} }
SettingsItem.TYPE_SPINBOX -> {
spinboxBinding = DialogSpinboxBinding.inflate(layoutInflater)
val item = settingsViewModel.clickedItem as SpinBoxSetting
val currentValue = item.getSelectedValue()
spinboxBinding.editValue.setText(currentValue.toString())
spinboxBinding.textInputLayout.hint = getString(item.valueHint)
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(item.title)
.setView(spinboxBinding.root)
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
.create()
val updateButtonState = { enabled: Boolean ->
dialog.setOnShowListener { dialogInterface ->
(dialogInterface as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled = enabled
}
if (dialog.isShowing) {
dialog.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled = enabled
}
}
val updateValidity = { value: Int ->
val isValid = value in item.min..item.max
if (isValid) {
spinboxBinding.textInputLayout.error = null
} else {
spinboxBinding.textInputLayout.error = getString(
if (value < item.min) R.string.value_too_low else R.string.value_too_high,
if (value < item.min) item.min else item.max
)
}
updateButtonState(isValid)
}
spinboxBinding.buttonDecrement.setOnClickListener {
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
val newValue = current - 1
spinboxBinding.editValue.setText(newValue.toString())
updateValidity(newValue)
}
spinboxBinding.buttonIncrement.setOnClickListener {
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
val newValue = current + 1
spinboxBinding.editValue.setText(newValue.toString())
updateValidity(newValue)
}
spinboxBinding.editValue.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
val value = s.toString().toIntOrNull()
if (value != null) {
updateValidity(value)
} else {
spinboxBinding.textInputLayout.error = getString(R.string.invalid_value)
updateButtonState(false)
}
}
})
updateValidity(currentValue)
dialog
}
SettingsItem.TYPE_STRING_INPUT -> { SettingsItem.TYPE_STRING_INPUT -> {
stringInputBinding = DialogEditTextBinding.inflate(layoutInflater) stringInputBinding = DialogEditTextBinding.inflate(layoutInflater)
val item = settingsViewModel.clickedItem as StringInputSetting val item = settingsViewModel.clickedItem as StringInputSetting
@ -281,6 +355,14 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value) sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value)
} }
is SpinBoxSetting -> {
val spinBoxSetting = settingsViewModel.clickedItem as SpinBoxSetting
val value = spinboxBinding.editValue.text.toString().toIntOrNull()
if (value != null && value in spinBoxSetting.min..spinBoxSetting.max) {
spinBoxSetting.setSelectedValue(value)
}
}
is StringInputSetting -> { is StringInputSetting -> {
val stringInputSetting = settingsViewModel.clickedItem as StringInputSetting val stringInputSetting = settingsViewModel.clickedItem as StringInputSetting
stringInputSetting.setSelectedValue( stringInputSetting.setSelectedValue(

View file

@ -97,6 +97,7 @@ class SettingsFragmentPresenter(
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl) MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl) MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl) MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
MenuTag.SECTION_AUDIO -> addAudioSettings(sl) MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
MenuTag.SECTION_INPUT -> addInputSettings(sl) MenuTag.SECTION_INPUT -> addInputSettings(sl)
MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0) MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0)
@ -156,6 +157,14 @@ class SettingsFragmentPresenter(
menuKey = MenuTag.SECTION_SOC_OVERLAY menuKey = MenuTag.SECTION_SOC_OVERLAY
) )
) )
add(
SubmenuSetting(
titleId = R.string.input_overlay_options,
iconId = R.drawable.ic_controller,
descriptionId = R.string.input_overlay_options_description,
menuKey = MenuTag.SECTION_INPUT_OVERLAY
)
)
} }
add( add(
SubmenuSetting( SubmenuSetting(
@ -264,6 +273,13 @@ class SettingsFragmentPresenter(
} }
} }
private fun addInputOverlaySettings(sl: ArrayList<SettingsItem>) {
sl.apply {
add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key)
add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key)
}
}
private fun addSocOverlaySettings(sl: ArrayList<SettingsItem>) { private fun addSocOverlaySettings(sl: ArrayList<SettingsItem>) {
sl.apply { sl.apply {
add(HeaderSetting(R.string.stats_overlay_customization)) add(HeaderSetting(R.string.stats_overlay_customization))

View file

@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SpinBoxSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
class SpinBoxViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
private lateinit var setting: SpinBoxSetting
override fun bind(item: SettingsItem) {
setting = item as SpinBoxSetting
binding.textSettingName.text = setting.title
binding.textSettingDescription.setVisible(item.description.isNotEmpty())
binding.textSettingDescription.text = setting.description
binding.textSettingValue.setVisible(true)
binding.textSettingValue.text = setting.getSelectedValue().toString()
binding.buttonClear.setVisible(setting.clearable)
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
}
setStyle(setting.isEditable, binding)
}
override fun onClick(clicked: View) {
if (setting.isEditable) {
adapter.onSpinBoxClick(setting, bindingAdapterPosition)
}
}
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
return adapter.onLongClick(setting, bindingAdapterPosition)
}
return false
}
}

View file

@ -96,6 +96,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private var perfStatsUpdater: (() -> Unit)? = null private var perfStatsUpdater: (() -> Unit)? = null
private var socUpdater: (() -> Unit)? = null private var socUpdater: (() -> Unit)? = null
val handler = Handler(Looper.getMainLooper())
private var isOverlayVisible = true
private var _binding: FragmentEmulationBinding? = null private var _binding: FragmentEmulationBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
@ -452,7 +455,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
/** /**
* Ask user if they want to launch with default settings when custom settings fail * Ask user if they want to launch with default settings when custom settings fail
*/ */
private suspend fun askUserToLaunchWithDefaultSettings(gameTitle: String, errorMessage: String): Boolean { private suspend fun askUserToLaunchWithDefaultSettings(
gameTitle: String,
errorMessage: String
): Boolean {
return suspendCoroutine { continuation -> return suspendCoroutine { continuation ->
requireActivity().runOnUiThread { requireActivity().runOnUiThread {
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
@ -728,6 +734,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
updateShowStatsOverlay() updateShowStatsOverlay()
updateSocOverlay() updateSocOverlay()
initializeOverlayAutoHide()
// Re update binding when the specs values get initialized properly // Re update binding when the specs values get initialized properly
binding.inGameMenu.getHeaderView(0).apply { binding.inGameMenu.getHeaderView(0).apply {
val titleView = findViewById<TextView>(R.id.text_game_title) val titleView = findViewById<TextView>(R.id.text_game_title)
@ -917,6 +925,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
updatePauseMenuEntry(emulationState.isPaused) updatePauseMenuEntry(emulationState.isPaused)
} }
} }
// if the overlay auto-hide setting is changed while paused,
// we need to reinitialize the auto-hide timer
initializeOverlayAutoHide()
} }
private fun resetInputOverlay() { private fun resetInputOverlay() {
@ -924,6 +937,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
IntSetting.OVERLAY_OPACITY.reset() IntSetting.OVERLAY_OPACITY.reset()
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.post {
binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement() binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
binding.surfaceInputOverlay.resetIndividualControlScale()
} }
} }
@ -1034,7 +1048,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL status == BatteryManager.BATTERY_STATUS_FULL
if (isCharging) { if (isCharging) {
sb.append(" ${getString(R.string.charging)}") sb.append(" ${getString(R.string.charging)}")
@ -1546,6 +1560,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
.setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int -> .setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int ->
setControlScale(50) setControlScale(50)
setControlOpacity(100) setControlOpacity(100)
binding.surfaceInputOverlay.resetIndividualControlScale()
} }
.show() .show()
} }
@ -1726,4 +1741,61 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!) private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!)
private val socUpdateHandler = Handler(Looper.myLooper()!!) private val socUpdateHandler = Handler(Looper.myLooper()!!)
} }
}
private fun startOverlayAutoHideTimer(seconds: Int) {
handler.removeCallbacksAndMessages(null)
handler.postDelayed({
if (isOverlayVisible) {
hideOverlay()
}
}, seconds * 1000L)
}
fun handleScreenTap(isLongTap: Boolean) {
val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt()
val shouldProceed = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() && BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean()
if (!shouldProceed) {
return
}
// failsafe
if (autoHideSeconds == 0) {
showOverlay()
return
}
if (!isOverlayVisible && !isLongTap) {
showOverlay()
}
startOverlayAutoHideTimer(autoHideSeconds)
}
private fun initializeOverlayAutoHide() {
val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt()
val autoHideEnabled = BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean()
val showOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
if (autoHideEnabled && showOverlay) {
showOverlay()
startOverlayAutoHideTimer(autoHideSeconds)
}
}
fun showOverlay() {
if (!isOverlayVisible) {
isOverlayVisible = true
ViewUtils.showView(binding.surfaceInputOverlay, 500)
}
}
private fun hideOverlay() {
if (isOverlayVisible) {
isOverlayVisible = false
ViewUtils.hideView(binding.surfaceInputOverlay, 500)
}
}
}

View file

@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay package org.yuzu.yuzu_emu.overlay
@ -13,6 +13,8 @@ import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.graphics.drawable.VectorDrawable import android.graphics.drawable.VectorDrawable
import android.os.Build import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet import android.util.AttributeSet
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.MotionEvent import android.view.MotionEvent
@ -52,6 +54,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
private var scaleDialog: OverlayScaleDialog? = null
private var touchStartX = 0f
private var touchStartY = 0f
private var hasMoved = false
private val moveThreshold = 20f
private lateinit var windowInsets: WindowInsets private lateinit var windowInsets: WindowInsets
var layout = OverlayLayout.Landscape var layout = OverlayLayout.Landscape
@ -254,23 +262,44 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
) { ) {
buttonBeingConfigured = button buttonBeingConfigured = button
buttonBeingConfigured!!.onConfigureTouch(event) buttonBeingConfigured!!.onConfigureTouch(event)
touchStartX = event.getX(pointerIndex)
touchStartY = event.getY(pointerIndex)
hasMoved = false
} }
MotionEvent.ACTION_MOVE -> if (buttonBeingConfigured != null) { MotionEvent.ACTION_MOVE -> if (buttonBeingConfigured != null) {
buttonBeingConfigured!!.onConfigureTouch(event) val moveDistance = kotlin.math.sqrt(
invalidate() (event.getX(pointerIndex) - touchStartX).let { it * it } +
return true (event.getY(pointerIndex) - touchStartY).let { it * it }
)
if (moveDistance > moveThreshold) {
hasMoved = true
buttonBeingConfigured!!.onConfigureTouch(event)
invalidate()
return true
}
} }
MotionEvent.ACTION_UP, MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) { MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
// Persist button position by saving new place. if (!hasMoved) {
saveControlPosition( showScaleDialog(
buttonBeingConfigured!!.overlayControlData.id, buttonBeingConfigured,
buttonBeingConfigured!!.bounds.centerX(), null,
buttonBeingConfigured!!.bounds.centerY(), null,
layout fingerPositionX,
) fingerPositionY
)
} else {
saveControlPosition(
buttonBeingConfigured!!.overlayControlData.id,
buttonBeingConfigured!!.bounds.centerX(),
buttonBeingConfigured!!.bounds.centerY(),
individuaScale = buttonBeingConfigured!!.overlayControlData.individualScale,
layout
)
}
buttonBeingConfigured = null buttonBeingConfigured = null
} }
} }
@ -287,23 +316,46 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
) { ) {
dpadBeingConfigured = dpad dpadBeingConfigured = dpad
dpadBeingConfigured!!.onConfigureTouch(event) dpadBeingConfigured!!.onConfigureTouch(event)
touchStartX = event.getX(pointerIndex)
touchStartY = event.getY(pointerIndex)
hasMoved = false
} }
MotionEvent.ACTION_MOVE -> if (dpadBeingConfigured != null) { MotionEvent.ACTION_MOVE -> if (dpadBeingConfigured != null) {
dpadBeingConfigured!!.onConfigureTouch(event) val moveDistance = kotlin.math.sqrt(
invalidate() (event.getX(pointerIndex) - touchStartX).let { it * it } +
return true (event.getY(pointerIndex) - touchStartY).let { it * it }
)
if (moveDistance > moveThreshold) {
hasMoved = true
dpadBeingConfigured!!.onConfigureTouch(event)
invalidate()
return true
}
} }
MotionEvent.ACTION_UP, MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) { MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) {
// Persist button position by saving new place. if (!hasMoved) {
saveControlPosition( // This was a click, show scale dialog for dpad
OverlayControl.COMBINED_DPAD.id, showScaleDialog(
dpadBeingConfigured!!.bounds.centerX(), null,
dpadBeingConfigured!!.bounds.centerY(), dpadBeingConfigured,
layout null,
) fingerPositionX,
fingerPositionY
)
} else {
// This was a move, save position
saveControlPosition(
OverlayControl.COMBINED_DPAD.id,
dpadBeingConfigured!!.bounds.centerX(),
dpadBeingConfigured!!.bounds.centerY(),
individuaScale = dpadBeingConfigured!!.individualScale,
layout
)
}
dpadBeingConfigured = null dpadBeingConfigured = null
} }
} }
@ -317,21 +369,43 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
) { ) {
joystickBeingConfigured = joystick joystickBeingConfigured = joystick
joystickBeingConfigured!!.onConfigureTouch(event) joystickBeingConfigured!!.onConfigureTouch(event)
touchStartX = event.getX(pointerIndex)
touchStartY = event.getY(pointerIndex)
hasMoved = false
} }
MotionEvent.ACTION_MOVE -> if (joystickBeingConfigured != null) { MotionEvent.ACTION_MOVE -> if (joystickBeingConfigured != null) {
joystickBeingConfigured!!.onConfigureTouch(event) val moveDistance = kotlin.math.sqrt(
invalidate() (event.getX(pointerIndex) - touchStartX).let { it * it } +
(event.getY(pointerIndex) - touchStartY).let { it * it }
)
if (moveDistance > moveThreshold) {
hasMoved = true
joystickBeingConfigured!!.onConfigureTouch(event)
invalidate()
}
} }
MotionEvent.ACTION_UP, MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) { MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) {
saveControlPosition( if (!hasMoved) {
joystickBeingConfigured!!.prefId, showScaleDialog(
joystickBeingConfigured!!.bounds.centerX(), null,
joystickBeingConfigured!!.bounds.centerY(), null,
layout joystickBeingConfigured,
) fingerPositionX,
fingerPositionY
)
} else {
saveControlPosition(
joystickBeingConfigured!!.prefId,
joystickBeingConfigured!!.bounds.centerX(),
joystickBeingConfigured!!.bounds.centerY(),
individuaScale = joystickBeingConfigured!!.individualScale,
layout
)
}
joystickBeingConfigured = null joystickBeingConfigured = null
} }
} }
@ -607,25 +681,117 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
invalidate() invalidate()
} }
private fun saveControlPosition(id: String, x: Int, y: Int, layout: OverlayLayout) { private fun saveControlPosition(
id: String,
x: Int,
y: Int,
individuaScale: Float,
layout: OverlayLayout
) {
val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight)) val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
val min = windowSize.first val min = windowSize.first
val max = windowSize.second val max = windowSize.second
val overlayControlData = NativeConfig.getOverlayControlData() val overlayControlData = NativeConfig.getOverlayControlData()
val data = overlayControlData.firstOrNull { it.id == id } val data = overlayControlData.firstOrNull { it.id == id }
val newPosition = Pair((x - min.x).toDouble() / max.x, (y - min.y).toDouble() / max.y) val newPosition = Pair((x - min.x).toDouble() / max.x, (y - min.y).toDouble() / max.y)
when (layout) { when (layout) {
OverlayLayout.Landscape -> data?.landscapePosition = newPosition OverlayLayout.Landscape -> data?.landscapePosition = newPosition
OverlayLayout.Portrait -> data?.portraitPosition = newPosition OverlayLayout.Portrait -> data?.portraitPosition = newPosition
OverlayLayout.Foldable -> data?.foldablePosition = newPosition OverlayLayout.Foldable -> data?.foldablePosition = newPosition
} }
data?.individualScale = individuaScale
NativeConfig.setOverlayControlData(overlayControlData) NativeConfig.setOverlayControlData(overlayControlData)
} }
fun setIsInEditMode(editMode: Boolean) { fun setIsInEditMode(editMode: Boolean) {
inEditMode = editMode inEditMode = editMode
if (!editMode) {
scaleDialog?.dismiss()
scaleDialog = null
}
} }
private fun showScaleDialog(
button: InputOverlayDrawableButton?,
dpad: InputOverlayDrawableDpad?,
joystick: InputOverlayDrawableJoystick?,
x: Int, y: Int
) {
val overlayControlData = NativeConfig.getOverlayControlData()
// prevent dialog from being spam opened
scaleDialog?.dismiss()
when {
button != null -> {
val buttonData =
overlayControlData.firstOrNull { it.id == button.overlayControlData.id }
if (buttonData != null) {
scaleDialog =
OverlayScaleDialog(context, button.overlayControlData) { newScale ->
saveControlPosition(
button.overlayControlData.id,
button.bounds.centerX(),
button.bounds.centerY(),
individuaScale = newScale,
layout
)
refreshControls()
}
scaleDialog?.showDialog(x,y, button.bounds.width(), button.bounds.height())
}
}
dpad != null -> {
val dpadData =
overlayControlData.firstOrNull { it.id == OverlayControl.COMBINED_DPAD.id }
if (dpadData != null) {
scaleDialog = OverlayScaleDialog(context, dpadData) { newScale ->
saveControlPosition(
OverlayControl.COMBINED_DPAD.id,
dpad.bounds.centerX(),
dpad.bounds.centerY(),
newScale,
layout
)
refreshControls()
}
scaleDialog?.showDialog(x,y, dpad.bounds.width(), dpad.bounds.height())
}
}
joystick != null -> {
val joystickData = overlayControlData.firstOrNull { it.id == joystick.prefId }
if (joystickData != null) {
scaleDialog = OverlayScaleDialog(context, joystickData) { newScale ->
saveControlPosition(
joystick.prefId,
joystick.bounds.centerX(),
joystick.bounds.centerY(),
individuaScale = newScale,
layout
)
refreshControls()
}
scaleDialog?.showDialog(x,y, joystick.bounds.width(), joystick.bounds.height())
}
}
}
}
/** /**
* Applies and saves all default values for the overlay * Applies and saves all default values for the overlay
*/ */
@ -664,12 +830,24 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
val overlayControlData = NativeConfig.getOverlayControlData() val overlayControlData = NativeConfig.getOverlayControlData()
overlayControlData.forEach { overlayControlData.forEach {
it.enabled = OverlayControl.from(it.id)?.defaultVisibility == true it.enabled = OverlayControl.from(it.id)?.defaultVisibility == true
it.individualScale = OverlayControl.from(it.id)?.defaultIndividualScaleResource!!
} }
NativeConfig.setOverlayControlData(overlayControlData) NativeConfig.setOverlayControlData(overlayControlData)
refreshControls() refreshControls()
} }
fun resetIndividualControlScale() {
val overlayControlData = NativeConfig.getOverlayControlData()
overlayControlData.forEach { data ->
val defaultControlData = OverlayControl.from(data.id) ?: return@forEach
data.individualScale = defaultControlData.defaultIndividualScaleResource
}
NativeConfig.setOverlayControlData(overlayControlData)
NativeConfig.saveGlobalConfig()
refreshControls()
}
private fun defaultOverlayPositionByLayout(layout: OverlayLayout) { private fun defaultOverlayPositionByLayout(layout: OverlayLayout) {
val overlayControlData = NativeConfig.getOverlayControlData() val overlayControlData = NativeConfig.getOverlayControlData()
for (data in overlayControlData) { for (data in overlayControlData) {
@ -860,6 +1038,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat() scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
scale /= 100f scale /= 100f
// Apply individual scale
scale *= overlayControlData.individualScale
// Initialize the InputOverlayDrawableButton. // Initialize the InputOverlayDrawableButton.
val defaultStateBitmap = getBitmap(context, defaultResId, scale) val defaultStateBitmap = getBitmap(context, defaultResId, scale)
val pressedStateBitmap = getBitmap(context, pressedResId, scale) val pressedStateBitmap = getBitmap(context, pressedResId, scale)
@ -922,11 +1103,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
// Resources handle for fetching the initial Drawable resource. // Resources handle for fetching the initial Drawable resource.
val res = context.resources val res = context.resources
// Get the dpad control data for individual scale
val overlayControlData = NativeConfig.getOverlayControlData()
val dpadData = overlayControlData.firstOrNull { it.id == OverlayControl.COMBINED_DPAD.id }
// Decide scale based on button ID and user preference // Decide scale based on button ID and user preference
var scale = 0.25f var scale = 0.25f
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat() scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
scale /= 100f scale /= 100f
// Apply individual scale
if (dpadData != null) {
scale *= dpadData.individualScale
}
// Initialize the InputOverlayDrawableDpad. // Initialize the InputOverlayDrawableDpad.
val defaultStateBitmap = val defaultStateBitmap =
getBitmap(context, defaultResId, scale) getBitmap(context, defaultResId, scale)
@ -1000,6 +1190,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat() scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
scale /= 100f scale /= 100f
// Apply individual scale
scale *= overlayControlData.individualScale
// Initialize the InputOverlayDrawableJoystick. // Initialize the InputOverlayDrawableJoystick.
val bitmapOuter = getBitmap(context, resOuter, scale) val bitmapOuter = getBitmap(context, resOuter, scale)
val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f) val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f)

View file

@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay package org.yuzu.yuzu_emu.overlay
@ -42,6 +42,8 @@ class InputOverlayDrawableDpad(
val width: Int val width: Int
val height: Int val height: Int
var individualScale: Float = 1.0f
private val defaultStateBitmap: BitmapDrawable private val defaultStateBitmap: BitmapDrawable
private val pressedOneDirectionStateBitmap: BitmapDrawable private val pressedOneDirectionStateBitmap: BitmapDrawable
private val pressedTwoDirectionsStateBitmap: BitmapDrawable private val pressedTwoDirectionsStateBitmap: BitmapDrawable

View file

@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay package org.yuzu.yuzu_emu.overlay
@ -51,6 +51,8 @@ class InputOverlayDrawableJoystick(
val width: Int val width: Int
val height: Int val height: Int
var individualScale: Float = 1.0f
private var opacity: Int = 0 private var opacity: Int = 0
private var virtBounds: Rect private var virtBounds: Rect

View file

@ -0,0 +1,124 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay
import android.app.Dialog
import android.content.Context
import android.view.Gravity
import android.view.LayoutInflater
import android.view.WindowManager
import android.widget.TextView
import com.google.android.material.button.MaterialButton
import com.google.android.material.slider.Slider
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
class OverlayScaleDialog(
context: Context,
private val overlayControlData: OverlayControlData,
private val onScaleChanged: (Float) -> Unit
) : Dialog(context) {
private var currentScale = overlayControlData.individualScale
private val originalScale = overlayControlData.individualScale
private lateinit var scaleValueText: TextView
private lateinit var scaleSlider: Slider
init {
setupDialog()
}
private fun setupDialog() {
val view = LayoutInflater.from(context).inflate(R.layout.dialog_overlay_scale, null)
setContentView(view)
window?.setBackgroundDrawable(null)
window?.apply {
attributes = attributes.apply {
flags = flags and WindowManager.LayoutParams.FLAG_DIM_BEHIND.inv()
flags = flags or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
}
}
scaleValueText = view.findViewById(R.id.scaleValueText)
scaleSlider = view.findViewById(R.id.scaleSlider)
val resetButton = view.findViewById<MaterialButton>(R.id.resetButton)
val confirmButton = view.findViewById<MaterialButton>(R.id.confirmButton)
val cancelButton = view.findViewById<MaterialButton>(R.id.cancelButton)
scaleValueText.text = String.format("%.1fx", currentScale)
scaleSlider.value = currentScale
scaleSlider.addOnChangeListener { _, value, input ->
if (input) {
currentScale = value
scaleValueText.text = String.format("%.1fx", currentScale)
}
}
scaleSlider.addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
override fun onStartTrackingTouch(slider: Slider) {
// pass
}
override fun onStopTrackingTouch(slider: Slider) {
onScaleChanged(currentScale)
}
})
resetButton.setOnClickListener {
currentScale = 1.0f
scaleSlider.value = 1.0f
scaleValueText.text = String.format("%.1fx", currentScale)
onScaleChanged(currentScale)
}
confirmButton.setOnClickListener {
overlayControlData.individualScale = currentScale
//slider value is already saved on touch dispatch but just to be sure
onScaleChanged(currentScale)
dismiss()
}
// both cancel button and back gesture should revert the scale change
cancelButton.setOnClickListener {
onScaleChanged(originalScale)
dismiss()
}
setOnCancelListener {
onScaleChanged(originalScale)
dismiss()
}
}
fun showDialog(anchorX: Int, anchorY: Int, anchorHeight: Int, anchorWidth: Int) {
show()
show()
// TODO: this calculation is a bit rough, improve it later on
window?.let { window ->
val layoutParams = window.attributes
layoutParams.gravity = Gravity.TOP or Gravity.START
val density = context.resources.displayMetrics.density
val dialogWidthPx = (320 * density).toInt()
val dialogHeightPx = (400 * density).toInt() // set your estimated dialog height
val screenHeight = context.resources.displayMetrics.heightPixels
layoutParams.x = anchorX + anchorWidth / 2 - dialogWidthPx / 2
layoutParams.y = anchorY + anchorHeight / 2 - dialogHeightPx / 2
layoutParams.width = dialogWidthPx
window.attributes = layoutParams
}
}
}

View file

@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay.model package org.yuzu.yuzu_emu.overlay.model
@ -12,126 +12,144 @@ enum class OverlayControl(
val defaultVisibility: Boolean, val defaultVisibility: Boolean,
@IntegerRes val defaultLandscapePositionResources: Pair<Int, Int>, @IntegerRes val defaultLandscapePositionResources: Pair<Int, Int>,
@IntegerRes val defaultPortraitPositionResources: Pair<Int, Int>, @IntegerRes val defaultPortraitPositionResources: Pair<Int, Int>,
@IntegerRes val defaultFoldablePositionResources: Pair<Int, Int> @IntegerRes val defaultFoldablePositionResources: Pair<Int, Int>,
val defaultIndividualScaleResource: Float,
) { ) {
BUTTON_A( BUTTON_A(
"button_a", "button_a",
true, true,
Pair(R.integer.BUTTON_A_X, R.integer.BUTTON_A_Y), Pair(R.integer.BUTTON_A_X, R.integer.BUTTON_A_Y),
Pair(R.integer.BUTTON_A_X_PORTRAIT, R.integer.BUTTON_A_Y_PORTRAIT), Pair(R.integer.BUTTON_A_X_PORTRAIT, R.integer.BUTTON_A_Y_PORTRAIT),
Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE) Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE),
1.0f
), ),
BUTTON_B( BUTTON_B(
"button_b", "button_b",
true, true,
Pair(R.integer.BUTTON_B_X, R.integer.BUTTON_B_Y), Pair(R.integer.BUTTON_B_X, R.integer.BUTTON_B_Y),
Pair(R.integer.BUTTON_B_X_PORTRAIT, R.integer.BUTTON_B_Y_PORTRAIT), Pair(R.integer.BUTTON_B_X_PORTRAIT, R.integer.BUTTON_B_Y_PORTRAIT),
Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE) Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE),
1.0f
), ),
BUTTON_X( BUTTON_X(
"button_x", "button_x",
true, true,
Pair(R.integer.BUTTON_X_X, R.integer.BUTTON_X_Y), Pair(R.integer.BUTTON_X_X, R.integer.BUTTON_X_Y),
Pair(R.integer.BUTTON_X_X_PORTRAIT, R.integer.BUTTON_X_Y_PORTRAIT), Pair(R.integer.BUTTON_X_X_PORTRAIT, R.integer.BUTTON_X_Y_PORTRAIT),
Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE) Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE),
1.0f
), ),
BUTTON_Y( BUTTON_Y(
"button_y", "button_y",
true, true,
Pair(R.integer.BUTTON_Y_X, R.integer.BUTTON_Y_Y), Pair(R.integer.BUTTON_Y_X, R.integer.BUTTON_Y_Y),
Pair(R.integer.BUTTON_Y_X_PORTRAIT, R.integer.BUTTON_Y_Y_PORTRAIT), Pair(R.integer.BUTTON_Y_X_PORTRAIT, R.integer.BUTTON_Y_Y_PORTRAIT),
Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE) Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE),
1.0f
), ),
BUTTON_PLUS( BUTTON_PLUS(
"button_plus", "button_plus",
true, true,
Pair(R.integer.BUTTON_PLUS_X, R.integer.BUTTON_PLUS_Y), Pair(R.integer.BUTTON_PLUS_X, R.integer.BUTTON_PLUS_Y),
Pair(R.integer.BUTTON_PLUS_X_PORTRAIT, R.integer.BUTTON_PLUS_Y_PORTRAIT), Pair(R.integer.BUTTON_PLUS_X_PORTRAIT, R.integer.BUTTON_PLUS_Y_PORTRAIT),
Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE) Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE),
1.0f
), ),
BUTTON_MINUS( BUTTON_MINUS(
"button_minus", "button_minus",
true, true,
Pair(R.integer.BUTTON_MINUS_X, R.integer.BUTTON_MINUS_Y), Pair(R.integer.BUTTON_MINUS_X, R.integer.BUTTON_MINUS_Y),
Pair(R.integer.BUTTON_MINUS_X_PORTRAIT, R.integer.BUTTON_MINUS_Y_PORTRAIT), Pair(R.integer.BUTTON_MINUS_X_PORTRAIT, R.integer.BUTTON_MINUS_Y_PORTRAIT),
Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE) Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE),
1.0f
), ),
BUTTON_HOME( BUTTON_HOME(
"button_home", "button_home",
false, false,
Pair(R.integer.BUTTON_HOME_X, R.integer.BUTTON_HOME_Y), Pair(R.integer.BUTTON_HOME_X, R.integer.BUTTON_HOME_Y),
Pair(R.integer.BUTTON_HOME_X_PORTRAIT, R.integer.BUTTON_HOME_Y_PORTRAIT), Pair(R.integer.BUTTON_HOME_X_PORTRAIT, R.integer.BUTTON_HOME_Y_PORTRAIT),
Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE) Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE),
1.0f
), ),
BUTTON_CAPTURE( BUTTON_CAPTURE(
"button_capture", "button_capture",
false, false,
Pair(R.integer.BUTTON_CAPTURE_X, R.integer.BUTTON_CAPTURE_Y), Pair(R.integer.BUTTON_CAPTURE_X, R.integer.BUTTON_CAPTURE_Y),
Pair(R.integer.BUTTON_CAPTURE_X_PORTRAIT, R.integer.BUTTON_CAPTURE_Y_PORTRAIT), Pair(R.integer.BUTTON_CAPTURE_X_PORTRAIT, R.integer.BUTTON_CAPTURE_Y_PORTRAIT),
Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE) Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE),
1.0f
), ),
BUTTON_L( BUTTON_L(
"button_l", "button_l",
true, true,
Pair(R.integer.BUTTON_L_X, R.integer.BUTTON_L_Y), Pair(R.integer.BUTTON_L_X, R.integer.BUTTON_L_Y),
Pair(R.integer.BUTTON_L_X_PORTRAIT, R.integer.BUTTON_L_Y_PORTRAIT), Pair(R.integer.BUTTON_L_X_PORTRAIT, R.integer.BUTTON_L_Y_PORTRAIT),
Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE) Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE),
1.0f
), ),
BUTTON_R( BUTTON_R(
"button_r", "button_r",
true, true,
Pair(R.integer.BUTTON_R_X, R.integer.BUTTON_R_Y), Pair(R.integer.BUTTON_R_X, R.integer.BUTTON_R_Y),
Pair(R.integer.BUTTON_R_X_PORTRAIT, R.integer.BUTTON_R_Y_PORTRAIT), Pair(R.integer.BUTTON_R_X_PORTRAIT, R.integer.BUTTON_R_Y_PORTRAIT),
Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE) Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE),
1.0f
), ),
BUTTON_ZL( BUTTON_ZL(
"button_zl", "button_zl",
true, true,
Pair(R.integer.BUTTON_ZL_X, R.integer.BUTTON_ZL_Y), Pair(R.integer.BUTTON_ZL_X, R.integer.BUTTON_ZL_Y),
Pair(R.integer.BUTTON_ZL_X_PORTRAIT, R.integer.BUTTON_ZL_Y_PORTRAIT), Pair(R.integer.BUTTON_ZL_X_PORTRAIT, R.integer.BUTTON_ZL_Y_PORTRAIT),
Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE) Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE),
1.0f
), ),
BUTTON_ZR( BUTTON_ZR(
"button_zr", "button_zr",
true, true,
Pair(R.integer.BUTTON_ZR_X, R.integer.BUTTON_ZR_Y), Pair(R.integer.BUTTON_ZR_X, R.integer.BUTTON_ZR_Y),
Pair(R.integer.BUTTON_ZR_X_PORTRAIT, R.integer.BUTTON_ZR_Y_PORTRAIT), Pair(R.integer.BUTTON_ZR_X_PORTRAIT, R.integer.BUTTON_ZR_Y_PORTRAIT),
Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE) Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE),
1.0f
), ),
BUTTON_STICK_L( BUTTON_STICK_L(
"button_stick_l", "button_stick_l",
true, true,
Pair(R.integer.BUTTON_STICK_L_X, R.integer.BUTTON_STICK_L_Y), Pair(R.integer.BUTTON_STICK_L_X, R.integer.BUTTON_STICK_L_Y),
Pair(R.integer.BUTTON_STICK_L_X_PORTRAIT, R.integer.BUTTON_STICK_L_Y_PORTRAIT), Pair(R.integer.BUTTON_STICK_L_X_PORTRAIT, R.integer.BUTTON_STICK_L_Y_PORTRAIT),
Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE) Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE),
1.0f
), ),
BUTTON_STICK_R( BUTTON_STICK_R(
"button_stick_r", "button_stick_r",
true, true,
Pair(R.integer.BUTTON_STICK_R_X, R.integer.BUTTON_STICK_R_Y), Pair(R.integer.BUTTON_STICK_R_X, R.integer.BUTTON_STICK_R_Y),
Pair(R.integer.BUTTON_STICK_R_X_PORTRAIT, R.integer.BUTTON_STICK_R_Y_PORTRAIT), Pair(R.integer.BUTTON_STICK_R_X_PORTRAIT, R.integer.BUTTON_STICK_R_Y_PORTRAIT),
Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE) Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE),
1.0f
), ),
STICK_L( STICK_L(
"stick_l", "stick_l",
true, true,
Pair(R.integer.STICK_L_X, R.integer.STICK_L_Y), Pair(R.integer.STICK_L_X, R.integer.STICK_L_Y),
Pair(R.integer.STICK_L_X_PORTRAIT, R.integer.STICK_L_Y_PORTRAIT), Pair(R.integer.STICK_L_X_PORTRAIT, R.integer.STICK_L_Y_PORTRAIT),
Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE) Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE),
1.0f
), ),
STICK_R( STICK_R(
"stick_r", "stick_r",
true, true,
Pair(R.integer.STICK_R_X, R.integer.STICK_R_Y), Pair(R.integer.STICK_R_X, R.integer.STICK_R_Y),
Pair(R.integer.STICK_R_X_PORTRAIT, R.integer.STICK_R_Y_PORTRAIT), Pair(R.integer.STICK_R_X_PORTRAIT, R.integer.STICK_R_Y_PORTRAIT),
Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE) Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE),
1.0f
), ),
COMBINED_DPAD( COMBINED_DPAD(
"combined_dpad", "combined_dpad",
true, true,
Pair(R.integer.COMBINED_DPAD_X, R.integer.COMBINED_DPAD_Y), Pair(R.integer.COMBINED_DPAD_X, R.integer.COMBINED_DPAD_Y),
Pair(R.integer.COMBINED_DPAD_X_PORTRAIT, R.integer.COMBINED_DPAD_Y_PORTRAIT), Pair(R.integer.COMBINED_DPAD_X_PORTRAIT, R.integer.COMBINED_DPAD_Y_PORTRAIT),
Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE) Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE),
1.0f
); );
fun getDefaultPositionForLayout(layout: OverlayLayout): Pair<Double, Double> { fun getDefaultPositionForLayout(layout: OverlayLayout): Pair<Double, Double> {
@ -173,7 +191,8 @@ enum class OverlayControl(
defaultVisibility, defaultVisibility,
getDefaultPositionForLayout(OverlayLayout.Landscape), getDefaultPositionForLayout(OverlayLayout.Landscape),
getDefaultPositionForLayout(OverlayLayout.Portrait), getDefaultPositionForLayout(OverlayLayout.Portrait),
getDefaultPositionForLayout(OverlayLayout.Foldable) getDefaultPositionForLayout(OverlayLayout.Foldable),
defaultIndividualScaleResource
) )
companion object { companion object {

View file

@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay.model package org.yuzu.yuzu_emu.overlay.model
@ -8,7 +8,8 @@ data class OverlayControlData(
var enabled: Boolean, var enabled: Boolean,
var landscapePosition: Pair<Double, Double>, var landscapePosition: Pair<Double, Double>,
var portraitPosition: Pair<Double, Double>, var portraitPosition: Pair<Double, Double>,
var foldablePosition: Pair<Double, Double> var foldablePosition: Pair<Double, Double>,
var individualScale: Float
) { ) {
fun positionFromLayout(layout: OverlayLayout): Pair<Double, Double> = fun positionFromLayout(layout: OverlayLayout): Pair<Double, Double> =
when (layout) { when (layout) {

View file

@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
@ -170,7 +170,8 @@ object DirectoryInitialization {
buttonEnabled, buttonEnabled,
Pair(landscapeXPosition, landscapeYPosition), Pair(landscapeXPosition, landscapeYPosition),
Pair(portraitXPosition, portraitYPosition), Pair(portraitXPosition, portraitYPosition),
Pair(foldableXPosition, foldableYPosition) Pair(foldableXPosition, foldableYPosition),
OverlayControl.map[buttonId]?.defaultIndividualScaleResource ?: 1.0f
) )
overlayControlDataMap[buttonId] = controlData overlayControlDataMap[buttonId] = controlData
setOverlayData = true setOverlayData = true

View file

@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include <common/logging/log.h> #include <common/logging/log.h>
#include <input_common/main.h> #include <input_common/main.h>
@ -103,6 +103,7 @@ void AndroidConfig::ReadOverlayValues() {
ReadDoubleSetting(std::string("foldable\\x_position")); ReadDoubleSetting(std::string("foldable\\x_position"));
control_data.foldable_position.second = control_data.foldable_position.second =
ReadDoubleSetting(std::string("foldable\\y_position")); ReadDoubleSetting(std::string("foldable\\y_position"));
control_data.individual_scale = static_cast<float>(ReadDoubleSetting(std::string("individual_scale")));
AndroidSettings::values.overlay_control_data.push_back(control_data); AndroidSettings::values.overlay_control_data.push_back(control_data);
} }
EndArray(); EndArray();
@ -255,6 +256,7 @@ void AndroidConfig::SaveOverlayValues() {
control_data.foldable_position.first); control_data.foldable_position.first);
WriteDoubleSetting(std::string("foldable\\y_position"), WriteDoubleSetting(std::string("foldable\\y_position"),
control_data.foldable_position.second); control_data.foldable_position.second);
WriteDoubleSetting(std::string("individual_scale"), static_cast<double>(control_data.individual_scale));
} }
EndArray(); EndArray();

View file

@ -24,6 +24,7 @@ namespace AndroidSettings {
std::pair<double, double> landscape_position; std::pair<double, double> landscape_position;
std::pair<double, double> portrait_position; std::pair<double, double> portrait_position;
std::pair<double, double> foldable_position; std::pair<double, double> foldable_position;
float individual_scale;
}; };
struct Values { struct Values {
@ -79,6 +80,15 @@ namespace AndroidSettings {
Settings::Category::Overlay, Settings::Category::Overlay,
Settings::Specialization::Paired, true, Settings::Specialization::Paired, true,
true}; true};
Settings::Setting<bool> enable_input_overlay_auto_hide{linkage, false,
"enable_input_overlay_auto_hide",
Settings::Category::Overlay,
Settings::Specialization::Default, true,
true,};
Settings::Setting<u32> input_overlay_auto_hide{linkage, 5, "input_overlay_auto_hide",
Settings::Category::Overlay,
Settings::Specialization::Default, true, true, &enable_input_overlay_auto_hide};
Settings::Setting<bool> perf_overlay_background{linkage, false, "perf_overlay_background", Settings::Setting<bool> perf_overlay_background{linkage, false, "perf_overlay_background",
Settings::Category::Overlay, Settings::Category::Overlay,
Settings::Specialization::Default, true, Settings::Specialization::Default, true,

View file

@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include <string> #include <string>
@ -369,7 +369,9 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getOverlayControlData(JN
env->NewObject(Common::Android::GetOverlayControlDataClass(), env->NewObject(Common::Android::GetOverlayControlDataClass(),
Common::Android::GetOverlayControlDataConstructor(), Common::Android::GetOverlayControlDataConstructor(),
Common::Android::ToJString(env, control_data.id), control_data.enabled, Common::Android::ToJString(env, control_data.id), control_data.enabled,
jlandscapePosition, jportraitPosition, jfoldablePosition); jlandscapePosition, jportraitPosition, jfoldablePosition,
control_data.individual_scale);
env->SetObjectArrayElement(joverlayControlDataArray, i, jcontrolData); env->SetObjectArrayElement(joverlayControlDataArray, i, jcontrolData);
} }
return joverlayControlDataArray; return joverlayControlDataArray;
@ -418,9 +420,12 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setOverlayControlData(
env, env,
env->GetObjectField(jfoldablePosition, Common::Android::GetPairSecondField()))); env->GetObjectField(jfoldablePosition, Common::Android::GetPairSecondField())));
float individual_scale = static_cast<float>(env->GetFloatField(
joverlayControlData, Common::Android::GetOverlayControlDataIndividualScaleField()));
AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{ AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{
Common::Android::GetJString(env, jidString), enabled, landscape_position, Common::Android::GetJString(env, jidString), enabled, landscape_position,
portrait_position, foldable_position}); portrait_position, foldable_position, individual_scale});
} }
} }

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="320dp"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="8dp"
app:cardBackgroundColor="#CC222222">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/scaleValueText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="1.0x"
android:textAppearance="?attr/textAppearanceBody1"
android:gravity="center"
android:layout_marginBottom="4dp"
android:textColor="#FFFFFF"
android:textStyle="bold" />
<com.google.android.material.slider.Slider
android:id="@+id/scaleSlider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:valueFrom="0.5"
android:valueTo="4.0"
android:stepSize="0.1"
android:value="1.0"
android:layout_marginBottom="8dp"
app:trackColorActive="@color/eden_border_gradient_start"
app:trackColorInactive="@color/eden_border_gradient_end"
app:tickColor="@color/eden_border_gradient_start"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="end">
<com.google.android.material.button.MaterialButton
android:id="@+id/resetButton"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/reset"
android:layout_marginEnd="4dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/cancelButton"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/cancel"
android:layout_marginEnd="4dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/confirmButton"
style="@style/Widget.Material3.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/eden_button_secondary_bg"
android:textColor="@color/eden_border_gradient_end"
android:text="@string/confirm" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginTop="32dp"
android:orientation="horizontal"
android:paddingLeft="@dimen/spacing_large"
android:paddingRight="@dimen/spacing_large">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_decrement"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/decrement"
android:text="-" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/text_input_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/spacing_medlarge"
android:layout_marginRight="@dimen/spacing_medlarge"
android:layout_weight="1"
android:hint="Value">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:textAlignment="textStart"
tools:text="0" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/button_increment"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/increment"
android:text="+" />
</LinearLayout>
</RelativeLayout>

View file

@ -251,7 +251,9 @@
<item>@string/scaling_filter_nearest_neighbor</item> <item>@string/scaling_filter_nearest_neighbor</item>
<item>@string/scaling_filter_bilinear</item> <item>@string/scaling_filter_bilinear</item>
<item>@string/scaling_filter_bicubic</item> <item>@string/scaling_filter_bicubic</item>
<item>@string/scaling_filter_spline1</item>
<item>@string/scaling_filter_gaussian</item> <item>@string/scaling_filter_gaussian</item>
<item>@string/scaling_filter_lanczos</item>
<item>@string/scaling_filter_scale_force</item> <item>@string/scaling_filter_scale_force</item>
<item>@string/scaling_filter_fsr</item> <item>@string/scaling_filter_fsr</item>
<item>@string/scaling_filter_area</item> <item>@string/scaling_filter_area</item>
@ -265,6 +267,8 @@
<item>4</item> <item>4</item>
<item>5</item> <item>5</item>
<item>6</item> <item>6</item>
<item>7</item>
<item>8</item>
</integer-array> </integer-array>
<string-array name="rendererAntiAliasingNames"> <string-array name="rendererAntiAliasingNames">

View file

@ -12,6 +12,24 @@
<string name="app_notification_channel_id" translatable="false">Eden</string> <string name="app_notification_channel_id" translatable="false">Eden</string>
<string name="app_notification_channel_description">Eden Switch emulator notifications</string> <string name="app_notification_channel_description">Eden Switch emulator notifications</string>
<string name="app_notification_running">Eden is Running</string> <string name="app_notification_running">Eden is Running</string>
<string name="seconds">Seconds</string>
<!-- Spinbox strings -->
<string name="increment">Increment</string>
<string name="decrement">Decrement</string>
<string name="value">Value</string>
<string name="value_too_low">Value must be at least %1$d</string>
<string name="value_too_high">Value must be at most %1$d</string>
<string name="invalid_value">Invalid value</string>
<!-- Input Overlay -->
<string name="overlay_auto_hide">Overlay Auto Hide</string>
<string name="overlay_auto_hide_description">Automatically hide the touch controls overlay after the specified time of inactivity.</string>
<string name="enable_input_overlay_auto_hide">Enable Overlay Auto Hide</string>
<string name="input_overlay_options">Input Overlay</string>
<string name="input_overlay_options_description">Configure on-screen controls</string>
<!-- Stats Overlay settings --> <!-- Stats Overlay settings -->
@ -855,6 +873,7 @@
<string name="touchscreen">Touchscreen</string> <string name="touchscreen">Touchscreen</string>
<string name="lock_drawer">Lock drawer</string> <string name="lock_drawer">Lock drawer</string>
<string name="unlock_drawer">Unlock drawer</string> <string name="unlock_drawer">Unlock drawer</string>
<string name="reset">Reset</string>
<string name="load_settings">Loading settings…</string> <string name="load_settings">Loading settings…</string>
@ -865,12 +884,12 @@
<string name="abort_button">Abort</string> <string name="abort_button">Abort</string>
<string name="continue_button">Continue</string> <string name="continue_button">Continue</string>
<string name="system_archive_not_found">System Archive Not Found</string> <string name="system_archive_not_found">System Archive Not Found</string>
<string name="system_archive_not_found_message">%s is missing. Please dump your system archives.\nContinuing emulation may result in crashes and bugs.</string> <string name="system_archive_not_found_message">%s is missing. Please dump your system archives.\nContinuing emulation may result in crashes.</string>
<string name="system_archive_general">A system archive</string> <string name="system_archive_general">A system archive</string>
<string name="save_load_error">Save/Load Error</string> <string name="save_load_error">Save/Load Error</string>
<string name="fatal_error">Fatal Error</string> <string name="fatal_error">Fatal Error</string>
<string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string> <string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes.</string>
<string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string> <string name="performance_warning">Turning off this setting will significantly degrade performance. It's recommended that you leave this setting enabled.</string>
<string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string> <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
<string name="memory_formatted">%1$s %2$s</string> <string name="memory_formatted">%1$s %2$s</string>
<string name="no_game_present">No bootable game present!</string> <string name="no_game_present">No bootable game present!</string>
@ -941,12 +960,12 @@
<!-- Renderer Accuracy --> <!-- Renderer Accuracy -->
<string name="renderer_accuracy_normal">Normal</string> <string name="renderer_accuracy_normal">Normal</string>
<string name="renderer_accuracy_high">High</string> <string name="renderer_accuracy_high">High</string>
<string name="renderer_accuracy_extreme">Extreme (Slow)</string> <string name="renderer_accuracy_extreme">Extreme</string>
<!-- DMA Accuracy --> <!-- DMA Accuracy -->
<string name="dma_accuracy_default">Default</string> <string name="dma_accuracy_default">Default</string>
<string name="dma_accuracy_unsafe">Unsafe (fast)</string> <string name="dma_accuracy_unsafe">Unsafe</string>
<string name="dma_accuracy_safe">Safe (stable)</string> <string name="dma_accuracy_safe">Safe</string>
<!-- ASTC Decoding Method --> <!-- ASTC Decoding Method -->
<string name="accelerate_astc">ASTC Decoding Method</string> <string name="accelerate_astc">ASTC Decoding Method</string>
@ -992,7 +1011,9 @@
<string name="scaling_filter_nearest_neighbor">Nearest Neighbor</string> <string name="scaling_filter_nearest_neighbor">Nearest Neighbor</string>
<string name="scaling_filter_bilinear">Bilinear</string> <string name="scaling_filter_bilinear">Bilinear</string>
<string name="scaling_filter_bicubic">Bicubic</string> <string name="scaling_filter_bicubic">Bicubic</string>
<string name="scaling_filter_spline1">Spline-1</string>
<string name="scaling_filter_gaussian">Gaussian</string> <string name="scaling_filter_gaussian">Gaussian</string>
<string name="scaling_filter_lanczos">Lanczos</string>
<string name="scaling_filter_scale_force">ScaleForce</string> <string name="scaling_filter_scale_force">ScaleForce</string>
<string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string> <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
<string name="scaling_filter_area">Area</string> <string name="scaling_filter_area">Area</string>

View file

@ -1,7 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include <jni.h> #include <jni.h>
@ -49,6 +46,7 @@ static jclass s_overlay_control_data_class;
static jmethodID s_overlay_control_data_constructor; static jmethodID s_overlay_control_data_constructor;
static jfieldID s_overlay_control_data_id_field; static jfieldID s_overlay_control_data_id_field;
static jfieldID s_overlay_control_data_enabled_field; static jfieldID s_overlay_control_data_enabled_field;
static jfieldID s_overlay_control_data_individual_scale_field;
static jfieldID s_overlay_control_data_landscape_position_field; static jfieldID s_overlay_control_data_landscape_position_field;
static jfieldID s_overlay_control_data_portrait_position_field; static jfieldID s_overlay_control_data_portrait_position_field;
static jfieldID s_overlay_control_data_foldable_position_field; static jfieldID s_overlay_control_data_foldable_position_field;
@ -244,6 +242,10 @@ namespace Common::Android {
return s_overlay_control_data_enabled_field; return s_overlay_control_data_enabled_field;
} }
jfieldID GetOverlayControlDataIndividualScaleField() {
return s_overlay_control_data_individual_scale_field;
}
jfieldID GetOverlayControlDataLandscapePositionField() { jfieldID GetOverlayControlDataLandscapePositionField() {
return s_overlay_control_data_landscape_position_field; return s_overlay_control_data_landscape_position_field;
} }
@ -494,7 +496,7 @@ namespace Common::Android {
reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class)); reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
s_overlay_control_data_constructor = s_overlay_control_data_constructor =
env->GetMethodID(overlay_control_data_class, "<init>", env->GetMethodID(overlay_control_data_class, "<init>",
"(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V"); "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;F)V");
s_overlay_control_data_id_field = s_overlay_control_data_id_field =
env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;"); env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
s_overlay_control_data_enabled_field = s_overlay_control_data_enabled_field =
@ -505,6 +507,8 @@ namespace Common::Android {
env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;"); env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
s_overlay_control_data_foldable_position_field = s_overlay_control_data_foldable_position_field =
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;"); env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
s_overlay_control_data_individual_scale_field =
env->GetFieldID(overlay_control_data_class, "individualScale", "F");
env->DeleteLocalRef(overlay_control_data_class); env->DeleteLocalRef(overlay_control_data_class);
const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch"); const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch");

View file

@ -1,7 +1,4 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#pragma once #pragma once
@ -67,6 +64,7 @@ jclass GetOverlayControlDataClass();
jmethodID GetOverlayControlDataConstructor(); jmethodID GetOverlayControlDataConstructor();
jfieldID GetOverlayControlDataIdField(); jfieldID GetOverlayControlDataIdField();
jfieldID GetOverlayControlDataEnabledField(); jfieldID GetOverlayControlDataEnabledField();
jfieldID GetOverlayControlDataIndividualScaleField();
jfieldID GetOverlayControlDataLandscapePositionField(); jfieldID GetOverlayControlDataLandscapePositionField();
jfieldID GetOverlayControlDataPortraitPositionField(); jfieldID GetOverlayControlDataPortraitPositionField();
jfieldID GetOverlayControlDataFoldablePositionField(); jfieldID GetOverlayControlDataFoldablePositionField();

View file

@ -178,7 +178,7 @@ struct Values {
SwitchableSetting<std::string> audio_input_device_id{ SwitchableSetting<std::string> audio_input_device_id{
linkage, "auto", "input_device", Category::Audio, Specialization::RuntimeList}; linkage, "auto", "input_device", Category::Audio, Specialization::RuntimeList};
SwitchableSetting<AudioMode, true> sound_index{ SwitchableSetting<AudioMode, true> sound_index{
linkage, AudioMode::Stereo, AudioMode::Mono, AudioMode::Surround, linkage, AudioMode::Stereo,
"sound_index", Category::SystemAudio, Specialization::Default, true, "sound_index", Category::SystemAudio, Specialization::Default, true,
true}; true};
SwitchableSetting<u8, true> volume{linkage, SwitchableSetting<u8, true> volume{linkage,
@ -199,8 +199,6 @@ struct Values {
SwitchableSetting<bool> use_multi_core{linkage, true, "use_multi_core", Category::Core}; SwitchableSetting<bool> use_multi_core{linkage, true, "use_multi_core", Category::Core};
SwitchableSetting<MemoryLayout, true> memory_layout_mode{linkage, SwitchableSetting<MemoryLayout, true> memory_layout_mode{linkage,
MemoryLayout::Memory_4Gb, MemoryLayout::Memory_4Gb,
MemoryLayout::Memory_4Gb,
MemoryLayout::Memory_12Gb,
"memory_layout_mode", "memory_layout_mode",
Category::Core, Category::Core,
Specialization::Default, Specialization::Default,
@ -240,9 +238,8 @@ struct Values {
#endif #endif
"cpu_backend", "cpu_backend",
Category::Cpu}; Category::Cpu};
SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto, SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto,
CpuAccuracy::Auto, CpuAccuracy::Paranoid, "cpu_accuracy", Category::Cpu};
"cpu_accuracy", Category::Cpu};
SwitchableSetting<bool> use_fast_cpu_time{linkage, SwitchableSetting<bool> use_fast_cpu_time{linkage,
false, false,
@ -324,10 +321,10 @@ struct Values {
// Renderer // Renderer
SwitchableSetting<RendererBackend, true> renderer_backend{ SwitchableSetting<RendererBackend, true> renderer_backend{
linkage, RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Null, linkage, RendererBackend::Vulkan,
"backend", Category::Renderer}; "backend", Category::Renderer};
SwitchableSetting<ShaderBackend, true> shader_backend{ SwitchableSetting<ShaderBackend, true> shader_backend{
linkage, ShaderBackend::SpirV, ShaderBackend::Glsl, ShaderBackend::SpirV, linkage, ShaderBackend::SpirV,
"shader_backend", Category::Renderer, Specialization::RuntimeList}; "shader_backend", Category::Renderer, Specialization::RuntimeList};
SwitchableSetting<int> vulkan_device{linkage, 0, "vulkan_device", Category::Renderer, SwitchableSetting<int> vulkan_device{linkage, 0, "vulkan_device", Category::Renderer,
Specialization::RuntimeList}; Specialization::RuntimeList};
@ -342,8 +339,6 @@ struct Values {
Category::Renderer}; Category::Renderer};
SwitchableSetting<SpirvOptimizeMode, true> optimize_spirv_output{linkage, SwitchableSetting<SpirvOptimizeMode, true> optimize_spirv_output{linkage,
SpirvOptimizeMode::Never, SpirvOptimizeMode::Never,
SpirvOptimizeMode::Never,
SpirvOptimizeMode::Always,
"optimize_spirv_output", "optimize_spirv_output",
Category::Renderer}; Category::Renderer};
SwitchableSetting<bool> use_asynchronous_gpu_emulation{ SwitchableSetting<bool> use_asynchronous_gpu_emulation{
@ -354,12 +349,10 @@ struct Values {
#else #else
AstcDecodeMode::Gpu, AstcDecodeMode::Gpu,
#endif #endif
AstcDecodeMode::Cpu,
AstcDecodeMode::CpuAsynchronous,
"accelerate_astc", "accelerate_astc",
Category::Renderer}; Category::Renderer};
SwitchableSetting<VSyncMode, true> vsync_mode{ SwitchableSetting<VSyncMode, true> vsync_mode{
linkage, VSyncMode::Fifo, VSyncMode::Immediate, VSyncMode::FifoRelaxed, linkage, VSyncMode::Fifo,
"use_vsync", Category::Renderer, Specialization::RuntimeList, true, "use_vsync", Category::Renderer, Specialization::RuntimeList, true,
true}; true};
SwitchableSetting<NvdecEmulation> nvdec_emulation{linkage, NvdecEmulation::Gpu, SwitchableSetting<NvdecEmulation> nvdec_emulation{linkage, NvdecEmulation::Gpu,
@ -372,8 +365,6 @@ struct Values {
#else #else
FullscreenMode::Exclusive, FullscreenMode::Exclusive,
#endif #endif
FullscreenMode::Borderless,
FullscreenMode::Exclusive,
"fullscreen_mode", "fullscreen_mode",
Category::Renderer, Category::Renderer,
Specialization::Default, Specialization::Default,
@ -381,8 +372,6 @@ struct Values {
true}; true};
SwitchableSetting<AspectRatio, true> aspect_ratio{linkage, SwitchableSetting<AspectRatio, true> aspect_ratio{linkage,
AspectRatio::R16_9, AspectRatio::R16_9,
AspectRatio::R16_9,
AspectRatio::Stretch,
"aspect_ratio", "aspect_ratio",
Category::Renderer, Category::Renderer,
Specialization::Default, Specialization::Default,
@ -430,8 +419,6 @@ struct Values {
#else #else
GpuAccuracy::High, GpuAccuracy::High,
#endif #endif
GpuAccuracy::Normal,
GpuAccuracy::Extreme,
"gpu_accuracy", "gpu_accuracy",
Category::RendererAdvanced, Category::RendererAdvanced,
Specialization::Default, Specialization::Default,
@ -442,8 +429,6 @@ struct Values {
SwitchableSetting<DmaAccuracy, true> dma_accuracy{linkage, SwitchableSetting<DmaAccuracy, true> dma_accuracy{linkage,
DmaAccuracy::Default, DmaAccuracy::Default,
DmaAccuracy::Default,
DmaAccuracy::Safe,
"dma_accuracy", "dma_accuracy",
Category::RendererAdvanced, Category::RendererAdvanced,
Specialization::Default, Specialization::Default,
@ -456,20 +441,14 @@ struct Values {
#else #else
AnisotropyMode::Automatic, AnisotropyMode::Automatic,
#endif #endif
AnisotropyMode::Automatic,
AnisotropyMode::X16,
"max_anisotropy", "max_anisotropy",
Category::RendererAdvanced}; Category::RendererAdvanced};
SwitchableSetting<AstcRecompression, true> astc_recompression{linkage, SwitchableSetting<AstcRecompression, true> astc_recompression{linkage,
AstcRecompression::Uncompressed, AstcRecompression::Uncompressed,
AstcRecompression::Uncompressed,
AstcRecompression::Bc3,
"astc_recompression", "astc_recompression",
Category::RendererAdvanced}; Category::RendererAdvanced};
SwitchableSetting<VramUsageMode, true> vram_usage_mode{linkage, SwitchableSetting<VramUsageMode, true> vram_usage_mode{linkage,
VramUsageMode::Conservative, VramUsageMode::Conservative,
VramUsageMode::Conservative,
VramUsageMode::Aggressive,
"vram_usage_mode", "vram_usage_mode",
Category::RendererAdvanced}; Category::RendererAdvanced};
SwitchableSetting<bool> skip_cpu_inner_invalidation{linkage, SwitchableSetting<bool> skip_cpu_inner_invalidation{linkage,
@ -595,14 +574,10 @@ struct Values {
// System // System
SwitchableSetting<Language, true> language_index{linkage, SwitchableSetting<Language, true> language_index{linkage,
Language::EnglishAmerican, Language::EnglishAmerican,
Language::Japanese,
Language::Serbian,
"language_index", "language_index",
Category::System}; Category::System};
SwitchableSetting<Region, true> region_index{linkage, Region::Usa, Region::Japan, SwitchableSetting<Region, true> region_index{linkage, Region::Usa, "region_index", Category::System};
Region::Taiwan, "region_index", Category::System}; SwitchableSetting<TimeZone, true> time_zone_index{linkage, TimeZone::Auto,
SwitchableSetting<TimeZone, true> time_zone_index{linkage, TimeZone::Auto,
TimeZone::Auto, TimeZone::Zulu,
"time_zone_index", Category::System}; "time_zone_index", Category::System};
// Measured in seconds since epoch // Measured in seconds since epoch
SwitchableSetting<bool> custom_rtc_enabled{ SwitchableSetting<bool> custom_rtc_enabled{

View file

@ -10,6 +10,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include <string_view>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
@ -18,8 +19,10 @@ namespace Settings {
template <typename T> template <typename T>
struct EnumMetadata { struct EnumMetadata {
static std::vector<std::pair<std::string, T>> Canonicalizations(); static std::vector<std::pair<std::string_view, T>> Canonicalizations();
static u32 Index(); static u32 Index();
static constexpr T GetFirst();
static constexpr T GetLast();
}; };
#define PAIR_45(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_46(N, __VA_ARGS__)) #define PAIR_45(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_46(N, __VA_ARGS__))
@ -69,138 +72,101 @@ struct EnumMetadata {
#define PAIR_1(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_2(N, __VA_ARGS__)) #define PAIR_1(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_2(N, __VA_ARGS__))
#define PAIR(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_1(N, __VA_ARGS__)) #define PAIR(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_1(N, __VA_ARGS__))
#define ENUM(NAME, ...) \ #define PP_HEAD(A, ...) A
enum class NAME : u32 { __VA_ARGS__ }; \
template <> \ #define ENUM(NAME, ...) \
inline std::vector<std::pair<std::string, NAME>> EnumMetadata<NAME>::Canonicalizations() { \ enum class NAME : u32 { __VA_ARGS__ }; \
return {PAIR(NAME, __VA_ARGS__)}; \ template<> inline std::vector<std::pair<std::string_view, NAME>> EnumMetadata<NAME>::Canonicalizations() { \
} \ return {PAIR(NAME, __VA_ARGS__)}; \
template <> \ } \
inline u32 EnumMetadata<NAME>::Index() { \ template<> inline u32 EnumMetadata<NAME>::Index() { \
return __COUNTER__; \ return __COUNTER__; \
} \
template<> inline constexpr NAME EnumMetadata<NAME>::GetFirst() { \
return NAME::PP_HEAD(__VA_ARGS__); \
} \
template<> inline constexpr NAME EnumMetadata<NAME>::GetLast() { \
return (std::vector<std::pair<std::string_view, NAME>>{PAIR(NAME, __VA_ARGS__)}).back().second; \
} }
// AudioEngine must be specified discretely due to having existing but slightly different // AudioEngine must be specified discretely due to having existing but slightly different
// canonicalizations // canonicalizations
// TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id // TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id
enum class AudioEngine : u32 { enum class AudioEngine : u32 { Auto, Cubeb, Sdl2, Null, Oboe, };
Auto, template<>
Cubeb, inline std::vector<std::pair<std::string_view, AudioEngine>> EnumMetadata<AudioEngine>::Canonicalizations() {
Sdl2,
Null,
Oboe,
};
template <>
inline std::vector<std::pair<std::string, AudioEngine>>
EnumMetadata<AudioEngine>::Canonicalizations() {
return { return {
{"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2}, {"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2},
{"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe}, {"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe},
}; };
} }
/// @brief This is just a sufficiently large number that is more than the number of other enums declared here
template <> template<>
inline u32 EnumMetadata<AudioEngine>::Index() { inline u32 EnumMetadata<AudioEngine>::Index() {
// This is just a sufficiently large number that is more than the number of other enums declared
// here
return 100; return 100;
} }
template<>
inline constexpr AudioEngine EnumMetadata<AudioEngine>::GetFirst() {
return AudioEngine::Auto;
}
template<>
inline constexpr AudioEngine EnumMetadata<AudioEngine>::GetLast() {
return AudioEngine::Oboe;
}
ENUM(AudioMode, Mono, Stereo, Surround); ENUM(AudioMode, Mono, Stereo, Surround);
static_assert(EnumMetadata<AudioMode>::GetFirst() == AudioMode::Mono);
static_assert(EnumMetadata<AudioMode>::GetLast() == AudioMode::Surround);
ENUM(Language, Japanese, EnglishAmerican, French, German, Italian, Spanish, Chinese, Korean, Dutch, ENUM(Language, Japanese, EnglishAmerican, French, German, Italian, Spanish, Chinese, Korean, Dutch,
Portuguese, Russian, Taiwanese, EnglishBritish, FrenchCanadian, SpanishLatin, Portuguese, Russian, Taiwanese, EnglishBritish, FrenchCanadian, SpanishLatin,
ChineseSimplified, ChineseTraditional, PortugueseBrazilian, Serbian); ChineseSimplified, ChineseTraditional, PortugueseBrazilian, Serbian);
ENUM(Region, Japan, Usa, Europe, Australia, China, Korea, Taiwan); ENUM(Region, Japan, Usa, Europe, Australia, China, Korea, Taiwan);
ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt, Gb, GbEire, Gmt, ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt, Gb, GbEire, Gmt,
GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica, GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica,
Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt, Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt,
Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu); Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu);
ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16); ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16);
ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous); ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous);
ENUM(AstcRecompression, Uncompressed, Bc1, Bc3); ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed); ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed);
ENUM(VramUsageMode, Conservative, Aggressive); ENUM(VramUsageMode, Conservative, Aggressive);
ENUM(RendererBackend, OpenGL, Vulkan, Null); ENUM(RendererBackend, OpenGL, Vulkan, Null);
ENUM(ShaderBackend, Glsl, Glasm, SpirV); ENUM(ShaderBackend, Glsl, Glasm, SpirV);
ENUM(GpuAccuracy, Normal, High, Extreme); ENUM(GpuAccuracy, Normal, High, Extreme);
ENUM(DmaAccuracy, Default, Unsafe, Safe); ENUM(DmaAccuracy, Default, Unsafe, Safe);
ENUM(CpuBackend, Dynarmic, Nce); ENUM(CpuBackend, Dynarmic, Nce);
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid); ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
ENUM(CpuClock, Boost, Fast) ENUM(CpuClock, Boost, Fast)
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb, Memory_10Gb, Memory_12Gb); ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb, Memory_10Gb, Memory_12Gb);
ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never); ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
ENUM(FullscreenMode, Borderless, Exclusive); ENUM(FullscreenMode, Borderless, Exclusive);
ENUM(NvdecEmulation, Off, Cpu, Gpu); ENUM(NvdecEmulation, Off, Cpu, Gpu);
ENUM(ResolutionSetup, Res1_4X, Res1_2X, Res3_4X, Res1X, Res3_2X, Res2X, Res3X, Res4X, Res5X, Res6X, Res7X, Res8X);
ENUM(ResolutionSetup,
Res1_4X,
Res1_2X,
Res3_4X,
Res1X,
Res3_2X,
Res2X,
Res3X,
Res4X,
Res5X,
Res6X,
Res7X,
Res8X);
ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Spline1, Gaussian, Lanczos, ScaleForce, Fsr, Area, MaxEnum); ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Spline1, Gaussian, Lanczos, ScaleForce, Fsr, Area, MaxEnum);
ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum); ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum);
ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch); ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
ENUM(ConsoleMode, Handheld, Docked); ENUM(ConsoleMode, Handheld, Docked);
ENUM(AppletMode, HLE, LLE); ENUM(AppletMode, HLE, LLE);
ENUM(SpirvOptimizeMode, Never, OnLoad, Always); ENUM(SpirvOptimizeMode, Never, OnLoad, Always);
ENUM(GpuOverclock, Low, Medium, High) ENUM(GpuOverclock, Low, Medium, High)
ENUM(TemperatureUnits, Celsius, Fahrenheit) ENUM(TemperatureUnits, Celsius, Fahrenheit)
template <typename Type> template <typename Type>
inline std::string CanonicalizeEnum(Type id) { inline std::string_view CanonicalizeEnum(Type id) {
const auto group = EnumMetadata<Type>::Canonicalizations(); const auto group = EnumMetadata<Type>::Canonicalizations();
for (auto& [name, value] : group) { for (auto& [name, value] : group)
if (value == id) { if (value == id)
return name; return name;
}
}
return "unknown"; return "unknown";
} }
template <typename Type> template <typename Type>
inline Type ToEnum(const std::string& canonicalization) { inline Type ToEnum(const std::string& canonicalization) {
const auto group = EnumMetadata<Type>::Canonicalizations(); const auto group = EnumMetadata<Type>::Canonicalizations();
for (auto& [name, value] : group) { for (auto& [name, value] : group)
if (name == canonicalization) { if (name == canonicalization)
return value; return value;
}
}
return {}; return {};
} }
} // namespace Settings } // namespace Settings

View file

@ -72,10 +72,17 @@ public:
u32 specialization_ = Specialization::Default, bool save_ = true, u32 specialization_ = Specialization::Default, bool save_ = true,
bool runtime_modifiable_ = false, BasicSetting* other_setting_ = nullptr) bool runtime_modifiable_ = false, BasicSetting* other_setting_ = nullptr)
requires(ranged) requires(ranged)
: BasicSetting(linkage, name, category_, save_, runtime_modifiable_, specialization_, : BasicSetting(linkage, name, category_, save_, runtime_modifiable_, specialization_, other_setting_),
other_setting_),
value{default_val}, default_value{default_val}, maximum{max_val}, minimum{min_val} {} value{default_val}, default_value{default_val}, maximum{max_val}, minimum{min_val} {}
explicit Setting(Linkage& linkage, const Type& default_val,
const std::string& name, Category category_,
u32 specialization_ = Specialization::Default, bool save_ = true,
bool runtime_modifiable_ = false, BasicSetting* other_setting_ = nullptr)
requires(ranged && std::is_enum_v<Type>)
: BasicSetting(linkage, name, category_, save_, runtime_modifiable_, specialization_, other_setting_),
value{default_val}, default_value{default_val}, maximum{EnumMetadata<Type>::GetLast()}, minimum{EnumMetadata<Type>::GetFirst()} {}
/** /**
* Returns a reference to the setting's value. * Returns a reference to the setting's value.
* *
@ -119,9 +126,6 @@ protected:
return value_.has_value() ? std::to_string(*value_) : "none"; return value_.has_value() ? std::to_string(*value_) : "none";
} else if constexpr (std::is_same_v<Type, bool>) { } else if constexpr (std::is_same_v<Type, bool>) {
return value_ ? "true" : "false"; return value_ ? "true" : "false";
} else if constexpr (std::is_same_v<Type, AudioEngine>) {
// Compatibility with old AudioEngine setting being a string
return CanonicalizeEnum(value_);
} else if constexpr (std::is_floating_point_v<Type>) { } else if constexpr (std::is_floating_point_v<Type>) {
return fmt::format("{:f}", value_); return fmt::format("{:f}", value_);
} else if constexpr (std::is_enum_v<Type>) { } else if constexpr (std::is_enum_v<Type>) {
@ -207,7 +211,7 @@ public:
[[nodiscard]] std::string Canonicalize() const override final { [[nodiscard]] std::string Canonicalize() const override final {
if constexpr (std::is_enum_v<Type>) { if constexpr (std::is_enum_v<Type>) {
return CanonicalizeEnum(this->GetValue()); return std::string{CanonicalizeEnum(this->GetValue())};
} else { } else {
return ToString(this->GetValue()); return ToString(this->GetValue());
} }
@ -288,41 +292,32 @@ public:
* @param other_setting_ A second Setting to associate to this one in metadata * @param other_setting_ A second Setting to associate to this one in metadata
*/ */
template <typename T = BasicSetting> template <typename T = BasicSetting>
explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name, explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name, Category category_, u32 specialization_ = Specialization::Default, bool save_ = true, bool runtime_modifiable_ = false, T* other_setting_ = nullptr) requires(!ranged)
Category category_, u32 specialization_ = Specialization::Default, : Setting<Type, false>{ linkage, default_val, name, category_, specialization_, save_, runtime_modifiable_, other_setting_} {
bool save_ = true, bool runtime_modifiable_ = false,
typename std::enable_if<!ranged, T*>::type other_setting_ = nullptr)
: Setting<Type, false>{
linkage, default_val, name, category_, specialization_,
save_, runtime_modifiable_, other_setting_} {
linkage.restore_functions.emplace_back([this]() { this->SetGlobal(true); }); linkage.restore_functions.emplace_back([this]() { this->SetGlobal(true); });
} }
virtual ~SwitchableSetting() = default; virtual ~SwitchableSetting() = default;
/** /// @brief Sets a default value, minimum value, maximum value, and label.
* Sets a default value, minimum value, maximum value, and label. /// @param linkage Setting registry
* /// @param default_val Initial value of the setting, and default value of the setting
* @param linkage Setting registry /// @param min_val Sets the minimum allowed value of the setting
* @param default_val Initial value of the setting, and default value of the setting /// @param max_val Sets the maximum allowed value of the setting
* @param min_val Sets the minimum allowed value of the setting /// @param name Label for the setting
* @param max_val Sets the maximum allowed value of the setting /// @param category_ Category of the setting AKA INI group
* @param name Label for the setting /// @param specialization_ Suggestion for how frontend implementations represent this in a config
* @param category_ Category of the setting AKA INI group /// @param save_ Suggests that this should or should not be saved to a frontend config file
* @param specialization_ Suggestion for how frontend implementations represent this in a config /// @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
* @param save_ Suggests that this should or should not be saved to a frontend config file /// @param other_setting_ A second Setting to associate to this one in metadata
* @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
* @param other_setting_ A second Setting to associate to this one in metadata
*/
template <typename T = BasicSetting> template <typename T = BasicSetting>
explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const Type& min_val, explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const Type& min_val, const Type& max_val, const std::string& name, Category category_, u32 specialization_ = Specialization::Default, bool save_ = true, bool runtime_modifiable_ = false, T* other_setting_ = nullptr) requires(ranged)
const Type& max_val, const std::string& name, Category category_, : Setting<Type, true>{linkage, default_val, min_val, max_val, name, category_, specialization_, save_, runtime_modifiable_, other_setting_} {
u32 specialization_ = Specialization::Default, bool save_ = true, linkage.restore_functions.emplace_back([this]() { this->SetGlobal(true); });
bool runtime_modifiable_ = false, }
typename std::enable_if<ranged, T*>::type other_setting_ = nullptr)
: Setting<Type, true>{linkage, default_val, min_val, template <typename T = BasicSetting>
max_val, name, category_, explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name, Category category_, u32 specialization_ = Specialization::Default, bool save_ = true, bool runtime_modifiable_ = false, T* other_setting_ = nullptr) requires(ranged)
specialization_, save_, runtime_modifiable_, : Setting<Type, true>{linkage, default_val, EnumMetadata<Type>::GetFirst(), EnumMetadata<Type>::GetLast(), name, category_, specialization_, save_, runtime_modifiable_, other_setting_} {
other_setting_} {
linkage.restore_functions.emplace_back([this]() { this->SetGlobal(true); }); linkage.restore_functions.emplace_back([this]() { this->SetGlobal(true); });
} }

View file

@ -1274,8 +1274,4 @@ if (YUZU_USE_PRECOMPILED_HEADERS)
target_precompile_headers(core PRIVATE precompiled_headers.h) target_precompile_headers(core PRIVATE precompiled_headers.h)
endif() endif()
if (YUZU_ENABLE_LTO)
set_property(TARGET core PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
create_target_directory_groups(core) create_target_directory_groups(core)

View file

@ -8,6 +8,7 @@
#include <deque> #include <deque>
#include <mutex> #include <mutex>
#include <stack>
#include "common/math_util.h" #include "common/math_util.h"
#include "core/hle/service/apm/apm_controller.h" #include "core/hle/service/apm/apm_controller.h"
@ -23,6 +24,7 @@
#include "core/hle/service/am/hid_registration.h" #include "core/hle/service/am/hid_registration.h"
#include "core/hle/service/am/lifecycle_manager.h" #include "core/hle/service/am/lifecycle_manager.h"
#include "core/hle/service/am/process_holder.h" #include "core/hle/service/am/process_holder.h"
#include "core/hle/service/am/service/storage.h"
namespace Service::AM { namespace Service::AM {
@ -97,6 +99,9 @@ struct Applet {
std::deque<std::vector<u8>> preselected_user_launch_parameter{}; std::deque<std::vector<u8>> preselected_user_launch_parameter{};
std::deque<std::vector<u8>> friend_invitation_storage_channel{}; std::deque<std::vector<u8>> friend_invitation_storage_channel{};
// Context Stack
std::stack<SharedPointer<IStorage>> context_stack{};
// Caller applet // Caller applet
std::weak_ptr<Applet> caller_applet{}; std::weak_ptr<Applet> caller_applet{};
std::shared_ptr<AppletDataBroker> caller_applet_broker{}; std::shared_ptr<AppletDataBroker> caller_applet_broker{};

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -15,12 +18,12 @@ IProcessWindingController::IProcessWindingController(Core::System& system_,
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, D<&IProcessWindingController::GetLaunchReason>, "GetLaunchReason"}, {0, D<&IProcessWindingController::GetLaunchReason>, "GetLaunchReason"},
{11, D<&IProcessWindingController::OpenCallingLibraryApplet>, "OpenCallingLibraryApplet"}, {11, D<&IProcessWindingController::OpenCallingLibraryApplet>, "OpenCallingLibraryApplet"},
{21, nullptr, "PushContext"}, {21, D<&IProcessWindingController::PushContext>, "PushContext"},
{22, nullptr, "PopContext"}, {22, D<&IProcessWindingController::PopContext>, "PopContext"},
{23, nullptr, "CancelWindingReservation"}, {23, D<&IProcessWindingController::CancelWindingReservation>, "CancelWindingReservation"},
{30, nullptr, "WindAndDoReserved"}, {30, D<&IProcessWindingController::WindAndDoReserved>, "WindAndDoReserved"},
{40, nullptr, "ReserveToStartAndWaitAndUnwindThis"}, {40, D<&IProcessWindingController::ReserveToStartAndWaitAndUnwindThis>, "ReserveToStartAndWaitAndUnwindThis"},
{41, nullptr, "ReserveToStartAndWait"}, {41, D<&IProcessWindingController::ReserveToStartAndWait>, "ReserveToStartAndWait"},
}; };
// clang-format on // clang-format on
@ -51,4 +54,43 @@ Result IProcessWindingController::OpenCallingLibraryApplet(
R_SUCCEED(); R_SUCCEED();
} }
Result IProcessWindingController::PushContext(SharedPointer<IStorage> context) {
LOG_INFO(Service_AM, "called");
m_applet->context_stack.push(context);
R_SUCCEED();
}
Result IProcessWindingController::PopContext(Out<SharedPointer<IStorage>> out_context) {
LOG_INFO(Service_AM, "called");
if (m_applet->context_stack.empty()) {
LOG_ERROR(Service_AM, "Context stack is empty");
R_THROW(ResultUnknown);
}
*out_context = m_applet->context_stack.top();
m_applet->context_stack.pop();
R_SUCCEED();
}
Result IProcessWindingController::CancelWindingReservation() {
LOG_WARNING(Service_AM, "STUBBED");
R_SUCCEED();
}
Result IProcessWindingController::WindAndDoReserved() {
LOG_WARNING(Service_AM, "STUBBED");
R_SUCCEED();
}
Result IProcessWindingController::ReserveToStartAndWaitAndUnwindThis() {
LOG_WARNING(Service_AM, "STUBBED");
R_SUCCEED();
}
Result IProcessWindingController::ReserveToStartAndWait() {
LOG_WARNING(Service_AM, "STUBBED");
R_SUCCEED();
}
} // namespace Service::AM } // namespace Service::AM

View file

@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "core/hle/service/am/service/storage.h"
#include "core/hle/service/am/am_types.h" #include "core/hle/service/am/am_types.h"
#include "core/hle/service/cmif_types.h" #include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
@ -21,6 +25,12 @@ private:
Result GetLaunchReason(Out<AppletProcessLaunchReason> out_launch_reason); Result GetLaunchReason(Out<AppletProcessLaunchReason> out_launch_reason);
Result OpenCallingLibraryApplet( Result OpenCallingLibraryApplet(
Out<SharedPointer<ILibraryAppletAccessor>> out_calling_library_applet); Out<SharedPointer<ILibraryAppletAccessor>> out_calling_library_applet);
Result PushContext(SharedPointer<IStorage> in_context);
Result PopContext(Out<SharedPointer<IStorage>> out_context);
Result CancelWindingReservation();
Result WindAndDoReserved();
Result ReserveToStartAndWaitAndUnwindThis();
Result ReserveToStartAndWait();
const std::shared_ptr<Applet> m_applet; const std::shared_ptr<Applet> m_applet;
}; };

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -67,6 +70,7 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet>
{110, nullptr, "SetApplicationAlbumUserData"}, {110, nullptr, "SetApplicationAlbumUserData"},
{120, D<&ISelfController::SaveCurrentScreenshot>, "SaveCurrentScreenshot"}, {120, D<&ISelfController::SaveCurrentScreenshot>, "SaveCurrentScreenshot"},
{130, D<&ISelfController::SetRecordVolumeMuted>, "SetRecordVolumeMuted"}, {130, D<&ISelfController::SetRecordVolumeMuted>, "SetRecordVolumeMuted"},
{230, D<&ISelfController::Unknown230>, "Unknown230"},
{1000, nullptr, "GetDebugStorageChannel"}, {1000, nullptr, "GetDebugStorageChannel"},
}; };
// clang-format on // clang-format on
@ -404,4 +408,12 @@ Result ISelfController::SetRecordVolumeMuted(bool muted) {
R_SUCCEED(); R_SUCCEED();
} }
Result ISelfController::Unknown230(u32 in_val, Out<u16> out_val) {
LOG_WARNING(Service_AM, "(STUBBED) called, in_val={}", in_val);
*out_val = 0;
R_SUCCEED();
}
} // namespace Service::AM } // namespace Service::AM

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -63,6 +66,7 @@ private:
Result SetAlbumImageTakenNotificationEnabled(bool enabled); Result SetAlbumImageTakenNotificationEnabled(bool enabled);
Result SaveCurrentScreenshot(Capture::AlbumReportOption album_report_option); Result SaveCurrentScreenshot(Capture::AlbumReportOption album_report_option);
Result SetRecordVolumeMuted(bool muted); Result SetRecordVolumeMuted(bool muted);
Result Unknown230(u32 in_val, Out<u16> out_val);
Kernel::KProcess* const m_process; Kernel::KProcess* const m_process;
const std::shared_ptr<Applet> m_applet; const std::shared_ptr<Applet> m_applet;

View file

@ -24,6 +24,7 @@
#include "../rand_int.h" #include "../rand_int.h"
#include "../unicorn_emu/a32_unicorn.h" #include "../unicorn_emu/a32_unicorn.h"
#include "./testenv.h" #include "./testenv.h"
#include "../native/testenv.h"
#include "dynarmic/common/fp/fpcr.h" #include "dynarmic/common/fp/fpcr.h"
#include "dynarmic/common/fp/fpsr.h" #include "dynarmic/common/fp/fpsr.h"
#include "dynarmic/common/llvm_disassemble.h" #include "dynarmic/common/llvm_disassemble.h"
@ -46,7 +47,7 @@ using namespace Dynarmic;
template<typename Fn> template<typename Fn>
bool AnyLocationDescriptorForTerminalHas(IR::Terminal terminal, Fn fn) { bool AnyLocationDescriptorForTerminalHas(IR::Terminal terminal, Fn fn) {
return Common::VisitVariant<bool>(terminal, [&](auto t) -> bool { return boost::apply_visitor([&](auto t) -> bool {
using T = std::decay_t<decltype(t)>; using T = std::decay_t<decltype(t)>;
if constexpr (std::is_same_v<T, IR::Term::Invalid>) { if constexpr (std::is_same_v<T, IR::Term::Invalid>) {
return false; return false;
@ -72,7 +73,7 @@ bool AnyLocationDescriptorForTerminalHas(IR::Terminal terminal, Fn fn) {
ASSERT_MSG(false, "Invalid terminal type"); ASSERT_MSG(false, "Invalid terminal type");
return false; return false;
} }
}); }, terminal);
} }
bool ShouldTestInst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst, A32::ITState it_state = {}) { bool ShouldTestInst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst, A32::ITState it_state = {}) {

View file

@ -22,6 +22,7 @@
#include "../rand_int.h" #include "../rand_int.h"
#include "../unicorn_emu/a32_unicorn.h" #include "../unicorn_emu/a32_unicorn.h"
#include "./testenv.h" #include "./testenv.h"
#include "../native/testenv.h"
#include "dynarmic/frontend/A32/FPSCR.h" #include "dynarmic/frontend/A32/FPSCR.h"
#include "dynarmic/frontend/A32/PSR.h" #include "dynarmic/frontend/A32/PSR.h"
#include "dynarmic/frontend/A32/a32_location_descriptor.h" #include "dynarmic/frontend/A32/a32_location_descriptor.h"

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 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) 2016 MerryMage * Copyright (c) 2016 MerryMage
* SPDX-License-Identifier: 0BSD * SPDX-License-Identifier: 0BSD
@ -6,6 +9,7 @@
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include "./testenv.h" #include "./testenv.h"
#include "../native/testenv.h"
#include "dynarmic/frontend/A32/a32_location_descriptor.h" #include "dynarmic/frontend/A32/a32_location_descriptor.h"
#include "dynarmic/interface/A32/a32.h" #include "dynarmic/interface/A32/a32.h"

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 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) 2022 MerryMage * Copyright (c) 2022 MerryMage
* SPDX-License-Identifier: 0BSD * SPDX-License-Identifier: 0BSD
@ -8,6 +11,7 @@
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include "./testenv.h" #include "./testenv.h"
#include "../native/testenv.h"
#include "dynarmic/frontend/A32/a32_location_descriptor.h" #include "dynarmic/frontend/A32/a32_location_descriptor.h"
#include "dynarmic/interface/A32/a32.h" #include "dynarmic/interface/A32/a32.h"
#include "dynarmic/interface/A32/coprocessor.h" #include "dynarmic/interface/A32/coprocessor.h"

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 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) 2022 MerryMage * Copyright (c) 2022 MerryMage
* SPDX-License-Identifier: 0BSD * SPDX-License-Identifier: 0BSD
@ -8,6 +11,7 @@
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include "./testenv.h" #include "./testenv.h"
#include "../native/testenv.h"
using namespace Dynarmic; using namespace Dynarmic;

View file

@ -10,6 +10,7 @@
#include "dynarmic/common/common_types.h" #include "dynarmic/common/common_types.h"
#include "./testenv.h" #include "./testenv.h"
#include "../native/testenv.h"
#include "dynarmic/interface/A32/a32.h" #include "dynarmic/interface/A32/a32.h"
static Dynarmic::A32::UserConfig GetUserConfig(ThumbTestEnv* testenv) { static Dynarmic::A32::UserConfig GetUserConfig(ThumbTestEnv* testenv) {

View file

@ -17,7 +17,6 @@
#include "dynarmic/common/assert.h" #include "dynarmic/common/assert.h"
#include "dynarmic/common/common_types.h" #include "dynarmic/common/common_types.h"
#include "dynarmic/interface/A32/a32.h" #include "dynarmic/interface/A32/a32.h"
#include "../native/testenv.h"
template<typename InstructionType_, u32 infinite_loop_u32> template<typename InstructionType_, u32 infinite_loop_u32>
class A32TestEnv : public Dynarmic::A32::UserCallbacks { class A32TestEnv : public Dynarmic::A32::UserCallbacks {

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 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
@ -7,6 +10,7 @@
#include <oaknut/oaknut.hpp> #include <oaknut/oaknut.hpp>
#include "./testenv.h" #include "./testenv.h"
#include "../native/testenv.h"
#include "dynarmic/common/fp/fpsr.h" #include "dynarmic/common/fp/fpsr.h"
#include "dynarmic/interface/exclusive_monitor.h" #include "dynarmic/interface/exclusive_monitor.h"

View file

@ -12,6 +12,7 @@
#include "dynarmic/common/common_types.h" #include "dynarmic/common/common_types.h"
#include "./testenv.h" #include "./testenv.h"
#include "../native/testenv.h"
using namespace Dynarmic; using namespace Dynarmic;

View file

@ -19,6 +19,7 @@
#include "../rand_int.h" #include "../rand_int.h"
#include "../unicorn_emu/a64_unicorn.h" #include "../unicorn_emu/a64_unicorn.h"
#include "./testenv.h" #include "./testenv.h"
#include "../native/testenv.h"
#include "dynarmic/common/fp/fpcr.h" #include "dynarmic/common/fp/fpcr.h"
#include "dynarmic/common/fp/fpsr.h" #include "dynarmic/common/fp/fpsr.h"
#include "dynarmic/common/llvm_disassemble.h" #include "dynarmic/common/llvm_disassemble.h"

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 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
@ -6,6 +9,7 @@
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include "./testenv.h" #include "./testenv.h"
#include "../native/testenv.h"
#include "dynarmic/interface/A64/a64.h" #include "dynarmic/interface/A64/a64.h"
TEST_CASE("misaligned load/store do not use page_table when detect_misaligned_access_via_page_table is set", "[a64]") { TEST_CASE("misaligned load/store do not use page_table when detect_misaligned_access_via_page_table is set", "[a64]") {

View file

@ -5,6 +5,7 @@
#include <oaknut/oaknut.hpp> #include <oaknut/oaknut.hpp>
#include "./testenv.h" #include "./testenv.h"
#include "../native/testenv.h"
#include "dynarmic/interface/A64/a64.h" #include "dynarmic/interface/A64/a64.h"
using namespace Dynarmic; using namespace Dynarmic;

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 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
@ -6,6 +9,7 @@
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include "./testenv.h" #include "./testenv.h"
#include "../native/testenv.h"
#include "dynarmic/interface/A64/a64.h" #include "dynarmic/interface/A64/a64.h"
using namespace Dynarmic; using namespace Dynarmic;

View file

@ -12,7 +12,6 @@
#include "dynarmic/common/assert.h" #include "dynarmic/common/assert.h"
#include "dynarmic/common/common_types.h" #include "dynarmic/common/common_types.h"
#include "dynarmic/interface/A64/a64.h" #include "dynarmic/interface/A64/a64.h"
#include "../native/testenv.h"
using Vector = Dynarmic::A64::Vector; using Vector = Dynarmic::A64::Vector;

View file

@ -135,6 +135,8 @@ target_include_directories(dynarmic_tests PRIVATE . ../src)
target_compile_options(dynarmic_tests PRIVATE ${DYNARMIC_CXX_FLAGS}) target_compile_options(dynarmic_tests PRIVATE ${DYNARMIC_CXX_FLAGS})
target_compile_definitions(dynarmic_tests PRIVATE FMT_USE_USER_DEFINED_LITERALS=1) target_compile_definitions(dynarmic_tests PRIVATE FMT_USE_USER_DEFINED_LITERALS=1)
target_compile_options(dynarmic_tests PRIVATE -mavx2) if ("x86_64" IN_LIST ARCHITECTURE)
target_compile_options(dynarmic_tests PRIVATE -mavx2)
endif()
add_test(dynarmic_tests dynarmic_tests --durations yes) add_test(dynarmic_tests dynarmic_tests --durations yes)

View file

@ -36,22 +36,12 @@ TEST_CASE("ASIMD Decoder: Ensure table order correctness", "[decode][a32][.]") {
const auto is_decode_error = [&get_ir](const A32::ASIMDMatcher<A32::TranslatorVisitor>& matcher, u32 instruction) { const auto is_decode_error = [&get_ir](const A32::ASIMDMatcher<A32::TranslatorVisitor>& matcher, u32 instruction) {
const auto block = get_ir(matcher, instruction); const auto block = get_ir(matcher, instruction);
return std::find_if(block.cbegin(), block.cend(), [](auto const& e) {
for (const auto& ir_inst : block) { return e.GetOpcode() == IR::Opcode::A32ExceptionRaised && A32::Exception(e.GetArg(1).GetU64()) == A32::Exception::DecodeError;
if (ir_inst.GetOpcode() == IR::Opcode::A32ExceptionRaised) { }) != block.cend();
if (static_cast<A32::Exception>(ir_inst.GetArg(1).GetU64()) == A32::Exception::DecodeError) {
return true;
}
}
}
return false;
}; };
for (auto iter = table.cbegin(); iter != table.cend(); ++iter) { for (auto iter = table.cbegin(); iter != table.cend(); ++iter) {
if (std::strncmp(iter->GetName(), "UNALLOCATED", 11) == 0) {
continue;
}
const u32 expect = iter->GetExpected(); const u32 expect = iter->GetExpected();
const u32 mask = iter->GetMask(); const u32 mask = iter->GetMask();
u32 x = 0; u32 x = 0;
@ -59,15 +49,17 @@ TEST_CASE("ASIMD Decoder: Ensure table order correctness", "[decode][a32][.]") {
const u32 instruction = expect | x; const u32 instruction = expect | x;
const bool iserr = is_decode_error(*iter, instruction); const bool iserr = is_decode_error(*iter, instruction);
const auto alternative = std::find_if(table.cbegin(), iter, [instruction](const auto& m) { return m.Matches(instruction); }); const auto alternative = std::find_if(table.cbegin(), iter, [instruction](const auto& m) {
return m.Matches(instruction);
});
const bool altiserr = is_decode_error(*alternative, instruction); const bool altiserr = is_decode_error(*alternative, instruction);
INFO("Instruction: " << std::hex << std::setfill('0') << std::setw(8) << instruction); INFO("Instruction: " << std::hex << std::setfill('0') << std::setw(8) << instruction);
INFO("Expect: " << std::hex << std::setfill('0') << std::setw(8) << expect); INFO("Expect: " << std::hex << std::setfill('0') << std::setw(8) << expect);
INFO("Fill: " << std::hex << std::setfill('0') << std::setw(8) << x); INFO("Fill: " << std::hex << std::setfill('0') << std::setw(8) << x);
INFO("Name: " << iter->GetName()); //INFO("Name: " << *A32::GetNameASIMD<A32::TranslatorVisitor>(instruction));
INFO("iserr: " << iserr); INFO("iserr: " << iserr);
INFO("alternative: " << alternative->GetName()); //INFO("alternative: " << alternative->GetName());
INFO("altiserr: " << altiserr); INFO("altiserr: " << altiserr);
REQUIRE(((!iserr && alternative == iter) || (iserr && alternative != iter && !altiserr))); REQUIRE(((!iserr && alternative == iter) || (iserr && alternative != iter && !altiserr)));
@ -75,4 +67,4 @@ TEST_CASE("ASIMD Decoder: Ensure table order correctness", "[decode][a32][.]") {
x = ((x | mask) + 1) & ~mask; x = ((x | mask) + 1) & ~mask;
} while (x != 0); } while (x != 0);
} }
} }

View file

@ -6,6 +6,7 @@
#include <immintrin.h> #include <immintrin.h>
#include "../A64/testenv.h" #include "../A64/testenv.h"
#include "../native/testenv.h"
#include "dynarmic/common/fp/fpsr.h" #include "dynarmic/common/fp/fpsr.h"
#include "dynarmic/interface/exclusive_monitor.h" #include "dynarmic/interface/exclusive_monitor.h"

View file

@ -32,27 +32,26 @@
#include "dynarmic/frontend/A64/translate/a64_translate.h" #include "dynarmic/frontend/A64/translate/a64_translate.h"
#include "dynarmic/frontend/A64/translate/impl/impl.h" #include "dynarmic/frontend/A64/translate/impl/impl.h"
#include "dynarmic/interface/A32/a32.h" #include "dynarmic/interface/A32/a32.h"
#include "dynarmic/interface/A32/config.h"
#include "dynarmic/interface/A32/disassembler.h" #include "dynarmic/interface/A32/disassembler.h"
#include "dynarmic/ir/basic_block.h" #include "dynarmic/ir/basic_block.h"
#include "dynarmic/ir/opt_passes.h" #include "dynarmic/ir/opt_passes.h"
using namespace Dynarmic; using namespace Dynarmic;
const char* GetNameOfA32Instruction(u32 instruction) { std::string_view GetNameOfA32Instruction(u32 instruction) {
if (auto vfp_decoder = A32::DecodeVFP<A32::TranslatorVisitor>(instruction)) { //if (auto const vfp_decoder = A32::DecodeVFP<A32::TranslatorVisitor>(instruction))
return vfp_decoder->get().GetName(); // return *A32::GetNameVFP<A32::TranslatorVisitor>(instruction);
} else if (auto asimd_decoder = A32::DecodeASIMD<A32::TranslatorVisitor>(instruction)) { //else if (auto const asimd_decoder = A32::DecodeASIMD<A32::TranslatorVisitor>(instruction))
return asimd_decoder->get().GetName(); // return *A32::GetNameASIMD<A32::TranslatorVisitor>(instruction);
} else if (auto decoder = A32::DecodeArm<A32::TranslatorVisitor>(instruction)) { //else if (auto const decoder = A32::DecodeArm<A32::TranslatorVisitor>(instruction))
return decoder->get().GetName(); // return *A32::GetNameARM<A32::TranslatorVisitor>(instruction);
}
return "<null>"; return "<null>";
} }
const char* GetNameOfA64Instruction(u32 instruction) { std::string_view GetNameOfA64Instruction(u32 instruction) {
if (auto decoder = A64::Decode<A64::TranslatorVisitor>(instruction)) { //if (auto const decoder = A64::Decode<A64::TranslatorVisitor>(instruction))
return decoder->get().GetName(); // return *A64::GetName<A64::TranslatorVisitor>(instruction);
}
return "<null>"; return "<null>";
} }
@ -66,7 +65,7 @@ void PrintA32Instruction(u32 instruction) {
fmt::print("should_continue: {}\n\n", should_continue); fmt::print("should_continue: {}\n\n", should_continue);
fmt::print("IR:\n"); fmt::print("IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block)); fmt::print("{}\n", IR::DumpBlock(ir_block));
Optimization::Optimize(ir_block, conf, {}); Optimization::Optimize(ir_block, A32::UserConfig{}, {});
fmt::print("Optimized IR:\n"); fmt::print("Optimized IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block)); fmt::print("{}\n", IR::DumpBlock(ir_block));
} }
@ -81,7 +80,7 @@ void PrintA64Instruction(u32 instruction) {
fmt::print("should_continue: {}\n\n", should_continue); fmt::print("should_continue: {}\n\n", should_continue);
fmt::print("IR:\n"); fmt::print("IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block)); fmt::print("{}\n", IR::DumpBlock(ir_block));
Optimization::Optimize(ir_block, conf, {}); Optimization::Optimize(ir_block, A64::UserConfig{}, {});
fmt::print("Optimized IR:\n"); fmt::print("Optimized IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block)); fmt::print("{}\n", IR::DumpBlock(ir_block));
} }
@ -99,7 +98,7 @@ void PrintThumbInstruction(u32 instruction) {
fmt::print("should_continue: {}\n\n", should_continue); fmt::print("should_continue: {}\n\n", should_continue);
fmt::print("IR:\n"); fmt::print("IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block)); fmt::print("{}\n", IR::DumpBlock(ir_block));
Optimization::Optimize(ir_block, conf, {}); Optimization::Optimize(ir_block, A32::UserConfig{}, {});
fmt::print("Optimized IR:\n"); fmt::print("Optimized IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block)); fmt::print("{}\n", IR::DumpBlock(ir_block));
} }

View file

@ -62,22 +62,20 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
Settings, Settings,
use_multi_core, use_multi_core,
tr("Multicore CPU Emulation"), tr("Multicore CPU Emulation"),
tr("This option increases CPU emulation thread use from 1 to the Switchs maximum of 4.\n" tr("This option increases CPU emulation thread use from 1 to the maximum of 4.\n"
"This is mainly a debug option and shouldnt be disabled.")); "This is mainly a debug option and shouldn't be disabled."));
INSERT( INSERT(
Settings, Settings,
memory_layout_mode, memory_layout_mode,
tr("Memory Layout"), tr("Memory Layout"),
tr("Increases the amount of emulated RAM from the stock 4GB of the retail Switch to the " tr("Increases the amount of emulated RAM from 4GB of the board to the "
"developer kit's 8/6GB.\nIts doesnt improve stability or performance and is intended " "devkit 8/6GB.\nDoesn't affect performance/stability but may allow HD texture "
"to let big texture mods fit in emulated RAM.\nEnabling it will increase memory " "mods to load."));
"use. It is not recommended to enable unless a specific game with a texture mod needs "
"it."));
INSERT(Settings, use_speed_limit, QString(), QString()); INSERT(Settings, use_speed_limit, QString(), QString());
INSERT(Settings, INSERT(Settings,
speed_limit, speed_limit,
tr("Limit Speed Percent"), tr("Limit Speed Percent"),
tr("Controls the game's maximum rendering speed, but its up to each game if it runs " tr("Controls the game's maximum rendering speed, but it's up to each game if it runs "
"faster or not.\n200% for a 30 FPS game is 60 FPS, and for a " "faster or not.\n200% for a 30 FPS game is 60 FPS, and for a "
"60 FPS game it will be 120 FPS.\nDisabling it means unlocking the framerate to the " "60 FPS game it will be 120 FPS.\nDisabling it means unlocking the framerate to the "
"maximum your PC can reach.")); "maximum your PC can reach."));
@ -86,15 +84,13 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
tr("Synchronize Core Speed"), tr("Synchronize Core Speed"),
tr("Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS " tr("Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS "
"without affecting game speed (animations, physics, etc.).\n" "without affecting game speed (animations, physics, etc.).\n"
"Compatibility varies by game; many (especially older ones) may not respond well.\n"
"Can help reduce stuttering at lower framerates.")); "Can help reduce stuttering at lower framerates."));
// Cpu // Cpu
INSERT(Settings, INSERT(Settings,
cpu_accuracy, cpu_accuracy,
tr("Accuracy:"), tr("Accuracy:"),
tr("This setting controls the accuracy of the emulated CPU.\nDon't change this unless " tr("Change the accuracy of the emulated CPU (for debugging only)."));
"you know what you are doing."));
INSERT(Settings, cpu_backend, tr("Backend:"), QString()); INSERT(Settings, cpu_backend, tr("Backend:"), QString());
INSERT(Settings, use_fast_cpu_time, QString(), QString()); INSERT(Settings, use_fast_cpu_time, QString(), QString());
@ -110,7 +106,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
cpu_ticks, cpu_ticks,
tr("Custom CPU Ticks"), tr("Custom CPU Ticks"),
tr("Set a custom value of CPU ticks. Higher values can increase performance, but may " tr("Set a custom value of CPU ticks. Higher values can increase performance, but may "
"also cause the game to freeze. A range of 7721000 is recommended.")); "cause deadlocks. A range of 77-21000 is recommended."));
INSERT(Settings, cpu_backend, tr("Backend:"), QString()); INSERT(Settings, cpu_backend, tr("Backend:"), QString());
// Cpu Debug // Cpu Debug
@ -144,8 +140,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
cpuopt_unsafe_fastmem_check, cpuopt_unsafe_fastmem_check,
tr("Disable address space checks"), tr("Disable address space checks"),
tr("This option improves speed by eliminating a safety check before every memory " tr("This option improves speed by eliminating a safety check before every memory "
"read/write in guest.\nDisabling it may allow a game to read/write the emulator's " "operation.\nDisabling it may allow arbitrary code execution."));
"memory."));
INSERT( INSERT(
Settings, Settings,
cpuopt_unsafe_ignore_global_monitor, cpuopt_unsafe_ignore_global_monitor,
@ -159,36 +154,31 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
Settings, Settings,
renderer_backend, renderer_backend,
tr("API:"), tr("API:"),
tr("Switches between the available graphics APIs.\nVulkan is recommended in most cases.")); tr("Changes the output graphics API.\nVulkan is recommended."));
INSERT(Settings, INSERT(Settings,
vulkan_device, vulkan_device,
tr("Device:"), tr("Device:"),
tr("This setting selects the GPU to use with the Vulkan backend.")); tr("This setting selects the GPU to use (Vulkan only)."));
INSERT(Settings, INSERT(Settings,
shader_backend, shader_backend,
tr("Shader Backend:"), tr("Shader Backend:"),
tr("The shader backend to use for the OpenGL renderer.\nGLSL is the fastest in " tr("The shader backend to use with OpenGL.\nGLSL is recommended."));
"performance and the best in rendering accuracy.\n"
"GLASM is a deprecated NVIDIA-only backend that offers much better shader building "
"performance at the cost of FPS and rendering accuracy.\n"
"SPIR-V compiles the fastest, but yields poor results on most GPU drivers."));
INSERT(Settings, INSERT(Settings,
resolution_setup, resolution_setup,
tr("Resolution:"), tr("Resolution:"),
tr("Forces the game to render at a different resolution.\nHigher resolutions require " tr("Forces to render at a different resolution.\n"
"much more VRAM and bandwidth.\n" "Higher resolutions require more VRAM and bandwidth.\n"
"Options lower than 1X can cause rendering issues.")); "Options lower than 1X can cause artifacts."));
INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QString()); INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QString());
INSERT(Settings, INSERT(Settings,
fsr_sharpening_slider, fsr_sharpening_slider,
tr("FSR Sharpness:"), tr("FSR Sharpness:"),
tr("Determines how sharpened the image will look while using FSRs dynamic contrast.")); tr("Determines how sharpened the image will look using FSR's dynamic contrast."));
INSERT(Settings, INSERT(Settings,
anti_aliasing, anti_aliasing,
tr("Anti-Aliasing Method:"), tr("Anti-Aliasing Method:"),
tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA has a " tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA "
"lower performance impact and can produce a better and more stable picture under " "can produce a more stable picture in lower resolutions."));
"very low resolutions."));
INSERT(Settings, INSERT(Settings,
fullscreen_mode, fullscreen_mode,
tr("Fullscreen Mode:"), tr("Fullscreen Mode:"),
@ -199,18 +189,17 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
INSERT(Settings, INSERT(Settings,
aspect_ratio, aspect_ratio,
tr("Aspect Ratio:"), tr("Aspect Ratio:"),
tr("Stretches the game to fit the specified aspect ratio.\nSwitch games only support " tr("Stretches the renderer to fit the specified aspect ratio.\nMost games only support "
"16:9, so custom game mods are required to get other ratios.\nAlso controls the " "16:9, so modifications are required to get other ratios.\nAlso controls the "
"aspect ratio of captured screenshots.")); "aspect ratio of captured screenshots."));
INSERT(Settings, INSERT(Settings,
use_disk_shader_cache, use_disk_shader_cache,
tr("Use disk pipeline cache"), tr("Use persistent pipeline cache"),
tr("Allows saving shaders to storage for faster loading on following game " tr("Allows saving shaders to storage for faster loading on following game "
"boots.\nDisabling " "boots.\nDisabling it is only intended for debugging."));
"it is only intended for debugging."));
INSERT(Settings, INSERT(Settings,
optimize_spirv_output, optimize_spirv_output,
tr("Optimize SPIRV output shader"), tr("Optimize SPIRV output"),
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."));
@ -229,37 +218,35 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
accelerate_astc, accelerate_astc,
tr("ASTC Decoding Method:"), tr("ASTC Decoding Method:"),
tr("This option controls how ASTC textures should be decoded.\n" tr("This option controls how ASTC textures should be decoded.\n"
"CPU: Use the CPU for decoding, slowest but safest method.\n" "CPU: Use the CPU for decoding.\n"
"GPU: Use the GPU's compute shaders to decode ASTC textures, recommended for most " "GPU: Use the GPU's compute shaders to decode ASTC textures (recommended).\n"
"games and users.\n" "CPU Asynchronously: Use the CPU to decode ASTC textures on demand. Eliminates"
"CPU Asynchronously: Use the CPU to decode ASTC textures as they arrive. Completely " "ASTC decoding\nstuttering but may present artifacts."));
"eliminates ASTC decoding\nstuttering at the cost of rendering issues while the "
"texture is being decoded."));
INSERT( INSERT(
Settings, Settings,
astc_recompression, astc_recompression,
tr("ASTC Recompression Method:"), tr("ASTC Recompression Method:"),
tr("Almost all desktop and laptop dedicated GPUs lack support for ASTC textures, forcing " tr("Most GPUs lack support for ASTC textures and must decompress to an"
"the emulator to decompress to an intermediate format any card supports, RGBA8.\n" "intermediate format: RGBA8.\n"
"This option recompresses RGBA8 to either the BC1 or BC3 format, saving VRAM but " "BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format,\n"
"negatively affecting image quality.")); " saving VRAM but degrading image quality."));
INSERT(Settings, INSERT(Settings,
vram_usage_mode, vram_usage_mode,
tr("VRAM Usage Mode:"), tr("VRAM Usage Mode:"),
tr("Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance.\nAggressive mode may severely impact the performance of other applications such as recording software.")); tr("Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance.\nAggressive mode may impact performance of other applications such as recording software."));
INSERT(Settings, INSERT(Settings,
skip_cpu_inner_invalidation, skip_cpu_inner_invalidation,
tr("Skip CPU Inner Invalidation"), tr("Skip CPU Inner Invalidation"),
tr("Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and " tr("Skips certain cache invalidations during memory updates, reducing CPU usage and "
"improving it's performance. This may cause glitches or crashes on some games.")); "improving latency. This may cause soft-crashes."));
INSERT( INSERT(
Settings, Settings,
vsync_mode, vsync_mode,
tr("VSync Mode:"), tr("VSync Mode:"),
tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen " tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
"refresh rate.\nFIFO Relaxed is similar to FIFO but allows tearing as it recovers from " "refresh rate.\nFIFO Relaxed allows tearing as it recovers from a slow down.\n"
"a slow down.\nMailbox can have lower latency than FIFO and does not tear but may drop " "Mailbox can have lower latency than FIFO and does not tear but may drop "
"frames.\nImmediate (no synchronization) just presents whatever is available and can " "frames.\nImmediate (no synchronization) presents whatever is available and can "
"exhibit tearing.")); "exhibit tearing."));
INSERT(Settings, bg_red, QString(), QString()); INSERT(Settings, bg_red, QString(), QString());
INSERT(Settings, bg_green, QString(), QString()); INSERT(Settings, bg_green, QString(), QString());
@ -267,7 +254,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
// Renderer (Advanced Graphics) // Renderer (Advanced Graphics)
INSERT(Settings, sync_memory_operations, tr("Sync Memory Operations"), INSERT(Settings, sync_memory_operations, tr("Sync Memory Operations"),
tr("Ensures data consistency between compute and memory operations.\nThis option should fix issues in some games, but may also reduce performance in some cases.\nUnreal Engine 4 games often see the most significant changes thereof.")); tr("Ensures data consistency between compute and memory operations.\nThis option fixes issues in games, but may degrade performance.\nUnreal Engine 4 games often see the most significant changes thereof."));
INSERT(Settings, INSERT(Settings,
async_presentation, async_presentation,
tr("Enable asynchronous presentation (Vulkan only)"), tr("Enable asynchronous presentation (Vulkan only)"),
@ -281,8 +268,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
INSERT(Settings, INSERT(Settings,
max_anisotropy, max_anisotropy,
tr("Anisotropic Filtering:"), tr("Anisotropic Filtering:"),
tr("Controls the quality of texture rendering at oblique angles.\nIts a light setting " tr("Controls the quality of texture rendering at oblique angles.\nSafe to set at 16x on most GPUs."));
"and safe to set at 16x on most GPUs."));
INSERT(Settings, INSERT(Settings,
gpu_accuracy, gpu_accuracy,
tr("GPU Accuracy:"), tr("GPU Accuracy:"),
@ -292,11 +278,11 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
INSERT(Settings, INSERT(Settings,
dma_accuracy, dma_accuracy,
tr("DMA Accuracy:"), tr("DMA Accuracy:"),
tr("Controls the DMA precision accuracy. Safe precision can fix issues in some games, but it can also impact performance in some cases.\nIf unsure, leave this on Default.")); tr("Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance."));
INSERT(Settings, INSERT(Settings,
use_asynchronous_shaders, use_asynchronous_shaders,
tr("Use asynchronous shader building (Hack)"), tr("Enable asynchronous shader compilation (Hack)"),
tr("Enables asynchronous shader compilation, which may reduce shader stutter.")); tr("May reduce shader stutter."));
INSERT(Settings, use_fast_gpu_time, QString(), QString()); INSERT(Settings, use_fast_gpu_time, QString(), QString());
INSERT(Settings, INSERT(Settings,
fast_gpu_time, fast_gpu_time,
@ -314,8 +300,8 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
Settings, Settings,
enable_compute_pipelines, enable_compute_pipelines,
tr("Enable Compute Pipelines (Intel Vulkan Only)"), tr("Enable Compute Pipelines (Intel Vulkan Only)"),
tr("Enable compute pipelines, required by some games.\nThis setting only exists for Intel " tr("Required by some games.\nThis setting only exists for Intel "
"proprietary drivers, and may crash if enabled.\nCompute pipelines are always enabled " "proprietary drivers and may crash if enabled.\nCompute pipelines are always enabled "
"on all other drivers.")); "on all other drivers."));
INSERT( INSERT(
Settings, Settings,
@ -343,12 +329,12 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
INSERT(Settings, INSERT(Settings,
dyna_state, dyna_state,
tr("Extended Dynamic State"), tr("Extended Dynamic State"),
tr("Controls the number of features that can be used in Extended Dynamic State.\nHigher numbers allow for more features and can increase performance, but may cause issues with some drivers and vendors.\nThe default value may vary depending on your system and hardware capabilities.\nThis value can be changed until stability and a better visual quality are achieved.")); tr("Controls the number of features that can be used in Extended Dynamic State.\nHigher numbers allow for more features and can increase performance, but may cause issues.\nThe default value is per-system."));
INSERT(Settings, INSERT(Settings,
provoking_vertex, provoking_vertex,
tr("Provoking Vertex"), tr("Provoking Vertex"),
tr("Improves lighting and vertex handling in certain games.\n" tr("Improves lighting and vertex handling in some games.\n"
"Only Vulkan 1.0+ devices support this extension.")); "Only Vulkan 1.0+ devices support this extension."));
INSERT(Settings, INSERT(Settings,
@ -363,8 +349,8 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
sample_shading_fraction, sample_shading_fraction,
tr("Sample Shading"), tr("Sample Shading"),
tr("Allows the fragment shader to execute per sample in a multi-sampled fragment " tr("Allows the fragment shader to execute per sample in a multi-sampled fragment "
"instead once per fragment. Improves graphics quality at the cost of some performance.\n" "instead of once per fragment. Improves graphics quality at the cost of performance.\n"
"Higher values improve quality more but also reduce performance to a greater extent.")); "Higher values improve quality but degrade performance."));
// Renderer (Debug) // Renderer (Debug)
@ -372,31 +358,30 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
INSERT(Settings, INSERT(Settings,
rng_seed, rng_seed,
tr("RNG Seed"), tr("RNG Seed"),
tr("Controls the seed of the random number generator.\nMainly used for speedrunning " tr("Controls the seed of the random number generator.\nMainly used for speedrunning."));
"purposes."));
INSERT(Settings, rng_seed_enabled, QString(), QString()); INSERT(Settings, rng_seed_enabled, QString(), QString());
INSERT(Settings, device_name, tr("Device Name"), tr("The name of the emulated Switch.")); INSERT(Settings, device_name, tr("Device Name"), tr("The name of the console."));
INSERT(Settings, INSERT(Settings,
custom_rtc, custom_rtc,
tr("Custom RTC Date:"), tr("Custom RTC Date:"),
tr("This option allows to change the emulated clock of the Switch.\n" tr("This option allows to change the clock of the console.\n"
"Can be used to manipulate time in games.")); "Can be used to manipulate time in games."));
INSERT(Settings, custom_rtc_enabled, QString(), QString()); INSERT(Settings, custom_rtc_enabled, QString(), QString());
INSERT(Settings, INSERT(Settings,
custom_rtc_offset, custom_rtc_offset,
QStringLiteral(" "), QStringLiteral(" "),
QStringLiteral("The number of seconds from the current unix time")); tr("The number of seconds from the current unix time"));
INSERT(Settings, INSERT(Settings,
language_index, language_index,
tr("Language:"), tr("Language:"),
tr("Note: this can be overridden when region setting is auto-select")); tr("This option can be overridden when region setting is auto-select"));
INSERT(Settings, region_index, tr("Region:"), tr("The region of the emulated Switch.")); INSERT(Settings, region_index, tr("Region:"), tr("The region of the console."));
INSERT(Settings, time_zone_index, tr("Time Zone:"), tr("The time zone of the emulated Switch.")); INSERT(Settings, time_zone_index, tr("Time Zone:"), tr("The time zone of the console."));
INSERT(Settings, sound_index, tr("Sound Output Mode:"), QString()); INSERT(Settings, sound_index, tr("Sound Output Mode:"), QString());
INSERT(Settings, INSERT(Settings,
use_docked_mode, use_docked_mode,
tr("Console Mode:"), tr("Console Mode:"),
tr("Selects if the console is emulated in Docked or Handheld mode.\nGames will change " tr("Selects if the console is in Docked or Handheld mode.\nGames will change "
"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());
@ -418,27 +403,26 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
// Ui General // Ui General
INSERT(UISettings, INSERT(UISettings,
select_user_on_boot, select_user_on_boot,
tr("Prompt for user on game boot"), tr("Prompt for user profile on boot"),
tr("Ask to select a user profile on each boot, useful if multiple people use Eden on " tr("Useful if multiple people use the same PC."));
"the same PC."));
INSERT(UISettings, INSERT(UISettings,
pause_when_in_background, pause_when_in_background,
tr("Pause emulation when in background"), tr("Pause when not in focus"),
tr("This setting pauses Eden when focusing other windows.")); tr("Pauses emulation when focusing on other windows."));
INSERT(UISettings, INSERT(UISettings,
confirm_before_stopping, confirm_before_stopping,
tr("Confirm before stopping emulation"), tr("Confirm before stopping emulation"),
tr("This setting overrides game prompts asking to confirm stopping the game.\nEnabling " tr("Overrides prompts asking to confirm stopping the emulation.\nEnabling "
"it bypasses such prompts and directly exits the emulation.")); "it bypasses such prompts and directly exits the emulation."));
INSERT(UISettings, INSERT(UISettings,
hide_mouse, hide_mouse,
tr("Hide mouse on inactivity"), tr("Hide mouse on inactivity"),
tr("This setting hides the mouse after 2.5s of inactivity.")); tr("Hides the mouse after 2.5s of inactivity."));
INSERT(UISettings, INSERT(UISettings,
controller_applet_disabled, controller_applet_disabled,
tr("Disable controller applet"), tr("Disable controller applet"),
tr("Forcibly disables the use of the controller applet by guests.\nWhen a guest " tr("Forcibly disables the use of the controller applet in emulated programs.\n"
"attempts to open the controller applet, it is immediately closed.")); "When a program attempts to open the controller applet, it is immediately closed."));
INSERT(UISettings, INSERT(UISettings,
check_for_updates, check_for_updates,
tr("Check for updates"), tr("Check for updates"),

View file

@ -399,10 +399,6 @@ if (YUZU_USE_PRECOMPILED_HEADERS)
target_precompile_headers(video_core PRIVATE precompiled_headers.h) target_precompile_headers(video_core PRIVATE precompiled_headers.h)
endif() endif()
if (YUZU_ENABLE_LTO)
set_property(TARGET video_core PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
if (ANDROID AND ARCHITECTURE_arm64) if (ANDROID AND ARCHITECTURE_arm64)
target_link_libraries(video_core PRIVATE adrenotools) target_link_libraries(video_core PRIVATE adrenotools)
endif() endif()

View file

@ -785,18 +785,13 @@ void BufferCache<P>::BindHostGraphicsUniformBuffers(size_t stage) {
} }
template <class P> template <class P>
void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index, void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index, bool needs_bind) {
bool needs_bind) { ++channel_state->uniform_cache_shots[0];
const Binding& binding = channel_state->uniform_buffers[stage][index]; const Binding& binding = channel_state->uniform_buffers[stage][index];
const DAddr device_addr = binding.device_addr; const DAddr device_addr = binding.device_addr;
const u32 size = (std::min)(binding.size, (*channel_state->uniform_buffer_sizes)[stage][index]); const u32 size = (std::min)(binding.size, (*channel_state->uniform_buffer_sizes)[stage][index]);
Buffer& buffer = slot_buffers[binding.buffer_id]; Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id); TouchBuffer(buffer, binding.buffer_id);
const bool sync_buffer = SynchronizeBuffer(buffer, device_addr, size);
if (sync_buffer) {
++channel_state->uniform_cache_hits[0];
}
++channel_state->uniform_cache_shots[0];
const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID && const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID &&
size <= channel_state->uniform_buffer_skip_cache_size && size <= channel_state->uniform_buffer_skip_cache_size &&
!memory_tracker.IsRegionGpuModified(device_addr, size); !memory_tracker.IsRegionGpuModified(device_addr, size);
@ -827,7 +822,10 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
device_memory.ReadBlockUnsafe(device_addr, span.data(), size); device_memory.ReadBlockUnsafe(device_addr, span.data(), size);
return; return;
} }
// Classic cached path
if (SynchronizeBuffer(buffer, device_addr, size)) {
++channel_state->uniform_cache_hits[0];
}
// Skip binding if it's not needed and if the bound buffer is not the fast version // Skip binding if it's not needed and if the bound buffer is not the fast version
// This exists to avoid instances where the fast buffer is bound and a GPU write happens // This exists to avoid instances where the fast buffer is bound and a GPU write happens
needs_bind |= HasFastUniformBufferBound(stage, binding_index); needs_bind |= HasFastUniformBufferBound(stage, binding_index);

View file

@ -45,9 +45,31 @@ SPDX-License-Identifier: GPL-2.0-or-later
<true/> <true/>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string></string> <string></string>
<!-- Fixed -->
<key>LSApplicationCategoryType</key>
<string>public.app-category.games</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>nsp</string>
<string>xci</string>
<string>nro</string>
</array>
<key>CFBundleTypeName</key>
<string>Switch File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
</array>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
<string>NSApplication</string> <string>NSApplication</string>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>
<string>True</string> <string>True</string>
<key>UIDesignRequiresCompatibility</key>
<true/> <!-- https://bugreports.qt.io/browse/QTBUG-138942 -->
</dict> </dict>
</plist> </plist>

View file

@ -270,10 +270,8 @@ void ConfigureAudio::UpdateAudioDevices(int sink_index) {
void ConfigureAudio::InitializeAudioSinkComboBox() { void ConfigureAudio::InitializeAudioSinkComboBox() {
sink_combo_box->clear(); sink_combo_box->clear();
sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
for (const auto& id : AudioCore::Sink::GetSinkIDs())
for (const auto& id : AudioCore::Sink::GetSinkIDs()) { sink_combo_box->addItem(QString::fromStdString(std::string{Settings::CanonicalizeEnum(id)}));
sink_combo_box->addItem(QString::fromStdString(Settings::CanonicalizeEnum(id)));
}
} }
void ConfigureAudio::RetranslateUI() { void ConfigureAudio::RetranslateUI() {

View file

@ -373,13 +373,13 @@ bool ConfigureProfileManager::LoadAvatarData() {
const auto romfs = nca->GetRomFS(); const auto romfs = nca->GetRomFS();
if (!romfs) { if (!romfs) {
QMessageBox::warning(this, tr("Error loading archive"), QMessageBox::warning(this, tr("Error loading archive"),
tr("Archive does not contain romfs. It is probably corrupt.")); tr("Could not locate RomFS. Your file or decryption keys may be corrupted."));
return false; return false;
} }
const auto extracted = FileSys::ExtractRomFS(romfs); const auto extracted = FileSys::ExtractRomFS(romfs);
if (!extracted) { if (!extracted) {
QMessageBox::warning(this, tr("Error extracting archive"), QMessageBox::warning(this, tr("Error extracting archive"),
tr("Archive could not be extracted. It is probably corrupt.")); tr("Could not extract RomFS. Your file or decryption keys may be corrupted."));
return false; return false;
} }
const auto chara_dir = extracted->GetSubdirectory("chara"); const auto chara_dir = extracted->GetSubdirectory("chara");

View file

@ -13,6 +13,7 @@
#include <QString> #include <QString>
#include <QStringLiteral> #include <QStringLiteral>
#include <QWidget> #include <QWidget>
#include <QObject>
#include <qobjectdefs.h> #include <qobjectdefs.h>
#include "qt_common/shared_translation.h" #include "qt_common/shared_translation.h"

View file

@ -58,11 +58,11 @@ class GameList : public QWidget {
public: public:
enum { enum {
COLUMN_NAME, COLUMN_NAME,
COLUMN_COMPATIBILITY,
COLUMN_ADD_ONS,
COLUMN_FILE_TYPE, COLUMN_FILE_TYPE,
COLUMN_SIZE, COLUMN_SIZE,
COLUMN_PLAY_TIME, COLUMN_PLAY_TIME,
COLUMN_ADD_ONS,
COLUMN_COMPATIBILITY,
COLUMN_COUNT, // Number of columns COLUMN_COUNT, // Number of columns
}; };

View file

@ -204,36 +204,24 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path,
const PlayTime::PlayTimeManager& play_time_manager, const PlayTime::PlayTimeManager& play_time_manager,
const FileSys::PatchManager& patch) const FileSys::PatchManager& patch)
{ {
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); auto const it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
// The game list uses 99 as compatibility number for untested games
QString compatibility = it != compatibility_list.end() ? it->second.first : QStringLiteral("99");
// The game list uses this as compatibility number for untested games auto const file_type = loader.GetFileType();
QString compatibility{QStringLiteral("99")}; auto const file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type));
if (it != compatibility_list.end()) {
compatibility = it->second.first;
}
const auto file_type = loader.GetFileType(); QString patch_versions = GetGameListCachedObject(fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] {
const auto file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type)); return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable());
});
QList<QStandardItem*> list{ return QList<QStandardItem*>{
new GameListItemPath(FormatGameName(path), icon, QString::fromStdString(name), new GameListItemPath(FormatGameName(path), icon, QString::fromStdString(name), file_type_string, program_id),
file_type_string, program_id),
new GameListItemCompat(compatibility),
new GameListItem(file_type_string), new GameListItem(file_type_string),
new GameListItemSize(size), new GameListItemSize(size),
new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)), new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
new GameListItem(patch_versions),
new GameListItemCompat(compatibility),
}; };
QString patch_versions;
patch_versions = GetGameListCachedObject(
fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] {
return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable());
});
list.insert(2, new GameListItem(patch_versions));
return list;
} }
} // Anonymous namespace } // Anonymous namespace