mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-21 07:18:33 +00:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4bf30b41a6 | |||
| 6390edb76d | |||
| f0ba93d75f | |||
| 68ff82679f | |||
| 543d4dfc8e | |||
| e3be392b8a | |||
| b754fcc49c | |||
| a4c37b5117 | |||
| 8206c7abac | |||
| faacc62b10 | |||
| b0a01ca629 | |||
| 945d5c373b | |||
| cb04b133ea | |||
| ff3de66bab | |||
| 3ce788b09c | |||
| 2a1b9278b9 | |||
| 842b5e0b3a | |||
| 2f83399798 | |||
| 2e52ca5753 | |||
| 69cd5ee0d1 | |||
| 1f0605c35d | |||
| b9c28ef6a1 | |||
| 809a61f600 | |||
| c7b24bad1a | |||
| 4244a6418d | |||
| 26044e18e1 | |||
| 04d08f0cf1 | |||
| 2bf037c7bb | |||
| f6668ef01b | |||
| 20a775ed08 | |||
| 3379556d89 | |||
| 8e687924b8 | |||
| d3e8c5e977 | |||
| 95e67e6533 | |||
| bdd1785b04 | |||
| f9e5ac191e | |||
| f3a674c064 | |||
| 4aa35bc835 | |||
| 894f2544a0 | |||
| ceffda663c | |||
| 27e7fa10d9 | |||
| d3bd4f42cf | |||
| 5a8fb951dd | |||
| 27942142cf | |||
| db647f9a40 | |||
| 35aeb0cf55 | |||
| 3423d3c6a5 | |||
| 607bc18ad4 | |||
| 2000fdfb7b | |||
| dc95cd09ee | |||
| e7a931d06b | |||
| 8648c27cbb | |||
| def76946f0 | |||
| 13d39f39aa | |||
| 7731b5bc86 | |||
| c4ab2f6b9e | |||
| dd12266c26 | |||
| a3d32a18b8 | |||
| c0ffc900cd | |||
| 3f52bf4b4b | |||
| 5ec94b1971 | |||
| 627770cd3a | |||
| a43664c0fd | |||
| a0f1cd1baf | |||
| 49a0ca6d5d | |||
| 5e1d5e82dc | |||
| ba9130fbf9 | |||
| 7d5f390ffb | |||
| 8fe1e6efa2 | |||
| ee197e6222 |
@@ -1,9 +1,9 @@
|
||||
name: tx-pull
|
||||
|
||||
on:
|
||||
# tuesday, saturday at 2pm
|
||||
# monday at 4pm
|
||||
schedule:
|
||||
- cron: '0 14 * * 2,6'
|
||||
- cron: '0 16 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -3,7 +3,7 @@ description: File a bug report
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu.
|
||||
value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with Eden.
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
@@ -43,7 +43,7 @@ body:
|
||||
id: log
|
||||
attributes:
|
||||
label: Log File
|
||||
description: A log file will help our developers to better diagnose and fix the issue. Instructions can be found [here](https://yuzu-emu.org/help/reference/log-files).
|
||||
description: A log file will help our developers to better diagnose and fix the issue. Instructions can be found [here](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md).
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
@@ -4,7 +4,7 @@ labels: "request"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Tech support does not belong here. You should only file an issue here if you are requesting a feature you believe would make yuzu better.
|
||||
value: Tech support does not belong here. You should only file an issue here if you are requesting a feature you believe would make Eden better.
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
@@ -23,6 +23,6 @@ body:
|
||||
id: why-feature
|
||||
attributes:
|
||||
label: Why would this feature be useful?
|
||||
description: A brief description of why this feature would make yuzu better.
|
||||
description: A brief description of why this feature would make Eden better.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
+35
-17
@@ -28,23 +28,23 @@ if (NOT DEFINED ARCHITECTURE)
|
||||
endif()
|
||||
|
||||
# Needed for FFmpeg w/ VAAPI and DRM
|
||||
if (PLATFORM_OPENBSD)
|
||||
if (OPENBSD)
|
||||
# OpenBSD 7.8 broke libcxx when upgrading, so we must define the PSTL backend manually
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include -D_LIBCPP_PSTL_BACKEND_SERIAL=1")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include -D_LIBCPP_PSTL_BACKEND_SERIAL=1")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R6/lib")
|
||||
elseif (PLATFORM_NETBSD)
|
||||
elseif (NETBSD)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include -I${CMAKE_SYSROOT}/usr/pkg/include/c++/v1")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include -I${CMAKE_SYSROOT}/usr/pkg/include/c++/v1")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R7/lib")
|
||||
endif()
|
||||
|
||||
# NetBSD: Fun for the whole family!
|
||||
if (PLATFORM_NETBSD)
|
||||
if (NETBSD)
|
||||
set(ENV{PKG_CONFIG_PATH} "${PKG_CONFIG_PATH}:${CMAKE_SYSROOT}/usr/pkg/lib/ffmpeg7/pkgconfig")
|
||||
endif()
|
||||
|
||||
cmake_dependent_option(YUZU_STATIC_ROOM "Build a static room executable only (CI only)" OFF "PLATFORM_LINUX" OFF)
|
||||
cmake_dependent_option(YUZU_STATIC_ROOM "Build a static room executable only (CI only)" OFF "LINUX" OFF)
|
||||
if (YUZU_STATIC_ROOM)
|
||||
set(YUZU_ROOM ON)
|
||||
set(YUZU_ROOM_STANDALONE ON)
|
||||
@@ -79,6 +79,10 @@ set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundl
|
||||
cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF)
|
||||
|
||||
option(ENABLE_DEBUG_TOOLS "Enable debugging tools (maxwell disassembler, SPIRV translator, etc)" OFF)
|
||||
option(ENABLE_WERROR "Enable -Werror diagnostics" ON)
|
||||
|
||||
# Lossless Scaling frame generation. Only Android.
|
||||
cmake_dependent_option(ENABLE_LSFG "Enable Lossless Scaling frame generation" ON "ANDROID" OFF)
|
||||
|
||||
# non-linux bundled qt are static
|
||||
if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX))
|
||||
@@ -157,6 +161,19 @@ if (CXX_CLANG_CL)
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-reserved-identifier>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-deprecated-declarations>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-cast-function-type-mismatch>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c99-extensions>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++17-compat>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++11-compat-reserved-user-defined-literal>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++11-compat-deprecated-writable-strings>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++11-compat-pedantic>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++11-compat>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++0x-compat>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++98-c++11-compat-binary-literal>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++98-compat-pedantic>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++98-compat>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c99-compat>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c98-compat>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c99-extensions>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:/EHsc>)
|
||||
# REQUIRED CPU features IN Windows-amd64
|
||||
if (ARCHITECTURE_x86_64)
|
||||
@@ -210,7 +227,7 @@ endif()
|
||||
|
||||
# ffmpeg
|
||||
option(YUZU_USE_BUNDLED_FFMPEG "Download bundled FFmpeg" ${EXT_DEFAULT})
|
||||
cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external source" "${PLATFORM_SUN}" "NOT WIN32 AND NOT ANDROID" OFF)
|
||||
cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external source" "${SOLARIS}" "NOT WIN32 AND NOT ANDROID" OFF)
|
||||
|
||||
# sirit
|
||||
set(BUNDLED_SIRIT_DEFAULT OFF)
|
||||
@@ -221,7 +238,7 @@ endif()
|
||||
option(YUZU_USE_BUNDLED_SIRIT "Download bundled sirit" ${BUNDLED_SIRIT_DEFAULT})
|
||||
|
||||
# FreeBSD 15+ has libusb, versions below should disable it
|
||||
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR PLATFORM_LINUX OR PLATFORM_FREEBSD OR APPLE" OFF)
|
||||
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR LINUX OR FREEBSD OR APPLE" OFF)
|
||||
|
||||
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT (WIN32 AND ARCHITECTURE_arm64) AND NOT APPLE" OFF)
|
||||
mark_as_advanced(FORCE ENABLE_OPENGL)
|
||||
@@ -235,7 +252,7 @@ option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
|
||||
|
||||
# Install udev rules on Linux (mainly for gyros)
|
||||
# Only acts on joysticks and nothing else.
|
||||
cmake_dependent_option(YUZU_INSTALL_UDEV_RULES "Install udev rules for gyro access" OFF "PLATFORM_LINUX" OFF)
|
||||
cmake_dependent_option(YUZU_INSTALL_UDEV_RULES "Install udev rules for gyro access" OFF "LINUX" OFF)
|
||||
|
||||
option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON)
|
||||
|
||||
@@ -248,7 +265,7 @@ cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable"
|
||||
|
||||
cmake_dependent_option(YUZU_CMD "Compile the eden-cli executable" ON "NOT ANDROID" OFF)
|
||||
|
||||
cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR PLATFORM_LINUX" OFF)
|
||||
cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF)
|
||||
|
||||
option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" ON)
|
||||
set(YUZU_TZDB_PATH "" CACHE STRING "Path to a pre-downloaded timezone database")
|
||||
@@ -258,7 +275,7 @@ cmake_dependent_option(YUZU_USE_BUNDLED_MOLTENVK "Download bundled MoltenVK lib"
|
||||
option(YUZU_DISABLE_LLVM "Disable LLVM (useful for CI)" OFF)
|
||||
|
||||
set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL OFF)
|
||||
if (EXT_DEFAULT OR PLATFORM_SUN OR PLATFORM_OPENBSD)
|
||||
if (EXT_DEFAULT OR SOLARIS OR OPENBSD)
|
||||
set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL ON)
|
||||
endif()
|
||||
|
||||
@@ -281,7 +298,7 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/hooks/pre-commit AND NOT EXISTS ${PROJECT_SOURCE
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX))
|
||||
if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX))
|
||||
set(HAS_NCE 1)
|
||||
add_compile_definitions(HAS_NCE=1)
|
||||
endif()
|
||||
@@ -290,7 +307,7 @@ if (YUZU_ROOM)
|
||||
add_compile_definitions(YUZU_ROOM)
|
||||
endif()
|
||||
|
||||
if (UNIX AND NOT (PLATFORM_LINUX OR WIN32))
|
||||
if (UNIX AND NOT (LINUX OR WIN32))
|
||||
if(CXX_APPLE OR CXX_CLANG)
|
||||
# libc++ has stop_token and jthread as experimental
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library")
|
||||
@@ -325,11 +342,12 @@ if (CXX_GCC OR CXX_CLANG)
|
||||
endif()
|
||||
elseif(ARCHITECTURE_arm64)
|
||||
# See https://gcc.gnu.org/onlinedocs/gcc/AArch64-Options.html
|
||||
set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, generic, armv9, native")
|
||||
set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, generic, optimized, armv9, native")
|
||||
set(mtune generic)
|
||||
|
||||
if (${YUZU_BUILD_PRESET} STREQUAL "generic")
|
||||
set(march armv8-a)
|
||||
elseif (${YUZU_BUILD_PRESET} STREQUAL "optimized")
|
||||
set(march armv8.2-a+lse+rcpc)
|
||||
elseif (${YUZU_BUILD_PRESET} STREQUAL "armv9")
|
||||
set(march armv9-a)
|
||||
endif()
|
||||
@@ -404,7 +422,7 @@ if (Boost_ADDED)
|
||||
|
||||
if (NOT MSVC OR CXX_CLANG)
|
||||
# boost sucks
|
||||
if (PLATFORM_SUN)
|
||||
if (SOLARIS)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthreads>)
|
||||
endif()
|
||||
|
||||
@@ -508,9 +526,9 @@ elseif (WIN32)
|
||||
# PSAPI is the Process Status API
|
||||
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version crypt32 rpcrt4 gdi32 wldap32 mswsock)
|
||||
endif()
|
||||
elseif (PLATFORM_MANAGARM)
|
||||
elseif (MANAGARM)
|
||||
set(PLATFORM_LIBRARIES iconv intl)
|
||||
elseif (PLATFORM_HAIKU)
|
||||
elseif (HAIKUOS)
|
||||
# Haiku is so special :)
|
||||
set(PLATFORM_LIBRARIES bsd /boot/system/lib/libnetwork.so)
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
|
||||
@@ -604,7 +622,7 @@ if (ENABLE_QT)
|
||||
|
||||
# Best practice is to ask for all components at once, so they are from the same version
|
||||
set(YUZU_QT_COMPONENTS Core Widgets Charts Concurrent Gui)
|
||||
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
|
||||
if (LINUX OR FREEBSD)
|
||||
list(APPEND YUZU_QT_COMPONENTS DBus)
|
||||
# yes Qt, we get it
|
||||
set(QT_NO_PRIVATE_MODULE_WARNING ON)
|
||||
|
||||
@@ -13,7 +13,7 @@ find_package_handle_standard_args(Opus
|
||||
VERSION_VAR OPUS_VERSION
|
||||
)
|
||||
|
||||
if (PLATFORM_MSYS)
|
||||
if (MSYS2)
|
||||
FixMsysPath(PkgConfig::OPUS)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ find_package_handle_standard_args(enet
|
||||
VERSION_VAR ENET_VERSION
|
||||
)
|
||||
|
||||
if (PLATFORM_MSYS)
|
||||
if (MSYS2)
|
||||
FixMsysPath(PkgConfig::ENET)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ find_package_handle_standard_args(libusb
|
||||
VERSION_VAR LIBUSB_VERSION
|
||||
)
|
||||
|
||||
if (PLATFORM_MSYS)
|
||||
if (MSYS2)
|
||||
FixMsysPath(PkgConfig::LIBUSB)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ else()
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_search_module(LZ4 QUIET IMPORTED_TARGET liblz4)
|
||||
|
||||
if (PLATFORM_MSYS)
|
||||
if (MSYS2)
|
||||
FixMsysPath(PkgConfig::LZ4)
|
||||
endif()
|
||||
|
||||
|
||||
+6
-2
@@ -21,10 +21,14 @@ Eden is free, open-source, copyleft software, licensed under the terms of the [G
|
||||
### Policies
|
||||
|
||||
- No LLM or AI usage, *period*, for patches, pull requests, issues, comments, debugging, brainstorming, etc.
|
||||
- For details on why, see the [detailed AI policy](docs/AI.md).
|
||||
- For details on why, see the [detailed AI policy](docs/policies/AI.md).
|
||||
- Usage of any form of profanity or otherwise unsavory language is generally discouraged.
|
||||
- This is primarily because it rarely helps to actually understand what's going on.
|
||||
- Remember that your comments should be focused and actually address what's happening. With very few exceptions, expletives are actively detrimental at best.
|
||||
- New code must follow the same general style as the surrounding codebase. Exceptions may be granted in certain cases.
|
||||
- Maintainers reserve the right to change your patches and pull requests at will. We will try to avoid this.
|
||||
- You should respect all decisions made by the [code owners](docs/CODEOWNERS) in your particular subsystem. If you feel they are overstepping or are incorrect, don't be afraid to stand your ground!
|
||||
- You should generally respect all decisions made by the [code owners](docs/CODEOWNERS) in your particular subsystem.
|
||||
- However, if you feel they are overstepping or are incorrect, don't be afraid to stand your ground! Maintainers are not always correct.
|
||||
- While we do *not* adhere to the terms of a formal code of conduct, you will generally be expected to respect other developers, contributors, and community members.
|
||||
- You **must** have basic knowledge of [Git](https://git-scm.com/learn). Knowing how to manage your branches and follow proper fork policies is a necessity.
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ These options control dependencies.
|
||||
- `YUZU_INSTALL_UDEV_RULES` (OFF) Install udev rules to enable hidraw access
|
||||
- Needed for gyroscopes
|
||||
- Only available on Linux
|
||||
- `ENABLE_DEBUG_TOOLS` (OFF) Enables debugging and development tools, see [tools](../tools/README.md).
|
||||
- `ENABLE_WERROR` (ON) Enables warnings as errors (-Werror).
|
||||
|
||||
### Flavors
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Everyone has their own way of viewing good/bad C++ practices, my general outline
|
||||
- The reason is because the project has `-fno-rtti` disabled by default, due to the costs of dynamic polymorphism.
|
||||
- Always copy-on-value for objects with `sizeof(void *) >= sizeof(T) * 2`, i.e objects sized as 2 pointers or less, for bigger objects you can use ref/pointer as usual.
|
||||
- Try using move semantics instead of references, whenever possible.
|
||||
- Remember function parameters are extremelly cheap as fuck, don't be afraid to place upto 8 parameters on a given function.
|
||||
- Function parameters are cheap. Don't be afraid to use as many as needed (API usability permitting).
|
||||
- Don't save a reference in structures of a parent object, i.e:
|
||||
|
||||
```c++
|
||||
|
||||
Vendored
+1
-1
@@ -164,7 +164,7 @@ if (NOT ANDROID)
|
||||
AddJsonPackage(sdl3)
|
||||
else()
|
||||
message(STATUS "Using bundled SDL3")
|
||||
if (PLATFORM_FREEBSD)
|
||||
if (FREEBSD)
|
||||
set(BUILD_SHARED_LIBS ON)
|
||||
endif()
|
||||
AddJsonPackage(sdl3-ci)
|
||||
|
||||
+23
-80
@@ -1,45 +1,46 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2025 crueter
|
||||
# SPDX-FileCopyrightText: Copyright 2026 crueter
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
## DetectPlatform ##
|
||||
|
||||
# This is a small helper that sets PLATFORM_<platform> variables for various
|
||||
# This is a small helper that sets platform variables for various
|
||||
# operating systems and distributions. Note that Apple, Windows, Android, etc.
|
||||
# are not covered, as CMake already does that for us.
|
||||
|
||||
# It also sets CXX_<compiler> for the C++ compiler.
|
||||
|
||||
# Furthermore, some platforms have really silly requirements/quirks, so this
|
||||
# also does a few of those.
|
||||
|
||||
# This module contains contributions from the Eden Emulator Project,
|
||||
# notably from crueter and Lizzie.
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} STREQUAL "SunOS")
|
||||
set(PLATFORM_SUN ON)
|
||||
set(SOLARIS ON)
|
||||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenOrbis")
|
||||
set(PLATFORM_PS4 ON)
|
||||
set(OPENORBIS ON)
|
||||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "managarm")
|
||||
set(PLATFORM_MANAGARM ON)
|
||||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
|
||||
set(PLATFORM_FREEBSD ON)
|
||||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
|
||||
set(PLATFORM_OPENBSD ON)
|
||||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "NetBSD")
|
||||
set(PLATFORM_NETBSD ON)
|
||||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "DragonFly")
|
||||
set(PLATFORM_DRAGONFLYBSD ON)
|
||||
set(MANAGARM ON)
|
||||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Haiku")
|
||||
set(PLATFORM_HAIKU ON)
|
||||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(PLATFORM_LINUX ON)
|
||||
set(HAIKUOS ON)
|
||||
endif()
|
||||
|
||||
# BSD
|
||||
if (DEFINED BSD)
|
||||
if ("${BSD}" STREQUAL "DragonFlyBSD")
|
||||
set(DRAGONFLYBSD ON)
|
||||
elseif ("${BSD}" STREQUAL "FreeBSD")
|
||||
set(FREEBSD ON)
|
||||
elseif ("${BSD}" STREQUAL "OpenBSD")
|
||||
set(OPENBSD ON)
|
||||
elseif ("${BSD}" STREQUAL "NetBSD")
|
||||
set(NETBSD ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# dumb heuristic to detect msys2
|
||||
if (CMAKE_COMMAND MATCHES "msys64")
|
||||
set(PLATFORM_MSYS ON)
|
||||
set(MSYS2 ON)
|
||||
endif()
|
||||
|
||||
# compiler checks
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set(CXX_CLANG ON)
|
||||
if (MSVC)
|
||||
@@ -47,8 +48,6 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
endif()
|
||||
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
set(CXX_GCC ON)
|
||||
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
set(CXX_CL ON)
|
||||
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM")
|
||||
set(CXX_ICC ON)
|
||||
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||
@@ -61,37 +60,13 @@ if(MINGW AND CXX_CLANG)
|
||||
set(CMAKE_SYSTEM_VERSION 10.0.0)
|
||||
endif()
|
||||
|
||||
# NB: this does not account for SPARC
|
||||
if (PLATFORM_SUN)
|
||||
# Terrific OpenIndiana pkg shenanigans
|
||||
list(APPEND CMAKE_PREFIX_PATH
|
||||
"${CMAKE_SYSROOT}/usr/lib/qt/6.6/lib/amd64/cmake")
|
||||
list(APPEND CMAKE_MODULE_PATH
|
||||
"${CMAKE_SYSROOT}/usr/lib/qt/6.6/lib/amd64/cmake")
|
||||
|
||||
# Amazing - absolutely incredible
|
||||
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SYSROOT}/usr/lib/amd64/cmake")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SYSROOT}/usr/lib/amd64/cmake")
|
||||
|
||||
# For some mighty reason, doing a normal release build sometimes
|
||||
# may not trigger the proper -O3 switch to materialize
|
||||
if (CMAKE_BUILD_TYPE MATCHES "Release")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
|
||||
endif()
|
||||
if (CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# MSYS2 utilities
|
||||
|
||||
# Sometimes, PkgConfig modules will incorrectly reference / when CMake
|
||||
# wants you to reference it as C:/msys64/. This function corrects that.
|
||||
# Example in a Find module:
|
||||
#[[
|
||||
if (PLATFORM_MSYS)
|
||||
if (MSYS2)
|
||||
FixMsysPath(PkgConfig::OPUS)
|
||||
endif()
|
||||
]]
|
||||
@@ -116,41 +91,9 @@ function(FixMsysPath target)
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${include_dir})
|
||||
endfunction()
|
||||
|
||||
# MSYSTEM handling + program_path
|
||||
if (PLATFORM_MSYS)
|
||||
# really, really dumb heuristic to detect what environment we are in
|
||||
macro(system var)
|
||||
if (CMAKE_COMMAND MATCHES ${var})
|
||||
set(MSYSTEM ${var})
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
system(mingw64)
|
||||
system(clang64)
|
||||
system(clangarm64)
|
||||
system(ucrt64)
|
||||
|
||||
if (NOT DEFINED MSYSTEM)
|
||||
set(MSYSTEM msys2)
|
||||
endif()
|
||||
|
||||
# We generally want to prioritize environment-specific binaries if possible
|
||||
# some, like autoconf, are not present on environments besides msys2 though
|
||||
set(CMAKE_PROGRAM_PATH C:/msys64/${MSYSTEM}/bin C:/msys64/usr/bin)
|
||||
set(ENV{PKG_CONFIG_PATH} C:/msys64/${MSYSTEM}/lib/pkgconfig)
|
||||
endif()
|
||||
|
||||
# This saves a truly ridiculous amount of time during linking
|
||||
# In my tests, without this, Eden takes 2 mins, with this, it takes 3-5 seconds
|
||||
# or on GitHub Actions, 10 minutes -> 3 seconds
|
||||
# Saves linking time
|
||||
if (MINGW)
|
||||
set(MINGW_FLAGS "-Wl,--strip-all -Wl,--gc-sections")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE
|
||||
"${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${MINGW_FLAGS}")
|
||||
endif()
|
||||
|
||||
# awesome
|
||||
if (PLATFORM_FREEBSD OR PLATFORM_DRAGONFLYBSD)
|
||||
set(CMAKE_EXE_LINKER_FLAGS
|
||||
"${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/local/lib")
|
||||
endif()
|
||||
|
||||
Vendored
+5
-5
@@ -56,7 +56,7 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (PLATFORM_PS4 OR PLATFORM_MANAGARM)
|
||||
if (OPENORBIS OR MANAGARM)
|
||||
# Doesn't support VA-API, don't go thru the embarrassment of trying to enable it
|
||||
list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi)
|
||||
elseif (ANDROID)
|
||||
@@ -75,7 +75,7 @@ elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING AND NOT ANDROID)
|
||||
if(X11_FOUND)
|
||||
if (NOT APPLE)
|
||||
# In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so
|
||||
if(PLATFORM_SUN)
|
||||
if(SOLARIS)
|
||||
list(APPEND FFmpeg_HWACCEL_LIBRARIES
|
||||
X11
|
||||
"${CMAKE_SYSROOT}/usr/lib/xorg/amd64/libdrm.so")
|
||||
@@ -158,7 +158,7 @@ elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING AND NOT ANDROID)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (PLATFORM_PS4)
|
||||
if (OPENORBIS)
|
||||
list(APPEND FFmpeg_CROSS_COMPILE_LIBS
|
||||
-lkernel
|
||||
-lSceUserService
|
||||
@@ -172,7 +172,7 @@ if (PLATFORM_PS4)
|
||||
--extra-cxxflags=${CMAKE_SYSROOT}/usr/include
|
||||
--extra-libs="${FFmpeg_CROSS_COMPILE_LIBS}"
|
||||
)
|
||||
elseif (PLATFORM_MANAGARM)
|
||||
elseif (MANAGARM)
|
||||
# Required for proper stuff
|
||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
|
||||
--disable-pthreads
|
||||
@@ -299,7 +299,7 @@ else()
|
||||
set(FFmpeg_BUILD_LIBRARIES ${FFmpeg_LIBRARIES})
|
||||
|
||||
# BSD make or Solaris make don't support ffmpeg make-j8
|
||||
if (PLATFORM_LINUX OR ANDROID OR APPLE OR WIN32 OR PLATFORM_FREEBSD)
|
||||
if (LINUX OR ANDROID OR APPLE OR WIN32 OR FREEBSD)
|
||||
set(FFmpeg_MAKE_ARGS -j${SYSTEM_THREADS})
|
||||
else()
|
||||
set(FFmpeg_MAKE_ARGS "")
|
||||
|
||||
Vendored
+1
-1
@@ -11,7 +11,7 @@ if (NOT libusb_ADDED)
|
||||
endif()
|
||||
|
||||
# TODO: *BSD fails to compile--may need different configs/symbols
|
||||
if (MINGW OR PLATFORM_LINUX OR APPLE)
|
||||
if (MINGW OR LINUX OR APPLE)
|
||||
set(LIBUSB_FOUND ON CACHE BOOL "libusb is present" FORCE)
|
||||
set(LIBUSB_VERSION "1.0.24" CACHE STRING "libusb version string" FORCE)
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -1177,7 +1177,7 @@ static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int re
|
||||
#endif
|
||||
|
||||
#ifndef STBI_NO_TGA
|
||||
// test tga last because it's a crappy test!
|
||||
// test tga last because it's a bad test!
|
||||
if (stbi__tga_test(s))
|
||||
return stbi__tga_load(s,x,y,comp,req_comp, ri);
|
||||
#endif
|
||||
@@ -7662,7 +7662,7 @@ static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp)
|
||||
if (stbi__hdr_info(s, x, y, comp)) return 1;
|
||||
#endif
|
||||
|
||||
// test tga last because it's a crappy test!
|
||||
// test tga last because it's a bad test!
|
||||
#ifndef STBI_NO_TGA
|
||||
if (stbi__tga_info(s, x, y, comp))
|
||||
return 1;
|
||||
|
||||
+21
-9
@@ -155,12 +155,24 @@ else()
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WERROR)
|
||||
add_compile_options(
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=all>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=extra>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=missing-declarations>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=shadow>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=unused>)
|
||||
else()
|
||||
add_compile_options(
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wall>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wextra>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wmissing-declarations>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wshadow>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wunused>
|
||||
# Some compilers could particularly misbehave :)
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-error-all>)
|
||||
endif()
|
||||
add_compile_options(
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=all>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=extra>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=missing-declarations>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=shadow>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=unused>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-attributes>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-invalid-offsetof>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-unused-parameter>
|
||||
@@ -169,9 +181,9 @@ else()
|
||||
if (CXX_CLANG OR CXX_ICC OR CXX_APPLE) # Clang, AppleClang, or Intel C++
|
||||
if (NOT MSVC)
|
||||
add_compile_options(
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=shadow-uncaptured-local>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=implicit-fallthrough>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=type-limits>)
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wshadow-uncaptured-local>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wimplicit-fallthrough>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wtype-limits>)
|
||||
endif()
|
||||
add_compile_options(
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-braced-scalar-init>
|
||||
@@ -181,7 +193,7 @@ else()
|
||||
|
||||
if (ARCHITECTURE_x86_64)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mcx16>)
|
||||
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
|
||||
if (LINUX OR FREEBSD)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mtls-dialect=gnu2>)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -188,6 +188,7 @@ android {
|
||||
create("mainline") {
|
||||
dimension = "version"
|
||||
isDefault = true
|
||||
minSdk = 33
|
||||
|
||||
manifestPlaceholders += mapOf("appNameBase" to "Eden")
|
||||
resValue("string", "app_name_suffixed", "Eden")
|
||||
@@ -199,6 +200,7 @@ android {
|
||||
|
||||
create("genshinSpoof") {
|
||||
dimension = "version"
|
||||
minSdk = 35
|
||||
manifestPlaceholders += mapOf("appNameBase" to "Eden Optimized")
|
||||
resValue("string", "app_name_suffixed", "Eden Optimized")
|
||||
applicationId = "com.miHoYo.Yuanshen"
|
||||
@@ -216,6 +218,7 @@ android {
|
||||
|
||||
create("legacy") {
|
||||
dimension = "version"
|
||||
minSdk = 29
|
||||
manifestPlaceholders += mapOf("appNameBase" to "Eden Legacy")
|
||||
resValue("string", "app_name_suffixed", "Eden Legacy")
|
||||
applicationId = "dev.legacy.eden_emulator"
|
||||
|
||||
@@ -218,6 +218,8 @@ object NativeLibrary {
|
||||
|
||||
external fun logSettings()
|
||||
|
||||
external fun refreshThreadPolicies()
|
||||
|
||||
external fun getDebugKnobAt(index: Int): Boolean
|
||||
|
||||
/**
|
||||
@@ -537,6 +539,40 @@ object NativeLibrary {
|
||||
*/
|
||||
external fun installKeys(path: String, ext: String): Int
|
||||
|
||||
/**
|
||||
* @return Whether this GPU can run the Lossless Scaling frame generation shaders,
|
||||
* which are built against the Vulkan memory model.
|
||||
*/
|
||||
external fun supportsFrameGeneration(): Boolean
|
||||
|
||||
/**
|
||||
* @return Path the user-supplied Lossless Scaling library is expected at.
|
||||
*/
|
||||
external fun getLosslessDllPath(): String
|
||||
|
||||
/**
|
||||
* Parses the installed Lossless Scaling library and checks that every shader the
|
||||
* frame generation chain needs is present.
|
||||
*
|
||||
* @return The result code, matching the losslessDllResults array.
|
||||
*/
|
||||
external fun validateLosslessDll(): Int
|
||||
|
||||
/**
|
||||
* Translates the frame generation shaders out of the installed Lossless Scaling library
|
||||
* and writes them to the SPIR-V cache. Slow, so call it off the main thread.
|
||||
*
|
||||
* @return The result code, matching the losslessDllResults array.
|
||||
*/
|
||||
external fun prepareLosslessDll(): Int
|
||||
|
||||
/**
|
||||
* Deletes the installed Lossless Scaling library.
|
||||
*
|
||||
* @return Whether the library is gone after the call.
|
||||
*/
|
||||
external fun removeLosslessDll(): Boolean
|
||||
|
||||
/**
|
||||
* Checks the PatchManager for any addons that are available
|
||||
*
|
||||
|
||||
@@ -559,6 +559,15 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
private fun enableFullscreenImmersive() {
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
window.decorView.systemUiVisibility =
|
||||
View.SYSTEM_UI_FLAG_FULLSCREEN or
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
|
||||
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
|
||||
controller.hide(WindowInsetsCompat.Type.systemBars())
|
||||
controller.systemBarsBehavior =
|
||||
|
||||
+4
-1
@@ -18,7 +18,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"),
|
||||
FIX_BLOOM_EFFECTS("fix_bloom_effects"),
|
||||
EMULATE_BGR565("emulate_bgr565"),
|
||||
RESCALE_HACK("rescale_hack"),
|
||||
CPUOPT_UNSAFE_HOST_MMU("cpuopt_unsafe_host_mmu"),
|
||||
USE_DOCKED_MODE("use_docked_mode"),
|
||||
USE_AUTO_STUB("use_auto_stub"),
|
||||
@@ -37,6 +36,10 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
|
||||
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
|
||||
RENDERER_SAMPLE_SHADING("sample_shading"),
|
||||
RENDERER_FRAME_GEN("frame_gen"),
|
||||
RENDERER_FRAME_GEN_FP16("frame_gen_fp16"),
|
||||
RENDERER_FRAME_GEN_FLOW_SCALE_AUTO("frame_gen_flow_scale_auto"),
|
||||
RENDERER_FRAME_GEN_DUMP_FLOW("frame_gen_dump_flow"),
|
||||
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
|
||||
PICTURE_IN_PICTURE("picture_in_picture"),
|
||||
USE_CUSTOM_RTC("custom_rtc_enabled"),
|
||||
|
||||
@@ -19,6 +19,10 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
RENDERER_ASTC_DECODE_METHOD("accelerate_astc"),
|
||||
RENDERER_ACCURACY("gpu_accuracy"),
|
||||
RENDERER_RESOLUTION("resolution_setup"),
|
||||
RENDERER_FRAME_GEN_MULTIPLIER("frame_gen_multiplier"),
|
||||
RENDERER_FRAME_GEN_TARGET_RATE("frame_gen_target_rate"),
|
||||
RENDERER_FRAME_GEN_QUEUE_TARGET("frame_gen_queue_target"),
|
||||
RENDERER_FRAME_GEN_FLOW_SCALE("frame_gen_flow_scale"),
|
||||
RENDERER_VSYNC("use_vsync"),
|
||||
RENDERER_SCALING_FILTER("scaling_filter"),
|
||||
RENDERER_ANTI_ALIASING("anti_aliasing"),
|
||||
|
||||
@@ -11,6 +11,7 @@ object Settings {
|
||||
SECTION_ROOT(R.string.advanced_settings),
|
||||
SECTION_SYSTEM(R.string.preferences_system),
|
||||
SECTION_RENDERER(R.string.preferences_graphics),
|
||||
SECTION_FRAME_GEN(R.string.frame_gen),
|
||||
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
|
||||
SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
|
||||
SECTION_SOC_OVERLAY(R.string.soc_overlay_options),
|
||||
|
||||
+105
-8
@@ -21,6 +21,7 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.network.NetDataValidators
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
/**
|
||||
@@ -65,6 +66,19 @@ abstract class SettingsItem(
|
||||
return NativeLibrary.isFirmwareAvailable()
|
||||
}
|
||||
|
||||
if (setting.key in frameGenKeys &&
|
||||
!(LosslessScalingHelper.isInstalled() && LosslessScalingHelper.isSupportedByGpu())
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// A frame rate target moves the multiplier on its own
|
||||
if (setting.key == IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key &&
|
||||
frameGenTargetRate != 0
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Can't edit settings that aren't saveable in per-game config even if they are switchable
|
||||
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
|
||||
return false
|
||||
@@ -88,7 +102,31 @@ abstract class SettingsItem(
|
||||
val clearable: Boolean
|
||||
get() = !setting.global && NativeConfig.isPerGameConfigLoaded()
|
||||
|
||||
private val frameGenTargetRate: Int
|
||||
get() {
|
||||
val key = IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key
|
||||
val needsGlobal = if (NativeLibrary.isRunning() &&
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
!NativeConfig.usingGlobal(key)
|
||||
} else {
|
||||
NativeConfig.usingGlobal(key)
|
||||
}
|
||||
return IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.getInt(needsGlobal)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val frameGenKeys = setOf(
|
||||
BooleanSetting.RENDERER_FRAME_GEN.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key,
|
||||
BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key,
|
||||
BooleanSetting.RENDERER_FRAME_GEN_FP16.key,
|
||||
BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key
|
||||
)
|
||||
|
||||
const val TYPE_HEADER = 0
|
||||
const val TYPE_SWITCH = 1
|
||||
const val TYPE_SINGLE_CHOICE = 2
|
||||
@@ -586,6 +624,7 @@ abstract class SettingsItem(
|
||||
IntSetting.FSR_SHARPENING_SLIDER,
|
||||
titleId = R.string.fsr_sharpness,
|
||||
descriptionId = R.string.fsr_sharpness_description,
|
||||
max = 200,
|
||||
units = "%"
|
||||
)
|
||||
)
|
||||
@@ -594,7 +633,7 @@ abstract class SettingsItem(
|
||||
IntSetting.ANDROID_PIPELINE_WORKERS,
|
||||
titleId = R.string.pipeline_worker_cores,
|
||||
descriptionId = R.string.pipeline_worker_cores_description,
|
||||
min = 4,
|
||||
min = 2,
|
||||
max = 8,
|
||||
units = "cores"
|
||||
)
|
||||
@@ -607,6 +646,71 @@ abstract class SettingsItem(
|
||||
valuesId = R.array.rendererAntiAliasingValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_FRAME_GEN,
|
||||
titleId = R.string.frame_gen,
|
||||
descriptionId = R.string.frame_gen_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_MULTIPLIER,
|
||||
titleId = R.string.frame_gen_multiplier,
|
||||
descriptionId = R.string.frame_gen_multiplier_description,
|
||||
choicesId = R.array.frameGenMultiplierNames,
|
||||
valuesId = R.array.frameGenMultiplierValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_TARGET_RATE,
|
||||
titleId = R.string.frame_gen_target_rate,
|
||||
descriptionId = R.string.frame_gen_target_rate_description,
|
||||
choicesId = R.array.frameGenTargetRateNames,
|
||||
valuesId = R.array.frameGenTargetRateValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET,
|
||||
titleId = R.string.frame_gen_queue_target,
|
||||
descriptionId = R.string.frame_gen_queue_target_description,
|
||||
choicesId = R.array.frameGenQueueTargetNames,
|
||||
valuesId = R.array.frameGenQueueTargetValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO,
|
||||
titleId = R.string.frame_gen_flow_scale_auto,
|
||||
descriptionId = R.string.frame_gen_flow_scale_auto_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SliderSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE,
|
||||
titleId = R.string.frame_gen_flow_scale,
|
||||
descriptionId = R.string.frame_gen_flow_scale_description,
|
||||
min = 25,
|
||||
max = 100,
|
||||
units = "%"
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_FRAME_GEN_FP16,
|
||||
titleId = R.string.frame_gen_fp16,
|
||||
descriptionId = R.string.frame_gen_fp16_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW,
|
||||
titleId = R.string.frame_gen_dump_flow,
|
||||
descriptionId = R.string.frame_gen_dump_flow_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_SCREEN_LAYOUT,
|
||||
@@ -773,13 +877,6 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.fix_bloom_effects_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RESCALE_HACK,
|
||||
titleId = R.string.rescale_hack,
|
||||
descriptionId = R.string.rescale_hack_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.EMULATE_BGR565,
|
||||
|
||||
+3
-1
@@ -382,7 +382,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
}
|
||||
scSetting.setSelectedValue(value)
|
||||
|
||||
if (scSetting.setting.key == IntSetting.RENDERER_SCALING_FILTER.key) {
|
||||
if (scSetting.setting.key == IntSetting.RENDERER_SCALING_FILTER.key ||
|
||||
scSetting.setting.key == IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key
|
||||
) {
|
||||
settingsViewModel.setShouldReloadSettingsList(true)
|
||||
}
|
||||
|
||||
|
||||
+40
-3
@@ -27,6 +27,7 @@ import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.FullscreenHelper
|
||||
@@ -76,6 +77,41 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun addFrameGenSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
if (!LosslessScalingHelper.isSupportedByGpu()) {
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.frame_gen_unsupported,
|
||||
descriptionId = R.string.frame_gen_unsupported_description,
|
||||
isRunnable = false
|
||||
) {}
|
||||
)
|
||||
} else if (!LosslessScalingHelper.isInstalled()) {
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.lossless_scaling_missing,
|
||||
descriptionId = R.string.lossless_scaling_missing_description,
|
||||
isRunnable = false
|
||||
) {}
|
||||
)
|
||||
}
|
||||
|
||||
add(BooleanSetting.RENDERER_FRAME_GEN.key)
|
||||
add(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key)
|
||||
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key)
|
||||
add(IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key)
|
||||
add(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key)
|
||||
if (!BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean(
|
||||
getNeedsGlobalForKey(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key)
|
||||
)
|
||||
) {
|
||||
add(IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key)
|
||||
}
|
||||
add(BooleanSetting.RENDERER_FRAME_GEN_FP16.key)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSharpnessScalingFilterSelected(): Boolean {
|
||||
val needsGlobal = getNeedsGlobalForKey(IntSetting.RENDERER_SCALING_FILTER.key)
|
||||
val selectedFilter = IntSetting.RENDERER_SCALING_FILTER.getInt(needsGlobal)
|
||||
@@ -120,6 +156,7 @@ class SettingsFragmentPresenter(
|
||||
MenuTag.SECTION_ROOT -> addConfigSettings(sl)
|
||||
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||
MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl)
|
||||
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
|
||||
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
|
||||
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
|
||||
@@ -250,8 +287,9 @@ class SettingsFragmentPresenter(
|
||||
add(BooleanSetting.USE_CUSTOM_RTC.key)
|
||||
add(LongSetting.CUSTOM_RTC.key)
|
||||
|
||||
add(HeaderSetting(R.string.cpu))
|
||||
add(HeaderSetting(R.string.clocks))
|
||||
add(IntSetting.FAST_CPU_TIME.key)
|
||||
add(IntSetting.FAST_GPU_TIME.key)
|
||||
add(BooleanSetting.CORE_SYNC_CORE_SPEED.key)
|
||||
|
||||
add(IntSetting.MEMORY_LAYOUT.key)
|
||||
@@ -298,11 +336,9 @@ class SettingsFragmentPresenter(
|
||||
|
||||
add(HeaderSetting(R.string.hacks))
|
||||
|
||||
add(IntSetting.FAST_GPU_TIME.key)
|
||||
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
|
||||
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
|
||||
add(BooleanSetting.EMULATE_BGR565.key)
|
||||
add(BooleanSetting.RESCALE_HACK.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
|
||||
add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
|
||||
@@ -1296,6 +1332,7 @@ class SettingsFragmentPresenter(
|
||||
add(BooleanSetting.DUMP_GUEST_SHADERS.key)
|
||||
add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key)
|
||||
add(BooleanSetting.DUMP_MACROS.key)
|
||||
add(BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key)
|
||||
add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key)
|
||||
add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key)
|
||||
add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key)
|
||||
|
||||
+2
@@ -29,6 +29,7 @@ enum class SettingsSubscreen {
|
||||
DRIVER_MANAGER,
|
||||
DRIVER_FETCHER,
|
||||
FREEDRENO_SETTINGS,
|
||||
LOSSLESS_MANAGER,
|
||||
APPLET_LAUNCHER,
|
||||
INSTALLABLE,
|
||||
GAME_FOLDERS,
|
||||
@@ -126,6 +127,7 @@ class SettingsSubscreenActivity : AppCompatActivity() {
|
||||
SettingsSubscreen.DRIVER_MANAGER -> R.id.driverManagerFragment
|
||||
SettingsSubscreen.DRIVER_FETCHER -> R.id.driverFetcherFragment
|
||||
SettingsSubscreen.FREEDRENO_SETTINGS -> R.id.freedrenoSettingsFragment
|
||||
SettingsSubscreen.LOSSLESS_MANAGER -> R.id.losslessManagerFragment
|
||||
SettingsSubscreen.APPLET_LAUNCHER -> R.id.appletLauncherFragment
|
||||
SettingsSubscreen.INSTALLABLE -> R.id.installableFragment
|
||||
SettingsSubscreen.GAME_FOLDERS -> R.id.gameFoldersFragment
|
||||
|
||||
@@ -1182,7 +1182,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
container,
|
||||
IntSetting.FSR_SHARPENING_SLIDER,
|
||||
minValue = 0,
|
||||
maxValue = 100,
|
||||
maxValue = 200,
|
||||
units = "%"
|
||||
)
|
||||
}
|
||||
@@ -1451,6 +1451,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
NativeLibrary.refreshThreadPolicies()
|
||||
val b = _binding ?: return
|
||||
updateStatsPosition(IntSetting.PERF_OVERLAY_POSITION.getInt())
|
||||
updateSocPosition(IntSetting.SOC_OVERLAY_POSITION.getInt())
|
||||
|
||||
@@ -369,6 +369,21 @@ class GamePropertiesFragment : Fragment() {
|
||||
)
|
||||
)
|
||||
}
|
||||
add(
|
||||
SubmenuProperty(
|
||||
R.string.frame_gen,
|
||||
R.string.frame_gen_per_game_description,
|
||||
R.drawable.ic_duck,
|
||||
action = {
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
args.game,
|
||||
Settings.MenuTag.SECTION_FRAME_GEN
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
if (GpuDriverHelper.isAdrenoGpu()) {
|
||||
add(
|
||||
SubmenuProperty(
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
|
||||
class HomeSettingsFragment : Fragment() {
|
||||
@@ -170,6 +171,24 @@ class HomeSettingsFragment : Fragment() {
|
||||
)
|
||||
)
|
||||
}
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.lossless_scaling,
|
||||
R.string.lossless_scaling_description,
|
||||
R.drawable.ic_duck,
|
||||
{
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.LOSSLESS_MANAGER,
|
||||
null
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
},
|
||||
{ true },
|
||||
0,
|
||||
0,
|
||||
LosslessScalingHelper.statusText
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.multiplayer,
|
||||
@@ -337,6 +356,7 @@ class HomeSettingsFragment : Fragment() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
driverViewModel.updateDriverNameForGame(null)
|
||||
LosslessScalingHelper.refreshStatus()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentLosslessManagerBinding
|
||||
import org.yuzu.yuzu_emu.features.fetcher.SpacingItemDecoration
|
||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class LosslessManagerFragment : Fragment() {
|
||||
private var _binding: FragmentLosslessManagerBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentLosslessManagerBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.toolbarLossless.setNavigationOnClickListener {
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
binding.losslessOptionsList.apply {
|
||||
layoutManager =
|
||||
GridLayoutManager(requireContext(), resources.getInteger(R.integer.grid_columns))
|
||||
addItemDecoration(
|
||||
SpacingItemDecoration(resources.getDimensionPixelSize(R.dimen.spacing_small))
|
||||
)
|
||||
}
|
||||
|
||||
LosslessScalingHelper.statusText.collect(viewLifecycleOwner) { refreshOptions() }
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
||||
private fun refreshOptions() {
|
||||
binding.losslessOptionsList.adapter = HomeSettingAdapter(
|
||||
requireActivity() as AppCompatActivity,
|
||||
viewLifecycleOwner,
|
||||
buildOptions()
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildOptions(): List<HomeSetting> {
|
||||
val installed = LosslessScalingHelper.isInstalled()
|
||||
return listOf(
|
||||
HomeSetting(
|
||||
if (installed) R.string.lossless_scaling_replace else R.string.lossless_scaling_install,
|
||||
if (installed) {
|
||||
R.string.lossless_scaling_replace_description
|
||||
} else {
|
||||
R.string.lossless_scaling_install_description
|
||||
},
|
||||
R.drawable.ic_install,
|
||||
{ dllPickerLauncher.launch(arrayOf("*/*")) },
|
||||
{ !NativeLibrary.isRunning() },
|
||||
R.string.lossless_scaling_locked,
|
||||
R.string.lossless_scaling_locked_description,
|
||||
LosslessScalingHelper.statusText
|
||||
),
|
||||
HomeSetting(
|
||||
R.string.lossless_scaling_remove,
|
||||
R.string.lossless_scaling_remove_description,
|
||||
R.drawable.ic_delete,
|
||||
{ confirmRemoval() },
|
||||
{ installed && !NativeLibrary.isRunning() },
|
||||
if (installed) {
|
||||
R.string.lossless_scaling_locked
|
||||
} else {
|
||||
R.string.lossless_scaling_remove_unavailable
|
||||
},
|
||||
if (installed) {
|
||||
R.string.lossless_scaling_locked_description
|
||||
} else {
|
||||
R.string.lossless_scaling_remove_unavailable_description
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun confirmRemoval() {
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.lossless_scaling_remove,
|
||||
descriptionId = R.string.lossless_scaling_remove_confirmation,
|
||||
positiveButtonTitleId = R.string.lossless_scaling_remove,
|
||||
positiveAction = { LosslessScalingHelper.remove() },
|
||||
showNegativeButton = true,
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
|
||||
private val dllPickerLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val resultStrings = resources.getStringArray(R.array.losslessDllResults)
|
||||
ProgressDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
R.string.lossless_scaling_installing,
|
||||
false
|
||||
) { _, _ ->
|
||||
val installResult = LosslessScalingHelper.install(result)
|
||||
if (installResult == LosslessScalingHelper.RESULT_OK) {
|
||||
getString(R.string.lossless_scaling_install_success)
|
||||
} else {
|
||||
MessageDialogFragment.newInstance(
|
||||
titleId = R.string.lossless_scaling_install_failed,
|
||||
descriptionString = resultStrings[installResult]
|
||||
)
|
||||
}
|
||||
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
binding.appbarLossless.updateMargins(
|
||||
left = barInsets.left + cutoutInsets.left,
|
||||
right = barInsets.right + cutoutInsets.right
|
||||
)
|
||||
|
||||
binding.scrollViewLossless.updatePadding(bottom = barInsets.bottom)
|
||||
|
||||
binding.losslessOptionsList.updatePadding(
|
||||
left = barInsets.left + cutoutInsets.left,
|
||||
right = barInsets.right + cutoutInsets.right
|
||||
)
|
||||
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
@@ -42,6 +42,7 @@ import org.yuzu.yuzu_emu.model.SetupPage
|
||||
import org.yuzu.yuzu_emu.model.PageState
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
@@ -202,6 +203,24 @@ class SetupFragment : Fragment() {
|
||||
R.string.install_firmware_warning_help,
|
||||
)
|
||||
)
|
||||
add(
|
||||
PageButton(
|
||||
R.drawable.ic_duck,
|
||||
R.string.lossless_scaling,
|
||||
R.string.lossless_scaling_setup_description,
|
||||
{
|
||||
pageButtonCallback = it
|
||||
getLosslessDll.launch(arrayOf("*/*"))
|
||||
},
|
||||
{
|
||||
if (LosslessScalingHelper.isInstalled()) {
|
||||
ButtonState.BUTTON_ACTION_COMPLETE
|
||||
} else {
|
||||
ButtonState.BUTTON_ACTION_INCOMPLETE
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
add(
|
||||
PageButton(
|
||||
R.drawable.ic_controller,
|
||||
@@ -446,6 +465,32 @@ class SetupFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
val getLosslessDll =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val resultStrings = resources.getStringArray(R.array.losslessDllResults)
|
||||
ProgressDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
R.string.lossless_scaling_installing,
|
||||
false
|
||||
) { _, _ ->
|
||||
val installResult = LosslessScalingHelper.install(result)
|
||||
if (installResult == LosslessScalingHelper.RESULT_OK) {
|
||||
getString(R.string.lossless_scaling_install_success)
|
||||
} else {
|
||||
MessageDialogFragment.newInstance(
|
||||
titleId = R.string.lossless_scaling_install_failed,
|
||||
descriptionString = resultStrings[installResult]
|
||||
)
|
||||
}
|
||||
}.apply {
|
||||
onDialogComplete = { checkForButtonState.invoke() }
|
||||
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
val getGamesDirectory =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
||||
if (result != null) {
|
||||
|
||||
+13
-1
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
@@ -72,6 +72,18 @@ class SystemInfoDialogFragment : DialogFragment() {
|
||||
|
||||
val vulkanDriver = NativeLibrary.getVulkanDriverVersion()
|
||||
appendLine("${getString(R.string.vulkan_driver_version)}: $vulkanDriver")
|
||||
|
||||
val frameGen = NativeLibrary.supportsFrameGeneration()
|
||||
appendLine(
|
||||
"${getString(R.string.frame_generation_support)}: " +
|
||||
getString(
|
||||
if (frameGen) {
|
||||
R.string.frame_generation_supported
|
||||
} else {
|
||||
R.string.frame_generation_unsupported
|
||||
}
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
appendLine("${getString(R.string.error_getting_emulator_info)}: ${e.message}")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.net.Uri
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import java.io.File
|
||||
|
||||
object LosslessScalingHelper {
|
||||
const val RESULT_OK = 0
|
||||
const val RESULT_NOT_INSTALLED = 1
|
||||
|
||||
private val _statusText = MutableStateFlow("")
|
||||
val statusText: StateFlow<String> = _statusText.asStateFlow()
|
||||
|
||||
private var installed: Boolean? = null
|
||||
private var gpuSupported: Boolean? = null
|
||||
|
||||
fun isInstalled(): Boolean = installed ?: refreshStatus()
|
||||
|
||||
fun isSupportedByGpu(): Boolean {
|
||||
val cached = gpuSupported
|
||||
if (cached != null) {
|
||||
return cached
|
||||
}
|
||||
val result = NativeLibrary.supportsFrameGeneration()
|
||||
gpuSupported = result
|
||||
return result
|
||||
}
|
||||
|
||||
fun refreshStatus(): Boolean {
|
||||
val result = NativeLibrary.validateLosslessDll() == RESULT_OK
|
||||
installed = result
|
||||
|
||||
val context = YuzuApplication.appContext
|
||||
_statusText.value = if (result) {
|
||||
context.getString(R.string.lossless_scaling_installed)
|
||||
} else {
|
||||
context.getString(R.string.lossless_scaling_not_installed)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun install(source: Uri): Int {
|
||||
val destination = File(NativeLibrary.getLosslessDllPath())
|
||||
destination.parentFile?.mkdirs()
|
||||
|
||||
val copied = FileUtil.copyUriToInternalStorage(
|
||||
source,
|
||||
destination.parent!!,
|
||||
destination.name
|
||||
)
|
||||
if (copied == null) {
|
||||
refreshStatus()
|
||||
return RESULT_NOT_INSTALLED
|
||||
}
|
||||
|
||||
val result = NativeLibrary.prepareLosslessDll()
|
||||
if (result != RESULT_OK) {
|
||||
NativeLibrary.removeLosslessDll()
|
||||
}
|
||||
refreshStatus()
|
||||
return result
|
||||
}
|
||||
|
||||
fun remove(): Boolean {
|
||||
val removed = NativeLibrary.removeLosslessDll()
|
||||
refreshStatus()
|
||||
return removed
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,9 @@ void AndroidConfig::SaveAllValues() {
|
||||
}
|
||||
|
||||
void AndroidConfig::ReadAndroidValues() {
|
||||
Settings::values.rescale_hack.SetValue(false);
|
||||
ReadAndroidUIValues();
|
||||
if (global) {
|
||||
ReadAndroidUIValues();
|
||||
ReadUIValues();
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
|
||||
Settings::values.ext_content_from_game_dirs = ReadBooleanSetting(
|
||||
@@ -223,8 +224,8 @@ void AndroidConfig::ReadAndroidControlValues() {
|
||||
}
|
||||
|
||||
void AndroidConfig::SaveAndroidValues() {
|
||||
SaveAndroidUIValues();
|
||||
if (global) {
|
||||
SaveAndroidUIValues();
|
||||
SaveUIValues();
|
||||
SaveOverlayValues();
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ namespace AndroidSettings {
|
||||
&show_performance_overlay};
|
||||
|
||||
|
||||
Settings::Setting<s32> pipeline_worker_count{linkage, 4, "pipeline_worker_count",
|
||||
Settings::SwitchableSetting<s32> pipeline_worker_count{linkage, 2, "pipeline_worker_count",
|
||||
Settings::Category::Android,
|
||||
Settings::Specialization::Default,
|
||||
true,
|
||||
|
||||
@@ -124,9 +124,18 @@ float EmuWindow_Android::GetFrameTimeVerifiedHint() const {
|
||||
return QuantizeFrameRateHint(verified_rate);
|
||||
}
|
||||
|
||||
float EmuWindow_Android::GetPresentedFrameMultiplier() {
|
||||
if (!Settings::values.frame_gen.GetValue()) {
|
||||
return 1.0f;
|
||||
}
|
||||
return static_cast<float>(std::clamp<u32>(Settings::values.frame_gen_multiplier.GetValue(), 2, 4));
|
||||
}
|
||||
|
||||
float EmuWindow_Android::GetFrameRateHint() const {
|
||||
const float observed_rate = std::clamp(m_smoothed_present_rate, 0.0f, 240.0f);
|
||||
const float frame_time_verified_hint = GetFrameTimeVerifiedHint();
|
||||
const float presented_multiplier = GetPresentedFrameMultiplier();
|
||||
const float observed_rate =
|
||||
std::clamp(m_smoothed_present_rate * presented_multiplier, 0.0f, 240.0f);
|
||||
const float frame_time_verified_hint = GetFrameTimeVerifiedHint() * presented_multiplier;
|
||||
|
||||
if (m_last_frame_rate_hint > 0.0f && observed_rate > 0.0f) {
|
||||
const float tolerance = std::max(m_last_frame_rate_hint * 0.12f, 4.0f);
|
||||
@@ -150,9 +159,9 @@ float EmuWindow_Android::GetFrameRateHint() const {
|
||||
return frame_time_verified_hint;
|
||||
}
|
||||
|
||||
constexpr float NominalFrameRate = 60.0f;
|
||||
const float nominal_rate = 60.0f * presented_multiplier;
|
||||
if (!Settings::values.use_speed_limit.GetValue()) {
|
||||
return NominalFrameRate;
|
||||
return QuantizeFrameRateHint(nominal_rate);
|
||||
}
|
||||
|
||||
const u16 speed_limit = Settings::SpeedLimit();
|
||||
@@ -161,7 +170,7 @@ float EmuWindow_Android::GetFrameRateHint() const {
|
||||
}
|
||||
|
||||
const float speed_limited_rate =
|
||||
NominalFrameRate * (static_cast<float>(std::min<u16>(speed_limit, 100)) / 100.0f);
|
||||
nominal_rate * (static_cast<float>(std::min<u16>(speed_limit, 100)) / 100.0f);
|
||||
return QuantizeFrameRateHint(speed_limited_rate);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ private:
|
||||
void UpdateObservedFrameRate();
|
||||
[[nodiscard]] float GetFrameRateHint() const;
|
||||
[[nodiscard]] float GetFrameTimeVerifiedHint() const;
|
||||
[[nodiscard]] static float GetPresentedFrameMultiplier();
|
||||
[[nodiscard]] static float QuantizeFrameRateHint(float frame_rate);
|
||||
|
||||
float m_window_width{};
|
||||
|
||||
@@ -20,132 +20,96 @@ struct RomMetadata {
|
||||
std::vector<u8> icon;
|
||||
bool isHomebrew;
|
||||
};
|
||||
static ankerl::unordered_dense::map<std::string, RomMetadata> m_rom_metadata_cache;
|
||||
|
||||
ankerl::unordered_dense::map<std::string, RomMetadata> m_rom_metadata_cache;
|
||||
static RomMetadata CacheRomMetadata(const std::string& path) {
|
||||
auto& instance = EmulationSession::GetInstance();
|
||||
const auto file = Core::GetGameFileFromPath(instance.System().GetFilesystem(), path);
|
||||
if (auto loader = Loader::GetLoader(instance.System(), file, 0, 0); loader) {
|
||||
RomMetadata entry;
|
||||
loader->ReadTitle(entry.title);
|
||||
loader->ReadProgramId(entry.programId);
|
||||
loader->ReadIcon(entry.icon);
|
||||
|
||||
RomMetadata CacheRomMetadata(const std::string& path) {
|
||||
const auto file =
|
||||
Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path);
|
||||
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
|
||||
const FileSys::PatchManager pm{
|
||||
entry.programId,
|
||||
instance.System().GetFileSystemController(),
|
||||
instance.System().GetContentProvider()
|
||||
};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
|
||||
RomMetadata entry;
|
||||
loader->ReadTitle(entry.title);
|
||||
loader->ReadProgramId(entry.programId);
|
||||
loader->ReadIcon(entry.icon);
|
||||
|
||||
const FileSys::PatchManager pm{
|
||||
entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(),
|
||||
EmulationSession::GetInstance().System().GetContentProvider()};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
|
||||
if (control.first != nullptr) {
|
||||
entry.developer = control.first->GetDeveloperName();
|
||||
entry.version = control.first->GetVersionString();
|
||||
} else {
|
||||
FileSys::NACP nacp;
|
||||
if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) {
|
||||
entry.developer = nacp.GetDeveloperName();
|
||||
if (control.first != nullptr) {
|
||||
entry.developer = control.first->GetDeveloperName();
|
||||
entry.version = control.first->GetVersionString();
|
||||
} else {
|
||||
entry.developer = "";
|
||||
FileSys::NACP nacp{};
|
||||
entry.developer = loader->ReadControlData(nacp) == Loader::ResultStatus::Success
|
||||
? nacp.GetDeveloperName()
|
||||
: "";
|
||||
entry.version = "1.0.0";
|
||||
}
|
||||
|
||||
entry.version = "1.0.0";
|
||||
if (loader->GetFileType() == Loader::FileType::NRO) {
|
||||
auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
|
||||
entry.isHomebrew = loader_nro->IsHomebrew();
|
||||
} else {
|
||||
entry.isHomebrew = false;
|
||||
}
|
||||
m_rom_metadata_cache[path] = entry;
|
||||
return entry;
|
||||
}
|
||||
|
||||
if (loader->GetFileType() == Loader::FileType::NRO) {
|
||||
auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
|
||||
entry.isHomebrew = loader_nro->IsHomebrew();
|
||||
} else {
|
||||
entry.isHomebrew = false;
|
||||
}
|
||||
|
||||
m_rom_metadata_cache[path] = entry;
|
||||
|
||||
return entry;
|
||||
return {};
|
||||
}
|
||||
|
||||
RomMetadata GetRomMetadata(const std::string& path, bool reload = false) {
|
||||
if (reload) {
|
||||
static RomMetadata GetRomMetadata(const std::string& path, bool reload = false) {
|
||||
if (reload)
|
||||
return CacheRomMetadata(path);
|
||||
}
|
||||
|
||||
if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
|
||||
return search->second;
|
||||
}
|
||||
|
||||
if (auto it = m_rom_metadata_cache.find(path); it != m_rom_metadata_cache.end())
|
||||
return it->second;
|
||||
return CacheRomMetadata(path);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
const auto file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(
|
||||
Common::Android::GetJString(env, jpath), FileSys::OpenMode::Read);
|
||||
if (!file) {
|
||||
return false;
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
if (auto const file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(Common::Android::GetJString(env, jpath), FileSys::OpenMode::Read); file) {
|
||||
if (auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file); loader) {
|
||||
auto const file_type = loader->GetFileType();
|
||||
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error)
|
||||
return false;
|
||||
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) && !Loader::IsBootableGameContainer(file, file_type))
|
||||
return false;
|
||||
u64 program_id = 0;
|
||||
return loader->ReadProgramId(program_id) == Loader::ResultStatus::Success;
|
||||
}
|
||||
}
|
||||
|
||||
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file);
|
||||
if (!loader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto file_type = loader->GetFileType();
|
||||
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) &&
|
||||
!Loader::IsBootableGameContainer(file, file_type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 program_id = 0;
|
||||
Loader::ResultStatus res = loader->ReadProgramId(program_id);
|
||||
if (res != Loader::ResultStatus::Success) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return Common::Android::ToJString(
|
||||
env, GetRomMetadata(Common::Android::GetJString(env, jpath)).title);
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath)).title);
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return Common::Android::ToJString(
|
||||
env, std::to_string(GetRomMetadata(Common::Android::GetJString(env, jpath)).programId));
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
return Common::Android::ToJString(env, std::to_string(GetRomMetadata(Common::Android::GetJString(env, jpath)).programId));
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return Common::Android::ToJString(
|
||||
env, GetRomMetadata(Common::Android::GetJString(env, jpath)).developer);
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath)).developer);
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj,
|
||||
jstring jpath, jboolean jreload) {
|
||||
return Common::Android::ToJString(
|
||||
env, GetRomMetadata(Common::Android::GetJString(env, jpath), jreload).version);
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj, jstring jpath, jboolean jreload) {
|
||||
return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath), jreload).version);
|
||||
}
|
||||
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
auto icon_data = GetRomMetadata(Common::Android::GetJString(env, jpath)).icon;
|
||||
jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
|
||||
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
|
||||
reinterpret_cast<jbyte*>(icon_data.data()));
|
||||
jbyteArray icon = env->NewByteArray(jsize(icon_data.size()));
|
||||
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), reinterpret_cast<jbyte*>(icon_data.data()));
|
||||
return icon;
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return static_cast<jboolean>(
|
||||
GetRomMetadata(Common::Android::GetJString(env, jpath)).isHomebrew);
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
return jboolean(GetRomMetadata(Common::Android::GetJString(env, jpath)).isHomebrew);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_GameMetadata_resetMetadata(JNIEnv* env, jobject obj) {
|
||||
|
||||
@@ -44,12 +44,14 @@ extern "C" {
|
||||
#include "common/android/android_common.h"
|
||||
#include "common/android/id_cache.h"
|
||||
#include "common/dynamic_library.h"
|
||||
#include "common/fs/fs_util.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/thread.h"
|
||||
#include "frontend_common/play_time_manager.h"
|
||||
#include "core/constants.h"
|
||||
#include "core/core.h"
|
||||
@@ -89,6 +91,7 @@ extern "C" {
|
||||
#include "hid_core/hid_types.h"
|
||||
#include "input_common/drivers/virtual_amiibo.h"
|
||||
#include "jni/native.h"
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
#include "video_core/capture.h"
|
||||
@@ -997,7 +1000,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuSummary(JNIEnv* env, jobject
|
||||
FILE* f = std::fopen(CPUINFO_PATH, "r");
|
||||
if (!f) return Common::Android::ToJString(env, result);
|
||||
|
||||
char buf[512];
|
||||
char buf[4096];
|
||||
|
||||
if (f) {
|
||||
std::set<std::string> feature_set;
|
||||
@@ -1028,7 +1031,13 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuSummary(JNIEnv* env, jobject
|
||||
bool has_dotprod = feature_set.count("asimddp") || feature_set.count("dotprod");
|
||||
bool has_i8mm = feature_set.count("i8mm");
|
||||
bool has_bf16 = feature_set.count("bf16");
|
||||
bool has_fp16 = feature_set.count("fphp") || feature_set.count("asimdhp");
|
||||
bool has_atomics = feature_set.count("atomics") || feature_set.count("lse");
|
||||
bool has_lse2 = feature_set.count("uscat");
|
||||
bool has_rcpc = feature_set.count("lrcpc");
|
||||
bool has_rcpc2 = feature_set.count("ilrcpc");
|
||||
bool has_flagm = feature_set.count("flagm");
|
||||
bool has_flagm2 = feature_set.count("flagm2");
|
||||
|
||||
std::string features;
|
||||
if (has_neon || has_fp) {
|
||||
@@ -1036,6 +1045,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuSummary(JNIEnv* env, jobject
|
||||
if (has_dotprod) features += "+DP";
|
||||
if (has_i8mm) features += "+I8MM";
|
||||
if (has_bf16) features += "+BF16";
|
||||
if (has_fp16) features += "+FP16";
|
||||
}
|
||||
|
||||
if (has_sve) {
|
||||
@@ -1052,6 +1062,19 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuSummary(JNIEnv* env, jobject
|
||||
if (has_atomics) {
|
||||
if (!features.empty()) features += " | ";
|
||||
features += "LSE";
|
||||
if (has_lse2) features += "2";
|
||||
}
|
||||
|
||||
if (has_rcpc) {
|
||||
if (!features.empty()) features += " | ";
|
||||
features += "RCpc";
|
||||
if (has_rcpc2) features += "2";
|
||||
}
|
||||
|
||||
if (has_flagm) {
|
||||
if (!features.empty()) features += " | ";
|
||||
features += "FlagM";
|
||||
if (has_flagm2) features += "2";
|
||||
}
|
||||
|
||||
if (!features.empty()) {
|
||||
@@ -1089,6 +1112,34 @@ VkPhysicalDeviceProperties GetVulkanDeviceProperties() {
|
||||
const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld);
|
||||
return physical_device.GetProperties();
|
||||
}
|
||||
|
||||
bool GetVulkanMemoryModelSupport() {
|
||||
Common::DynamicLibrary library;
|
||||
if (!library.Open("libvulkan.so")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Vulkan::vk::InstanceDispatch dld;
|
||||
const auto instance = Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_1);
|
||||
const auto physical_devices = instance.EnumeratePhysicalDevices();
|
||||
if (physical_devices.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld);
|
||||
|
||||
VkPhysicalDeviceVulkanMemoryModelFeatures memory_model{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES,
|
||||
.pNext = nullptr,
|
||||
};
|
||||
VkPhysicalDeviceFeatures2 features{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||||
.pNext = &memory_model,
|
||||
};
|
||||
physical_device.GetFeatures2(features);
|
||||
|
||||
return memory_model.vulkanMemoryModel == VK_TRUE;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getVulkanDriverVersion(JNIEnv* env, jobject jobj) {
|
||||
@@ -1164,6 +1215,14 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getVulkanApiVersion(JNIEnv* env, j
|
||||
}
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_supportsFrameGeneration(JNIEnv* env, jobject jobj) {
|
||||
try {
|
||||
return static_cast<jboolean>(GetVulkanMemoryModelSupport());
|
||||
} catch (...) {
|
||||
return static_cast<jboolean>(false);
|
||||
}
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGpuModel(JNIEnv* env, jobject jobj) {
|
||||
const auto props = GetVulkanDeviceProperties();
|
||||
if (props.deviceID == 0) {
|
||||
@@ -1182,6 +1241,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj
|
||||
Settings::LogSettings();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jobject jobj) {
|
||||
Common::RefreshThreadPolicies();
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
|
||||
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
|
||||
}
|
||||
@@ -1385,6 +1448,39 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_installKeys(JNIEnv* env, jclass clazz
|
||||
return static_cast<int>(FirmwareManager::InstallKeys(path, ext));
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getLosslessDllPath(JNIEnv* env, jclass clazz) {
|
||||
#ifdef HAS_LSFG
|
||||
const auto path = VideoCore::FrameGen::GetLosslessDllPath();
|
||||
return Common::Android::ToJString(env, Common::FS::PathToUTF8String(path));
|
||||
#else
|
||||
return Common::Android::ToJString(env, "");
|
||||
#endif
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_validateLosslessDll(JNIEnv* env, jclass clazz) {
|
||||
#ifdef HAS_LSFG
|
||||
return static_cast<jint>(VideoCore::FrameGen::GetInstalledLosslessStatus());
|
||||
#else
|
||||
return static_cast<jint>(VideoCore::FrameGen::LosslessStatus::NotInstalled);
|
||||
#endif
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_prepareLosslessDll(JNIEnv* env, jclass clazz) {
|
||||
#ifdef HAS_LSFG
|
||||
return static_cast<jint>(VideoCore::FrameGen::BuildShaderCache());
|
||||
#else
|
||||
return static_cast<jint>(VideoCore::FrameGen::LosslessStatus::NotInstalled);
|
||||
#endif
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_removeLosslessDll(JNIEnv* env, jclass clazz) {
|
||||
#ifdef HAS_LSFG
|
||||
return static_cast<jboolean>(VideoCore::FrameGen::RemoveInstalledLosslessDll());
|
||||
#else
|
||||
return static_cast<jboolean>(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj,
|
||||
jstring jpath,
|
||||
jstring jprogramId) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M11,10.6a8,5.2 0 1,0 0,10.4a8,5.2 0 1,0 0,-10.4z" />
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M4.6,12.6L0.8,10.2L3.2,15.5z" />
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M15.3,3.5a4,4 0 1,0 0,8a4,4 0 1,0 0,-8zM16.6,4.9a1,1 0 1,0 0,2a1,1 0 1,0 0,-2z" />
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M18.6,6.5L23.2,7.8L18.6,9.3z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorSurface">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_lossless"
|
||||
style="@style/Widget.Eden.TransparentTopAppBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:touchscreenBlocksFocus="false"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar_lossless"
|
||||
style="@style/Widget.Eden.TransparentTopToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:touchscreenBlocksFocus="false"
|
||||
app:navigationIcon="@drawable/ic_back"
|
||||
app:title="@string/lossless_scaling" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/scroll_view_lossless"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:clipToPadding="false"
|
||||
android:defaultFocusHighlightEnabled="false"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar_lossless">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingTop="16dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/lossless_options_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:nestedScrollingEnabled="false" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -47,6 +47,12 @@
|
||||
android:defaultValue="@null" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/losslessManagerFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.LosslessManagerFragment"
|
||||
android:label="@string/lossless_scaling"
|
||||
tools:layout="@layout/fragment_lossless_manager" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/appletLauncherFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.AppletLauncherFragment"
|
||||
|
||||
@@ -958,7 +958,6 @@
|
||||
<string name="clock_fast">سريع (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">تعطيل</string>
|
||||
<string name="fast_gpu_medium">متوسط (256)</string>
|
||||
<string name="fast_gpu_high">مرتفع (512)</string>
|
||||
|
||||
@@ -1089,7 +1088,6 @@
|
||||
<string name="theme_mode_light">فاتح</string>
|
||||
<string name="theme_mode_dark">داكن</string>
|
||||
|
||||
<string name="multiplier_none">لا شيء</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">خلفيات سوداء</string>
|
||||
|
||||
@@ -884,7 +884,6 @@ Wirklich fortfahren?</string>
|
||||
<string name="clock_fast">Schnell (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Aus</string>
|
||||
<string name="fast_gpu_medium">Mittel (256)</string>
|
||||
<string name="fast_gpu_high">Hoch (512)</string>
|
||||
|
||||
@@ -1005,7 +1004,6 @@ Wirklich fortfahren?</string>
|
||||
<string name="theme_mode_light">Hell</string>
|
||||
<string name="theme_mode_dark">Dunkel</string>
|
||||
|
||||
<string name="multiplier_none">Keine</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">Schwarze Hintergründe</string>
|
||||
|
||||
@@ -950,7 +950,6 @@
|
||||
<string name="clock_fast">Rápido (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Desactivado</string>
|
||||
<string name="fast_gpu_medium">Medio (256)</string>
|
||||
<string name="fast_gpu_high">Alto (512)</string>
|
||||
|
||||
@@ -1081,7 +1080,6 @@
|
||||
<string name="theme_mode_light">Claro</string>
|
||||
<string name="theme_mode_dark">Oscuro</string>
|
||||
|
||||
<string name="multiplier_none">Nada</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">Fondos oscuros</string>
|
||||
|
||||
@@ -808,9 +808,6 @@
|
||||
<string name="multiplier_x4">x4</string>
|
||||
<string name="multiplier_x8">x8</string>
|
||||
<string name="multiplier_x16">x16</string>
|
||||
<string name="multiplier_x32">x32</string>
|
||||
<string name="multiplier_x64">x64</string>
|
||||
<string name="multiplier_none">None</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">پسزمینه مشکی</string>
|
||||
|
||||
@@ -897,7 +897,6 @@
|
||||
<string name="clock_fast">Rapide (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Désactivé</string>
|
||||
<string name="fast_gpu_medium">Moyen (256)</string>
|
||||
<string name="fast_gpu_high">Élevé (512)</string>
|
||||
|
||||
@@ -1017,7 +1016,6 @@
|
||||
<string name="theme_mode_light">Lumineux</string>
|
||||
<string name="theme_mode_dark">Sombre</string>
|
||||
|
||||
<string name="multiplier_none">Aucun</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">Arrière-plan noir</string>
|
||||
|
||||
@@ -871,7 +871,6 @@
|
||||
<string name="clock_fast">Szybkie (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Wyłączone</string>
|
||||
<string name="fast_gpu_medium">Średnie (256)</string>
|
||||
<string name="fast_gpu_high">Wysokie (512)</string>
|
||||
|
||||
@@ -949,7 +948,6 @@
|
||||
<string name="theme_mode_light">Jasny</string>
|
||||
<string name="theme_mode_dark">Ciemny</string>
|
||||
|
||||
<string name="multiplier_none">Brak</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">Czarne tła</string>
|
||||
|
||||
@@ -897,7 +897,6 @@
|
||||
<string name="theme_mode_light">Claro</string>
|
||||
<string name="theme_mode_dark">Escuro</string>
|
||||
|
||||
<string name="multiplier_none">Nenhum</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">Planos de fundo pretos</string>
|
||||
|
||||
@@ -954,7 +954,6 @@
|
||||
<string name="clock_fast">Быстрая (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Выкл.</string>
|
||||
<string name="fast_gpu_medium">Среднее (256)</string>
|
||||
<string name="fast_gpu_high">Высокое (512)</string>
|
||||
|
||||
@@ -1085,7 +1084,6 @@
|
||||
<string name="theme_mode_light">Светлая</string>
|
||||
<string name="theme_mode_dark">Темная</string>
|
||||
|
||||
<string name="multiplier_none">Отключено</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">Чёрный фон</string>
|
||||
|
||||
@@ -943,7 +943,6 @@
|
||||
<string name="clock_fast">Швидко (2000 МГц)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Вимкнено</string>
|
||||
<string name="fast_gpu_medium">Середньо (256)</string>
|
||||
<string name="fast_gpu_high">Високо (512)</string>
|
||||
|
||||
@@ -1067,7 +1066,6 @@
|
||||
<string name="theme_mode_light">Світла</string>
|
||||
<string name="theme_mode_dark">Темна</string>
|
||||
|
||||
<string name="multiplier_none">Жодного</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">Чорний фон</string>
|
||||
|
||||
@@ -948,7 +948,6 @@
|
||||
<string name="clock_fast">快速 (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">关闭</string>
|
||||
<string name="fast_gpu_medium">中 (256)</string>
|
||||
<string name="fast_gpu_high">高 (512)</string>
|
||||
|
||||
@@ -1079,7 +1078,6 @@
|
||||
<string name="theme_mode_light">浅色</string>
|
||||
<string name="theme_mode_dark">深色</string>
|
||||
|
||||
<string name="multiplier_none">无</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">使用黑色背景</string>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<string name="stats_overlay_options">疊加層</string>
|
||||
<string name="enable_stats_overlay_">啟用效能統計疊加層</string>
|
||||
<string name="stats_overlay_options_description">設定疊加層中顯示的資訊</string>
|
||||
<string name="show_fps">顯示FPS</string>
|
||||
<string name="show_fps">顯示影格率</string>
|
||||
<string name="show_fps_description">顯示當前影格率</string>
|
||||
<string name="show_frametime">顯示影格時間</string>
|
||||
<string name="show_app_ram_usage">顯示應用程式的記憶體用量</string>
|
||||
@@ -926,7 +926,6 @@
|
||||
<string name="theme_mode_light">淺色</string>
|
||||
<string name="theme_mode_dark">深色</string>
|
||||
|
||||
<string name="multiplier_none">無</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">黑色背景</string>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</integer-array>
|
||||
|
||||
<string-array name="clockNames">
|
||||
<item>@string/off</item>
|
||||
<item>@string/clock_normal</item>
|
||||
<item>@string/clock_boost</item>
|
||||
<item>@string/clock_fast</item>
|
||||
</string-array>
|
||||
@@ -162,6 +162,48 @@
|
||||
<item>@string/resolution_four</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="frameGenMultiplierNames">
|
||||
<item>@string/frame_gen_multiplier_2x</item>
|
||||
<item>@string/frame_gen_multiplier_3x</item>
|
||||
<item>@string/frame_gen_multiplier_4x</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="frameGenMultiplierValues">
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="frameGenTargetRateNames">
|
||||
<item>@string/frame_gen_target_rate_off</item>
|
||||
<item>@string/frame_gen_target_rate_60</item>
|
||||
<item>@string/frame_gen_target_rate_90</item>
|
||||
<item>@string/frame_gen_target_rate_120</item>
|
||||
<item>@string/frame_gen_target_rate_144</item>
|
||||
<item>@string/frame_gen_target_rate_165</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="frameGenTargetRateValues">
|
||||
<item>0</item>
|
||||
<item>60</item>
|
||||
<item>90</item>
|
||||
<item>120</item>
|
||||
<item>144</item>
|
||||
<item>165</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="frameGenQueueTargetNames">
|
||||
<item>@string/frame_gen_queue_target_0</item>
|
||||
<item>@string/frame_gen_queue_target_1</item>
|
||||
<item>@string/frame_gen_queue_target_2</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="frameGenQueueTargetValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="rendererVSyncNames">
|
||||
<item>@string/renderer_vsync_immediate</item>
|
||||
<item>@string/renderer_vsync_mailbox</item>
|
||||
@@ -471,9 +513,6 @@
|
||||
<item>@string/multiplier_x4</item>
|
||||
<item>@string/multiplier_x8</item>
|
||||
<item>@string/multiplier_x16</item>
|
||||
<item>@string/multiplier_x32</item>
|
||||
<item>@string/multiplier_x64</item>
|
||||
<item>@string/multiplier_none</item>
|
||||
</string-array>
|
||||
<integer-array name="anisoValues">
|
||||
<item>0</item>
|
||||
@@ -482,9 +521,6 @@
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
<item>6</item>
|
||||
<item>7</item>
|
||||
<item>8</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="verticalAlignmentEntries">
|
||||
@@ -547,7 +583,7 @@
|
||||
</integer-array>
|
||||
|
||||
<string-array name="gpuEntries">
|
||||
<item>@string/off</item>
|
||||
<item>@string/fast_gpu_normal</item>
|
||||
<item>@string/fast_gpu_medium</item>
|
||||
<item>@string/fast_gpu_high</item>
|
||||
</string-array>
|
||||
@@ -638,6 +674,16 @@
|
||||
<item>@string/error_keys_failed_init</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="losslessDllResults">
|
||||
<item>""</item>
|
||||
<item>@string/error_lossless_copy_failed</item>
|
||||
<item>@string/error_lossless_unreadable</item>
|
||||
<item>@string/error_lossless_not_pe</item>
|
||||
<item>@string/error_lossless_missing_shaders</item>
|
||||
<item>@string/error_lossless_translation_failed</item>
|
||||
<item>@string/error_lossless_cache_failed</item>
|
||||
</string-array>
|
||||
|
||||
<!-- GPU Logging Arrays -->
|
||||
<string-array name="gpuLogLevelEntries">
|
||||
<item>Off</item>
|
||||
|
||||
@@ -298,6 +298,67 @@
|
||||
<string name="gpu_driver_fetcher">GPU driver fetcher</string>
|
||||
<string name="gpu_driver_manager">GPU driver manager</string>
|
||||
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
||||
<string name="frame_gen">Frame generation</string>
|
||||
<string name="frame_gen_per_game_description">Configure frame generation for this game</string>
|
||||
<string name="frame_gen_description">Insert interpolated frames between rendered ones using Lossless Scaling. Forces FIFO presentation while enabled.</string>
|
||||
<string name="frame_gen_multiplier">Frame multiplier</string>
|
||||
<string name="frame_gen_multiplier_description">How many frames to display for each rendered frame. Higher values cost proportionally more GPU time. Asking for more than your display can present will slow emulation down.</string>
|
||||
<string name="frame_gen_multiplier_2x">2x</string>
|
||||
<string name="frame_gen_multiplier_3x">3x</string>
|
||||
<string name="frame_gen_multiplier_4x">4x</string>
|
||||
<string name="frame_gen_target_rate">Target frame rate</string>
|
||||
<string name="frame_gen_target_rate_description">Pick the rate your display can actually show. The multiplier then rises or falls on its own to hold it, and rolls back any step that makes the game itself run slower.</string>
|
||||
<string name="frame_gen_target_rate_off">Use a fixed multiplier</string>
|
||||
<string name="frame_gen_target_rate_60">60 FPS</string>
|
||||
<string name="frame_gen_target_rate_90">90 FPS</string>
|
||||
<string name="frame_gen_target_rate_120">120 FPS</string>
|
||||
<string name="frame_gen_target_rate_144">144 FPS</string>
|
||||
<string name="frame_gen_target_rate_165">165 FPS</string>
|
||||
<string name="frame_gen_queue_target">Frame queue target</string>
|
||||
<string name="frame_gen_queue_target_description">How many finished frames may wait ahead of the display. Larger queues absorb GPU spikes at the cost of input latency.</string>
|
||||
<string name="frame_gen_queue_target_0">Lowest latency (Unbuffered)</string>
|
||||
<string name="frame_gen_queue_target_1">Balanced (1 frame)</string>
|
||||
<string name="frame_gen_queue_target_2">Smoothest (2 frames)</string>
|
||||
<string name="frame_gen_flow_scale_auto">Match motion estimation to the game</string>
|
||||
<string name="frame_gen_flow_scale_auto_description">Estimate motion at the resolution the game actually renders instead of the upscaled output. Costs nothing in accuracy, since upscaling adds no motion detail.</string>
|
||||
<string name="frame_gen_flow_scale">Motion estimation resolution</string>
|
||||
<string name="frame_gen_flow_scale_description">Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance.</string>
|
||||
<string name="frame_gen_fp16">Half precision shaders</string>
|
||||
<string name="frame_gen_fp16_description">Use the 16-bit shader variant. Falls back automatically if the driver or the file lacks it.</string>
|
||||
<string name="frame_gen_dump_flow">Dump generated frame</string>
|
||||
<string name="frame_gen_dump_flow_description">Write the optical flow mip levels and the interpolated frame to the lossless/debug folder once, for troubleshooting</string>
|
||||
<string name="frame_gen_unsupported">Frame generation unavailable</string>
|
||||
<string name="frame_gen_unsupported_description">This GPU driver does not support the Vulkan memory model, which the Lossless Scaling shaders require.</string>
|
||||
<string name="lossless_scaling_setup_description">Optional. Provide your own Lossless.dll to enable frame generation later</string>
|
||||
<string name="lossless_scaling_install">Install Lossless.dll</string>
|
||||
<string name="lossless_scaling_install_description">Frame generation needs your own legal copy of Lossless.dll from Lossless Scaling</string>
|
||||
<string name="lossless_scaling_replace_description">Select a different copy of Lossless.dll</string>
|
||||
<string name="frame_generation_support">Frame generation</string>
|
||||
<string name="frame_generation_supported">Supported</string>
|
||||
<string name="frame_generation_unsupported">Unsupported (no Vulkan memory model)</string>
|
||||
<string name="lossless_scaling">Lossless Scaling</string>
|
||||
<string name="lossless_scaling_description">Provide your own copy of Lossless.dll to enable frame generation</string>
|
||||
<string name="lossless_scaling_installed">Installed</string>
|
||||
<string name="lossless_scaling_not_installed">Not installed</string>
|
||||
<string name="lossless_scaling_replace">Replace</string>
|
||||
<string name="lossless_scaling_remove">Remove</string>
|
||||
<string name="lossless_scaling_remove_description">Delete the installed Lossless.dll and its prepared shaders</string>
|
||||
<string name="lossless_scaling_remove_confirmation">Frame generation will stop working until you install Lossless.dll again. Your original file is not affected.</string>
|
||||
<string name="lossless_scaling_missing">Lossless.dll not installed</string>
|
||||
<string name="lossless_scaling_missing_description">Install it from Settings › Lossless Scaling to use frame generation.</string>
|
||||
<string name="lossless_scaling_locked">Close the game first</string>
|
||||
<string name="lossless_scaling_locked_description">Lossless.dll cannot be changed while a game is running.</string>
|
||||
<string name="lossless_scaling_remove_unavailable">Nothing to remove</string>
|
||||
<string name="lossless_scaling_remove_unavailable_description">Lossless.dll is not installed yet.</string>
|
||||
<string name="lossless_scaling_installing">Preparing frame generation shaders…</string>
|
||||
<string name="lossless_scaling_install_success">Lossless.dll installed successfully</string>
|
||||
<string name="lossless_scaling_install_failed">Could not install Lossless.dll</string>
|
||||
<string name="error_lossless_copy_failed">The selected file could not be copied.</string>
|
||||
<string name="error_lossless_unreadable">The selected file could not be read.</string>
|
||||
<string name="error_lossless_not_pe">The selected file is not a Windows library. Select Lossless.dll from your Lossless Scaling installation.</string>
|
||||
<string name="error_lossless_missing_shaders">This copy of Lossless.dll does not contain the frame generation shaders. Update Lossless Scaling and try again.</string>
|
||||
<string name="error_lossless_translation_failed">The frame generation shaders could not be translated. This version of Lossless Scaling is not supported yet.</string>
|
||||
<string name="error_lossless_cache_failed">The translated shaders could not be written to storage. Check that there is free space available.</string>
|
||||
<string name="advanced_settings">Advanced settings</string>
|
||||
<string name="settings_description">Configure emulator settings</string>
|
||||
<string name="search_recently_played">Recently played</string>
|
||||
@@ -450,8 +511,8 @@
|
||||
<string name="set_custom_rtc">Set custom RTC</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">CPU Overclock</string>
|
||||
<string name="fast_cpu_time_description">Forces the emulated CPU to run at a higher clock, reducing certain FPS limiters. Use Boost (1700MHz) to run at the Switch\'s highest native clock, or Fast (2000MHz) to run at 2x clock.</string>
|
||||
<string name="fast_cpu_time">CPU Clocks</string>
|
||||
<string name="fast_cpu_time_description">Raises the clock the emulated CPU reports, which removes some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly.</string>
|
||||
<string name="custom_cpu_ticks">Custom CPU Ticks</string>
|
||||
<string name="custom_cpu_ticks_description">Set a custom value of CPU ticks. Higher values can increase performance, but may also cause the game to freeze. A range of 77–21000 is recommended.</string>
|
||||
<string name="cpu_ticks">Ticks</string>
|
||||
@@ -512,8 +573,8 @@
|
||||
|
||||
<string name="hacks">Hacks</string>
|
||||
|
||||
<string name="fast_gpu_time">Fast GPU Time</string>
|
||||
<string name="fast_gpu_time_description">Forces most games to run at their highest native resolution. Use 256 for maximal performance and 512 for maximal graphics fidelity.</string>
|
||||
<string name="fast_gpu_time">GPU Clocks</string>
|
||||
<string name="fast_gpu_time_description">Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch\'s clocks.</string>
|
||||
<string name="skip_cpu_inner_invalidation">Skip CPU Inner Invalidation</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games.</string>
|
||||
<string name="fix_bloom_effects">Fix Bloom Effects</string>
|
||||
@@ -560,6 +621,7 @@
|
||||
|
||||
<!-- Debug settings strings -->
|
||||
<string name="cpu">CPU</string>
|
||||
<string name="clocks">Clocks</string>
|
||||
<string name="use_auto_stub">Use Auto Stub</string>
|
||||
<string name="use_auto_stub_description">Automatically stub missing services and functions. This may improve compatibility but can cause crashes and stability issues.</string>
|
||||
|
||||
@@ -968,14 +1030,15 @@
|
||||
<string name="memory_6gb">6GB (Unsafe)</string>
|
||||
<string name="memory_8gb">8GB (Unsafe)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Fast (2000MHz)</string>
|
||||
<!-- CPU clock levels -->
|
||||
<string name="clock_normal">Normal</string>
|
||||
<string name="clock_boost">Boost</string>
|
||||
<string name="clock_fast">Overclock</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Off</string>
|
||||
<string name="fast_gpu_medium">Medium (256)</string>
|
||||
<string name="fast_gpu_high">High (512)</string>
|
||||
<!-- GPU clock levels -->
|
||||
<string name="fast_gpu_normal">Normal</string>
|
||||
<string name="fast_gpu_medium">Boost</string>
|
||||
<string name="fast_gpu_high">Overclock</string>
|
||||
|
||||
<!-- GPU swizzle texture size -->
|
||||
<string name="gpu_texturesizeswizzle_verysmall">Very Small (16 MB)</string>
|
||||
@@ -1183,9 +1246,6 @@
|
||||
<string name="multiplier_x4" translatable="false">x4</string>
|
||||
<string name="multiplier_x8" translatable="false">x8</string>
|
||||
<string name="multiplier_x16" translatable="false">x16</string>
|
||||
<string name="multiplier_x32" translatable="false">x32</string>
|
||||
<string name="multiplier_x64" translatable="false">x64</string>
|
||||
<string name="multiplier_none">None</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">Black backgrounds</string>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<game-mode-config
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:supportsBatteryGameMode="true"
|
||||
android:supportsBatteryGameMode="false"
|
||||
android:supportsPerformanceGameMode="true"
|
||||
android:allowGameDownscaling="false"
|
||||
android:allowGameFpsOverride="false"/>
|
||||
@@ -222,7 +222,7 @@ if (MSVC)
|
||||
)
|
||||
else()
|
||||
target_compile_options(audio_core PRIVATE
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=conversion>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wconversion>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -18,20 +18,15 @@
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
constexpr u32 TempBufferSize = 0x3F00;
|
||||
constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
|
||||
|
||||
/**
|
||||
* Decode PCM data. Only s16 or f32 is supported.
|
||||
*
|
||||
* @tparam T - Type to decode. Only s16 and f32 are supported.
|
||||
* @param memory - Core memory for reading samples.
|
||||
* @param out_buffer - Output mix buffer to receive the samples.
|
||||
* @param req - Information for how to decode.
|
||||
* @return Number of samples decoded.
|
||||
*/
|
||||
/// @brief Decode PCM data. Only s16 or f32 is supported.
|
||||
/// @tparam T - Type to decode. Only s16 and f32 are supported.
|
||||
/// @param memory - Core memory for reading samples.
|
||||
/// @param out_buffer - Output mix buffer to receive the samples.
|
||||
/// @param req - Information for how to decode.
|
||||
/// @return Number of samples decoded.
|
||||
template <typename T>
|
||||
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, const DecodeArg& req) {
|
||||
constexpr s32 min{(std::numeric_limits<s16>::min)()};
|
||||
constexpr s32 max{(std::numeric_limits<s16>::max)()};
|
||||
|
||||
@@ -94,16 +89,12 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
return samples_to_decode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode ADPCM data.
|
||||
*
|
||||
* @param memory - Core memory for reading samples.
|
||||
* @param out_buffer - Output mix buffer to receive the samples.
|
||||
* @param req - Information for how to decode.
|
||||
* @return Number of samples decoded.
|
||||
*/
|
||||
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
/// @brief Decode ADPCM data.
|
||||
/// @param memory - Core memory for reading samples.
|
||||
/// @param out_buffer - Output mix buffer to receive the samples.
|
||||
/// @param req - Information for how to decode.
|
||||
/// @return Number of samples decoded.
|
||||
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, const DecodeArg& req) {
|
||||
constexpr u32 SamplesPerFrame{14};
|
||||
constexpr u32 NibblesPerFrame{16};
|
||||
|
||||
@@ -115,8 +106,7 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto end{(req.end_offset % SamplesPerFrame) +
|
||||
NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
|
||||
auto end{(req.end_offset % SamplesPerFrame) + NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
|
||||
if (req.end_offset % SamplesPerFrame) {
|
||||
end += 3;
|
||||
} else {
|
||||
@@ -133,52 +123,49 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto samples_to_read{samples_to_process};
|
||||
auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
|
||||
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
|
||||
samples_remaining_in_frame};
|
||||
|
||||
auto samples_to_read = samples_to_process;
|
||||
auto samples_remaining_in_frame = start_pos % SamplesPerFrame;
|
||||
auto position_in_frame = (start_pos / SamplesPerFrame) * NibblesPerFrame + samples_remaining_in_frame;
|
||||
if (samples_remaining_in_frame) {
|
||||
position_in_frame += 2;
|
||||
}
|
||||
|
||||
const auto size{(std::max)((samples_to_process / 8U) * SamplesPerFrame, 8U)};
|
||||
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> wavebuffer(
|
||||
memory, req.buffer + position_in_frame / 2, size);
|
||||
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> wavebuffer(memory, req.buffer + position_in_frame / 2, size);
|
||||
|
||||
auto context{req.adpcm_context};
|
||||
auto header{context->header};
|
||||
u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)};
|
||||
u8 scale{static_cast<u8>(header & 0xFU)};
|
||||
s32 coeff0{req.coefficients[coeff_index * 2 + 0]};
|
||||
s32 coeff1{req.coefficients[coeff_index * 2 + 1]};
|
||||
auto context = req.adpcm_context;
|
||||
auto header = context->header;
|
||||
u8 scale = u8(header & 0xfU);
|
||||
u8 coeff_index = u8((header >> 4U) & 0x7u);
|
||||
s32 coeff0 = req.coefficients[coeff_index * 2 + 0];
|
||||
s32 coeff1 = req.coefficients[coeff_index * 2 + 1];
|
||||
|
||||
auto yn0{context->yn0};
|
||||
auto yn1{context->yn1};
|
||||
|
||||
static constexpr std::array<s32, 16> Steps{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
|
||||
auto yn0 = context->yn0;
|
||||
auto yn1 = context->yn1;
|
||||
auto const get_step = [](u32 index) {
|
||||
// Emulates the following table
|
||||
// 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
|
||||
constexpr u64 steps_table = 0x1234567876543210ull;
|
||||
constexpr u64 steps_sign = 0b1111111100000000ull;
|
||||
auto const r = s32((steps_table >> (index * 4)) & 0xf);
|
||||
return ((steps_sign >> index) & 1) == 0 ? -r : r;
|
||||
};
|
||||
|
||||
const auto decode_sample = [&](const s32 code) -> s16 {
|
||||
auto const decode_sample = [&](const s32 code) -> s16 {
|
||||
const auto xn = code * (1 << scale);
|
||||
const auto prediction = coeff0 * yn0 + coeff1 * yn1;
|
||||
const auto sample = ((xn << 11) + 0x400 + prediction) >> 11;
|
||||
const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF);
|
||||
yn1 = yn0;
|
||||
yn0 = static_cast<s16>(saturated);
|
||||
return yn0;
|
||||
return yn0 = s16(saturated);
|
||||
};
|
||||
|
||||
u32 read_index{0};
|
||||
u32 write_index{0};
|
||||
|
||||
while (samples_to_read > 0) {
|
||||
u32 read_index = 0;
|
||||
for (u32 write_index = 0; samples_to_read > 0 && write_index < out_buffer.size(); ) {
|
||||
// Are we at a new frame?
|
||||
if ((position_in_frame % NibblesPerFrame) == 0) {
|
||||
header = wavebuffer[read_index++];
|
||||
coeff_index = (header >> 4) & 0xF;
|
||||
scale = header & 0xF;
|
||||
scale = header & 0xFu;
|
||||
coeff_index = (header >> 4) & 0x7u;
|
||||
coeff0 = req.coefficients[coeff_index * 2 + 0];
|
||||
coeff1 = req.coefficients[coeff_index * 2 + 1];
|
||||
position_in_frame += 2;
|
||||
@@ -187,14 +174,12 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
if (samples_to_read >= SamplesPerFrame) {
|
||||
// Can grab all samples until the next header
|
||||
for (u32 i = 0; i < SamplesPerFrame / 2; i++) {
|
||||
auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]};
|
||||
auto code1{Steps[wavebuffer[read_index] & 0xF]};
|
||||
read_index++;
|
||||
|
||||
auto code0 = get_step((wavebuffer[read_index + i] >> 4) & 0xF);
|
||||
auto code1 = get_step(wavebuffer[read_index + i] & 0xF);
|
||||
out_buffer[write_index++] = decode_sample(code0);
|
||||
out_buffer[write_index++] = decode_sample(code1);
|
||||
}
|
||||
|
||||
read_index += SamplesPerFrame / 2;
|
||||
position_in_frame += SamplesPerFrame;
|
||||
samples_to_read -= SamplesPerFrame;
|
||||
continue;
|
||||
@@ -202,15 +187,14 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
}
|
||||
|
||||
// Decode a single sample
|
||||
auto code{wavebuffer[read_index]};
|
||||
auto code = wavebuffer[read_index];
|
||||
if (position_in_frame & 1) {
|
||||
code &= 0xF;
|
||||
read_index++;
|
||||
} else {
|
||||
code >>= 4;
|
||||
}
|
||||
|
||||
out_buffer[write_index++] = decode_sample(Steps[code]);
|
||||
out_buffer[write_index++] = decode_sample(get_step(code));
|
||||
|
||||
position_in_frame++;
|
||||
samples_to_read--;
|
||||
@@ -219,27 +203,21 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
context->header = header;
|
||||
context->yn0 = yn0;
|
||||
context->yn1 = yn1;
|
||||
|
||||
return samples_to_process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode implementation.
|
||||
* Decode wavebuffers according to the given args.
|
||||
*
|
||||
* @param memory - Core memory to read data from.
|
||||
* @param args - The wavebuffer data, and information for how to decode it.
|
||||
*/
|
||||
/// @brief Decode implementation.
|
||||
/// Decode wavebuffers according to the given args.
|
||||
///
|
||||
/// @param memory - Core memory to read data from.
|
||||
/// @param args - The wavebuffer data, and information for how to decode it.
|
||||
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
|
||||
static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index,
|
||||
auto& played_samples, auto& consumed) -> void {
|
||||
constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index, auto& played_samples, auto& consumed) -> void {
|
||||
voice_state.wave_buffer_valid[index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_samples = 0;
|
||||
}
|
||||
|
||||
index = (index + 1) % MaxWaveBuffers;
|
||||
consumed++;
|
||||
};
|
||||
@@ -255,8 +233,9 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
return;
|
||||
}
|
||||
|
||||
auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]};
|
||||
if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) {
|
||||
// 0 -> 4, 1 -> 8, 2 -> 4
|
||||
auto pitch = u32((0x040804ul >> (u32(args.src_quality) * 8)) & 0xfful);
|
||||
if (u32(pitch + size_required.to_int_floor()) > TempBufferSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -272,7 +251,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
bool is_buffer_starved{false};
|
||||
u32 offset{voice_state.offset};
|
||||
|
||||
auto output_buffer{args.output};
|
||||
auto output_buffer = args.output;
|
||||
std::array<s16, TempBufferSize> temp_buffer{};
|
||||
|
||||
while (remaining_sample_count > 0) {
|
||||
@@ -294,8 +273,8 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
if (wavebuffer_index >= MaxWaveBuffers) {
|
||||
LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index);
|
||||
wavebuffer_index = 0;
|
||||
voice_state.wave_buffer_valid.fill(false);
|
||||
wavebuffers_consumed = MaxWaveBuffers;
|
||||
voice_state.wave_buffer_valid.fill(false);
|
||||
}
|
||||
|
||||
if (!voice_state.wave_buffer_valid[wavebuffer_index]) {
|
||||
@@ -303,12 +282,9 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
break;
|
||||
}
|
||||
|
||||
auto& wavebuffer{args.wave_buffers[wavebuffer_index]};
|
||||
|
||||
if (offset == 0 && args.sample_format == SampleFormat::Adpcm &&
|
||||
wavebuffer.context != 0) {
|
||||
memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context,
|
||||
wavebuffer.context_size);
|
||||
auto& wavebuffer = args.wave_buffers[wavebuffer_index];
|
||||
if (offset == 0 && args.sample_format == SampleFormat::Adpcm && wavebuffer.context != 0) {
|
||||
memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context, wavebuffer.context_size);
|
||||
}
|
||||
|
||||
auto start_offset{wavebuffer.start_offset};
|
||||
@@ -351,9 +327,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
case SampleFormat::Adpcm: {
|
||||
decode_arg.adpcm_context = &voice_state.adpcm_context;
|
||||
memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size);
|
||||
samples_decoded = DecodeAdpcm(
|
||||
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
|
||||
decode_arg);
|
||||
samples_decoded = DecodeAdpcm( memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, decode_arg);
|
||||
} break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -5,21 +8,13 @@
|
||||
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input,
|
||||
const Common::FixedPoint<49, 15>& sample_rate_ratio,
|
||||
Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
|
||||
if (sample_rate_ratio == 1.0f) {
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
output[i] = input[i];
|
||||
}
|
||||
} else {
|
||||
u32 read_index{0};
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
output[i] = input[read_index + (fraction >= 0.5f)];
|
||||
fraction += sample_rate_ratio;
|
||||
read_index += static_cast<u32>(fraction.to_int_floor());
|
||||
fraction.clear_int();
|
||||
}
|
||||
static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input, const Common::FixedPoint<49, 15>& sample_rate_ratio, Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
|
||||
u32 read_index{0};
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
output[i] = input[read_index + (fraction >= 0.5f)];
|
||||
fraction += sample_rate_ratio;
|
||||
read_index += u32(fraction.to_int_floor());
|
||||
fraction.clear_int();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +290,7 @@ static void ResampleNormalQuality(std::span<s32> output, std::span<const s16> in
|
||||
auto lut{get_lut()};
|
||||
u32 read_index{0};
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
const auto lut_index{(fraction.get_frac() >> 8) * 4};
|
||||
const auto lut_index = ((fraction.get_frac() >> 8) << 2) & 511;
|
||||
const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
|
||||
const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
|
||||
const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
|
||||
@@ -845,7 +840,7 @@ static void ResampleHighQuality(std::span<s32> output, std::span<const s16> inpu
|
||||
auto lut{get_lut()};
|
||||
u32 read_index{0};
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
const auto lut_index{(fraction.get_frac() >> 8) * 8};
|
||||
const auto lut_index = ((fraction.get_frac() >> 8) << 3) & 1023;
|
||||
const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
|
||||
const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
|
||||
const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
|
||||
|
||||
@@ -121,6 +121,8 @@ add_library(
|
||||
swap.h
|
||||
thread.cpp
|
||||
thread.h
|
||||
adpf.cpp
|
||||
adpf.h
|
||||
thread_queue_list.h
|
||||
thread_worker.h
|
||||
threadsafe_queue.h
|
||||
@@ -218,7 +220,7 @@ endif()
|
||||
if(CXX_CLANG)
|
||||
target_compile_options(common PRIVATE
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-fsized-deallocation>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=unreachable-code-aggressive>)
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wunreachable-code-aggressive>)
|
||||
target_compile_definitions(
|
||||
common
|
||||
PRIVATE
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/adpf.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/logging.h"
|
||||
|
||||
namespace Common::ADPF {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::chrono::nanoseconds DEFAULT_TARGET = std::chrono::nanoseconds{16'666'667};
|
||||
|
||||
struct AHintManager;
|
||||
struct AHintSession;
|
||||
|
||||
using PFN_GetManager = AHintManager* (*)();
|
||||
using PFN_CreateSession = AHintSession* (*)(AHintManager*, const s32*, size_t, s64);
|
||||
using PFN_CloseSession = void (*)(AHintSession*);
|
||||
using PFN_UpdateTarget = int (*)(AHintSession*, s64);
|
||||
using PFN_ReportActual = int (*)(AHintSession*, s64);
|
||||
using PFN_SetThreads = int (*)(AHintSession*, const pid_t*, size_t);
|
||||
using PFN_SetPowerEfficiency = int (*)(AHintSession*, bool);
|
||||
|
||||
struct Api {
|
||||
PFN_GetManager get_manager = nullptr;
|
||||
PFN_CreateSession create_session = nullptr;
|
||||
PFN_CloseSession close_session = nullptr;
|
||||
PFN_UpdateTarget update_target = nullptr;
|
||||
PFN_ReportActual report_actual = nullptr;
|
||||
PFN_SetThreads set_threads = nullptr;
|
||||
PFN_SetPowerEfficiency set_power_efficiency = nullptr;
|
||||
AHintManager* manager = nullptr;
|
||||
bool usable = false;
|
||||
};
|
||||
|
||||
const Api& Resolve() {
|
||||
static const Api api = [] {
|
||||
Api resolved;
|
||||
void* library = dlopen("libandroid.so", RTLD_NOW);
|
||||
if (library == nullptr) {
|
||||
LOG_INFO(Common, "libandroid.so unavailable, ADPF is disabled");
|
||||
return resolved;
|
||||
}
|
||||
const auto load = [library](const char* name) { return dlsym(library, name); };
|
||||
|
||||
resolved.get_manager = reinterpret_cast<PFN_GetManager>(load("APerformanceHint_getManager"));
|
||||
resolved.create_session =
|
||||
reinterpret_cast<PFN_CreateSession>(load("APerformanceHint_createSession"));
|
||||
resolved.close_session =
|
||||
reinterpret_cast<PFN_CloseSession>(load("APerformanceHint_closeSession"));
|
||||
resolved.update_target =
|
||||
reinterpret_cast<PFN_UpdateTarget>(load("APerformanceHint_updateTargetWorkDuration"));
|
||||
resolved.report_actual =
|
||||
reinterpret_cast<PFN_ReportActual>(load("APerformanceHint_reportActualWorkDuration"));
|
||||
resolved.set_threads =
|
||||
reinterpret_cast<PFN_SetThreads>(load("APerformanceHint_setThreads"));
|
||||
resolved.set_power_efficiency = reinterpret_cast<PFN_SetPowerEfficiency>(
|
||||
load("APerformanceHint_setPreferPowerEfficiency"));
|
||||
|
||||
if (resolved.get_manager == nullptr || resolved.create_session == nullptr ||
|
||||
resolved.close_session == nullptr || resolved.update_target == nullptr ||
|
||||
resolved.report_actual == nullptr) {
|
||||
LOG_INFO(Common, "Performance hint API not exported, ADPF is disabled");
|
||||
return resolved;
|
||||
}
|
||||
|
||||
resolved.manager = resolved.get_manager();
|
||||
if (resolved.manager == nullptr) {
|
||||
LOG_INFO(Common, "Device does not provide a performance hint manager");
|
||||
return resolved;
|
||||
}
|
||||
|
||||
resolved.usable = true;
|
||||
LOG_INFO(Common, "ADPF available, setThreads {}, power efficiency {}",
|
||||
resolved.set_threads != nullptr ? "yes" : "no",
|
||||
resolved.set_power_efficiency != nullptr ? "yes" : "no");
|
||||
return resolved;
|
||||
}();
|
||||
return api;
|
||||
}
|
||||
|
||||
struct SessionState {
|
||||
AHintSession* handle = nullptr;
|
||||
std::vector<pid_t> threads;
|
||||
bool unsupported = false;
|
||||
};
|
||||
|
||||
std::mutex g_mutex;
|
||||
std::array<SessionState, 2> g_sessions;
|
||||
|
||||
constexpr s64 MAX_REPORTED_TARGETS = 4;
|
||||
|
||||
std::atomic<s64> g_target_ns{DEFAULT_TARGET.count()};
|
||||
thread_local std::chrono::steady_clock::time_point t_last_frame{};
|
||||
|
||||
SessionState& StateOf(Session session) {
|
||||
return g_sessions[static_cast<size_t>(session)];
|
||||
}
|
||||
|
||||
bool IsBackgroundUsable(const Api& api) {
|
||||
return api.set_power_efficiency != nullptr;
|
||||
}
|
||||
|
||||
void CloseLocked(SessionState& state) {
|
||||
if (state.handle != nullptr) {
|
||||
Resolve().close_session(state.handle);
|
||||
state.handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AHintSession* CreateSessionFor(Session session, const std::vector<pid_t>& threads) {
|
||||
const Api& api = Resolve();
|
||||
const s64 target = session == Session::Render ? g_target_ns.load(std::memory_order_relaxed) : 0;
|
||||
|
||||
std::vector<s32> ids;
|
||||
ids.reserve(threads.size());
|
||||
for (const pid_t tid : threads) {
|
||||
ids.push_back(static_cast<s32>(tid));
|
||||
}
|
||||
|
||||
AHintSession* handle = api.create_session(api.manager, ids.data(), ids.size(), target);
|
||||
if (handle == nullptr && target == 0) {
|
||||
handle = api.create_session(api.manager, ids.data(), ids.size(), DEFAULT_TARGET.count());
|
||||
}
|
||||
if (handle == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (session == Session::Background && api.set_power_efficiency != nullptr) {
|
||||
api.set_power_efficiency(handle, true);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool SyncLocked(Session session, SessionState& state) {
|
||||
if (state.threads.empty()) {
|
||||
CloseLocked(state);
|
||||
return false;
|
||||
}
|
||||
|
||||
const Api& api = Resolve();
|
||||
if (state.handle != nullptr && api.set_threads != nullptr) {
|
||||
std::vector<pid_t> ids = state.threads;
|
||||
if (api.set_threads(state.handle, ids.data(), ids.size()) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
AHintSession* const replacement = CreateSessionFor(session, state.threads);
|
||||
if (replacement == nullptr) {
|
||||
if (state.handle == nullptr) {
|
||||
state.unsupported = true;
|
||||
}
|
||||
LOG_WARNING(Common, "Could not open a performance hint session for {} threads, falling back",
|
||||
state.threads.size());
|
||||
return false;
|
||||
}
|
||||
CloseLocked(state);
|
||||
state.handle = replacement;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
bool IsSessionSupported(Session session) {
|
||||
const Api& api = Resolve();
|
||||
if (!api.usable) {
|
||||
return false;
|
||||
}
|
||||
if (session == Session::Background && !IsBackgroundUsable(api)) {
|
||||
return false;
|
||||
}
|
||||
std::scoped_lock lock{g_mutex};
|
||||
return !StateOf(session).unsupported;
|
||||
}
|
||||
|
||||
bool AddCurrentThread(Session session) {
|
||||
if (!IsSessionSupported(session)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pid_t tid = gettid();
|
||||
std::scoped_lock lock{g_mutex};
|
||||
|
||||
for (size_t i = 0; i < g_sessions.size(); ++i) {
|
||||
SessionState& state = g_sessions[i];
|
||||
if (static_cast<size_t>(session) == i) {
|
||||
continue;
|
||||
}
|
||||
const auto it = std::find(state.threads.begin(), state.threads.end(), tid);
|
||||
if (it != state.threads.end()) {
|
||||
state.threads.erase(it);
|
||||
SyncLocked(static_cast<Session>(i), state);
|
||||
}
|
||||
}
|
||||
|
||||
SessionState& state = StateOf(session);
|
||||
const bool added =
|
||||
std::find(state.threads.begin(), state.threads.end(), tid) == state.threads.end();
|
||||
if (added) {
|
||||
state.threads.push_back(tid);
|
||||
}
|
||||
if (!SyncLocked(session, state)) {
|
||||
if (added) {
|
||||
std::erase(state.threads, tid);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RemoveCurrentThread() {
|
||||
if (!Resolve().usable) {
|
||||
return;
|
||||
}
|
||||
const pid_t tid = gettid();
|
||||
std::scoped_lock lock{g_mutex};
|
||||
for (size_t i = 0; i < g_sessions.size(); ++i) {
|
||||
SessionState& state = g_sessions[i];
|
||||
if (std::erase(state.threads, tid) != 0) {
|
||||
SyncLocked(static_cast<Session>(i), state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetTargetWorkDuration(std::chrono::nanoseconds target) {
|
||||
const Api& api = Resolve();
|
||||
if (!api.usable || target.count() <= 0) {
|
||||
return;
|
||||
}
|
||||
if (g_target_ns.exchange(target.count(), std::memory_order_relaxed) == target.count()) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SessionState& state = StateOf(Session::Render);
|
||||
if (state.handle != nullptr) {
|
||||
api.update_target(state.handle, target.count());
|
||||
}
|
||||
}
|
||||
|
||||
void ReportFrameInterval() {
|
||||
const Api& api = Resolve();
|
||||
if (!api.usable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto previous = t_last_frame;
|
||||
t_last_frame = now;
|
||||
if (previous.time_since_epoch().count() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
s64 actual = std::chrono::duration_cast<std::chrono::nanoseconds>(now - previous).count();
|
||||
if (actual <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const s64 ceiling = g_target_ns.load(std::memory_order_relaxed) * MAX_REPORTED_TARGETS;
|
||||
actual = (std::min)(actual, ceiling);
|
||||
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SessionState& state = StateOf(Session::Render);
|
||||
if (state.handle != nullptr) {
|
||||
api.report_actual(state.handle, actual);
|
||||
}
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
if (!Resolve().usable) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{g_mutex};
|
||||
for (SessionState& state : g_sessions) {
|
||||
CloseLocked(state);
|
||||
state.threads.clear();
|
||||
state.unsupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Common::ADPF
|
||||
|
||||
#else
|
||||
|
||||
namespace Common::ADPF {
|
||||
|
||||
bool IsSessionSupported(Session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AddCurrentThread(Session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void RemoveCurrentThread() {}
|
||||
|
||||
void SetTargetWorkDuration(std::chrono::nanoseconds) {}
|
||||
|
||||
void ReportFrameInterval() {}
|
||||
|
||||
void Shutdown() {}
|
||||
|
||||
} // namespace Common::ADPF
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace Common::ADPF {
|
||||
|
||||
enum class Session {
|
||||
Render,
|
||||
Background,
|
||||
};
|
||||
|
||||
bool IsSessionSupported(Session session);
|
||||
|
||||
bool AddCurrentThread(Session session);
|
||||
void RemoveCurrentThread();
|
||||
|
||||
void SetTargetWorkDuration(std::chrono::nanoseconds target);
|
||||
|
||||
void ReportFrameInterval();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
} // namespace Common::ADPF
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -20,6 +20,7 @@
|
||||
#define KEYS_DIR "keys"
|
||||
#define LOAD_DIR "load"
|
||||
#define LOG_DIR "log"
|
||||
#define LOSSLESS_DIR "lossless"
|
||||
#define NAND_DIR "nand"
|
||||
#define PLAY_TIME_DIR "play_time"
|
||||
#define SCREENSHOTS_DIR "screenshots"
|
||||
@@ -37,3 +38,5 @@
|
||||
|
||||
// yuzu-specific files
|
||||
#define LOG_FILE "eden_log.txt"
|
||||
#define LOSSLESS_DLL_FILE "Lossless.dll"
|
||||
#define LOSSLESS_CACHE_FILE "lsfg_spirv.cache"
|
||||
|
||||
@@ -157,6 +157,7 @@ public:
|
||||
GenerateEdenPath(EdenPath::KeysDir, eden_path / KEYS_DIR);
|
||||
GenerateEdenPath(EdenPath::LoadDir, eden_path / LOAD_DIR);
|
||||
GenerateEdenPath(EdenPath::LogDir, eden_path / LOG_DIR);
|
||||
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
|
||||
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
|
||||
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
|
||||
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -23,6 +23,7 @@ enum class EdenPath {
|
||||
KeysDir, // Where key files are stored.
|
||||
LoadDir, // Where cheat/mod files are stored.
|
||||
LogDir, // Where log files are stored.
|
||||
LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
|
||||
NANDDir, // Where the emulated NAND is stored.
|
||||
PlayTimeDir, // Where play time data is stored.
|
||||
SaveDir, // Where save data is stored.
|
||||
|
||||
@@ -380,6 +380,28 @@ void UpdateRescalingInfo() {
|
||||
TranslateResolutionInfo(setup, info);
|
||||
}
|
||||
|
||||
u32 FrameGenMultiplier() {
|
||||
return std::clamp(values.frame_gen_multiplier.GetValue(), MIN_FRAME_GEN_MULTIPLIER,
|
||||
MAX_FRAME_GEN_MULTIPLIER);
|
||||
}
|
||||
|
||||
size_t FrameGenGenerations() {
|
||||
if (!values.frame_gen.GetValue()) {
|
||||
return 0;
|
||||
}
|
||||
return FrameGenMultiplier() - 1;
|
||||
}
|
||||
|
||||
size_t FrameGenMaxGenerations() {
|
||||
if (!values.frame_gen.GetValue()) {
|
||||
return 0;
|
||||
}
|
||||
if (values.frame_gen_target_rate.GetValue() != 0) {
|
||||
return MAX_FRAME_GEN_MULTIPLIER - 1;
|
||||
}
|
||||
return FrameGenMultiplier() - 1;
|
||||
}
|
||||
|
||||
void RestoreGlobalState(bool is_powered_on) {
|
||||
// If a game is running, DO NOT restore the global settings state
|
||||
if (is_powered_on) {
|
||||
|
||||
+95
-19
@@ -260,10 +260,10 @@ struct Values {
|
||||
Category::Cpu};
|
||||
SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto,
|
||||
"cpu_accuracy", Category::Cpu};
|
||||
SwitchableSetting<CpuClock> fast_cpu_time{linkage,
|
||||
CpuClock::Off,
|
||||
SwitchableSetting<CpuClock> cpu_clock{linkage,
|
||||
CpuClock::Normal,
|
||||
"fast_cpu_time",
|
||||
Category::Cpu,
|
||||
Category::System,
|
||||
Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
@@ -352,7 +352,7 @@ struct Values {
|
||||
true};
|
||||
|
||||
SwitchableSetting<ScalingFilter> scaling_filter{linkage,
|
||||
ScalingFilter::Bilinear,
|
||||
ScalingFilter::NearestNeighbor,
|
||||
"scaling_filter",
|
||||
Category::Renderer,
|
||||
Specialization::Default,
|
||||
@@ -388,6 +388,69 @@ struct Values {
|
||||
true,
|
||||
true};
|
||||
|
||||
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
|
||||
Specialization::Default, true, false};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_multiplier{linkage,
|
||||
2,
|
||||
2,
|
||||
4,
|
||||
"frame_gen_multiplier",
|
||||
Category::Renderer,
|
||||
Specialization::Countable,
|
||||
true,
|
||||
false,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_target_rate{linkage,
|
||||
0,
|
||||
0,
|
||||
240,
|
||||
"frame_gen_target_rate",
|
||||
Category::Renderer,
|
||||
Specialization::Countable,
|
||||
true,
|
||||
true,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<bool> frame_gen_flow_scale_auto{linkage,
|
||||
true,
|
||||
"frame_gen_flow_scale_auto",
|
||||
Category::Renderer,
|
||||
Specialization::Default,
|
||||
true,
|
||||
false,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_flow_scale{linkage,
|
||||
75,
|
||||
25,
|
||||
100,
|
||||
"frame_gen_flow_scale",
|
||||
Category::Renderer,
|
||||
Specialization::Countable |
|
||||
Specialization::Percentage,
|
||||
true,
|
||||
true,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_queue_target{linkage,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
"frame_gen_queue_target",
|
||||
Category::Renderer,
|
||||
Specialization::Countable,
|
||||
true,
|
||||
false,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<bool> frame_gen_fp16{linkage, true, "frame_gen_fp16", Category::Renderer,
|
||||
Specialization::Default, true, false, &frame_gen};
|
||||
|
||||
SwitchableSetting<bool> frame_gen_dump_flow{linkage, false, "frame_gen_dump_flow",
|
||||
Category::Renderer};
|
||||
|
||||
SwitchableSetting<bool> use_asynchronous_gpu_emulation{linkage,
|
||||
#ifdef __ANDROID__
|
||||
false,
|
||||
@@ -540,13 +603,13 @@ struct Values {
|
||||
#endif
|
||||
|
||||
// Renderer Hacks //
|
||||
SwitchableSetting<GpuOverclock> fast_gpu_time{linkage,
|
||||
GpuOverclock::Medium,
|
||||
"fast_gpu_time",
|
||||
Category::RendererHacks,
|
||||
Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
SwitchableSetting<GpuClock> gpu_clock{linkage,
|
||||
GpuClock::Boost,
|
||||
"fast_gpu_time",
|
||||
Category::System,
|
||||
Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
|
||||
SwitchableSetting<bool> skip_cpu_inner_invalidation{linkage,
|
||||
false,
|
||||
@@ -569,13 +632,8 @@ struct Values {
|
||||
SwitchableSetting<bool> emulate_bgr565{linkage, false, "emulate_bgr565",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> rescale_hack{linkage,
|
||||
#ifdef __ANDROID__
|
||||
true,
|
||||
#else
|
||||
false,
|
||||
#endif
|
||||
"rescale_hack", Category::RendererHacks};
|
||||
SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack",
|
||||
Category::RendererHacks};
|
||||
SwitchableSetting<bool> enable_gpu_buffer_readback{linkage,
|
||||
false,
|
||||
"enable_gpu_buffer_readback",
|
||||
@@ -702,7 +760,15 @@ struct Values {
|
||||
|
||||
// Controls
|
||||
InputSetting<std::array<PlayerInput, 10>> players;
|
||||
|
||||
Setting<bool> disable_wgi_xinput{
|
||||
linkage, false, "disable_wgi_xinput", Category::Controls, Specialization::Default,
|
||||
// Only read/write disable_wgi_xinput on Windows platforms
|
||||
#ifdef _WIN32
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
};
|
||||
Setting<bool> enable_raw_input{
|
||||
linkage, false, "enable_raw_input", Category::Controls, Specialization::Default,
|
||||
// Only read/write enable_raw_input on Windows platforms
|
||||
@@ -867,10 +933,20 @@ struct Values {
|
||||
|
||||
// Per-game overrides
|
||||
bool use_squashed_iterated_blend;
|
||||
|
||||
};
|
||||
|
||||
extern Values values;
|
||||
|
||||
constexpr u32 MIN_FRAME_GEN_MULTIPLIER = 2;
|
||||
constexpr u32 MAX_FRAME_GEN_MULTIPLIER = 4;
|
||||
|
||||
[[nodiscard]] u32 FrameGenMultiplier();
|
||||
|
||||
[[nodiscard]] size_t FrameGenGenerations();
|
||||
|
||||
[[nodiscard]] size_t FrameGenMaxGenerations();
|
||||
|
||||
bool getDebugKnobAt(u8 i);
|
||||
|
||||
void UpdateGPUAccuracy();
|
||||
|
||||
@@ -128,7 +128,7 @@ ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt
|
||||
GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica,
|
||||
Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt,
|
||||
Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu);
|
||||
ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16, X32, X64, None);
|
||||
ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16);
|
||||
ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous);
|
||||
ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
|
||||
ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_90, Target_120);
|
||||
@@ -140,7 +140,7 @@ ENUM(DmaAccuracy, Default, Unsafe, Safe);
|
||||
ENUM(GpuFenceBehavior, Default, Immediate, Balanced, Accurate, Strict);
|
||||
ENUM(CpuBackend, Dynarmic, Nce);
|
||||
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid, Debugging);
|
||||
ENUM(CpuClock, Off, Boost, Fast)
|
||||
ENUM(CpuClock, Normal, Boost, Overclock)
|
||||
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb, Memory_10Gb, Memory_12Gb);
|
||||
ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
|
||||
ENUM(FullscreenMode, Borderless, Exclusive);
|
||||
@@ -152,7 +152,7 @@ ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
|
||||
ENUM(ConsoleMode, Handheld, Docked);
|
||||
ENUM(AppletMode, HLE, LLE);
|
||||
ENUM(SpirvOptimizeMode, Never, OnLoad, Always);
|
||||
ENUM(GpuOverclock, Normal, Medium, High)
|
||||
ENUM(GpuClock, Normal, Boost, Overclock)
|
||||
ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge)
|
||||
ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
|
||||
|
||||
+508
-36
@@ -1,5 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
@@ -9,6 +10,7 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "common/adpf.h"
|
||||
#include "common/error.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/assert.h"
|
||||
@@ -39,6 +41,402 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/resource.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
constexpr int NICE_AUDIO = -16;
|
||||
constexpr int NICE_URGENT_DISPLAY = -8;
|
||||
constexpr int NICE_DISPLAY = -4;
|
||||
constexpr int NICE_DEFAULT = 0;
|
||||
constexpr int NICE_BACKGROUND = 10;
|
||||
|
||||
int LowestAllowedNice() {
|
||||
static const int lowest = [] {
|
||||
rlimit limit{};
|
||||
if (getrlimit(RLIMIT_NICE, &limit) != 0) {
|
||||
return 0;
|
||||
}
|
||||
if (limit.rlim_cur >= 40) {
|
||||
return -20;
|
||||
}
|
||||
return 20 - static_cast<int>(limit.rlim_cur);
|
||||
}();
|
||||
return lowest;
|
||||
}
|
||||
|
||||
int NiceValueForPriority(Common::ThreadPriority priority) {
|
||||
const int wanted = [priority] {
|
||||
switch (priority) {
|
||||
case Common::ThreadPriority::Low: return NICE_BACKGROUND;
|
||||
case Common::ThreadPriority::Normal: return NICE_DEFAULT;
|
||||
case Common::ThreadPriority::High: return NICE_DISPLAY;
|
||||
case Common::ThreadPriority::VeryHigh: return NICE_URGENT_DISPLAY;
|
||||
case Common::ThreadPriority::Critical: return NICE_AUDIO;
|
||||
default: return NICE_DEFAULT;
|
||||
}
|
||||
}();
|
||||
return (std::max)(wanted, (std::min)(NICE_DEFAULT, LowestAllowedNice()));
|
||||
}
|
||||
} // Anonymous namespace
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <sys/utsname.h>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
constexpr size_t ANDROID_MINIMUM_PERFORMANCE_CORES = 4;
|
||||
|
||||
// A core counts as a performance core while it is within this much of the fastest one.
|
||||
constexpr s64 ANDROID_PERFORMANCE_CAPACITY_PERCENT = 50;
|
||||
|
||||
constexpr std::chrono::nanoseconds ANDROID_POLICY_POLL_INTERVAL = std::chrono::milliseconds{500};
|
||||
|
||||
enum class CoreGroup {
|
||||
Unrestricted,
|
||||
Performance,
|
||||
Efficiency,
|
||||
};
|
||||
|
||||
struct ThreadPolicy {
|
||||
pid_t tid;
|
||||
CoreGroup group;
|
||||
s32 nice_value;
|
||||
bool has_nice;
|
||||
};
|
||||
|
||||
struct CoreInfo {
|
||||
s64 weight;
|
||||
u64 midr;
|
||||
s32 cpu;
|
||||
};
|
||||
|
||||
struct CpuTopologyState {
|
||||
std::mutex topology_mutex;
|
||||
cpu_set_t allowed{};
|
||||
cpu_set_t performance{};
|
||||
cpu_set_t efficiency{};
|
||||
bool separated = false;
|
||||
bool initialized = false;
|
||||
|
||||
pid_t canary_tid = 0;
|
||||
cpu_set_t canary_mask{};
|
||||
bool canary_valid = false;
|
||||
|
||||
std::atomic<s64> next_poll_ns{0};
|
||||
|
||||
std::mutex policy_mutex;
|
||||
std::vector<ThreadPolicy> policies;
|
||||
};
|
||||
|
||||
CpuTopologyState& State() {
|
||||
static CpuTopologyState* const state = new CpuTopologyState();
|
||||
return *state;
|
||||
}
|
||||
|
||||
struct PolicyRegistration {
|
||||
~PolicyRegistration() {
|
||||
const pid_t tid = gettid();
|
||||
::Common::ADPF::RemoveCurrentThread();
|
||||
CpuTopologyState& state = State();
|
||||
std::scoped_lock lock{state.policy_mutex};
|
||||
std::erase_if(state.policies,
|
||||
[tid](const ThreadPolicy& policy) { return policy.tid == tid; });
|
||||
}
|
||||
};
|
||||
|
||||
thread_local PolicyRegistration t_policy_registration;
|
||||
|
||||
s32 PossibleCpuCount() {
|
||||
std::ifstream file("/sys/devices/system/cpu/possible");
|
||||
std::string list;
|
||||
if (file && std::getline(file, list) && !list.empty()) {
|
||||
s64 highest = -1;
|
||||
const char* cursor = list.c_str();
|
||||
while (*cursor != '\0') {
|
||||
char* end = nullptr;
|
||||
const s64 value = std::strtol(cursor, &end, 10);
|
||||
if (end == cursor) {
|
||||
break;
|
||||
}
|
||||
highest = (std::max)(highest, value);
|
||||
cursor = end;
|
||||
while (*cursor == '-' || *cursor == ',') {
|
||||
++cursor;
|
||||
}
|
||||
}
|
||||
if (highest >= 0) {
|
||||
return static_cast<s32>((std::min<s64>)(highest + 1, CPU_SETSIZE));
|
||||
}
|
||||
}
|
||||
const s64 configured = sysconf(_SC_NPROCESSORS_CONF);
|
||||
if (configured > 0) {
|
||||
return static_cast<s32>((std::min<s64>)(configured, CPU_SETSIZE));
|
||||
}
|
||||
return static_cast<s32>((std::min<s64>)(std::thread::hardware_concurrency(), CPU_SETSIZE));
|
||||
}
|
||||
|
||||
s64 ReadCpuScalar(s32 cpu, const char* node) {
|
||||
s64 value = 0;
|
||||
std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) + "/" + node);
|
||||
if (!file || !(file >> value) || value <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
u64 ReadCpuMidr(s32 cpu) {
|
||||
u64 midr = 0;
|
||||
std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) +
|
||||
"/regs/identification/midr_el1");
|
||||
if (!file || !(file >> std::hex >> midr)) {
|
||||
return 0;
|
||||
}
|
||||
return midr;
|
||||
}
|
||||
|
||||
std::vector<CoreInfo> CollectCores(const cpu_set_t& allowed, s32 total, const char* node,
|
||||
bool require_all) {
|
||||
std::vector<CoreInfo> cores;
|
||||
for (s32 cpu = 0; cpu < total; ++cpu) {
|
||||
if (!CPU_ISSET(cpu, &allowed)) {
|
||||
continue;
|
||||
}
|
||||
const s64 weight = ReadCpuScalar(cpu, node);
|
||||
if (weight <= 0) {
|
||||
if (require_all) {
|
||||
return {};
|
||||
}
|
||||
LOG_WARNING(Common, "Could not read {} for CPU {}, treating it as an efficiency core",
|
||||
node, cpu);
|
||||
continue;
|
||||
}
|
||||
cores.push_back(CoreInfo{weight, ReadCpuMidr(cpu), cpu});
|
||||
}
|
||||
return cores;
|
||||
}
|
||||
|
||||
bool WeightsAreUniform(const std::vector<CoreInfo>& cores) {
|
||||
return std::all_of(cores.begin(), cores.end(),
|
||||
[&](const CoreInfo& core) { return core.weight == cores.front().weight; });
|
||||
}
|
||||
|
||||
bool MidrsAreDistinct(const std::vector<CoreInfo>& cores) {
|
||||
return std::none_of(cores.begin(), cores.end(),
|
||||
[](const CoreInfo& core) { return core.midr == 0; }) &&
|
||||
std::any_of(cores.begin(), cores.end(),
|
||||
[&](const CoreInfo& core) { return core.midr != cores.front().midr; });
|
||||
}
|
||||
|
||||
void ComputeTopologyLocked(CpuTopologyState& state) {
|
||||
state.initialized = true;
|
||||
state.separated = false;
|
||||
CPU_ZERO(&state.allowed);
|
||||
CPU_ZERO(&state.performance);
|
||||
CPU_ZERO(&state.efficiency);
|
||||
|
||||
if (sched_getaffinity(getpid(), sizeof(state.allowed), &state.allowed) != 0) {
|
||||
LOG_WARNING(Common, "Could not query process CPU affinity: {}",
|
||||
::Common::GetLastErrorMsg());
|
||||
return;
|
||||
}
|
||||
|
||||
const s32 total = PossibleCpuCount();
|
||||
auto cores = CollectCores(state.allowed, total, "cpu_capacity", true);
|
||||
if (cores.empty() || (WeightsAreUniform(cores) && MidrsAreDistinct(cores))) {
|
||||
auto by_frequency = CollectCores(state.allowed, total, "cpufreq/cpuinfo_max_freq", false);
|
||||
if (!by_frequency.empty()) {
|
||||
cores = std::move(by_frequency);
|
||||
}
|
||||
}
|
||||
if (cores.empty()) {
|
||||
LOG_WARNING(Common, "Could not determine CPU topology, thread placement is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (WeightsAreUniform(cores)) {
|
||||
if (MidrsAreDistinct(cores)) {
|
||||
LOG_WARNING(Common, "CPU clusters differ but rank identically, thread placement is "
|
||||
"disabled");
|
||||
} else {
|
||||
LOG_INFO(Common, "CPU cores are symmetric, thread placement is disabled");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(cores.begin(), cores.end(), [](const CoreInfo& lhs, const CoreInfo& rhs) {
|
||||
if (lhs.weight != rhs.weight) {
|
||||
return lhs.weight > rhs.weight;
|
||||
}
|
||||
return lhs.cpu < rhs.cpu;
|
||||
});
|
||||
|
||||
const s64 fastest = cores.front().weight;
|
||||
size_t taken = 0;
|
||||
for (const auto& core : cores) {
|
||||
const bool fast_enough =
|
||||
core.weight * 100 >= fastest * ANDROID_PERFORMANCE_CAPACITY_PERCENT;
|
||||
if (!fast_enough && taken >= ANDROID_MINIMUM_PERFORMANCE_CORES) {
|
||||
break;
|
||||
}
|
||||
CPU_SET(core.cpu, &state.performance);
|
||||
++taken;
|
||||
}
|
||||
if (taken == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (s32 cpu = 0; cpu < total; ++cpu) {
|
||||
if (CPU_ISSET(cpu, &state.allowed) && !CPU_ISSET(cpu, &state.performance)) {
|
||||
CPU_SET(cpu, &state.efficiency);
|
||||
}
|
||||
}
|
||||
|
||||
state.separated = CPU_COUNT(&state.efficiency) > 0;
|
||||
LOG_INFO(Common, "CPU topology: {} performance cores, {} efficiency cores, separation {}",
|
||||
CPU_COUNT(&state.performance), CPU_COUNT(&state.efficiency),
|
||||
state.separated ? "enabled" : "unavailable");
|
||||
}
|
||||
|
||||
void EnsureTopologyLocked(CpuTopologyState& state) {
|
||||
if (!state.initialized) {
|
||||
ComputeTopologyLocked(state);
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshTopologyLocked(CpuTopologyState& state) {
|
||||
if (!state.initialized) {
|
||||
ComputeTopologyLocked(state);
|
||||
return;
|
||||
}
|
||||
cpu_set_t current;
|
||||
CPU_ZERO(¤t);
|
||||
if (sched_getaffinity(getpid(), sizeof(current), ¤t) != 0) {
|
||||
return;
|
||||
}
|
||||
if (std::memcmp(¤t, &state.allowed, sizeof(current)) != 0) {
|
||||
ComputeTopologyLocked(state);
|
||||
}
|
||||
}
|
||||
|
||||
bool ApplyCoreGroupLocked(CpuTopologyState& state, pid_t tid, CoreGroup group,
|
||||
bool* gone = nullptr) {
|
||||
const bool restrict_group = group != CoreGroup::Unrestricted && state.separated;
|
||||
const cpu_set_t* mask = &state.allowed;
|
||||
if (restrict_group) {
|
||||
mask = group == CoreGroup::Performance ? &state.performance : &state.efficiency;
|
||||
}
|
||||
if (CPU_COUNT(mask) == 0) {
|
||||
return false;
|
||||
}
|
||||
if (sched_setaffinity(tid, sizeof(*mask), mask) != 0) {
|
||||
if (gone != nullptr && errno == ESRCH) {
|
||||
*gone = true;
|
||||
return false;
|
||||
}
|
||||
LOG_WARNING(Common, "Could not restrict thread {} to its core group: {}", tid,
|
||||
::Common::GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KernelPreservesRequestedAffinity() {
|
||||
utsname info{};
|
||||
if (uname(&info) != 0) {
|
||||
return false;
|
||||
}
|
||||
s32 major = 0;
|
||||
s32 minor = 0;
|
||||
if (std::sscanf(info.release, "%d.%d", &major, &minor) != 2) {
|
||||
return false;
|
||||
}
|
||||
return major > 6 || (major == 6 && minor >= 2);
|
||||
}
|
||||
|
||||
void SnapshotCanaryLocked(CpuTopologyState& state) {
|
||||
state.canary_valid = false;
|
||||
if (!state.separated) {
|
||||
return;
|
||||
}
|
||||
for (const auto& policy : state.policies) {
|
||||
if (policy.group == CoreGroup::Unrestricted) {
|
||||
continue;
|
||||
}
|
||||
cpu_set_t mask;
|
||||
CPU_ZERO(&mask);
|
||||
if (sched_getaffinity(policy.tid, sizeof(mask), &mask) != 0) {
|
||||
continue;
|
||||
}
|
||||
state.canary_tid = policy.tid;
|
||||
state.canary_mask = mask;
|
||||
state.canary_valid = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool DueForPoll(CpuTopologyState& state) {
|
||||
const auto now = std::chrono::steady_clock::now().time_since_epoch();
|
||||
const s64 now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
|
||||
s64 next = state.next_poll_ns.load(std::memory_order_relaxed);
|
||||
if (now_ns < next) {
|
||||
return false;
|
||||
}
|
||||
return state.next_poll_ns.compare_exchange_strong(
|
||||
next, now_ns + ANDROID_POLICY_POLL_INTERVAL.count(), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
ThreadPolicy& AcquirePolicyLocked(CpuTopologyState& state, pid_t tid) {
|
||||
for (auto& policy : state.policies) {
|
||||
if (policy.tid == tid) {
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
return state.policies.emplace_back(ThreadPolicy{tid, CoreGroup::Unrestricted, 0, false});
|
||||
}
|
||||
|
||||
void SetCurrentThreadCoreGroup(CoreGroup group) {
|
||||
const pid_t tid = gettid();
|
||||
(void)&t_policy_registration;
|
||||
|
||||
CoreGroup effective = group;
|
||||
if (tid == getpid() && group != CoreGroup::Unrestricted) {
|
||||
LOG_WARNING(Common, "Refusing to place the main thread: the CPU topology is read from it");
|
||||
effective = CoreGroup::Unrestricted;
|
||||
}
|
||||
|
||||
CpuTopologyState& state = State();
|
||||
std::scoped_lock topology_lock{state.topology_mutex};
|
||||
EnsureTopologyLocked(state);
|
||||
ApplyCoreGroupLocked(state, tid, effective);
|
||||
|
||||
std::scoped_lock policy_lock{state.policy_mutex};
|
||||
AcquirePolicyLocked(state, tid).group = effective;
|
||||
if (!state.canary_valid) {
|
||||
SnapshotCanaryLocked(state);
|
||||
}
|
||||
}
|
||||
|
||||
void RememberCurrentThreadNice(pid_t tid, s32 nice_value) {
|
||||
(void)&t_policy_registration;
|
||||
CpuTopologyState& state = State();
|
||||
std::scoped_lock lock{state.policy_mutex};
|
||||
ThreadPolicy& policy = AcquirePolicyLocked(state, tid);
|
||||
policy.nice_value = nice_value;
|
||||
policy.has_nice = true;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
#endif
|
||||
|
||||
#include "common/cpu_features.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#ifdef _MSC_VER
|
||||
@@ -48,7 +446,6 @@
|
||||
#endif
|
||||
#include "common/x64/rdtsc.h"
|
||||
#endif
|
||||
#include "core/core_timing.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -78,21 +475,31 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||
}
|
||||
}();
|
||||
set_thread_priority(find_thread(NULL), priority);
|
||||
#else
|
||||
pthread_t this_thread = pthread_self();
|
||||
const auto scheduling_type = SCHED_OTHER;
|
||||
s32 max_prio = sched_get_priority_max(scheduling_type);
|
||||
s32 min_prio = sched_get_priority_min(scheduling_type);
|
||||
u32 level = (std::max)(u32(new_priority) + 1, 4U);
|
||||
|
||||
struct sched_param params;
|
||||
if (max_prio > min_prio) {
|
||||
params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
|
||||
} else {
|
||||
params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
|
||||
#elif defined(__ANDROID__)
|
||||
const int nice_value = NiceValueForPriority(new_priority);
|
||||
const pid_t tid = gettid();
|
||||
if (setpriority(PRIO_PROCESS, static_cast<id_t>(tid), nice_value) != 0) {
|
||||
LOG_WARNING(Common, "Could not set thread nice value to {}: {}", nice_value,
|
||||
GetLastErrorMsg());
|
||||
return;
|
||||
}
|
||||
RememberCurrentThreadNice(tid, nice_value);
|
||||
#elif defined(__linux__)
|
||||
const int nice_value = NiceValueForPriority(new_priority);
|
||||
if (setpriority(PRIO_PROCESS, 0, nice_value) != 0) {
|
||||
LOG_DEBUG(Common, "Could not set thread nice value to {}: {}", nice_value,
|
||||
GetLastErrorMsg());
|
||||
}
|
||||
#else
|
||||
const s32 max_prio = sched_get_priority_max(SCHED_OTHER);
|
||||
const s32 min_prio = sched_get_priority_min(SCHED_OTHER);
|
||||
if (max_prio > min_prio) {
|
||||
const u32 level = (std::min)(static_cast<u32>(new_priority), 4U);
|
||||
sched_param params{};
|
||||
params.sched_priority =
|
||||
min_prio + static_cast<s32>(static_cast<u32>(max_prio - min_prio) * level) / 4;
|
||||
pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶ms);
|
||||
}
|
||||
|
||||
pthread_setschedparam(this_thread, scheduling_type, ¶ms);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -132,29 +539,94 @@ void SetCurrentThreadName(const char* name) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void PinCurrentThreadToPerformanceCore(size_t core_id) {
|
||||
ASSERT(core_id < 4);
|
||||
// If we set a flag for a CPU that doesn't exist, the thread may not be allowed to
|
||||
// run in ANY processor!
|
||||
auto const total_cores = std::thread::hardware_concurrency();
|
||||
if (core_id < total_cores) {
|
||||
void SetCurrentThreadToPerformanceCores() {
|
||||
#if defined(__ANDROID__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
sched_setaffinity(pthread_self(), sizeof(set), &set);
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
|
||||
#elif defined(_WIN32)
|
||||
DWORD set = 1UL << core_id;
|
||||
SetThreadAffinityMask(GetCurrentThread(), set);
|
||||
#else
|
||||
// No pin functionality implemented
|
||||
#endif
|
||||
if (ADPF::AddCurrentThread(ADPF::Session::Render)) {
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
|
||||
return;
|
||||
}
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Performance);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadToEfficiencyCores() {
|
||||
#if defined(__ANDROID__)
|
||||
if (ADPF::AddCurrentThread(ADPF::Session::Background)) {
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
|
||||
return;
|
||||
}
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Efficiency);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadToBackgroundWork() {
|
||||
#if defined(__ANDROID__)
|
||||
ADPF::AddCurrentThread(ADPF::Session::Background);
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadToAllCores() {
|
||||
#if defined(__ANDROID__)
|
||||
ADPF::RemoveCurrentThread();
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RefreshThreadPolicies() {
|
||||
#if defined(__ANDROID__)
|
||||
CpuTopologyState& state = State();
|
||||
std::scoped_lock topology_lock{state.topology_mutex};
|
||||
RefreshTopologyLocked(state);
|
||||
|
||||
std::scoped_lock policy_lock{state.policy_mutex};
|
||||
std::erase_if(state.policies, [&state](const ThreadPolicy& policy) {
|
||||
bool gone = false;
|
||||
if (policy.has_nice &&
|
||||
setpriority(PRIO_PROCESS, static_cast<id_t>(policy.tid), policy.nice_value) != 0 &&
|
||||
errno == ESRCH) {
|
||||
gone = true;
|
||||
}
|
||||
if (!gone) {
|
||||
ApplyCoreGroupLocked(state, policy.tid, policy.group, &gone);
|
||||
}
|
||||
return gone;
|
||||
});
|
||||
SnapshotCanaryLocked(state);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PollThreadPolicies() {
|
||||
#if defined(__ANDROID__)
|
||||
static const bool needed = !KernelPreservesRequestedAffinity();
|
||||
if (!needed) {
|
||||
return;
|
||||
}
|
||||
|
||||
CpuTopologyState& state = State();
|
||||
if (!DueForPoll(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pid_t tid;
|
||||
cpu_set_t expected;
|
||||
{
|
||||
std::scoped_lock lock{state.topology_mutex};
|
||||
if (!state.canary_valid) {
|
||||
return;
|
||||
}
|
||||
tid = state.canary_tid;
|
||||
expected = state.canary_mask;
|
||||
}
|
||||
|
||||
cpu_set_t current;
|
||||
CPU_ZERO(¤t);
|
||||
if (sched_getaffinity(tid, sizeof(current), ¤t) == 0 &&
|
||||
std::memcmp(¤t, &expected, sizeof(current)) == 0) {
|
||||
return;
|
||||
}
|
||||
RefreshThreadPolicies();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
|
||||
+13
-1
@@ -99,8 +99,20 @@ enum class ThreadPriority : u32 {
|
||||
Critical = 4,
|
||||
};
|
||||
|
||||
enum class ThreadPlacement : u32 {
|
||||
Default = 0,
|
||||
Background = 1,
|
||||
Efficiency = 2,
|
||||
};
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
||||
void SetCurrentThreadName(const char* name);
|
||||
void PinCurrentThreadToPerformanceCore(size_t core_id);
|
||||
void SetCurrentThreadToPerformanceCores();
|
||||
void SetCurrentThreadToEfficiencyCores();
|
||||
void SetCurrentThreadToBackgroundWork();
|
||||
void SetCurrentThreadToAllCores();
|
||||
|
||||
void RefreshThreadPolicies();
|
||||
void PollThreadPolicies();
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
@@ -37,10 +37,25 @@ class StatefulThreadWorker {
|
||||
using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>;
|
||||
|
||||
public:
|
||||
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {})
|
||||
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {},
|
||||
ThreadPlacement placement = ThreadPlacement::Default)
|
||||
: workers_queued{num_workers}, thread_name{std::move(name)} {
|
||||
const auto lambda = [this, func](std::stop_token stop_token) {
|
||||
const auto lambda = [this, func, placement](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName(thread_name.c_str());
|
||||
if (placement != ThreadPlacement::Default) {
|
||||
Common::SetCurrentThreadPriority(ThreadPriority::Low);
|
||||
}
|
||||
switch (placement) {
|
||||
case ThreadPlacement::Efficiency:
|
||||
Common::SetCurrentThreadToEfficiencyCores();
|
||||
break;
|
||||
case ThreadPlacement::Background:
|
||||
Common::SetCurrentThreadToBackgroundWork();
|
||||
break;
|
||||
default:
|
||||
Common::SetCurrentThreadToAllCores();
|
||||
break;
|
||||
}
|
||||
{
|
||||
[[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func()};
|
||||
while (!stop_token.stop_requested()) {
|
||||
|
||||
@@ -208,4 +208,12 @@ UUID UUID::MakeRandomRFC4122V4() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
UUID UUID::MakeRFC4122V5(std::span<u8, 20> sha1) {
|
||||
UUID uuid{};
|
||||
std::memcpy(&uuid.uuid, sha1.data(), sizeof(UUID));
|
||||
uuid.uuid[8] = 0x80 | (uuid.uuid[8] & 0x3F);
|
||||
uuid.uuid[6] = 0x50 | (uuid.uuid[6] & 0xF);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
+16
-20
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -5,6 +8,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "common/common_types.h"
|
||||
@@ -86,28 +90,20 @@ struct UUID {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a random UUID.
|
||||
*
|
||||
* @returns A random UUID.
|
||||
*/
|
||||
static UUID MakeRandom();
|
||||
/// @brief Creates a random UUID.
|
||||
/// @returns A random UUID.
|
||||
[[nodiscard]] static UUID MakeRandom();
|
||||
|
||||
/**
|
||||
* Creates a random UUID with a seed.
|
||||
*
|
||||
* @param seed A seed to initialize the Mersenne-Twister RNG
|
||||
*
|
||||
* @returns A random UUID.
|
||||
*/
|
||||
static UUID MakeRandomWithSeed(u32 seed);
|
||||
/// @brief Creates a random UUID with a seed.
|
||||
/// @param seed A seed to initialize the Mersenne-Twister RNG
|
||||
/// @returns A random UUID.
|
||||
[[nodiscard]] static UUID MakeRandomWithSeed(u32 seed);
|
||||
|
||||
/**
|
||||
* Creates a random UUID. The generated UUID is RFC 4122 Version 4 compliant.
|
||||
*
|
||||
* @returns A random UUID that is RFC 4122 Version 4 compliant.
|
||||
*/
|
||||
static UUID MakeRandomRFC4122V4();
|
||||
/// @brief Creates a random UUID. The generated UUID is RFC 4122 Version 4 compliant.
|
||||
/// @returns A random UUID that is RFC 4122 Version 4 compliant.
|
||||
[[nodiscard]] static UUID MakeRandomRFC4122V4();
|
||||
|
||||
[[nodiscard]] static UUID MakeRFC4122V5(std::span<u8, 20> sha1);
|
||||
|
||||
friend constexpr bool operator==(const UUID& lhs, const UUID& rhs) = default;
|
||||
};
|
||||
|
||||
@@ -1173,7 +1173,7 @@ add_library(core STATIC
|
||||
|
||||
if (ENABLE_WIFI_SCAN)
|
||||
target_sources(core PRIVATE internal_network/wifi_scanner.cpp)
|
||||
if (PLATFORM_LINUX)
|
||||
if (LINUX)
|
||||
target_link_libraries(core PRIVATE iw)
|
||||
endif()
|
||||
else()
|
||||
@@ -1199,7 +1199,7 @@ if (MSVC)
|
||||
)
|
||||
else()
|
||||
target_compile_options(core PRIVATE
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=conversion>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wconversion>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-cast-function-type>
|
||||
$<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/adpf.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "common/string_util.h"
|
||||
@@ -385,6 +386,7 @@ struct System::Impl {
|
||||
|
||||
void ShutdownMainProcess() {
|
||||
SetShuttingDown(true);
|
||||
Common::ADPF::Shutdown();
|
||||
|
||||
// Reset per-game flags
|
||||
Settings::values.use_squashed_iterated_blend = false;
|
||||
|
||||
@@ -23,6 +23,21 @@ namespace Core::Timing {
|
||||
|
||||
constexpr s64 MAX_SLICE_LENGTH = 10000;
|
||||
|
||||
constexpr u32 CPU_CLOCK_BASE_MHZ = 1020;
|
||||
constexpr u32 CPU_CLOCK_BOOST_MHZ = 1734;
|
||||
constexpr u32 CPU_CLOCK_OVERCLOCK_MHZ = 2040;
|
||||
|
||||
constexpr u32 CpuClockTargetMhz(Settings::CpuClock clock) {
|
||||
switch (clock) {
|
||||
case Settings::CpuClock::Boost:
|
||||
return CPU_CLOCK_BOOST_MHZ;
|
||||
case Settings::CpuClock::Overclock:
|
||||
return CPU_CLOCK_OVERCLOCK_MHZ;
|
||||
default:
|
||||
return CPU_CLOCK_BASE_MHZ;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) {
|
||||
return std::make_shared<EventType>(std::move(callback), std::move(name));
|
||||
}
|
||||
@@ -58,7 +73,8 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
||||
if (is_multicore) {
|
||||
timer_thread = std::jthread([this](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("HostTiming");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
on_thread_init();
|
||||
has_started = true;
|
||||
|
||||
@@ -209,8 +225,9 @@ void CoreTiming::ResetTicks() {
|
||||
|
||||
u64 CoreTiming::GetClockTicks() const {
|
||||
u64 fres = is_multicore ? Common::g_wall_clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
|
||||
if (auto const overclock = Settings::values.fast_cpu_time.GetValue(); overclock != Settings::CpuClock::Off) {
|
||||
fres = u64(f64(fres) * (1.7 + 0.3 * u32(overclock)));
|
||||
if (const u32 target = CpuClockTargetMhz(Settings::values.cpu_clock.GetValue());
|
||||
target != CPU_CLOCK_BASE_MHZ) {
|
||||
fres = fres * target / CPU_CLOCK_BASE_MHZ;
|
||||
}
|
||||
if (::Settings::values.sync_core_speed.GetValue()) {
|
||||
auto const ticks = f64(fres);
|
||||
|
||||
@@ -174,12 +174,7 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) {
|
||||
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
|
||||
Common::SetCurrentThreadName(name.c_str());
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
#ifdef __ANDROID__
|
||||
// Aimed specifically for Snapdragon 8 Elite devices
|
||||
// This kills performance on desktop, but boosts perf for UMA devices
|
||||
// like the S8E. Mediatek and Mali likely won't suffer.
|
||||
Common::PinCurrentThreadToPerformanceCore(core);
|
||||
#endif
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
auto& data = core_data[core];
|
||||
data.host_context = Common::Fiber::ThreadToFiber();
|
||||
|
||||
|
||||
@@ -274,23 +274,22 @@ public:
|
||||
explicit NACP(VirtualFile file);
|
||||
~NACP();
|
||||
|
||||
const LanguageEntry& GetLanguageEntry() const;
|
||||
std::string GetApplicationName() const;
|
||||
std::string GetDeveloperName() const;
|
||||
u64 GetTitleId() const;
|
||||
u64 GetDLCBaseTitleId() const;
|
||||
std::string GetVersionString() const;
|
||||
u64 GetDefaultNormalSaveSize() const;
|
||||
u64 GetDefaultJournalSaveSize() const;
|
||||
u32 GetSupportedLanguages() const;
|
||||
std::vector<std::string> GetApplicationNames() const;
|
||||
std::vector<u8> GetRawBytes() const;
|
||||
bool GetUserAccountSwitchLock() const;
|
||||
u64 GetDeviceSaveDataSize() const;
|
||||
u32 GetParentalControlFlag() const;
|
||||
const std::array<u8, 0x20>& GetRatingAge() const;
|
||||
[[nodiscard]] const LanguageEntry& GetLanguageEntry() const;
|
||||
[[nodiscard]] std::string GetApplicationName() const;
|
||||
[[nodiscard]] std::string GetDeveloperName() const;
|
||||
[[nodiscard]] u64 GetTitleId() const;
|
||||
[[nodiscard]] u64 GetDLCBaseTitleId() const;
|
||||
[[nodiscard]] std::string GetVersionString() const;
|
||||
[[nodiscard]] u64 GetDefaultNormalSaveSize() const;
|
||||
[[nodiscard]] u64 GetDefaultJournalSaveSize() const;
|
||||
[[nodiscard]] u32 GetSupportedLanguages() const;
|
||||
[[nodiscard]] std::vector<std::string> GetApplicationNames() const;
|
||||
[[nodiscard]] std::vector<u8> GetRawBytes() const;
|
||||
[[nodiscard]] bool GetUserAccountSwitchLock() const;
|
||||
[[nodiscard]] u64 GetDeviceSaveDataSize() const;
|
||||
[[nodiscard]] u32 GetParentalControlFlag() const;
|
||||
[[nodiscard]] const std::array<u8, 0x20>& GetRatingAge() const;
|
||||
|
||||
private:
|
||||
RawNACP raw{};
|
||||
std::vector<LanguageEntry> language_entries;
|
||||
};
|
||||
|
||||
@@ -1156,4 +1156,14 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
|
||||
|
||||
return {std::move(nacp), icon_file};
|
||||
}
|
||||
|
||||
[[nodiscard]] PatchManager::Metadata PatchManager::GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept {
|
||||
const FileSys::PatchManager pm{application_id, system.GetFileSystemController(), system.GetContentProvider()};
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr)
|
||||
return metadata;
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(application_id), system.GetFileSystemController(), system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -105,6 +105,9 @@ public:
|
||||
// Version of GetControlMetadata that takes an arbitrary NCA
|
||||
[[nodiscard]] Metadata ParseControlNCA(const NCA& nca) const;
|
||||
|
||||
/// @brief Gets NACP metadata (accounting for any patches or updates)
|
||||
[[nodiscard]] static PatchManager::Metadata GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) const;
|
||||
|
||||
@@ -28,7 +28,7 @@ Result CloseHandle(Core::System& system, Handle handle) {
|
||||
|
||||
/// Clears the signaled state of an event or process.
|
||||
Result ResetSignal(Core::System& system, Handle handle) {
|
||||
LOG_DEBUG(Kernel_SVC, "called handle {:#08x}", handle);
|
||||
LOG_TRACE(Kernel_SVC, "called handle {:#08x}", handle);
|
||||
|
||||
// Get the current handle table.
|
||||
const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable();
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "common/uuid.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
@@ -154,20 +156,7 @@ Result IApplicationFunctions::GetDesiredLanguage(Out<u64> out_language_code) {
|
||||
// Default to 0 (all languages supported)
|
||||
u32 supported_languages = 0;
|
||||
|
||||
const auto res = [this] {
|
||||
const FileSys::PatchManager pm{m_applet->program_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(m_applet->program_id),
|
||||
system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
}();
|
||||
|
||||
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
|
||||
if (res.first != nullptr) {
|
||||
supported_languages = res.first->GetSupportedLanguages();
|
||||
}
|
||||
@@ -205,20 +194,7 @@ Result IApplicationFunctions::SetTerminateResult(Result terminate_result) {
|
||||
Result IApplicationFunctions::GetDisplayVersion(Out<DisplayVersion> out_display_version) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
const auto res = [this] {
|
||||
const FileSys::PatchManager pm{m_applet->program_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(m_applet->program_id),
|
||||
system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
}();
|
||||
|
||||
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
|
||||
if (res.first != nullptr) {
|
||||
const auto& version = res.first->GetVersionString();
|
||||
std::memcpy(out_display_version->string.data(), version.data(),
|
||||
@@ -347,8 +323,21 @@ Result IApplicationFunctions::NotifyRunning(Out<bool> out_became_running) {
|
||||
}
|
||||
|
||||
Result IApplicationFunctions::GetPseudoDeviceId(Out<Common::UUID> out_pseudo_device_id) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
*out_pseudo_device_id = {};
|
||||
LOG_WARNING(Service_AM, "(stubbed)");
|
||||
|
||||
// This should be hashed with the device specific hash
|
||||
// for now this will do
|
||||
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
|
||||
u8 hash[EVP_MAX_MD_SIZE];
|
||||
unsigned int hash_len = 0;
|
||||
auto const seed = res.first->raw.seed_for_pseudo_device_id;
|
||||
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||
auto const algorithm = EVP_sha1();
|
||||
EVP_DigestInit_ex(ctx, algorithm, nullptr);
|
||||
EVP_DigestUpdate(ctx, &seed, sizeof(seed));
|
||||
EVP_DigestFinal_ex(ctx, hash, &hash_len);
|
||||
EVP_MD_CTX_free(ctx);
|
||||
*out_pseudo_device_id = Common::UUID::MakeRFC4122V5(std::span<u8, 20>{hash, std::size(hash)});
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
||||
@@ -252,19 +252,7 @@ Result ILibraryAppletSelfAccessor::GetMainAppletApplicationDesiredLanguage(
|
||||
// Default to 0 (all languages supported)
|
||||
u32 supported_languages = 0;
|
||||
|
||||
const auto res = [this, identity] {
|
||||
const FileSys::PatchManager pm{identity.application_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(identity.application_id),
|
||||
system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
}();
|
||||
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, identity.application_id);
|
||||
|
||||
if (res.first != nullptr) {
|
||||
supported_languages = res.first->GetSupportedLanguages();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "common/adpf.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/core.h"
|
||||
@@ -86,11 +87,16 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers
|
||||
}
|
||||
|
||||
system.GPU().RequestComposite(std::move(output_layers), std::move(output_fences));
|
||||
Common::ADPF::ReportFrameInterval();
|
||||
system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs());
|
||||
system.GetPerfStats().EndSystemFrame();
|
||||
system.GetPerfStats().BeginSystemFrame();
|
||||
}
|
||||
|
||||
void nvdisp_disp0::WaitForComposite() {
|
||||
system.GPU().WaitForComposite();
|
||||
}
|
||||
|
||||
Kernel::KEvent* nvdisp_disp0::QueryEvent(u32 event_id) {
|
||||
LOG_CRITICAL(Service_NVDRV, "Unknown DISP Event {}", event_id);
|
||||
return nullptr;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -37,6 +40,8 @@ public:
|
||||
/// Performs a screen flip, compositing each buffer.
|
||||
void Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers);
|
||||
|
||||
void WaitForComposite();
|
||||
|
||||
Kernel::KEvent* QueryEvent(u32 event_id) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
@@ -264,7 +265,7 @@ NvResult nvhost_ctrl_gpu::ZCullGetInfo(IoctlNvgpuGpuZcullGetInfoArgs& params) {
|
||||
}
|
||||
|
||||
NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) {
|
||||
if (params.type > supported_types) {
|
||||
if (params.type == 0 || params.type > supported_types) {
|
||||
LOG_ERROR(Service_NVDRV, "ZBCSetTable: invalid type {:#x}", params.type);
|
||||
return NvResult::BadParameter;
|
||||
}
|
||||
@@ -279,42 +280,61 @@ NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) {
|
||||
color_entry.format = params.format;
|
||||
color_entry.ref_cnt = 1u;
|
||||
|
||||
auto color_it = std::ranges::find_if(zbc_colors,
|
||||
[&](const ZbcColorEntry& color_in_question) {
|
||||
return color_entry.format == color_in_question.format &&
|
||||
color_entry.color_ds == color_in_question.color_ds &&
|
||||
color_entry.color_l2 == color_in_question.color_l2;
|
||||
});
|
||||
const auto color_end = zbc_colors.begin() + zbc_used_color_entries;
|
||||
auto color_it = std::find_if(zbc_colors.begin(), color_end,
|
||||
[&](const ZbcColorEntry& color_in_question) {
|
||||
return color_entry.format == color_in_question.format &&
|
||||
color_entry.color_ds == color_in_question.color_ds &&
|
||||
color_entry.color_l2 == color_in_question.color_l2;
|
||||
});
|
||||
|
||||
if (color_it != zbc_colors.end()) {
|
||||
if (color_it != color_end) {
|
||||
++color_it->ref_cnt;
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused color entry fmt={:#x}, ref_cnt={:#x}",
|
||||
params.format, color_it->ref_cnt);
|
||||
} else {
|
||||
zbc_colors.push_back(color_entry);
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added color entry fmt={:#x}, index={:#x}",
|
||||
params.format, zbc_colors.size() - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (zbc_used_color_entries >= zbc_table_size) {
|
||||
LOG_WARNING(Service_NVDRV, "ZBCSetTable: color table is full, fmt={:#x}",
|
||||
params.format);
|
||||
return NvResult::InsufficientMemory;
|
||||
}
|
||||
|
||||
zbc_colors[zbc_used_color_entries] = color_entry;
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added color entry fmt={:#x}, index={:#x}",
|
||||
params.format, zbc_used_color_entries);
|
||||
++zbc_used_color_entries;
|
||||
break;
|
||||
}
|
||||
case ZBCTypes::depth: {
|
||||
ZbcDepthEntry depth_entry{params.depth, params.format, 1u};
|
||||
|
||||
auto depth_it = std::ranges::find_if(zbc_depths,
|
||||
[&](const ZbcDepthEntry& depth_entry_in_question) {
|
||||
return depth_entry.format == depth_entry_in_question.format &&
|
||||
depth_entry.depth == depth_entry_in_question.depth;
|
||||
});
|
||||
const auto depth_end = zbc_depths.begin() + zbc_used_depth_entries;
|
||||
auto depth_it = std::find_if(zbc_depths.begin(), depth_end,
|
||||
[&](const ZbcDepthEntry& depth_entry_in_question) {
|
||||
return depth_entry.format == depth_entry_in_question.format &&
|
||||
depth_entry.depth == depth_entry_in_question.depth;
|
||||
});
|
||||
|
||||
if (depth_it != zbc_depths.end()) {
|
||||
if (depth_it != depth_end) {
|
||||
++depth_it->ref_cnt;
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused depth entry fmt={:#x}, ref_cnt={:#x}",
|
||||
depth_entry.format, depth_it->ref_cnt);
|
||||
} else {
|
||||
zbc_depths.push_back(depth_entry);
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added depth entry fmt={:#x}, index={:#x}",
|
||||
depth_entry.format, zbc_depths.size() - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (zbc_used_depth_entries >= zbc_table_size) {
|
||||
LOG_WARNING(Service_NVDRV, "ZBCSetTable: depth table is full, fmt={:#x}",
|
||||
depth_entry.format);
|
||||
return NvResult::InsufficientMemory;
|
||||
}
|
||||
|
||||
zbc_depths[zbc_used_depth_entries] = depth_entry;
|
||||
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added depth entry fmt={:#x}, index={:#x}",
|
||||
depth_entry.format, zbc_used_depth_entries);
|
||||
++zbc_used_depth_entries;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,35 +349,34 @@ NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) {
|
||||
|
||||
std::scoped_lock lk(zbc_mutex);
|
||||
|
||||
if (params.type == 0) {
|
||||
params.index_size = zbc_table_size;
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
if (params.index_size >= zbc_table_size) {
|
||||
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid index {:#x}", params.index_size);
|
||||
return NvResult::BadParameter;
|
||||
}
|
||||
|
||||
switch (static_cast<ZBCTypes>(params.type)) {
|
||||
case ZBCTypes::color: {
|
||||
if (params.index_size >= zbc_colors.size()) {
|
||||
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid color index {:#x}", params.index_size);
|
||||
return NvResult::BadParameter;
|
||||
}
|
||||
|
||||
const auto& colors = zbc_colors[params.index_size];
|
||||
std::copy_n(colors.color_ds.begin(), colors.color_ds.size(), std::begin(params.color_ds));
|
||||
std::copy_n(colors.color_l2.begin(), colors.color_l2.size(), std::begin(params.color_l2));
|
||||
params.depth = 0;
|
||||
params.ref_cnt = colors.ref_cnt;
|
||||
params.format = colors.format;
|
||||
params.index_size = static_cast<u32>(zbc_colors.size());
|
||||
break;
|
||||
}
|
||||
case ZBCTypes::depth: {
|
||||
if (params.index_size >= zbc_depths.size()) {
|
||||
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid depth index {:#x}", params.index_size);
|
||||
return NvResult::BadParameter;
|
||||
}
|
||||
|
||||
const auto& depth_entry = zbc_depths[params.index_size];
|
||||
std::fill(std::begin(params.color_ds), std::end(params.color_ds), 0);
|
||||
std::fill(std::begin(params.color_l2), std::end(params.color_l2), 0);
|
||||
params.depth = depth_entry.depth;
|
||||
params.ref_cnt = depth_entry.ref_cnt;
|
||||
params.format = depth_entry.format;
|
||||
params.index_size = static_cast<u32>(zbc_depths.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
@@ -212,10 +212,14 @@ private:
|
||||
Kernel::KEvent* unknown_event;
|
||||
|
||||
// ZBC Tables
|
||||
static constexpr u32 zbc_table_size = 15u;
|
||||
static constexpr u32 supported_types = 2u;
|
||||
|
||||
std::mutex zbc_mutex{};
|
||||
std::vector<ZbcColorEntry> zbc_colors{};
|
||||
std::vector<ZbcDepthEntry> zbc_depths{};
|
||||
const u32 supported_types = 2u;
|
||||
std::array<ZbcColorEntry, zbc_table_size> zbc_colors{};
|
||||
std::array<ZbcDepthEntry, zbc_table_size> zbc_depths{};
|
||||
u32 zbc_used_color_entries{};
|
||||
u32 zbc_used_depth_entries{};
|
||||
};
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
@@ -57,18 +57,8 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
// Set default speed limit to 100%.
|
||||
*out_speed_scale = 1.0f;
|
||||
|
||||
// If no layers are available, skip the logic.
|
||||
bool any_visible = false;
|
||||
for (auto& layer : display.stack.layers) {
|
||||
if (layer->visible) {
|
||||
any_visible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!any_visible) {
|
||||
*out_speed_scale = 1.0f;
|
||||
return 1;
|
||||
}
|
||||
nvdisp.WaitForComposite();
|
||||
this->ReleaseFramebuffersLocked(display);
|
||||
|
||||
// Determine the number of vsync periods to wait before composing again.
|
||||
std::optional<s32> swap_interval{};
|
||||
@@ -158,55 +148,30 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
nvdisp.Composite(composition_stack);
|
||||
}
|
||||
|
||||
// Batch framebuffer releases, instead of one-into-one.
|
||||
std::vector<std::pair<Layer*, Framebuffer*>> to_release;
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (!framebuffer.is_acquired)
|
||||
continue;
|
||||
|
||||
auto layer = display.stack.FindLayer(layer_id);
|
||||
if (!layer)
|
||||
continue;
|
||||
|
||||
// Overlay layers always release after every compose
|
||||
// Non-overlay layers release based on their swap interval
|
||||
if (layer->is_overlay || framebuffer.release_frame_number <= m_frame_number) {
|
||||
to_release.emplace_back(layer.get(), &framebuffer);
|
||||
}
|
||||
}
|
||||
for (auto& [layer, framebuffer] : to_release) {
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer->item, android::Fence::NoFence());
|
||||
framebuffer->is_acquired = false;
|
||||
}
|
||||
|
||||
// Advance by 1 frame (60 FPS compositing)
|
||||
m_frame_number += 1;
|
||||
|
||||
// Release any necessary framebuffers (non-overlay layers only, as overlays are already released above).
|
||||
return 1;
|
||||
}
|
||||
|
||||
void HardwareComposer::ReleaseFramebuffersLocked(Display& display) {
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (!framebuffer.is_acquired) {
|
||||
// Already released.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (framebuffer.release_frame_number > m_frame_number) {
|
||||
const auto layer = display.stack.FindLayer(layer_id);
|
||||
if (!layer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const auto layer = display.stack.FindLayer(layer_id); layer != nullptr) {
|
||||
// Skip overlay layers as they were already released above
|
||||
if (layer->is_overlay) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: support release fence
|
||||
// This is needed to prevent screen tearing
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
|
||||
framebuffer.is_acquired = false;
|
||||
if (!layer->is_overlay && framebuffer.release_frame_number > m_frame_number) {
|
||||
continue;
|
||||
}
|
||||
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
|
||||
framebuffer.is_acquired = false;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
@@ -52,6 +52,7 @@ private:
|
||||
private:
|
||||
bool TryAcquireFramebufferLocked(Layer& layer, Framebuffer& framebuffer);
|
||||
CacheStatus CacheFramebufferLocked(Layer& layer, ConsumerId consumer_id);
|
||||
void ReleaseFramebuffersLocked(Display& display);
|
||||
};
|
||||
|
||||
} // namespace Service::Nvnflinger
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator
|
||||
@@ -84,7 +84,7 @@ private:
|
||||
RestrictionSettings restriction_settings{};
|
||||
std::array<char, 8> pin_code{};
|
||||
Capability capability{};
|
||||
// TODO: this is RAW as fuck
|
||||
// TODO: this is raw
|
||||
PlayTimerSettings raw_play_timer_settings{};
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
@@ -1020,7 +1020,7 @@ Result ISystemSettingsServer::GetBatteryLot(Out<BatteryLot> out_battery_lot) {
|
||||
c.lot_number[1] = 'H';
|
||||
c.lot_number[2] = 'A';
|
||||
c.lot_number[3] = 'C';
|
||||
// TODO: I have no fucking idea what the letters mean
|
||||
// TODO: what do the letters mean?
|
||||
c.lot_number[4] = 'H';
|
||||
c.lot_number[5] = 'Z';
|
||||
c.lot_number[6] = 'Z';
|
||||
|
||||
@@ -88,25 +88,10 @@ Result IApplicationDisplayService::GetIndirectDisplayTransactionService(
|
||||
}
|
||||
|
||||
Result IApplicationDisplayService::OpenDisplay(Out<u64> out_display_id, DisplayName display_name) {
|
||||
LOG_DEBUG(Service_VI, "called with display_name={}", display_name.data());
|
||||
|
||||
// Ensure the display name is null-terminated
|
||||
display_name[display_name.size() - 1] = '\0';
|
||||
|
||||
// According to switchbrew, only "Default", "External", "Edid", "Internal" and "Null" are valid
|
||||
const std::array<std::string_view, 5> valid_names = {
|
||||
"Default", "External", "Edid", "Internal", "Null"
|
||||
};
|
||||
|
||||
bool valid_name = false;
|
||||
for (const auto& name : valid_names) {
|
||||
if (name == display_name.data()) {
|
||||
valid_name = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
R_UNLESS(valid_name, ResultOperationFailed);
|
||||
LOG_DEBUG(Service_VI, "called with display_name={}", display_name.data());
|
||||
|
||||
R_RETURN(m_container->OpenDisplay(out_display_id, display_name));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/adpf.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/vi/conductor.h"
|
||||
@@ -14,6 +18,8 @@
|
||||
|
||||
constexpr auto FrameNs = std::chrono::nanoseconds{1000000000 / 60};
|
||||
|
||||
constexpr s64 UNLOCKED_TARGET_DIVISOR = 4;
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
Conductor::Conductor(Core::System& system, Container& container, DisplayList& displays)
|
||||
@@ -68,6 +74,9 @@ void Conductor::UnlinkVsyncEvent(u64 display_id, Event* event) {
|
||||
}
|
||||
|
||||
void Conductor::ProcessVsync() {
|
||||
Common::PollThreadPolicies();
|
||||
Common::ADPF::SetTargetWorkDuration(std::chrono::nanoseconds{this->GetFramePeriodNs()});
|
||||
|
||||
for (auto& [display_id, manager] : m_vsync_managers) {
|
||||
m_container.ComposeOnDisplay(&m_swap_interval, &m_compose_speed_scale, display_id);
|
||||
manager.SignalVsync(m_system.Kernel());
|
||||
@@ -76,6 +85,8 @@ void Conductor::ProcessVsync() {
|
||||
|
||||
void Conductor::VsyncThread(std::stop_token token) {
|
||||
Common::SetCurrentThreadName("VSyncThread");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
|
||||
while (!token.stop_requested()) {
|
||||
m_signal.Wait();
|
||||
@@ -114,4 +125,25 @@ s64 Conductor::GetNextTicks() const {
|
||||
return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
|
||||
}
|
||||
|
||||
s64 Conductor::GetFramePeriodNs() const {
|
||||
const auto& settings = Settings::values;
|
||||
f32 speed_scale = 1.f;
|
||||
bool unlocked = false;
|
||||
if (settings.use_multi_core.GetValue()) {
|
||||
if (settings.use_speed_limit.GetValue()) {
|
||||
speed_scale = 100.f / Settings::SpeedLimit();
|
||||
} else {
|
||||
unlocked = true;
|
||||
}
|
||||
}
|
||||
speed_scale /= m_compose_speed_scale;
|
||||
|
||||
const f32 effective_fps = 60.f / static_cast<f32>(m_swap_interval);
|
||||
s64 period = static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
|
||||
if (unlocked) {
|
||||
period /= UNLOCKED_TARGET_DIVISOR;
|
||||
}
|
||||
return std::clamp<s64>(period, 1'000'000, 100'000'000);
|
||||
}
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -44,6 +44,7 @@ private:
|
||||
void ProcessVsync();
|
||||
void VsyncThread(std::stop_token token);
|
||||
s64 GetNextTicks() const;
|
||||
s64 GetFramePeriodNs() const;
|
||||
|
||||
private:
|
||||
Core::System& m_system;
|
||||
|
||||
@@ -531,7 +531,8 @@ int TranslateTypeToNative(Type type) {
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(MPLS) \
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(PFSYNC)
|
||||
#elif defined(__linux__)
|
||||
// Other platforms get fucked
|
||||
// Other platforms may not support some niche protocols.
|
||||
// This is usually not an issue
|
||||
#define NETWORK_PROTOCOL_TRANSLATE_LIST \
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(IP) \
|
||||
/*NETWORK_PROTOCOL_TRANSLATE_ELEM(HOPOPTS)*/ \
|
||||
|
||||
@@ -25,7 +25,7 @@ endif()
|
||||
# Dynarmic project options
|
||||
option(DYNARMIC_ENABLE_CPU_FEATURE_DETECTION "Turning this off causes dynarmic to assume the host CPU doesn't support anything later than SSE3" ON)
|
||||
|
||||
if (PLATFORM_OPENBSD OR PLATFORM_DRAGONFLY OR PLATFORM_NETBSD)
|
||||
if (OPENBSD OR DRAGONFLY OR NETBSD)
|
||||
set(REQUIRE_WX ON)
|
||||
else()
|
||||
set(REQUIRE_WX OFF)
|
||||
|
||||
@@ -363,7 +363,7 @@ elseif (APPLE)
|
||||
backend/exception_handler_macos_mig.c
|
||||
)
|
||||
endif()
|
||||
elseif (UNIX AND NOT PLATFORM_HAIKU)
|
||||
elseif (UNIX AND NOT HAIKUOS)
|
||||
# Haiku lacks <ucontext.h>
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
target_link_libraries(dynarmic PRIVATE rt)
|
||||
|
||||
@@ -234,7 +234,7 @@ private:
|
||||
BlockOfCode block_of_code;
|
||||
A32EmitX64 emitter;
|
||||
Optimization::PolyfillOptions polyfill_options;
|
||||
// Keep it here, you don't wanna mess with the fuckery that's initializer lists
|
||||
// Keep it here in order to not mess with initializer lists
|
||||
const A32::UserConfig conf;
|
||||
Jit* jit_interface;
|
||||
|
||||
|
||||
@@ -2160,8 +2160,6 @@ void EmitFPVectorToFixed(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) {
|
||||
ROUNDING_MODE_CASE(CASE, 0x3e) \
|
||||
ROUNDING_MODE_CASE(CASE, 0x3f)
|
||||
|
||||
// FUCK YOU MSVC, FUCKING DEPTH CANT EVEN HANDLE 8+16+32+64 DEPTH OF A ELSE STATMENT YOU FUCKING STUPID
|
||||
// BURN MSVC BURN IT STUPID COMPILER CAN'T EVEN COMPILE THE MOST BASIC C++
|
||||
ROUNDING_MODE_SWITCH(ToNearest_TieEven)
|
||||
ROUNDING_MODE_SWITCH(TowardsPlusInfinity)
|
||||
ROUNDING_MODE_SWITCH(TowardsMinusInfinity)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user