Compare commits

..

74 Commits

Author SHA1 Message Date
CamilleLaVey e46c19391d Add small_vector to CopyMSAA with reserve image on upload path 2026-08-22 23:18:10 -04:00
CamilleLaVey 3d453e6236 [TEST] Unify another section of the blits on MSAA path 2026-08-22 23:13:20 -04:00
CamilleLaVey 9392ccf124 color write meows 2026-08-22 21:55:02 -04:00
CamilleLaVey 16b9b5aa0c Revert "[TEST] Save space with Sync2" 2026-08-22 21:41:04 -04:00
CamilleLaVey 2df4744b53 Some meows 2026-08-22 21:40:47 -04:00
CamilleLaVey faf82ccdd7 [TEST] Unify paths on MSAA part 2 2026-08-22 20:13:10 -04:00
CamilleLaVey 8f18e82931 [TEST] Unify blit helpers of msaa and depth stencil msaa 2026-08-22 16:03:44 -04:00
CamilleLaVey 7102ff8ecb Disable msaa tiler resolve + removal dead code from branch 2026-08-22 15:27:03 -04:00
CamilleLaVey 385b844e2a Remove additional feedback loop checks 2026-08-22 14:58:23 -04:00
CamilleLaVey 518f571fcf [TEST] Save space with Sync2 2026-08-22 04:42:36 -04:00
CamilleLaVey e0d4318042 Fix build 2026-08-22 03:51:12 -04:00
CamilleLaVey 06ac01adb0 [TEST] MSAA unification paths on MSAA and DepthMSAA 2026-08-22 03:41:54 -04:00
CamilleLaVey a6d49872a1 [TEST] Adjustments on the pipeline cache + optimized vertex buffer refinement 2026-08-22 03:34:01 -04:00
CamilleLaVey b0b4817162 [TEST] Adjustments on MSAA 2026-08-22 02:49:06 -04:00
CamilleLaVey 9f776cfca7 [TEST] Adjustments on color write enable 2026-08-22 01:22:01 -04:00
CamilleLaVey 5b1613087c Changes requested by reviewer meower 2026-08-22 01:07:34 -04:00
CamilleLaVey b22a796f4b Remove strings from rescale hack 2026-08-22 00:51:44 -04:00
CamilleLaVey 04cfc60756 Fix licenses 2026-08-22 00:48:13 -04:00
CamilleLaVey 6a3ff818e0 Revert "Extract pitch unswizzle pass from vk-experiments7" 2026-08-22 00:45:54 -04:00
CamilleLaVey 22b51d0c61 Extract pitch unswizzle pass from vk-experiments7 2026-08-21 23:19:49 -04:00
CamilleLaVey 78ce48e4ca add finish on block linear pass 2026-08-21 23:00:15 -04:00
CamilleLaVey 9510516e68 Fix build 2026-08-21 22:41:03 -04:00
CamilleLaVey 33ae95f4ae [TEST] Add Exchange initialization on 3D pass for linear unswizzling 2026-08-21 22:30:07 -04:00
CamilleLaVey 1cd118eccc [TEST] Return FP16 + FP32 SZ nan-preserve on QCOM 2026-08-21 21:32:19 -04:00
CamilleLaVey 4bf30b41a6 ASDÑlasfu mfudgh rq 2026-08-20 19:49:12 -04:00
CamilleLaVey 6390edb76d Remove FP32 sz nan preserve on QCOM 2026-08-20 16:33:14 -04:00
CamilleLaVey f0ba93d75f Here we go again 2026-08-20 15:36:22 -04:00
CamilleLaVey 68ff82679f GIVE ME SOMETHING 2026-08-20 02:54:02 -04:00
CamilleLaVey 543d4dfc8e [TEST] Bypass unresolved render 2026-08-20 01:48:16 -04:00
CamilleLaVey e3be392b8a [TEST] Wondering around cbuf live route 2026-08-19 23:58:21 -04:00
CamilleLaVey b754fcc49c [TEST] Ensure resolve shadow reads on msaa + shader extencil fallback 2026-08-19 22:40:54 -04:00
CamilleLaVey a4c37b5117 Flush deferred clears tied to scissor entries on framebuffer 2026-08-19 20:49:54 -04:00
CamilleLaVey 8206c7abac [TEST] Add clipping distances to GS 2026-08-19 19:05:47 -04:00
CamilleLaVey faacc62b10 [TEST] Prune on passthrough stores 2026-08-19 17:42:09 -04:00
CamilleLaVey b0a01ca629 [TEST] Add varyings to the legacy fallback when no gemoetry shader passthrough is available 2026-08-19 15:43:19 -04:00
CamilleLaVey 945d5c373b [TEST] Added another MSAA depth stencil shaders to convert into non msaa + sanitize MSAA UINT/SINT path 2026-08-19 03:13:47 -04:00
CamilleLaVey cb04b133ea [TEST] Ported fix from tiled-gpu-v2 for ZBCTable 2026-08-19 02:54:25 -04:00
CamilleLaVey ff3de66bab Quick adjustment on deferred clears order 2026-08-19 02:33:19 -04:00
CamilleLaVey 3ce788b09c Forcing off rescale hack 2026-08-19 01:55:02 -04:00
CamilleLaVey 2a1b9278b9 [TEST] MSAA depth stencil discard again 2026-08-19 00:41:14 -04:00
CamilleLaVey 842b5e0b3a [TEST] New MSAA shader for UINT/SINT color 2026-08-18 23:03:37 -04:00
CamilleLaVey 2f83399798 [TEST] Removal of IsSafeDownload death path 2026-08-18 22:35:04 -04:00
CamilleLaVey 2e52ca5753 [TEST] Corroborating the dowload/copy on MSAA path 2026-08-18 21:22:25 -04:00
CamilleLaVey 69cd5ee0d1 [TEST] Improve the geometry shader handling 2026-08-18 20:51:29 -04:00
CamilleLaVey 1f0605c35d [TEST] Remove the whole geometry shader changes 2026-08-18 20:07:28 -04:00
CamilleLaVey b9c28ef6a1 [TEST] Return the previous geometry shader passthrough handling 2026-08-18 16:25:31 -04:00
CamilleLaVey 809a61f600 [TEST] Quick revert on MSAA clamp 2026-08-17 22:11:43 -04:00
CamilleLaVey c7b24bad1a [TEST] Vertex, Samplers limits adjustments based on hardware support + MSAA Sample Counts degradation 2026-08-17 20:00:06 -04:00
CamilleLaVey 4244a6418d Fix license header + removal of ternary on depth stencil routes for msaa uploads 2026-08-17 17:57:13 -04:00
CamilleLaVey 26044e18e1 Re-apply commit 2026-08-17 00:57:17 -04:00
CamilleLaVey 04d08f0cf1 [TEST] Blitting on MSAA 2026-08-16 22:41:06 -04:00
CamilleLaVey 2bf037c7bb [TEST] Swizzling adjustment on the new fast path 2026-08-16 20:52:37 -04:00
CamilleLaVey f6668ef01b [TEST] Adjustments over the vkformat undefined and border color swizzle 2026-08-16 19:59:23 -04:00
CamilleLaVey 20a775ed08 Another meow 2026-08-16 19:34:45 -04:00
CamilleLaVey 3379556d89 [TEST] Return Xenoblade workaround + extend srgb handling on border colors 2026-08-16 18:57:37 -04:00
CamilleLaVey 8e687924b8 Funny meows 2026-08-16 18:38:56 -04:00
CamilleLaVey d3e8c5e977 [TEST] CustomBoderColor changes 2026-08-16 18:38:56 -04:00
CamilleLaVey 95e67e6533 [TEST] Aniso fixes 2026-08-16 18:38:56 -04:00
CamilleLaVey bdd1785b04 [TEST] Quick change on depth's handling 2026-08-16 18:38:56 -04:00
CamilleLaVey f9e5ac191e [TEST] Depth stencil resolve + MSAA depth stencil 2026-08-16 18:38:56 -04:00
CamilleLaVey f3a674c064 [TEST] MSAA uploads 2026-08-16 18:38:56 -04:00
CamilleLaVey 4aa35bc835 [TEST] Fix vkFormat accessors 2026-08-16 18:38:56 -04:00
CamilleLaVey 894f2544a0 [TEST] Geometry shader changes + barrier fixes 2026-08-16 18:38:56 -04:00
CamilleLaVey ceffda663c [TEST] Fix TFB subdependency 2026-08-16 18:38:56 -04:00
CamilleLaVey 27e7fa10d9 [TEST] Fix missing barrier + removal of required aligments checks inside swizzle textures 2026-08-16 18:38:56 -04:00
CamilleLaVey d3bd4f42cf [TEST] Adjustments on pipeline hash collisions 2026-08-16 18:38:56 -04:00
CamilleLaVey 5a8fb951dd [TEST] Remove subpass dependency 2026-08-16 18:38:56 -04:00
CamilleLaVey 27942142cf [TEST] Ported PSO optimizations from tiled-gpu-v2 2026-08-16 18:38:56 -04:00
CamilleLaVey db647f9a40 [TEST] Another bind vertex buffer optimization from previous handling 2026-08-16 18:38:56 -04:00
CamilleLaVey 35aeb0cf55 [TEST] Playing with SGSR 2026-08-16 18:38:56 -04:00
CamilleLaVey 3423d3c6a5 [TEST] fast path on swizzle decoding 2026-08-16 18:38:56 -04:00
CamilleLaVey 607bc18ad4 [TEST] Bring the unswizzle work from tiled-gpu-v2 2026-08-16 18:38:55 -04:00
CamilleLaVey 2000fdfb7b [vulkan, android] Initial implementation of LSFG-VK (#4263)
Pretty much self explanatory, own implementation based on LSFG-VK available with some additional reverse engineer work for the actual use of shaders inside Lossless.dll, we don't store/ bundle any tools that bypass security and self ownership: to use this feature is required to own your own copy of Lossless Scaling and I encourage to support developers behind this work.

Special thanks:

1.- THS - Original developer behind Lossless Scaling
2.- PancakeTAS - Developer behind LSFG-VK
3.- The Big Smolio The Sleeper (@gidoly)

Co-authored-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4263
2026-08-17 00:30:19 +02:00
xbzk dc95cd09ee [android] Add Samsung DeX fullscreen probes (#4245)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
Shotgun attempt to fix fullscreen issue on samsung dex.

External test mapping:
bit 1, value 2: disable runtime fullscreen attempts
bit 2, value 4: force runtime user-landscape
bit 3, value 8: add legacy sticky system UI flags
bit 4, value 16: add delayed retries
bit 5, value 32: runtime orientation unspecified
bit 6, value 64: add FLAG_LAYOUT_NO_LIMITS
Good smoke masks: 0, 8, 16, 24, 4, 12, 20, 28, 32, 40, 64, 72, 88, 2.

EDIT: 8 (bit 3) did the trick. All cleaned up.
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4245
Reviewed-by: Samuel <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-08-15 14:21:52 +02:00
181 changed files with 8534 additions and 2002 deletions
-92
View File
@@ -1,92 +0,0 @@
#!/bin/sh -ex
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
NUM_JOBS=$(nproc 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)
: "${CCACHE:=false}"
RETURN=0
usage() {
cat <<EOF
Usage: $0 [-b|--build-type BUILD_TYPE] [-o|--outdir OUTPUT_DIRECTORY]
Build script for Emscripten (using wasm64).
Options:
--build-type Set the CMake build type (Release|RelWithDebInfo|MinSizeRel|Debug)
Default: Release
--outdir Set the output directory
Default: build
EOF
exit "$RETURN"
}
die() {
echo "-- ! $*" >&2
RETURN=1 usage
}
type() {
[ -z "$1" ] && die "You must specify a valid type."
TYPE="$1"
}
outdir() {
[ -z "$1" ] && die "You must specify a valid output directory."
OUTDIR="$1"
}
while true; do
case "$1" in
-r|--release) DEVEL=false ;;
-b|--build-type) type "$2"; shift ;;
-o|--outdir) outdir "$2"; shift ;;
-h|--help) usage ;;
*) break ;;
esac
shift
done
: "${TYPE:=Release}"
: "${DEVEL:=true}"
: "${OUTDIR:=build}"
# -sMEMORY64 must be specified twice, see below
# The CMake toolchain file will match against MEMORY64 but will fail to match if:
# - it's either -sMEMORY64
# - or it's either -sMEMORY64=1
# The line in question:
# if (CMAKE_C_FLAGS MATCHES "MEMORY64")
# However why need to specify -sMEMORY64=1 then? Oh that's because if you didn't set
# the =1, it would assume you meant =0, which equates to not specifying it at all
# This seems to be fixed in later versions but occurs atleast on 4.0.3-git and below.
emcmake cmake -B "$OUTDIR" -G "Unix Makefiles" \
-DCMAKE_BUILD_TYPE=${TYPE} \
-DENABLE_OPENGL=OFF \
-DENABLE_LTO=OFF \
-DENABLE_QT=OFF \
-DENABLE_UNITY_BUILD=OFF \
-DENABLE_QT_TRANSLATION=OFF \
-DENABLE_CUBEB=OFF \
-DENABLE_LIBUSB=OFF \
-DENABLE_UPDATE_CHECKER=OFF \
-DENABLE_WEB_SERVICE=OFF \
-DUSE_DISCORD_PRESENCE=OFF \
-DENABLE_WIFI_SCAN=OFF \
-DUSE_FASTER_LINKER=ON \
-DYUZU_STATIC_BUILD=ON \
-DYUZU_USE_BUNDLED_OPENSSL=OFF \
-DYUZU_USE_EXTERNAL_FFMPEG=ON \
-Dzstd_FORCE_BUNDLED=ON \
-DOpenSSL_FORCE_BUNDLED=ON \
-DEMSCRIPTEN_SYSTEM_PROCESSOR=wasm \
-DCMAKE_C_FLAGS="-s MEMORY64 -m64 -pipe -sMEMORY64=1" \
-DCMAKE_CXX_FLAGS="-s MEMORY64 -m64 -pipe -sMEMORY64=1" \
-DCMAKE_EXE_LINKER_FLAGS="-sMEMORY64=1 -m64 -Wl,-mwasm64 -sASYNCIFY=1" \
-DCMAKE_C_LINK_FLAGS="-sMEMORY64=1 -m64 -Wl,-mwasm64 -sASYNCIFY=1" \
-DCMAKE_CXX_LINK_FLAGS="-sMEMORY64=1 -m64 -Wl,-mwasm64 -sASYNCIFY=1"
cmake --build "$OUTDIR" -- -j$NUM_JOBS
@@ -1,40 +0,0 @@
diff --git a/cmake/ConfigureOpenSSL.cmake b/cmake/ConfigureOpenSSL.cmake
index 3012e05..2ae23ff 100644
--- a/cmake/ConfigureOpenSSL.cmake
+++ b/cmake/ConfigureOpenSSL.cmake
@@ -108,7 +108,8 @@ function(configure_openssl)
)
if(NOT "${CONFIGURE_OPTIONS_OLD}" STREQUAL "")
- if(CONFIGURE_OPTIONS STREQUAL CONFIGURE_OPTIONS_OLD)
+ # TODO(lizzie): Emscripten has issues with rebuilding due to the wrapper it uses
+ if(CMAKE_SYSTEM_NAME MATCHES "Emscripten" OR CONFIGURE_OPTIONS STREQUAL CONFIGURE_OPTIONS_OLD)
message(STATUS "Found previous configure results. Don't perform configuration")
return()
endif()
@@ -134,10 +135,24 @@ function(configure_openssl)
set(VERBOSE_OPTION OUTPUT_QUIET)
endif()
+ if (CMAKE_SYSTEM_NAME MATCHES "Emscripten")
+ set(EMSCRIPTEN_CMAKE_WRAPPER "emcmake")
+ find_program(EMCC emcc REQUIRED)
+ set(EMSCRIPTEN_LINKER ${EMCC})
+ list(APPEND CONFIGURE_COMMAND wasm64)
+ else()
+ set(EMSCRIPTEN_CMAKE_WRAPPER "")
+ set(EMSCRIPTEN_LINKER ${CMAKE_LINKER})
+ endif ()
+
execute_process(
- COMMAND ${CMAKE_COMMAND} -E env
+ COMMAND ${EMSCRIPTEN_CMAKE_WRAPPER} ${CMAKE_COMMAND} -E env
"CFLAGS=${CMAKE_C_FLAGS}"
"CXXFLAGS=${CMAKE_CXX_FLAGS}"
+ "LDFLAGS=${CMAKE_CXX_LINK_FLAGS}"
+ "CC=${CMAKE_C_COMPILER}"
+ "CXX=${CMAKE_CXX_COMPILER}"
+ "LD=${EMSCRIPTEN_LINKER}"
${CONFIGURE_COMMAND}
WORKING_DIRECTORY ${CONFIGURE_BUILD_DIR}
${VERBOSE_OPTION}
-112
View File
@@ -1,112 +0,0 @@
diff --git a/Configurations/10-main.conf b/Configurations/10-main.conf
index e62721e..243feb4 100644
--- a/Configurations/10-main.conf
+++ b/Configurations/10-main.conf
@@ -1970,6 +1970,26 @@ my %targets = (
multilib => "64",
},
+ "wasm32" => {
+ inherit_from => [ "BASE_unix" ],
+ CC => "emcc",
+ CXX => "emc++",
+ cflags => combine("--target=wasm32-unknown-emscripten", threads("-pthread")),
+ cxxflags => combine("--target=wasm32-unknown-emscripten", threads("-pthread")),
+ lib_cppflags => add("-DL_ENDIAN"),
+ bn_ops => "THIRTY_TWO_BIT",
+ },
+ "wasm64" => {
+ inherit_from => [ "BASE_unix" ],
+ CC => "emcc",
+ CXX => "emc++",
+ cflags => combine("--target=wasm64-unknown-emscripten", threads("-pthread")),
+ cxxflags => combine("--target=wasm64-unknown-emscripten", threads("-pthread")),
+ lib_cppflags => add("-DL_ENDIAN"),
+ bn_ops => "SIXTY_FOUR_BIT_LONG",
+ },
+
+
#### uClinux
"uClinux-dist" => {
inherit_from => [ "BASE_unix" ],
diff --git a/crypto/rand/rand_lib.c b/crypto/rand/rand_lib.c
index d9e8f02..7faf347 100644
--- a/crypto/rand/rand_lib.c
+++ b/crypto/rand/rand_lib.c
@@ -379,17 +379,27 @@ void RAND_add(const void *buf, int num, double randomness)
#if !defined(OPENSSL_NO_DEPRECATED_1_1_0)
int RAND_pseudo_bytes(unsigned char *buf, int num)
{
+#if defined(__wasi__)
+ arc4random_buf(buf, num);
+ return 1;
+#elif defined(__EMSCRIPTEN__)
+ return 1;
+#else
const RAND_METHOD *meth = RAND_get_rand_method();
if (meth != NULL && meth->pseudorand != NULL)
return meth->pseudorand(buf, num);
ERR_raise(ERR_LIB_RAND, RAND_R_FUNC_NOT_IMPLEMENTED);
return -1;
+#endif
}
#endif
int RAND_status(void)
{
+#if defined(__EMSCRIPTEN__) || defined(__wasi__)
+ return 1;
+#else
EVP_RAND_CTX *rand;
#ifndef OPENSSL_NO_DEPRECATED_3_0
const RAND_METHOD *meth = RAND_get_rand_method();
@@ -401,6 +411,7 @@ int RAND_status(void)
if ((rand = RAND_get0_primary(NULL)) == NULL)
return 0;
return EVP_RAND_get_state(rand) == EVP_RAND_STATE_READY;
+#endif
}
#else /* !FIPS_MODULE */
@@ -420,6 +431,12 @@ const RAND_METHOD *RAND_get_rand_method(void)
int RAND_priv_bytes_ex(OSSL_LIB_CTX *ctx, unsigned char *buf, size_t num,
unsigned int strength)
{
+#if defined(__wasi__)
+ arc4random_buf(buf, num);
+ return 1;
+#elif defined(__EMSCRIPTEN__)
+ return 1;
+#else
RAND_GLOBAL *dgbl;
EVP_RAND_CTX *rand;
#if !defined(OPENSSL_NO_DEPRECATED_3_0) && !defined(FIPS_MODULE)
@@ -451,6 +468,7 @@ int RAND_priv_bytes_ex(OSSL_LIB_CTX *ctx, unsigned char *buf, size_t num,
return EVP_RAND_generate(rand, buf, num, strength, 0, NULL, 0);
return 0;
+#endif
}
int RAND_priv_bytes(unsigned char *buf, int num)
diff --git a/ssl/ssl_cert.c b/ssl/ssl_cert.c
index 3d21801..e026f8f 100644
--- a/ssl/ssl_cert.c
+++ b/ssl/ssl_cert.c
@@ -941,6 +941,7 @@ done:
return ret;
}
+#ifndef OPENSSL_NO_POSIX_IO
int SSL_add_dir_cert_subjects_to_stack(STACK_OF(X509_NAME) *stack,
const char *dir)
{
@@ -1016,6 +1017,7 @@ err:
return ret;
}
+#endif
static int add_uris_recursive(STACK_OF(X509_NAME) *stack,
const char *uri, int depth)
+11 -23
View File
@@ -81,6 +81,9 @@ cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSV
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))
set(YUZU_STATIC_BUILD ON)
@@ -339,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()
@@ -375,15 +379,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
# Prefer the -pthread flag on Linux.
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
# It is absolutely promordial to enable on Emscripten
# Not only this allows to use std::thread and std::jthread without exceptions
# but it also fixes several issues related to MT operations.
# ...and CMake doesn't include it by default even when we specify
# that we prefer the pthread flag; why is that? I don't know.
if (PLATFORM_EMSCRIPTEN)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthread>)
add_link_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthread>)
endif()
find_package(RenderDoc MODULE)
@@ -412,12 +407,7 @@ set(BUILD_TESTING OFF)
set(ENABLE_TESTING OFF)
# boost
if (PLATFORM_EMSCRIPTEN)
set(BOOST_INCLUDE_LIBRARIES algorithm icl pool container heap headers filesystem crc variant)
set(BOOST_CONTAINER_HEADER_ONLY ON)
else()
set(BOOST_INCLUDE_LIBRARIES algorithm icl pool container heap asio headers process filesystem crc variant)
endif()
set(BOOST_INCLUDE_LIBRARIES algorithm icl pool container heap asio headers process filesystem crc variant)
AddJsonPackage(boost)
@@ -438,12 +428,10 @@ if (Boost_ADDED)
target_compile_options(boost_heap INTERFACE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shadow>)
target_compile_options(boost_icl INTERFACE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shadow>)
# May not exist (i.e emscripten)
if (TARGET boost_asio)
target_compile_options(boost_asio INTERFACE
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-conversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-fallthrough>)
endif()
target_compile_options(boost_asio INTERFACE
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-conversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-fallthrough>
)
endif()
endif()
+3 -5
View File
@@ -108,7 +108,7 @@
"find_args": "MODULE GLOBAL",
"hash": "159ed94965018f2a371d45a3bfc1961e5fb1549e501ded70a6b4532d7fe99d0579c18b5195aff6e35f96f399b426cea2650ec9fb75ef80d4c9edeccb51f2e6c9",
"options": [
"HTTPLIB_REQUIRE_OPENSSL OFF",
"HTTPLIB_REQUIRE_OPENSSL ON",
"HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES ON"
],
"patches": [
@@ -190,8 +190,7 @@
"min_version": "3.0.0",
"package": "OpenSSL",
"patches": [
"0001-add-bundled-cert.patch",
"0002-wasm-support.patch"
"0001-add-bundled-cert.patch"
],
"repo": "openssl/openssl",
"version": "openssl-3.6.2"
@@ -214,8 +213,7 @@
"0001-cpmutil-compat.patch",
"0002-use-ccache.patch",
"0003-use-cmake-compiler-flags.patch",
"0004-use-shell-wrapper.patch",
"0005-wasm-support.patch"
"0004-use-shell-wrapper.patch"
],
"repo": "jimmy-park/openssl-cmake",
"version": "3.6.2"
-17
View File
@@ -12,7 +12,6 @@
- [NetBSD](#netbsd)
- [MSYS2](#msys2)
- [RedoxOS](#redoxos)
- [WebAssembly](#webassembly)
- [Windows](#windows)
- [Windows 7, Windows 8 and Windows 8.1](#windows-7-windows-8-and-windows-81)
- [Windows Vista and below](#windows-vista-and-below)
@@ -246,22 +245,6 @@ The package install may randomly hang at times, in which case it has to be resta
When CMake invokes certain file syscalls - it may sometimes cause crashes or corruptions on the (kernel?) address space - so reboot the system if there is a "hang" in CMake.
## WebAssembly
**It doesn't run on a browser yet.**
WebAssembly or "WASM" for short is a *mainly 32-bit* virtual "architecture" which we only bootstrap on 64-bit only. This means not only the program runs 2x slower than it would due to using JS BigInt, it also means we need to go out of our way to enable proper 64-bit support via `-sMEMORY64=1`, however once again, some Emscripten quirks force us to specify `-s MEMORY64` and `-sMEMORY64=1` at the same time: see the [CI build script](../.ci/wasm/build.sh).
The WebAssembly target is very heavy on resources and requires at least 4 times the normal amount of resources that a native build would. Additionally, the only supported environment is Firefox at the moment, node.js and `wasmtime are not supported (PRs welcome!)
If running under Firefox and you hit "out of memory" on dev console, close the entire tab, then open it back again, see [this issue](https://github.com/emscripten-core/emscripten/issues/8126).
To run the binary (after building) you should be fine with `node ./eden-cli.js`. For obvious reasons no Qt frontend is available on WASM, support for Vulkan is done charily via [llvmpipe2wasm](https://github.com/Devsh-Graphics-Programming/llvmpipe2wasm).
If you run into the error "acorn.js can't be found" check [this associated issue](https://github.com/emscripten-core/emscripten/issues/13368), the fix in short is `npm --global install acorn`. On FreeBSD you could run `npm` under root, or you could do the sane thing and do `sudo chown -R $USER /usr/local/lib/node_modules/ /usr/local/bin/` (remember to restore permissions afterwards!) unless you wish to run `npm` under root which is generally a bad idea.
2026-06-09: As of writing, no Dynarmic-based JIT is possible on this target, full interpreted emulation is the only reasonable option. While there is some efforts on making a JIT like [here](https://github.com/wingo/wasm-jit) or [here](https://wingolog.org/archives/2022/08/18/just-in-time-code-generation-within-webassembly), the result is so latency expensive we're better off using an interpreter instead.
## Windows
### Windows 7, Windows 8 and Windows 8.1
-8
View File
@@ -359,14 +359,6 @@ pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel b
[Caveats](./Caveats.md#haikuos).
</details>
<details>
<summary>WebAssembly</summary>
Emscripten: The default installation should provide enough.
[Caveats](./Caveats.md#wasm).
</details>
<details>
<summary>RedoxOS</summary>
+5 -9
View File
@@ -94,10 +94,6 @@ function(detect_architecture_symbols)
endfunction()
# arches here are put in a sane default order of importance
# EXCEPT FOR WASM, which must be probed for FIRST, because some genius
# decided to also allow the host architecture to be defined when building
# for the emscripten target, absolutely lovely detail.
#
# notably, amd64, arm64, and riscv (in order) are BY FAR the most common
# mips is pretty popular in embedded
# ppc64 is pretty popular in supercomputing
@@ -105,11 +101,6 @@ endfunction()
# ia64 exists
# the rest exist, but are probably less popular than ia64
detect_architecture_symbols(
ARCH wasm
SYMBOLS
"__EMSCRIPTEN__")
detect_architecture_symbols(
ARCH arm64
SYMBOLS
@@ -212,6 +203,11 @@ detect_architecture_symbols(
"__loongarch__"
"__loongarch64")
detect_architecture_symbols(
ARCH wasm
SYMBOLS
"__EMSCRIPTEN__")
# "generic" target
# If you have reached this point, you're on some as-of-yet unsupported architecture.
# See the docs up above for known unsupported architectures
-25
View File
@@ -20,9 +20,6 @@ elseif (${CMAKE_SYSTEM_NAME} STREQUAL "managarm")
set(MANAGARM ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Haiku")
set(HAIKUOS ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
set(EMSCRIPTEN ON)
message(WARNING "${CMAKE_LIBRARY_ARCHITECTURE} support is highly experimental!!!")
endif()
# BSD
@@ -100,25 +97,3 @@ if (MINGW)
set(CMAKE_EXE_LINKER_FLAGS_RELEASE
"${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${MINGW_FLAGS}")
endif()
if (PLATFORM_EMSCRIPTEN)
set(EMSCRIPTEN_C_FLAGS "-s MEMORY64 -m64 -pipe -sMEMORY64=1")
set(EMSCRIPTEN_LINK_FLAGS "-sMEMORY64=1 -m64 -Wl,-mwasm64 -sASYNCIFY=1")
# This prevents FFmpeg and other libraries from assuming it's the host's CPU
# Additionally some Emscripten installs may not be very good... generally
set(EMSCRIPTEN_SYSTEM_PROCESSOR wasm)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EMSCRIPTEN_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${EMSCRIPTEN_C_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${EMSCRIPTEN_LINK_FLAGS}")
set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} ${EMSCRIPTEN_LINK_FLAGS}")
set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} ${EMSCRIPTEN_LINK_FLAGS}")
unset(EMSCRIPTEN_C_FLAGS)
unset(EMSCRIPTEN_LINK_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()
+19 -56
View File
@@ -37,39 +37,26 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG)
elseif (NOT (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES CMAKE_SYSTEM_PROCESSOR
AND CMAKE_HOST_SYSTEM_NAME MATCHES CMAKE_SYSTEM_NAME))
string(TOLOWER "${CMAKE_SYSTEM_NAME}" FFmpeg_SYSTEM_NAME)
# All of these platforms are supported by ffmpeg as native build OSes
# anything else (like Redox or Managarm or PS4) is NOT natively supported
# hence, assume the "unix like" is just "none" for the sake of OUR sanity.
# If YOUR OS/platform has actual native support:
# 1. fucking congrats
# 2. feel free to add it on the condition below
if (NOT (PLATFORM_NETBSD OR PLATFORM_SUN OR PLATFORM_FREEBSD
OR PLATFORM_OPENBSD OR PLATFORM_DRAGONFLYBSD OR PLATFORM_HAIKU
OR PLATFORM_LINUX OR PLATFORM_MSYS OR WIN32 OR ANDROID OR APPLE))
if (FFmpeg_SYSTEM_NAME STREQUAL "openorbis" OR FFmpeg_SYSTEM_NAME STREQUAL "managarm")
set(FFmpeg_SYSTEM_NAME "none")
endif()
# TODO: Can we really do better? Auto-detection? Something clever?
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
--enable-cross-compile
--arch="${CMAKE_SYSTEM_PROCESSOR}"
--target-os="${FFmpeg_SYSTEM_NAME}")
if (PLATFORM_EMSCRIPTEN)
# funniest trolling from emscripten, such a classic!
# maybe I should PR so they use CMAKE_SYSROOT... y'know?
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --sysroot="${EMSCRIPTEN_SYSROOT}")
else()
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --sysroot="${CMAKE_SYSROOT}")
endif()
--target-os="${FFmpeg_SYSTEM_NAME}"
--sysroot="${CMAKE_SYSROOT}"
)
if (DEFINED FFmpeg_CROSS_PREFIX)
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --cross-prefix="${FFmpeg_CROSS_PREFIX}")
else()
message(WARNING "Please set FFmpeg_CROSS_PREFIX to your cross toolchain prefix, for example: ${CMAKE_STAGING_PREFIX}/bin/${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}")
message(WARNING "Please set FFmpeg_CROSS_PREFIX to your cross toolchain prefix, for example: \${CMAKE_STAGING_PREFIX}/bin/${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}-")
endif()
set(FFmpeg_IS_CROSS_COMPILING TRUE)
endif()
endif()
if (OPENORBIS OR MANAGARM OR EMSCRIPTEN)
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)
@@ -177,7 +164,8 @@ if (OPENORBIS)
-lSceUserService
-lSceSysmodule
-lSceNet
-lSceLibcInternal)
-lSceLibcInternal
)
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
--disable-pthreads
--extra-cflags=${CMAKE_SYSROOT}/usr/include
@@ -188,18 +176,8 @@ elseif (MANAGARM)
# Required for proper stuff
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
--disable-pthreads
--extra-libs="${FFmpeg_CROSS_COMPILE_LIBS}")
endif()
# Usually used by Emscripten
if (DEFINED CMAKE_AR)
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --ar=${CMAKE_AR})
endif()
if (DEFINED CMAKE_NM)
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --nm=${CMAKE_NM})
endif()
if (DEFINED CMAKE_RANLIB)
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --ranlib=${CMAKE_RANLIB})
--extra-libs="${FFmpeg_CROSS_COMPILE_LIBS}"
)
endif()
if (YUZU_USE_BUNDLED_FFMPEG)
@@ -229,7 +207,6 @@ else()
# Build FFmpeg from externals
message(STATUS "Using FFmpeg from externals")
set(FFmpeg_LD ${CMAKE_LINKER})
if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86_64|amd64)")
# FFmpeg has source that requires one of nasm or yasm to assemble it.
# REQUIRED throws an error if not found here during configuration rather than during compilation.
@@ -238,12 +215,6 @@ else()
message(FATAL_ERROR "One of either `nasm` or `yasm` not found but is required.")
endif()
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
# TODO: this is 100% redundant but we need it because CMAKE_LINKER can resolve to ld.lld
# which is NOT compatible
find_program(EMCC NAMES emcc REQUIRED)
set(FFmpeg_LD ${EMCC})
endif()
find_program(AUTOCONF autoconf)
if ("${AUTOCONF}" STREQUAL "AUTOCONF-NOTFOUND")
@@ -277,13 +248,7 @@ else()
CACHE PATH "Paths to FFmpeg libraries" FORCE)
endforeach()
# Some SDKs (especially wasm) require us, actually, WANT us to use their "emconfigure"
# otherwise they will cry a billion tears
if (PLATFORM_EMSCRIPTEN)
find_program(FFmpeg_CONFIGURE_WRAPPER emconfigure REQUIRED)
else()
find_program(FFmpeg_CONFIGURE_WRAPPER bash REQUIRED)
endif()
find_program(BASH_PROGRAM bash REQUIRED)
# `configure` parameters builds only exactly what yuzu needs from FFmpeg
# `--disable-vdpau` is needed to avoid linking issues
@@ -293,7 +258,7 @@ else()
OUTPUT
${FFmpeg_MAKEFILE}
COMMAND
${FFmpeg_CONFIGURE_WRAPPER} ${FFmpeg_PREFIX}/configure
${BASH_PROGRAM} ${FFmpeg_PREFIX}/configure
--disable-avdevice
--disable-avformat
--disable-doc
@@ -302,10 +267,6 @@ else()
--disable-ffprobe
--disable-network
--disable-swresample
--disable-autodetect
--disable-runtime-cpudetect
--disable-debug
--disable-programs
--enable-decoder=h264
--enable-decoder=vp8
--enable-decoder=vp9
@@ -313,7 +274,7 @@ else()
--enable-pic
--cc=${FFmpeg_CC}
--cxx=${FFmpeg_CXX}
--ld=${FFmpeg_LD}
--ld=${CMAKE_LINKER}
--extra-cflags=${CMAKE_C_FLAGS}
--extra-cxxflags=${CMAKE_CXX_FLAGS}
--extra-ldflags=${CMAKE_C_LINK_FLAGS}
@@ -329,7 +290,11 @@ else()
# Workaround for Ubuntu 18.04's older version of make not being able to call make as a child
# with context of the jobserver. Also helps ninja users.
cmake_host_system_information(RESULT SYSTEM_THREADS QUERY NUMBER_OF_LOGICAL_CORES)
execute_process(
COMMAND
nproc
OUTPUT_VARIABLE
SYSTEM_THREADS)
set(FFmpeg_BUILD_LIBRARIES ${FFmpeg_LIBRARIES})
@@ -337,8 +302,6 @@ else()
if (LINUX OR ANDROID OR APPLE OR WIN32 OR FREEBSD)
set(FFmpeg_MAKE_ARGS -j${SYSTEM_THREADS})
else()
# No GNU make implies that this system may be highly non-GNU
find_program(MAKE make required)
set(FFmpeg_MAKE_ARGS "")
endif()
@@ -346,7 +309,7 @@ else()
OUTPUT
${FFmpeg_BUILD_LIBRARIES}
COMMAND
${MAKE} ${FFmpeg_MAKE_ARGS}
gmake ${FFmpeg_MAKE_ARGS}
WORKING_DIRECTORY
${FFmpeg_BUILD_DIR}
)
-2
View File
@@ -14,9 +14,7 @@
namespace Tz {
namespace {
#ifndef EINVAL
#define EINVAL 22
#endif
static Rule gmtmem{};
static Rule* const gmtptr = &gmtmem;
+2 -5
View File
@@ -177,6 +177,7 @@ else()
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-invalid-offsetof>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-unused-parameter>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-missing-field-initializers>)
if (CXX_CLANG OR CXX_ICC OR CXX_APPLE) # Clang, AppleClang, or Intel C++
if (NOT MSVC)
add_compile_options(
@@ -190,11 +191,7 @@ else()
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-nullability-completeness>)
endif()
if (ARCHITECTURE_wasm)
# we are evil but fmt is even more evil
add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shorten-64-to-32>)
elseif (ARCHITECTURE_x86_64)
if (ARCHITECTURE_x86_64)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mcx16>)
if (LINUX OR FREEBSD)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mtls-dialect=gnu2>)
@@ -539,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 =
@@ -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),
@@ -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 = "%"
)
)
@@ -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,
@@ -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)
}
@@ -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)
@@ -302,7 +339,6 @@ class SettingsFragmentPresenter(
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)
@@ -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 = "%"
)
}
@@ -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) {
@@ -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{};
+92 -1
View File
@@ -44,6 +44,7 @@ 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"
@@ -90,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"
@@ -998,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;
@@ -1029,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) {
@@ -1037,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) {
@@ -1053,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()) {
@@ -1090,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) {
@@ -1165,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) {
@@ -1390,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"
@@ -514,8 +514,6 @@
<string name="fix_bloom_effects_description">يقلل من ضبابية التوهج في LA/EOW (Adreno A6XX - A7XX/ Turnip)، ويزيل التوهج في Burnout. تحذير: قد يسبب تشوهات رسومية في ألعاب أخرى.</string>
<string name="emulate_bgr565">محاكاة BGR565</string>
<string name="emulate_bgr565_description">يُصلح مشاكل انعكاس الألوان في الألعاب أو ظهور تشوهات غريبة أو ظلال غريبة.</string>
<string name="rescale_hack">تفعيل ميزة إعادة التحجيم القديمة</string>
<string name="rescale_hack_description">يُمكّن هذا الخيار من التعامل مع عملية إعادة تحجيم الألعاب بطريقة تقليدية باستخدام مسار إعادة التحجيم السريع</string>
<string name="renderer_asynchronous_shaders">استخدم تظليل غير متزامن</string>
<string name="renderer_asynchronous_shaders_description">يقوم بتجميع التظليل بشكل غير متزامن. قد يقلل ذلك من التقطعات ولكنه قد يؤدي أيضًا إلى حدوث أخطاء.</string>
<string name="gpu_unswizzle_settings">إعدادات إلغاء ترتيب بيانات وحدة معالجة الرسومات</string>
@@ -1088,7 +1086,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>
@@ -1004,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>
@@ -506,8 +506,6 @@
<string name="fix_bloom_effects_description">Reduce el efecto de resplandor en LA/EOW (Adreno A6XX - A7XX/ Turnip), elimina el resplandor en Burnout. Advertencia: puede causar artefactos gráficos en otros juegos.</string>
<string name="emulate_bgr565">Emular BGR565</string>
<string name="emulate_bgr565_description">Soluciona problemas con colores invertidos en juegos, artefactos o sombras extrañas.</string>
<string name="rescale_hack">Activar la pasada de reescalado heredada</string>
<string name="rescale_hack_description">Permite el manejo de versiones anteriores para el paso de configuración de reescalado para juegos mediante el uso de una ruta de reescalado rápida.</string>
<string name="renderer_asynchronous_shaders">Usar sombreadores asíncronos</string>
<string name="renderer_asynchronous_shaders_description">Compila los sombreadores de forma asíncrona. Esto puede reducir los tirones, pero también puede introducir errores gráficos.</string>
<string name="gpu_unswizzle_settings">Ajustes de desentrelazado de la GPU</string>
@@ -1080,7 +1078,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>
@@ -1016,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>
@@ -948,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>
@@ -510,8 +510,6 @@
<string name="fix_bloom_effects_description">Частично убирает размытие в LA/EOW (Adreno A6XX - A7XX/ Turnip), полностью отключает его в Burnout. Внимание: может вызывать графические артефакты в других играх.</string>
<string name="emulate_bgr565">Эмулировать BGR565</string>
<string name="emulate_bgr565_description">Исправляет проблемы с инвертированными цветами в играх, а также со странными артефактами или некорректными тенями.</string>
<string name="rescale_hack">Включить старый метод изменения разрешения</string>
<string name="rescale_hack_description">Включает старый метод обработки этапа перенастройки масштабирования для игр за счёт использования быстрого алгоритма перемасштабирования.</string>
<string name="renderer_asynchronous_shaders">Использовать асинхронные шейдеры</string>
<string name="renderer_asynchronous_shaders_description">Компилирует шейдеры асинхронно. Это может уменьшить подтормаживания, но также может вызвать графические артефакты.</string>
<string name="gpu_unswizzle_settings">Настройки распаковки текстур (Unswizzle)</string>
@@ -1084,7 +1082,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>
@@ -505,8 +505,6 @@
<string name="fix_bloom_effects_description">Зменшує розмиття світіння в LA/EOW (Adreno A6XX–A7XX / Turnip), прибирає світіння в Burnout. Увага: може спричинити графічні артефакти в інших іграх.</string>
<string name="emulate_bgr565">Емулювати BGR565</string>
<string name="emulate_bgr565_description">Виправляє проблеми з інвертованими кольорами в іграх або дивними артефактами чи тінями.</string>
<string name="rescale_hack">Увімкнути застаріле масштабування</string>
<string name="rescale_hack_description">Вмикає застарілу обробку масштабування для ігор, використовуючи швидкий шлях масштабування</string>
<string name="renderer_asynchronous_shaders">Асинхронні шейдери</string>
<string name="renderer_asynchronous_shaders_description">Компілює шейдери асинхронно. Це може зменшити затримки, але також може спричинити графічні баги.</string>
<string name="gpu_unswizzle_settings">Налаштування розпакування за допомогою ГП</string>
@@ -1066,7 +1064,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>
@@ -504,8 +504,6 @@
<string name="fix_bloom_effects_description">减少《智慧的再现》和《众神的三角力量2》(Adreno A6XX - A7XX/ Turnip)中的 bloom 模糊,并移除《Burnout》中的 bloom 效果。警告:可能会导致在其他游戏中出现图形异常。</string>
<string name="emulate_bgr565">模拟 BGR565</string>
<string name="emulate_bgr565_description">修复游戏中的颜色反转或是异常的画面瑕疵或阴影问题</string>
<string name="rescale_hack">启用旧版缩放处理</string>
<string name="rescale_hack_description">启用通过使用快速缩放路径,来为游戏提供缩放配置处理的传统处理方式</string>
<string name="renderer_asynchronous_shaders">使用异步着色器</string>
<string name="renderer_asynchronous_shaders_description">以异步方式编译着色器。采用此方式或可减少卡顿,但也可能引入故障点。</string>
<string name="gpu_unswizzle_settings">GPU Unswizzle 设置</string>
@@ -1078,7 +1076,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>
@@ -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>
+52 -6
View File
@@ -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">
@@ -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>
@@ -520,8 +581,6 @@
<string name="fix_bloom_effects_description">Reduces bloom blur in LA/EOW (Adreno A6XX - A7XX/ Turnip), removes bloom in Burnout. Warning: may cause graphical artifacts in other games.</string>
<string name="emulate_bgr565">Emulate BGR565</string>
<string name="emulate_bgr565_description">Fixes problems with inverted colors in games or strange artifacts or strange shadows.</string>
<string name="rescale_hack">Enable Legacy Rescale Pass</string>
<string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string>
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
@@ -1185,9 +1244,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 -5
View File
@@ -235,11 +235,7 @@ else()
target_link_libraries(common PUBLIC Boost::headers)
endif()
target_link_libraries(common PRIVATE OpenSSL::SSL)
target_link_libraries(common PUBLIC Boost::filesystem httplib::httplib nlohmann_json::nlohmann_json)
if (NOT PLATFORM_EMSCRIPTEN)
# Emscripten is: "bring your own implementation", boost doesn't add upstream support sadly
target_link_libraries(common PRIVATE Boost::context)
endif()
target_link_libraries(common PUBLIC Boost::filesystem Boost::context httplib::httplib nlohmann_json::nlohmann_json)
if (lz4_ADDED)
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)
+11 -84
View File
@@ -11,12 +11,7 @@
#include "common/fiber.h"
#include "common/virtual_buffer.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/fiber.h>
#else
#include <boost/context/detail/fcontext.hpp>
#endif
namespace Common {
@@ -27,103 +22,36 @@ constexpr size_t DEFAULT_STACK_SIZE = 512 * 4096;
#endif
constexpr u32 CANARY_VALUE = 0xDEADBEEF;
#ifdef __EMSCRIPTEN__
struct Fiber::FiberImpl {
FiberImpl() {}
u32 canary_1 = CANARY_VALUE;
std::array<u8, DEFAULT_STACK_SIZE> stack{};
std::array<u8, DEFAULT_STACK_SIZE> astack{};
u32 canary_2 = CANARY_VALUE;
emscripten_fiber_t* context{nullptr};
std::mutex guard;
std::function<void()> entry_point;
std::shared_ptr<Fiber> previous_fiber;
bool is_thread_fiber = false;
bool released = false;
};
Fiber::Fiber(std::function<void()>&& entry_point_func) : impl{std::make_unique<FiberImpl>()} {
impl->entry_point = std::move(entry_point_func);
emscripten_fiber_init(impl->context, [](void *user_data) -> void {
auto* fiber = static_cast<Fiber*>(user_data);
ASSERT(fiber && fiber->impl && fiber->impl->previous_fiber && fiber->impl->previous_fiber->impl);
ASSERT(fiber->impl->canary_1 == CANARY_VALUE);
ASSERT(fiber->impl->canary_2 == CANARY_VALUE);
fiber->impl->previous_fiber->impl->context = fiber->impl->context;
fiber->impl->previous_fiber->impl->guard.unlock();
fiber->impl->previous_fiber.reset();
fiber->impl->entry_point();
UNREACHABLE();
}, impl.get(), impl->stack.data(), impl->stack.size(), impl->astack.data(), impl->astack.size());
}
Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {}
Fiber::~Fiber() {
if (!impl->released) {
// Make sure the Fiber is not being used
const bool locked = impl->guard.try_lock();
ASSERT(locked && "Destroying a fiber that's still running");
if (locked) {
impl->guard.unlock();
}
}
}
void Fiber::Exit() {
ASSERT(impl->is_thread_fiber && "Exiting non main thread fiber");
if (impl->is_thread_fiber) {
impl->guard.unlock();
impl->released = true;
}
}
void Fiber::YieldTo(std::weak_ptr<Fiber> weak_from, Fiber& to) {
to.impl->guard.lock();
to.impl->previous_fiber = weak_from.lock();
emscripten_fiber_swap(to.impl->context, to.impl->previous_fiber->impl->context);
// "from" might no longer be valid if the thread was killed
if (auto from = weak_from.lock()) {
if (from->impl->previous_fiber == nullptr) {
ASSERT(false && "previous_fiber is nullptr!");
} else {
from->impl->previous_fiber->impl->guard.unlock();
from->impl->previous_fiber.reset();
}
}
}
std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
fiber->impl->guard.lock();
fiber->impl->is_thread_fiber = true;
return fiber;
}
#else
struct Fiber::FiberImpl {
FiberImpl() {}
u32 canary_1 = CANARY_VALUE;
std::array<u8, DEFAULT_STACK_SIZE> stack{};
std::array<u8, DEFAULT_STACK_SIZE> rewind_stack{};
u32 canary_2 = CANARY_VALUE;
boost::context::detail::fcontext_t context{};
boost::context::detail::fcontext_t rewind_context{};
std::mutex guard;
std::function<void()> entry_point;
std::function<void()> rewind_point;
std::shared_ptr<Fiber> previous_fiber;
u8* stack_limit = nullptr;
u8* rewind_stack_limit = nullptr;
bool is_thread_fiber = false;
bool released = false;
};
void Fiber::SetRewindPoint(std::function<void()>&& rewind_func) {
impl->rewind_point = std::move(rewind_func);
}
Fiber::Fiber(std::function<void()>&& entry_point_func) : impl{std::make_unique<FiberImpl>()} {
impl->entry_point = std::move(entry_point_func);
impl->stack_limit = impl->stack.data();
impl->rewind_stack_limit = impl->rewind_stack.data();
u8* stack_base = impl->stack_limit + DEFAULT_STACK_SIZE;
impl->context = boost::context::detail::make_fcontext(stack_base, impl->stack.size(), [](boost::context::detail::transfer_t transfer) -> void {
auto* fiber = static_cast<Fiber*>(transfer.data);
@@ -144,7 +72,7 @@ Fiber::~Fiber() {
if (!impl->released) {
// Make sure the Fiber is not being used
const bool locked = impl->guard.try_lock();
ASSERT(locked && "Destroying a fiber that's still running");
ASSERT_MSG(locked, "Destroying a fiber that's still running");
if (locked) {
impl->guard.unlock();
}
@@ -152,7 +80,7 @@ Fiber::~Fiber() {
}
void Fiber::Exit() {
ASSERT(impl->is_thread_fiber && "Exiting non main thread fiber");
ASSERT_MSG(impl->is_thread_fiber, "Exiting non main thread fiber");
if (impl->is_thread_fiber) {
impl->guard.unlock();
impl->released = true;
@@ -182,6 +110,5 @@ std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
fiber->impl->is_thread_fiber = true;
return fiber;
}
#endif
} // namespace Common
+2 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -45,6 +45,7 @@ public:
/// Fiber 'from' must be the currently running fiber.
static void YieldTo(std::weak_ptr<Fiber> weak_from, Fiber& to);
[[nodiscard]] static std::shared_ptr<Fiber> ThreadToFiber();
void SetRewindPoint(std::function<void()>&& rewind_func);
/// Only call from main thread's fiber
void Exit();
private:
+4 -1
View File
@@ -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"
+1 -5
View File
@@ -136,11 +136,6 @@ public:
eden_path = GetDataDirectory("XDG_DATA_HOME") / EDEN_DIR;
eden_path_cache = GetDataDirectory("XDG_CACHE_HOME") / EDEN_DIR;
eden_path_config = GetDataDirectory("XDG_CONFIG_HOME") / EDEN_DIR;
#if defined(__EMSCRIPTEN__) || defined(__wasi__) || defined(__managarm__)
// folders MAY not exist in this distrobution/OS
CreateParentDir(GetDataDirectory("XDG_CONFIG_HOME"));
CreateParentDir(GetDataDirectory("XDG_CACHE_HOME"));
#endif
} else {
eden_path_cache = eden_path / CACHE_DIR;
eden_path_config = eden_path / CONFIG_DIR;
@@ -162,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);
+2 -1
View File
@@ -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.
+6 -6
View File
@@ -394,7 +394,7 @@ private:
ankerl::unordered_dense::map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset
};
#elif defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__)
#elif defined(__OPENORBIS__) || defined(__managarm__)
// None of the luxuries of POSIX, all of the suffering
// For managarm: see https://github.com/managarm/managarm/issues/1370
#else // ^^^ Windows ^^^ vvv POSIX vvv
@@ -689,7 +689,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
: backing_size(backing_size_)
, virtual_size(virtual_size_)
{
#if defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__)
#if defined(__OPENORBIS__) || defined(__managarm__)
LOG_WARNING(HW_Memory, "Platform doesn't support fastmem");
fallback_buffer.emplace(backing_size);
backing_base = fallback_buffer->data();
@@ -723,7 +723,7 @@ HostMemory::HostMemory(HostMemory&&) noexcept = default;
HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default;
void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms, bool separate_heap) {
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
#if !(defined(__OPENORBIS__) || defined(__managarm__))
ASSERT(virtual_offset % PageAlignment == 0);
ASSERT(host_offset % PageAlignment == 0);
ASSERT(length % PageAlignment == 0);
@@ -737,7 +737,7 @@ void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length, M
}
void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap) {
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
#if !(defined(__OPENORBIS__) || defined(__managarm__))
ASSERT(virtual_offset % PageAlignment == 0);
ASSERT(length % PageAlignment == 0);
ASSERT(virtual_offset + length <= virtual_size);
@@ -749,7 +749,7 @@ void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap)
}
void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission perm) {
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
#if !(defined(__OPENORBIS__) || defined(__managarm__))
ASSERT(virtual_offset % PageAlignment == 0);
ASSERT(length % PageAlignment == 0);
ASSERT(virtual_offset + length <= virtual_size);
@@ -768,7 +768,7 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f
}
void HostMemory::EnableDirectMappedAddress() {
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
#if !(defined(__OPENORBIS__) || defined(__managarm__))
if (impl) {
impl->EnableDirectMappedAddress();
virtual_size += reinterpret_cast<uintptr_t>(virtual_base);
+1 -1
View File
@@ -77,7 +77,7 @@ private:
size_t backing_size{};
size_t virtual_size{};
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
#if !(defined(__OPENORBIS__) || defined(__managarm__))
// Low level handler for the platform dependent memory routines
class Impl;
std::unique_ptr<Impl> impl;
+22
View File
@@ -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) {
+76 -8
View File
@@ -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,
@@ -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",
@@ -875,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();
+1 -1
View File
@@ -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);
-4
View File
@@ -490,8 +490,6 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
LOG_DEBUG(Common, "Could not set thread nice value to {}: {}", nice_value,
GetLastErrorMsg());
}
#elif defined(__EMSCRIPTEN__)
// TODO: set priority?
#else
const s32 max_prio = sched_get_priority_max(SCHED_OTHER);
const s32 min_prio = sched_get_priority_min(SCHED_OTHER);
@@ -536,8 +534,6 @@ void SetCurrentThreadName(const char* name) {
// See for reference
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
(void)name;
#elif defined(__EMSCRIPTEN__)
// TODO: set thread name?
#else
pthread_setname_np(pthread_self(), name);
#endif
+5 -12
View File
@@ -38,6 +38,10 @@ add_library(core STATIC
debugger/debugger.cpp
debugger/debugger.h
debugger/debugger_interface.h
debugger/gdbstub.cpp
debugger/gdbstub.h
debugger/gdbstub_arch.cpp
debugger/gdbstub_arch.h
device_memory.cpp
device_memory.h
device_memory_manager.h
@@ -1166,14 +1170,6 @@ add_library(core STATIC
tools/freezer.h
tools/renderdoc.cpp
tools/renderdoc.h)
if (NOT PLATFORM_EMSCRIPTEN)
# incompatible with wasm's async model
target_sources(core PRIVATE
debugger/gdbstub.cpp
debugger/gdbstub.h
debugger/gdbstub_arch.cpp
debugger/gdbstub_arch.h)
endif()
if (ENABLE_WIFI_SCAN)
target_sources(core PRIVATE internal_network/wifi_scanner.cpp)
@@ -1217,10 +1213,7 @@ target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS})
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
if (BOOST_NO_HEADERS)
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::crc)
if (NOT PLATFORM_EMSCRIPTEN)
target_link_libraries(core PUBLIC Boost::asio Boost::process)
endif()
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc)
else()
target_link_libraries(core PUBLIC Boost::headers)
endif()
+2 -4
View File
@@ -6,20 +6,19 @@
#pragma once
#ifndef __EMSCRIPTEN__
#include <dynarmic/interface/halt_reason.h>
#endif
#include "core/arm/arm_interface.h"
namespace Core {
#ifndef __EMSCRIPTEN__
constexpr Dynarmic::HaltReason StepThread = Dynarmic::HaltReason::Step;
constexpr Dynarmic::HaltReason DataAbort = Dynarmic::HaltReason::MemoryAbort;
constexpr Dynarmic::HaltReason BreakLoop = Dynarmic::HaltReason::UserDefined2;
constexpr Dynarmic::HaltReason SupervisorCall = Dynarmic::HaltReason::UserDefined3;
constexpr Dynarmic::HaltReason InstructionBreakpoint = Dynarmic::HaltReason::UserDefined4;
constexpr Dynarmic::HaltReason PrefetchAbort = Dynarmic::HaltReason::UserDefined6;
constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) {
static_assert(u64(HaltReason::StepThread) == u64(StepThread));
static_assert(u64(HaltReason::DataAbort) == u64(DataAbort));
@@ -29,6 +28,5 @@ constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) {
static_assert(u64(HaltReason::PrefetchAbort) == u64(PrefetchAbort));
return HaltReason(hr);
}
#endif
} // namespace Core
+2 -27
View File
@@ -6,54 +6,30 @@
#include <mutex>
#include <utility>
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
// TODO: gdb stub compat with emscripten?
#else
#include <boost/asio.hpp>
#include <boost/version.hpp>
#if BOOST_VERSION > 108400 && (!defined(_WINDOWS) && !defined(__ANDROID__)) || defined(YUZU_BOOST_v1)
#define USE_BOOST_v1
#endif
#ifdef USE_BOOST_v1
#include <boost/process/v1/async_pipe.hpp>
#else
#include <boost/process/async_pipe.hpp>
#endif
#endif
#include "common/logging.h"
#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/debugger/debugger.h"
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
// TODO: gdbstub with emscripten?
#else
#include "core/debugger/debugger_interface.h"
#include "core/debugger/gdbstub.h"
#endif
#include "core/hle/kernel/global_scheduler_context.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scheduler.h"
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
namespace Core {
// Dummy
class DebuggerImpl {
char pad;
};
Debugger::Debugger(Core::System& system, u16 port) {}
Debugger::~Debugger() = default;
bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
return false;
}
bool Debugger::NotifyThreadWatchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) {
return false;
}
void Debugger::NotifyShutdown() {}
} // namespace Core
#else
template <typename Readable, typename Buffer, typename Callback>
static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
static_assert(std::is_trivial_v<Buffer>);
@@ -424,4 +400,3 @@ void Debugger::NotifyShutdown() {
}
} // namespace Core
#endif
+3 -10
View File
@@ -7,14 +7,8 @@
#include <random>
#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/arm/exclusive_monitor.h"
#ifndef __EMSCRIPTEN__
#include "core/arm/dynarmic/arm_dynarmic.h"
#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
#include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
#endif
#include "core/core.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
@@ -24,6 +18,8 @@
#include "core/hle/kernel/k_thread_queue.h"
#include "core/hle/kernel/k_worker_task_manager.h"
#include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
#ifdef HAS_NCE
#include "core/arm/nce/arm_nce.h"
#endif
@@ -1304,11 +1300,9 @@ void KProcess::LoadModule(KernelCore& kernel, CodeSet code_set, KProcessAddress
}
void KProcess::InitializeInterfaces(KernelCore& kernel) {
#ifdef __EMSCRIPTEN__
ASSERT(false && "unimplemented");
#else
m_exclusive_monitor =
Core::MakeExclusiveMonitor(this->GetMemory(), Core::Hardware::NUM_CPU_CORES);
#ifdef HAS_NCE
if (this->IsApplication() && Settings::IsNceEnabled()) {
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++)
@@ -1328,7 +1322,6 @@ void KProcess::InitializeInterfaces(KernelCore& kernel) {
static_cast<Core::DynarmicExclusiveMonitor&>(*m_exclusive_monitor), i);
}
}
#endif
}
bool KProcess::InsertWatchpoint(KernelCore& kernel, KProcessAddress addr, u64 size, DebugWatchpointType type) {
@@ -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,10 +349,16 @@ 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;
}
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);
if (params.index_size >= zbc_used_color_entries) {
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid color index {:#x}",
params.index_size);
return NvResult::BadParameter;
}
@@ -342,12 +368,13 @@ NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) {
params.depth = 0;
params.ref_cnt = colors.ref_cnt;
params.format = colors.format;
params.index_size = static_cast<u32>(zbc_colors.size());
params.index_size = zbc_used_color_entries;
break;
}
case ZBCTypes::depth: {
if (params.index_size >= zbc_depths.size()) {
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid depth index {:#x}", params.index_size);
if (params.index_size >= zbc_used_depth_entries) {
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid depth index {:#x}",
params.index_size);
return NvResult::BadParameter;
}
@@ -357,7 +384,8 @@ NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) {
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());
params.index_size = zbc_used_depth_entries;
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
+3 -13
View File
@@ -86,22 +86,15 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
// BEGONE cold clones of lambdas, for I have merged you all into a SINGLE lambda instead of
// spamming lambdas like it's some kind of lambda calculus class
std::vector<std::pair<std::string_view, void (*)(Core::System&)>> rt_services{
for (auto const& e : std::vector<std::pair<std::string_view, void (*)(Core::System&)>>{
{"audio", &Audio::LoopProcess},
{"FS", &FileSystem::LoopProcess},
{"ldn", &LDN::LoopProcess},
{"nvservices", &Nvidia::LoopProcess},
{"bsdsocket", &Sockets::LoopProcess},
};
#if defined(__EMSCRIPTEN__) || defined(__wasi__) || defined(__OPENORBIS__)
for (auto const& e : rt_services)
kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); });
kernel.RunOnGuestCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); });
#else
for (auto const& e : rt_services)
})
kernel.RunOnHostCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); }).detach();
kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach();
#endif
kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach();
// Avoid cold clones of lambdas -- succintly
for (auto const& e : std::vector<std::pair<std::string_view, void (*)(Core::System&)>>{
{"sm", &SM::LoopProcess},
@@ -125,10 +118,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
{"glue", &Glue::LoopProcess},
{"grc", &GRC::LoopProcess},
{"hid", &HID::LoopProcess},
// Must match with src/core/CMakeLists.txt for target_source of jit.cpp
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64) || defined(ARCHITECTURE_riscv64) || defined(ARCHITECTURE_loongarch64)
{"jit", &JIT::LoopProcess},
#endif
{"lbl", &LBL::LoopProcess},
{"Loader", &LDR::LoopProcess},
{"LogManager.Prod", &LM::LoopProcess},
@@ -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));
}
@@ -28,10 +28,7 @@ enum class NpadMcuState : u32 {
struct NpadMcuHolder {
NpadMcuState state;
INSERT_PADDING_BYTES(0x4);
union {
IAbstractedPad* abstracted_pad;
u64 abstracted_pad_raw;
};
IAbstractedPad* abstracted_pad;
};
static_assert(sizeof(NpadMcuHolder) == 0x10, "NpadMcuHolder is an invalid size");
@@ -33,15 +33,9 @@ public:
bool is_created{};
bool is_mapped{};
INSERT_PADDING_BYTES(0x5);
union {
Kernel::KSharedMemory* shared_memory = nullptr;
u64 shared_memory_raw;
};
Kernel::KSharedMemory* shared_memory;
INSERT_PADDING_BYTES(0x38);
union {
SharedMemoryFormat* address = nullptr;
u64 address_raw;
};
SharedMemoryFormat* address = nullptr;
};
// Correct size is 0x50 bytes
static_assert(sizeof(SharedMemoryHolder) == 0x50, "SharedMemoryHolder is an invalid size");
+4 -6
View File
@@ -15,6 +15,8 @@ add_library(input_common STATIC
drivers/tas_input.h
drivers/touch_screen.cpp
drivers/touch_screen.h
drivers/udp_client.cpp
drivers/udp_client.h
drivers/virtual_amiibo.cpp
drivers/virtual_amiibo.h
drivers/virtual_gamepad.cpp
@@ -32,12 +34,8 @@ add_library(input_common STATIC
input_poller.cpp
input_poller.h
main.cpp
main.h)
if (NOT PLATFORM_EMSCRIPTEN)
target_sources(input_common PRIVATE
drivers/udp_client.cpp
drivers/udp_client.h)
endif()
main.h
)
if (MSVC)
target_compile_options(input_common PRIVATE
+1 -21
View File
@@ -12,6 +12,7 @@
#include "input_common/drivers/mouse.h"
#include "input_common/drivers/tas_input.h"
#include "input_common/drivers/touch_screen.h"
#include "input_common/drivers/udp_client.h"
#include "input_common/drivers/virtual_amiibo.h"
#include "input_common/drivers/virtual_gamepad.h"
#include "input_common/helpers/stick_from_buttons.h"
@@ -20,9 +21,6 @@
#include "input_common/input_mapping.h"
#include "input_common/input_poller.h"
#include "input_common/main.h"
#ifndef __EMSCRIPTEN__
#include "input_common/drivers/udp_client.h"
#endif
#ifdef ENABLE_LIBUSB
#include "input_common/drivers/gc_adapter.h"
@@ -84,9 +82,7 @@ struct InputSubsystem::Impl {
#ifdef ENABLE_LIBUSB
RegisterEngine("gcpad", gcadapter);
#endif
#ifndef __EMSCRIPTEN__
RegisterEngine("cemuhookudp", udp_client);
#endif
RegisterEngine("tas", tas_input);
RegisterEngine("camera", camera);
#ifdef __ANDROID__
@@ -120,9 +116,7 @@ struct InputSubsystem::Impl {
#ifdef ENABLE_LIBUSB
UnregisterEngine(gcadapter);
#endif
#ifndef __EMSCRIPTEN__
UnregisterEngine(udp_client);
#endif
UnregisterEngine(tas_input);
UnregisterEngine(camera);
#ifdef __ANDROID__
@@ -158,10 +152,8 @@ struct InputSubsystem::Impl {
auto gcadapter_devices = gcadapter->GetInputDevices();
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
#endif
#ifndef __EMSCRIPTEN__
auto udp_devices = udp_client->GetInputDevices();
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
#endif
#ifdef HAVE_SDL3
auto joycon_devices = joycon->GetInputDevices();
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
@@ -194,11 +186,9 @@ struct InputSubsystem::Impl {
return gcadapter;
}
#endif
#ifndef __EMSCRIPTEN__
if (engine == udp_client->GetEngineName()) {
return udp_client;
}
#endif
#ifdef HAVE_SDL3
if (engine == sdl->GetEngineName()) {
return sdl;
@@ -281,11 +271,9 @@ struct InputSubsystem::Impl {
return true;
}
#endif
#ifndef __EMSCRIPTEN__
if (engine == udp_client->GetEngineName()) {
return true;
}
#endif
if (engine == tas_input->GetEngineName()) {
return true;
}
@@ -312,9 +300,7 @@ struct InputSubsystem::Impl {
#ifdef ENABLE_LIBUSB
gcadapter->BeginConfiguration();
#endif
#ifndef __EMSCRIPTEN__
udp_client->BeginConfiguration();
#endif
#ifdef HAVE_SDL3
sdl->BeginConfiguration();
joycon->BeginConfiguration();
@@ -330,9 +316,7 @@ struct InputSubsystem::Impl {
#ifdef ENABLE_LIBUSB
gcadapter->EndConfiguration();
#endif
#ifndef __EMSCRIPTEN__
udp_client->EndConfiguration();
#endif
#ifdef HAVE_SDL3
sdl->EndConfiguration();
joycon->EndConfiguration();
@@ -357,9 +341,7 @@ struct InputSubsystem::Impl {
std::shared_ptr<Mouse> mouse;
std::shared_ptr<TouchScreen> touch_screen;
std::shared_ptr<TasInput::Tas> tas_input;
#ifndef __EMSCRIPTEN__
std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
#endif
std::shared_ptr<Camera> camera;
std::shared_ptr<VirtualAmiibo> virtual_amiibo;
std::shared_ptr<VirtualGamepad> virtual_gamepad;
@@ -488,9 +470,7 @@ bool InputSubsystem::IsStickInverted(const Common::ParamPackage& params) const {
}
void InputSubsystem::ReloadInputDevices() {
#ifndef __EMSCRIPTEN__
impl->udp_client.get()->ReloadSockets();
#endif
}
void InputSubsystem::BeginMapping(Polling::InputType type) {
@@ -522,9 +522,6 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
PAIR(AnisotropyMode, X4, tr("4x")),
PAIR(AnisotropyMode, X8, tr("8x")),
PAIR(AnisotropyMode, X16, tr("16x")),
PAIR(AnisotropyMode, X32, tr("32x")),
PAIR(AnisotropyMode, X64, tr("64x")),
PAIR(AnisotropyMode, None, tr("None")),
}});
translations->insert(
{Settings::EnumMetadata<Settings::Language>::Index(),
+2 -1
View File
@@ -257,7 +257,8 @@ else()
# Bracket depth determines maximum size of a fold expression in Clang since 9c9974c3ccb6.
# And this in turns limits the size of a std::array.
$<$<CXX_COMPILER_ID:Clang>:-fbracket-depth=1024>
$<$<CXX_COMPILER_ID:AppleClang>:-fbracket-depth=1024>)
$<$<CXX_COMPILER_ID:AppleClang>:-fbracket-depth=1024>
)
endif()
create_target_directory_groups(shader_recompiler)
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -217,6 +220,22 @@ void EmitGetAttribute(EmitContext& ctx, IR::Inst& inst, IR::Attribute attr,
ctx.AddF32("{}={}{}.{};", inst, input_decorator, ctx.position_name, swizzle);
break;
}
case IR::Attribute::ClipDistance0:
case IR::Attribute::ClipDistance1:
case IR::Attribute::ClipDistance2:
case IR::Attribute::ClipDistance3:
case IR::Attribute::ClipDistance4:
case IR::Attribute::ClipDistance5:
case IR::Attribute::ClipDistance6:
case IR::Attribute::ClipDistance7: {
const u32 index{static_cast<u32>(attr) - static_cast<u32>(IR::Attribute::ClipDistance0)};
if (IsInputArray(ctx.stage)) {
ctx.AddF32("{}=gl_in[{}].gl_ClipDistance[{}];", inst, vertex, index);
} else {
ctx.AddF32("{}=gl_ClipDistance[{}];", inst, index);
}
break;
}
case IR::Attribute::PointSpriteS:
case IR::Attribute::PointSpriteT:
ctx.AddF32("{}=gl_PointCoord.{};", inst, swizzle);
@@ -306,9 +306,12 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile
} else if (program.is_geometry_passthrough && !profile.support_geometry_shader_passthrough) {
LOG_WARNING(Shader_GLSL, "Passthrough geometry program used but not supported");
}
header += fmt::format(
"layout({},max_vertices={})out;in gl_PerVertex{{vec4 gl_Position;}}gl_in[];",
OutputPrimitive(program.output_topology), program.output_vertices);
header += fmt::format("layout({},max_vertices={})out;in gl_PerVertex{{vec4 gl_Position;",
OutputPrimitive(program.output_topology), program.output_vertices);
if (program.info.loads.ClipDistances()) {
header += "float gl_ClipDistance[];";
}
header += "}gl_in[];";
break;
case Stage::Fragment:
stage_name = "fs";
@@ -335,7 +335,7 @@ void SetupDenormControl(const Profile& profile, const IR::Program& program, Emit
if (info.uses_fp32_denorms_flush && info.uses_fp32_denorms_preserve) {
LOG_DEBUG(Shader_SPIRV, "Fp32 denorm flush and preserve on the same shader");
} else if (info.uses_fp32_denorms_flush) {
if (profile.support_fp32_denorm_flush) {
if (profile.support_fp32_denorm_flush && !profile.has_broken_fp32_denorm_flush) {
ctx.AddCapability(spv::Capability::DenormFlushToZero);
ctx.AddExecutionMode(main_func, spv::ExecutionMode::DenormFlushToZero, 32U);
} else {
@@ -381,7 +381,8 @@ void SetupSignedNanCapabilities(const Profile& profile, const IR::Program& progr
ctx.AddCapability(spv::Capability::SignedZeroInfNanPreserve);
ctx.AddExecutionMode(main_func, spv::ExecutionMode::SignedZeroInfNanPreserve, 16U);
}
if (profile.support_fp32_signed_zero_nan_preserve) {
if (profile.support_fp32_signed_zero_nan_preserve &&
!profile.has_broken_fp32_signed_zero_nan_preserve) {
ctx.AddCapability(spv::Capability::SignedZeroInfNanPreserve);
ctx.AddExecutionMode(main_func, spv::ExecutionMode::SignedZeroInfNanPreserve, 32U);
}
@@ -367,6 +367,23 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) {
return ctx.OpSelect(ctx.F32[1], ctx.OpLoad(ctx.U1, ctx.front_face),
ctx.OpBitcast(ctx.F32[1], ctx.Const((std::numeric_limits<u32>::max)())),
ctx.f32_zero_value);
case IR::Attribute::ClipDistance0:
case IR::Attribute::ClipDistance1:
case IR::Attribute::ClipDistance2:
case IR::Attribute::ClipDistance3:
case IR::Attribute::ClipDistance4:
case IR::Attribute::ClipDistance5:
case IR::Attribute::ClipDistance6:
case IR::Attribute::ClipDistance7: {
const u32 base{static_cast<u32>(IR::Attribute::ClipDistance0)};
const u32 index{static_cast<u32>(attr) - base};
if (!ValidId(ctx.input_clip_distances) || index >= ctx.profile.max_user_clip_distances) {
return ctx.Const(1.0f);
}
const Id pointer{
AttrPointer(ctx, ctx.input_f32, vertex, ctx.input_clip_distances, ctx.Const(index))};
return ctx.OpLoad(ctx.F32[1], pointer);
}
case IR::Attribute::PointSpriteS:
return ctx.OpLoad(ctx.F32[1],
ctx.OpAccessChain(ctx.input_f32, ctx.point_coord, ctx.u32_zero_value));
@@ -1522,6 +1522,12 @@ void EmitContext::DefineInputs(const IR::Program& program) {
}
}
}
if (stage == Stage::Geometry && info.loads.ClipDistances() &&
profile.max_user_clip_distances > 0) {
const u32 used{(std::min)(profile.max_user_clip_distances, 8u)};
const Id type{TypeArray(F32[1], Const(used))};
input_clip_distances = DefineInput(*this, type, true, spv::BuiltIn::ClipDistance);
}
if (loads[IR::Attribute::InstanceId]) {
if (profile.support_vertex_instance_id) {
instance_id = DefineInput(*this, U32[1], true, spv::BuiltIn::InstanceId);
@@ -285,6 +285,7 @@ public:
Id point_coord{};
Id tess_coord{};
Id clip_distances{};
Id input_clip_distances{};
Id layer{};
Id viewport_index{};
Id viewport_mask{};
+1 -1
View File
@@ -31,7 +31,7 @@ struct CbufWordKey {
struct CbufWordKeyHash {
constexpr size_t operator()(const CbufWordKey& k) const noexcept {
return size_t((u64(k.index) << 32) ^ u64(k.offset));
return (size_t(k.index) << 32) ^ k.offset;
}
};
@@ -53,12 +53,6 @@ constexpr std::array RGBA_LUT{
R | G | B | A, //
};
void CheckAlignment(IR::Reg reg, size_t alignment) {
if (!IR::IsAligned(reg, alignment)) {
throw NotImplementedException("Unaligned source register {}", reg);
}
}
template <typename... Args>
IR::Value Composite(TranslatorVisitor& v, Args... regs) {
return v.ir.CompositeConstruct(v.F(regs)...);
@@ -89,67 +83,53 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
info.type.Assign(TextureType::Color2D);
return v.ir.ImageSampleExplicitLod(handle, Composite(v, reg_a, reg_b), zero, {}, info);
case 3: // 2D.LL
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::Color2D);
return v.ir.ImageSampleExplicitLod(handle, Composite(v, reg_a, reg_a + 1), v.F(reg_b), {},
info);
case 4: // 2D.DC
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::Color2D);
info.is_depth.Assign(1);
return v.ir.ImageSampleDrefImplicitLod(handle, Composite(v, reg_a, reg_a + 1), v.F(reg_b),
{}, {}, {}, info);
case 5: // 2D.LL.DC
CheckAlignment(reg_a, 2);
CheckAlignment(reg_b, 2);
info.type.Assign(TextureType::Color2D);
info.is_depth.Assign(1);
return v.ir.ImageSampleDrefExplicitLod(handle, Composite(v, reg_a, reg_a + 1),
v.F(reg_b + 1), v.F(reg_b), {}, info);
case 6: // 2D.LZ.DC
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::Color2D);
info.is_depth.Assign(1);
return v.ir.ImageSampleDrefExplicitLod(handle, Composite(v, reg_a, reg_a + 1), v.F(reg_b),
zero, {}, info);
case 7: // ARRAY_2D
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::ColorArray2D);
return v.ir.ImageSampleImplicitLod(
handle, v.ir.CompositeConstruct(v.F(reg_a + 1), v.F(reg_b), ReadArray(v, v.X(reg_a))),
{}, {}, {}, info);
case 8: // ARRAY_2D.LZ
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::ColorArray2D);
return v.ir.ImageSampleExplicitLod(
handle, v.ir.CompositeConstruct(v.F(reg_a + 1), v.F(reg_b), ReadArray(v, v.X(reg_a))),
zero, {}, info);
case 9: // ARRAY_2D.LZ.DC
CheckAlignment(reg_a, 2);
CheckAlignment(reg_b, 2);
info.type.Assign(TextureType::ColorArray2D);
info.is_depth.Assign(1);
return v.ir.ImageSampleDrefExplicitLod(
handle, v.ir.CompositeConstruct(v.F(reg_a + 1), v.F(reg_b), ReadArray(v, v.X(reg_a))),
v.F(reg_b + 1), zero, {}, info);
case 10: // 3D
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::Color3D);
return v.ir.ImageSampleImplicitLod(handle, Composite(v, reg_a, reg_a + 1, reg_b), {}, {},
{}, info);
case 11: // 3D.LZ
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::Color3D);
return v.ir.ImageSampleExplicitLod(handle, Composite(v, reg_a, reg_a + 1, reg_b), zero, {},
info);
case 12: // CUBE
CheckAlignment(reg_a, 2);
info.type.Assign(TextureType::ColorCube);
return v.ir.ImageSampleImplicitLod(handle, Composite(v, reg_a, reg_a + 1, reg_b), {}, {},
{}, info);
case 13: // CUBE.LL
CheckAlignment(reg_a, 2);
CheckAlignment(reg_b, 2);
info.type.Assign(TextureType::ColorCube);
return v.ir.ImageSampleExplicitLod(handle, Composite(v, reg_a, reg_a + 1, reg_b),
v.F(reg_b + 1), {}, info);
@@ -160,14 +140,16 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
unsigned Swizzle(u64 insn) {
const Encoding texs{insn};
u8 const encoding = u8(texs.swizzle);
const size_t encoding{texs.swizzle};
if (texs.dest_reg_b == IR::Reg::RZ) {
if (encoding >= RG_LUT.size())
if (encoding >= RG_LUT.size()) {
throw NotImplementedException("Illegal RG encoding {}", encoding);
}
return RG_LUT[encoding];
} else {
if (encoding >= RGBA_LUT.size())
if (encoding >= RGBA_LUT.size()) {
throw NotImplementedException("Illegal RGBA encoding {}", encoding);
}
return RGBA_LUT[encoding];
}
}
@@ -188,12 +170,10 @@ IR::Reg RegStoreComponent32(u64 insn, unsigned index) {
case 0:
return texs.dest_reg_a;
case 1:
CheckAlignment(texs.dest_reg_a, 2);
return texs.dest_reg_a + 1;
case 2:
return texs.dest_reg_b;
case 3:
CheckAlignment(texs.dest_reg_b, 2);
return texs.dest_reg_b + 1;
}
throw LogicError("Invalid store index {}", index);
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -34,12 +37,6 @@ union Encoding {
BitField<36, 13, u64> cbuf_offset;
};
void CheckAlignment(IR::Reg reg, size_t alignment) {
if (!IR::IsAligned(reg, alignment)) {
throw NotImplementedException("Unaligned source register {}", reg);
}
}
IR::Value MakeOffset(TranslatorVisitor& v, IR::Reg reg) {
const IR::U32 value{v.X(reg)};
return v.ir.CompositeConstruct(v.ir.BitFieldExtract(value, v.ir.Imm32(0), v.ir.Imm32(6), true),
@@ -60,18 +57,15 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
info.is_depth.Assign(tld4s.dc != 0 ? 1 : 0);
IR::Value coords;
if (tld4s.aoffi != 0) {
CheckAlignment(reg_a, 2);
coords = v.ir.CompositeConstruct(v.F(reg_a), v.F(reg_a + 1));
IR::Value offset = MakeOffset(v, reg_b);
if (tld4s.dc != 0) {
CheckAlignment(reg_b, 2);
IR::F32 dref = v.F(reg_b + 1);
return v.ir.ImageGatherDref(handle, coords, offset, {}, dref, info);
}
return v.ir.ImageGather(handle, coords, offset, {}, info);
}
if (tld4s.dc != 0) {
CheckAlignment(reg_a, 2);
coords = v.ir.CompositeConstruct(v.F(reg_a), v.F(reg_a + 1));
IR::F32 dref = v.F(reg_b);
return v.ir.ImageGatherDref(handle, coords, {}, {}, dref, info);
@@ -86,12 +80,10 @@ IR::Reg RegStoreComponent32(u64 insn, size_t index) {
case 0:
return tlds4.dest_reg_a;
case 1:
CheckAlignment(tlds4.dest_reg_a, 2);
return tlds4.dest_reg_a + 1;
case 2:
return tlds4.dest_reg_b;
case 3:
CheckAlignment(tlds4.dest_reg_b, 2);
return tlds4.dest_reg_b + 1;
}
throw LogicError("Invalid store index {}", index);
@@ -58,12 +58,6 @@ union Encoding {
BitField<53, 4, u64> encoding;
};
void CheckAlignment(IR::Reg reg, size_t alignment) {
if (!IR::IsAligned(reg, alignment)) {
throw NotImplementedException("Unaligned source register {}", reg);
}
}
IR::Value MakeOffset(TranslatorVisitor& v, IR::Reg reg) {
const IR::U32 value{v.X(reg)};
return v.ir.CompositeConstruct(v.ir.BitFieldExtract(value, v.ir.Imm32(0), v.ir.Imm32(4), true),
@@ -95,38 +89,31 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
coords = v.ir.CompositeConstruct(v.X(reg_a), v.X(reg_b));
break;
case 4:
CheckAlignment(reg_a, 2);
texture_type = Shader::TextureType::Color2D;
coords = v.ir.CompositeConstruct(v.X(reg_a), v.X(reg_a + 1));
offsets = MakeOffset(v, reg_b);
break;
case 5:
CheckAlignment(reg_a, 2);
texture_type = Shader::TextureType::Color2D;
coords = v.ir.CompositeConstruct(v.X(reg_a), v.X(reg_a + 1));
lod = v.X(reg_b);
break;
case 6:
CheckAlignment(reg_a, 2);
texture_type = Shader::TextureType::Color2D;
coords = v.ir.CompositeConstruct(v.X(reg_a), v.X(reg_a + 1));
multisample = v.X(reg_b);
break;
case 7:
CheckAlignment(reg_a, 2);
texture_type = Shader::TextureType::Color3D;
coords = v.ir.CompositeConstruct(v.X(reg_a), v.X(reg_a + 1), v.X(reg_b));
break;
case 8: {
CheckAlignment(reg_b, 2);
const IR::U32 array{v.ir.BitFieldExtract(v.X(reg_a), v.ir.Imm32(0), v.ir.Imm32(16))};
texture_type = Shader::TextureType::ColorArray2D;
coords = v.ir.CompositeConstruct(v.X(reg_b), v.X(reg_b + 1), array);
break;
}
case 12:
CheckAlignment(reg_a, 2);
CheckAlignment(reg_b, 2);
texture_type = Shader::TextureType::Color2D;
coords = v.ir.CompositeConstruct(v.X(reg_a), v.X(reg_a + 1));
lod = v.X(reg_b);
@@ -145,14 +132,16 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
unsigned Swizzle(u64 insn) {
const Encoding tlds{insn};
u8 const encoding = u8(tlds.swizzle);
const size_t encoding{tlds.swizzle};
if (tlds.dest_reg_b == IR::Reg::RZ) {
if (encoding >= RG_LUT.size())
if (encoding >= RG_LUT.size()) {
throw NotImplementedException("Illegal RG encoding {}", encoding);
}
return RG_LUT[encoding];
} else {
if (encoding >= RGBA_LUT.size())
if (encoding >= RGBA_LUT.size()) {
throw NotImplementedException("Illegal RGBA encoding {}", encoding);
}
return RGBA_LUT[encoding];
}
}
@@ -167,12 +156,10 @@ IR::Reg RegStoreComponent32(u64 insn, unsigned index) {
case 0:
return tlds.dest_reg_a;
case 1:
CheckAlignment(tlds.dest_reg_a, 2);
return tlds.dest_reg_a + 1;
case 2:
return tlds.dest_reg_b;
case 3:
CheckAlignment(tlds.dest_reg_b, 2);
return tlds.dest_reg_b + 1;
}
throw LogicError("Invalid store index {}", index);
@@ -144,12 +144,15 @@ std::map<IR::Attribute, IR::Attribute> GenerateLegacyToGenericMappings(
for (size_t i = 0; i < count; ++i) {
mapping.insert({attr + i, previous_stage_mapping.at(attr + i)});
}
} else {
for (size_t i = 0; i < count; ++i) {
mapping.insert({attr + i, unused_generics.front() + i});
}
unused_generics.pop();
return;
}
if (unused_generics.empty()) {
return;
}
for (size_t i = 0; i < count; ++i) {
mapping.insert({attr + i, unused_generics.front() + i});
}
unused_generics.pop();
};
for (size_t index = 0; index < 4; ++index) {
auto attr = IR::Attribute::ColorFrontDiffuseR + index * 4;
@@ -169,31 +172,98 @@ std::map<IR::Attribute, IR::Attribute> GenerateLegacyToGenericMappings(
return mapping;
}
struct PassthroughVertices {
u32 count;
u32 first;
u32 stride;
};
OutputTopology GetPassthroughTopology(InputTopology input_topology) {
switch (input_topology) {
case InputTopology::Points:
return OutputTopology::PointList;
case InputTopology::Lines:
case InputTopology::LinesAdjacency:
return OutputTopology::LineStrip;
case InputTopology::Triangles:
case InputTopology::TrianglesAdjacency:
return OutputTopology::TriangleStrip;
}
return OutputTopology::TriangleStrip;
}
PassthroughVertices GetPassthroughVertices(InputTopology input_topology) {
switch (input_topology) {
case InputTopology::Points:
return {1, 0, 1};
case InputTopology::Lines:
return {2, 0, 1};
case InputTopology::LinesAdjacency:
return {2, 1, 1};
case InputTopology::Triangles:
return {3, 0, 1};
case InputTopology::TrianglesAdjacency:
return {3, 0, 2};
}
return {3, 0, 1};
}
void EmitGeometryPassthrough(IR::IREmitter& ir, const IR::Program& program,
const Shader::VaryingState& passthrough_mask,
bool passthrough_position,
std::optional<IR::Attribute> passthrough_layer_attr) {
for (u32 i = 0; i < program.output_vertices; i++) {
// Assign generics from input
for (u32 j = 0; j < 32; j++) {
if (!passthrough_mask.Generic(j)) {
continue;
}
const IR::Attribute attr = IR::Attribute::Generic0X + (j * 4);
std::optional<IR::Attribute> passthrough_layer_attr,
InputTopology input_topology) {
const PassthroughVertices vertices{GetPassthroughVertices(input_topology)};
for (u32 vertex = 0; vertex < vertices.count; vertex++) {
const u32 i = vertices.first + vertex * vertices.stride;
const auto copy_vec4{[&ir, i](IR::Attribute attr) {
ir.SetAttribute(attr + 0, ir.GetAttribute(attr + 0, ir.Imm32(i)), ir.Imm32(0));
ir.SetAttribute(attr + 1, ir.GetAttribute(attr + 1, ir.Imm32(i)), ir.Imm32(0));
ir.SetAttribute(attr + 2, ir.GetAttribute(attr + 2, ir.Imm32(i)), ir.Imm32(0));
ir.SetAttribute(attr + 3, ir.GetAttribute(attr + 3, ir.Imm32(i)), ir.Imm32(0));
}};
// Assign generics from input
for (u32 j = 0; j < IR::NUM_GENERICS; j++) {
if (!passthrough_mask.Generic(j)) {
continue;
}
copy_vec4(IR::Attribute::Generic0X + (j * 4));
}
for (u32 j = 0; j < 4; j++) {
const IR::Attribute attr = IR::Attribute::ColorFrontDiffuseR + (j * 4);
if (!passthrough_mask.AnyComponent(attr)) {
continue;
}
copy_vec4(attr);
}
if (passthrough_mask[IR::Attribute::FogCoordinate]) {
ir.SetAttribute(IR::Attribute::FogCoordinate,
ir.GetAttribute(IR::Attribute::FogCoordinate, ir.Imm32(i)),
ir.Imm32(0));
}
for (u32 j = 0; j < IR::NUM_FIXEDFNCTEXTURE; j++) {
const IR::Attribute attr = IR::Attribute::FixedFncTexture0S + (j * 4);
if (!passthrough_mask.AnyComponent(attr)) {
continue;
}
copy_vec4(attr);
}
for (u32 j = 0; j < 8; j++) {
const IR::Attribute attr = IR::Attribute::ClipDistance0 + j;
if (!passthrough_mask[attr]) {
continue;
}
ir.SetAttribute(attr, ir.GetAttribute(attr, ir.Imm32(i)), ir.Imm32(0));
}
if (passthrough_position) {
// Assign position from input
const IR::Attribute attr = IR::Attribute::PositionX;
ir.SetAttribute(attr + 0, ir.GetAttribute(attr + 0, ir.Imm32(i)), ir.Imm32(0));
ir.SetAttribute(attr + 1, ir.GetAttribute(attr + 1, ir.Imm32(i)), ir.Imm32(0));
ir.SetAttribute(attr + 2, ir.GetAttribute(attr + 2, ir.Imm32(i)), ir.Imm32(0));
ir.SetAttribute(attr + 3, ir.GetAttribute(attr + 3, ir.Imm32(i)), ir.Imm32(0));
copy_vec4(IR::Attribute::PositionX);
}
if (passthrough_layer_attr) {
@@ -208,25 +278,16 @@ void EmitGeometryPassthrough(IR::IREmitter& ir, const IR::Program& program,
ir.EndPrimitive(ir.Imm32(0));
}
u32 GetOutputTopologyVertices(OutputTopology output_topology) {
switch (output_topology) {
case OutputTopology::PointList:
return 1;
case OutputTopology::LineStrip:
return 2;
default:
return 3;
}
}
void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInfo& host_info) {
void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInfo& host_info,
InputTopology input_topology) {
for (IR::Block* const block : program.blocks) {
for (IR::Inst& inst : block->Instructions()) {
if (inst.GetOpcode() == IR::Opcode::Epilogue) {
IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)};
EmitGeometryPassthrough(
ir, program, program.info.passthrough,
program.info.passthrough.AnyComponent(IR::Attribute::PositionX), {});
program.info.passthrough.AnyComponent(IR::Attribute::PositionX), {},
input_topology);
}
}
}
@@ -235,7 +296,8 @@ void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInf
} // Anonymous namespace
IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool,
Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info) {
Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info,
InputTopology input_topology) {
HostTranslateInfo normalized_host_info{host_info};
normalized_host_info.ApplyDescriptorLimitPolicy();
@@ -264,8 +326,10 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
}
if (!normalized_host_info.support_geometry_shader_passthrough) {
program.output_vertices = GetOutputTopologyVertices(program.output_topology);
LowerGeometryPassthrough(program, normalized_host_info);
program.output_topology = GetPassthroughTopology(input_topology);
program.output_vertices = GetPassthroughVertices(input_topology).count;
LowerGeometryPassthrough(program, normalized_host_info, input_topology);
program.is_geometry_passthrough = false;
}
}
break;
@@ -350,6 +414,52 @@ IR::Program MergeDualVertexPrograms(IR::Program& vertex_a, IR::Program& vertex_b
return result;
}
void PrunePassthroughStores(IR::Program& program, const VaryingState& previous_stage_stores) {
if (program.stage != Stage::Geometry || program.is_geometry_passthrough) {
return;
}
VaryingState pruned;
for (size_t index = 0; index < program.info.passthrough.mask.size(); ++index) {
if (!program.info.passthrough.mask[index] || previous_stage_stores.mask[index]) {
continue;
}
const IR::Attribute attr{static_cast<IR::Attribute>(index)};
if (attr >= IR::Attribute::PositionX && attr <= IR::Attribute::PositionW) {
continue;
}
pruned.mask[index] = true;
}
if (pruned.mask.none()) {
return;
}
const auto erase_matching{[&pruned](IR::Block* block, IR::Opcode opcode, bool skip_used) {
auto it{block->begin()};
while (it != block->end()) {
IR::Inst& inst{*it};
if (inst.GetOpcode() != opcode ||
!pruned.mask[static_cast<size_t>(inst.Arg(0).Attribute())]) {
++it;
continue;
}
if (skip_used && inst.HasUses()) {
++it;
continue;
}
inst.Invalidate();
it = block->Instructions().erase(it);
}
}};
for (IR::Block* const block : program.post_order_blocks) {
erase_matching(block, IR::Opcode::SetAttribute, false);
}
for (IR::Block* const block : program.post_order_blocks) {
erase_matching(block, IR::Opcode::GetAttribute, true);
}
program.info.stores.mask &= ~pruned.mask;
program.info.loads.mask &= ~pruned.mask;
program.info.passthrough.mask &= ~pruned.mask;
}
void ConvertLegacyToGeneric(IR::Program& program, const Shader::RuntimeInfo& runtime_info) {
auto& stores = program.info.stores;
if (stores.Legacy()) {
@@ -362,19 +472,23 @@ void ConvertLegacyToGeneric(IR::Program& program, const Shader::RuntimeInfo& run
program.info.legacy_stores_mapping =
GenerateLegacyToGenericMappings(stores, unused_output_generics, {});
for (IR::Block* const block : program.post_order_blocks) {
for (IR::Inst& inst : block->Instructions()) {
switch (inst.GetOpcode()) {
case IR::Opcode::SetAttribute: {
const auto attr = inst.Arg(0).Attribute();
if (IsLegacyAttribute(attr)) {
stores.Set(program.info.legacy_stores_mapping[attr], true);
inst.SetArg(0, Shader::IR::Value(program.info.legacy_stores_mapping[attr]));
}
break;
for (auto it = block->begin(); it != block->end();) {
IR::Inst& inst{*it};
if (inst.GetOpcode() != IR::Opcode::SetAttribute ||
!IsLegacyAttribute(inst.Arg(0).Attribute())) {
++it;
continue;
}
default:
break;
const auto& mapping{program.info.legacy_stores_mapping};
const auto mapped{mapping.find(inst.Arg(0).Attribute())};
if (mapped == mapping.end()) {
inst.Invalidate();
it = block->Instructions().erase(it);
continue;
}
stores.Set(mapped->second, true);
inst.SetArg(0, Shader::IR::Value(mapped->second));
++it;
}
}
}
@@ -396,10 +510,16 @@ void ConvertLegacyToGeneric(IR::Program& program, const Shader::RuntimeInfo& run
switch (inst.GetOpcode()) {
case IR::Opcode::GetAttribute: {
const auto attr = inst.Arg(0).Attribute();
if (IsLegacyAttribute(attr)) {
loads.Set(mappings[attr], true);
inst.SetArg(0, Shader::IR::Value(mappings[attr]));
if (!IsLegacyAttribute(attr)) {
break;
}
const auto mapped{mappings.find(attr)};
if (mapped == mappings.end()) {
inst.ReplaceUsesWith(IR::Value{0.0f});
break;
}
loads.Set(mapped->second, true);
inst.SetArg(0, Shader::IR::Value(mapped->second));
break;
}
default:
@@ -414,11 +534,12 @@ IR::Program GenerateGeometryPassthrough(ObjectPool<IR::Inst>& inst_pool,
ObjectPool<IR::Block>& block_pool,
const HostTranslateInfo& host_info,
IR::Program& source_program,
Shader::OutputTopology output_topology) {
Shader::OutputTopology output_topology,
InputTopology input_topology) {
IR::Program program;
program.stage = Stage::Geometry;
program.output_topology = output_topology;
program.output_vertices = GetOutputTopologyVertices(output_topology);
program.output_vertices = GetPassthroughVertices(input_topology).count;
program.is_geometry_passthrough = false;
program.info.loads.mask = source_program.info.stores.mask;
@@ -433,7 +554,7 @@ IR::Program GenerateGeometryPassthrough(ObjectPool<IR::Inst>& inst_pool,
IR::IREmitter ir{*current_block};
EmitGeometryPassthrough(ir, program, program.info.stores, true,
source_program.info.emulated_layer);
source_program.info.emulated_layer, input_topology);
IR::Block* return_block{block_pool.Create(inst_pool)};
IR::IREmitter{*return_block}.Epilogue();
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -18,13 +21,16 @@ namespace Shader::Maxwell {
[[nodiscard]] IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool,
ObjectPool<IR::Block>& block_pool, Environment& env,
Flow::CFG& cfg, const HostTranslateInfo& host_info);
Flow::CFG& cfg, const HostTranslateInfo& host_info,
InputTopology input_topology);
[[nodiscard]] IR::Program MergeDualVertexPrograms(IR::Program& vertex_a, IR::Program& vertex_b,
Environment& env_vertex_b);
void ConvertLegacyToGeneric(IR::Program& program, const RuntimeInfo& runtime_info);
void PrunePassthroughStores(IR::Program& program, const VaryingState& previous_stage_stores);
// Maxwell v1 and older Nvidia cards don't support setting gl_Layer from non-geometry stages.
// This creates a workaround by setting the layer as a generic output and creating a
// passthrough geometry shader that reads the generic and sets the layer.
@@ -32,6 +38,7 @@ void ConvertLegacyToGeneric(IR::Program& program, const RuntimeInfo& runtime_inf
ObjectPool<IR::Block>& block_pool,
const HostTranslateInfo& host_info,
IR::Program& source_program,
Shader::OutputTopology output_topology);
Shader::OutputTopology output_topology,
InputTopology input_topology);
} // namespace Shader::Maxwell
+4
View File
@@ -86,6 +86,10 @@ struct Profile {
bool has_broken_signed_operations{};
/// Float controls break when fp16 is enabled
bool has_broken_fp16_float_controls{};
/// Forcing fp32 signed zero, Inf and NaN preservation miscompiles on some drivers
bool has_broken_fp32_signed_zero_nan_preserve{};
/// Declaring fp32 denorm flush to zero miscompiles on some drivers
bool has_broken_fp32_denorm_flush{};
/// Dynamic vec4 indexing is broken on some OpenGL drivers
bool has_gl_component_indexing_bug{};
/// The precise type qualifier is broken in the fragment stage of some drivers
+32
View File
@@ -267,6 +267,38 @@ add_library(video_core STATIC
video_core.h
)
if (ENABLE_LSFG)
target_sources(video_core PRIVATE
frame_gen/lossless_dll.cpp
frame_gen/lossless_dll.h
frame_gen/lsfg_translate.cpp
frame_gen/lsfg_translate.h
renderer_vulkan/present/frame_gen.cpp
renderer_vulkan/present/frame_gen.h
renderer_vulkan/present/frame_gen_pacer.cpp
renderer_vulkan/present/frame_gen_pacer.h
renderer_vulkan/present/lsfg_alpha.cpp
renderer_vulkan/present/lsfg_alpha.h
renderer_vulkan/present/lsfg_beta.cpp
renderer_vulkan/present/lsfg_beta.h
renderer_vulkan/present/lsfg_chain.cpp
renderer_vulkan/present/lsfg_chain.h
renderer_vulkan/present/lsfg_common.cpp
renderer_vulkan/present/lsfg_common.h
renderer_vulkan/present/lsfg_delta.cpp
renderer_vulkan/present/lsfg_delta.h
renderer_vulkan/present/lsfg_gamma.cpp
renderer_vulkan/present/lsfg_gamma.h
renderer_vulkan/present/lsfg_generate.cpp
renderer_vulkan/present/lsfg_generate.h
renderer_vulkan/present/lsfg_mipmaps.cpp
renderer_vulkan/present/lsfg_mipmaps.h
renderer_vulkan/present/lsfg_shaders.cpp
renderer_vulkan/present/lsfg_shaders.h
)
target_compile_definitions(video_core PUBLIC HAS_LSFG)
endif()
if (ENABLE_OPENGL)
target_sources(video_core PRIVATE
renderer_opengl/present/filters.cpp
+29 -26
View File
@@ -7,6 +7,7 @@
#pragma once
#include <algorithm>
#include <bit>
#include <memory>
#include <numeric>
@@ -809,46 +810,48 @@ void BufferCache<P>::BindHostVertexBuffers() {
if (use_optimized_vertex_buffers) {
auto& flags = maxwell3d->dirty.flags;
u32 enabled_mask = enabled_vertex_buffers_mask;
HostBindings<Buffer> bindings{};
u32 last_index = (std::numeric_limits<u32>::max)();
const auto flush_bindings = [&]() {
if (bindings.buffers.empty()) {
return;
}
bindings.max_index = bindings.min_index + static_cast<u32>(bindings.buffers.size());
runtime.BindVertexBuffers(bindings);
bindings = HostBindings<Buffer>{};
last_index = (std::numeric_limits<u32>::max)();
};
while (enabled_mask != 0) {
const u32 index = std::countr_zero(enabled_mask);
enabled_mask &= (enabled_mask - 1);
const u32 enabled_mask = enabled_vertex_buffers_mask;
u32 dirty_mask = 0;
u32 pending_mask = enabled_mask;
while (pending_mask != 0) {
const u32 index = std::countr_zero(pending_mask);
pending_mask &= (pending_mask - 1);
const Binding& binding = VertexBufferSlot(index);
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
SynchronizeBuffer(buffer, binding.device_addr, binding.size);
if (!flags[Dirty::VertexBuffer0 + index]) {
flush_bindings();
continue;
if (flags[Dirty::VertexBuffer0 + index]) {
dirty_mask |= 1u << index;
}
}
if (dirty_mask == 0) {
return;
}
const u32 min_index = static_cast<u32>(std::countr_zero(dirty_mask));
const u32 max_index = 32u - static_cast<u32>(std::countl_zero(dirty_mask));
HostBindings<Buffer> bindings{};
bindings.min_index = min_index;
bindings.max_index = max_index;
for (u32 index = min_index; index < max_index; ++index) {
flags[Dirty::VertexBuffer0 + index] = false;
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
if ((enabled_mask & (1u << index)) == 0) {
bindings.buffers.push_back(&slot_buffers[NULL_BUFFER_ID]);
bindings.offsets.push_back(0);
bindings.sizes.push_back(0);
bindings.strides.push_back(stride);
continue;
}
const Binding& binding = VertexBufferSlot(index);
Buffer& buffer = slot_buffers[binding.buffer_id];
const u32 offset = buffer.Offset(binding.device_addr);
buffer.MarkUsage(offset, binding.size);
if (!bindings.buffers.empty() && index != last_index + 1) {
flush_bindings();
}
if (bindings.buffers.empty()) {
bindings.min_index = index;
}
bindings.buffers.push_back(&buffer);
bindings.offsets.push_back(offset);
bindings.sizes.push_back(binding.size);
bindings.strides.push_back(stride);
last_index = index;
}
flush_bindings();
runtime.BindVertexBuffers(bindings);
} else {
HostBindings<typename P::Buffer> host_bindings;
bool any_valid{false};
+553
View File
@@ -0,0 +1,553 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cstring>
#include <optional>
#include <span>
#include "common/cityhash.h"
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/fs_paths.h"
#include "common/fs/path_util.h"
#include "video_core/frame_gen/lossless_dll.h"
#include "video_core/frame_gen/lsfg_translate.h"
namespace VideoCore::FrameGen {
namespace {
constexpr u16 DOS_MAGIC = 0x5A4D;
constexpr u32 PE_SIGNATURE = 0x00004550;
constexpr u16 PE32_MAGIC = 0x010B;
constexpr u16 PE32_PLUS_MAGIC = 0x020B;
constexpr size_t DOS_LFANEW_OFFSET = 0x3C;
constexpr size_t COFF_HEADER_SIZE = 20;
constexpr size_t OPTIONAL_HEADER_SIZE_OFFSET = 16;
constexpr size_t SECTION_HEADER_SIZE = 40;
constexpr size_t DATA_DIRECTORY_ENTRY_SIZE = 8;
constexpr size_t DATA_DIRECTORY_OFFSET_PE32 = 96;
constexpr size_t DATA_DIRECTORY_OFFSET_PE32_PLUS = 112;
constexpr size_t RESOURCE_DATA_DIRECTORY_INDEX = 2;
constexpr size_t RESOURCE_DIRECTORY_SIZE = 16;
constexpr size_t RESOURCE_NAMED_COUNT_OFFSET = 12;
constexpr size_t RESOURCE_ID_COUNT_OFFSET = 14;
constexpr size_t RESOURCE_ENTRY_SIZE = 8;
constexpr u32 RESOURCE_SUBDIRECTORY_FLAG = 0x80000000;
constexpr u32 RESOURCE_TYPE_RCDATA = 10;
constexpr u32 MIPMAPS_SHADER_ID = 255;
constexpr u32 GENERATE_SHADER_ID = 256;
constexpr u32 PERFORMANCE_SHADER_ID_FIRST = 280;
constexpr u32 PERFORMANCE_SHADER_ID_LAST = 302;
constexpr u32 CACHE_MAGIC = 0x4746534C;
constexpr u32 CACHE_VERSION = 2;
struct CacheHeader {
u32 magic;
u32 version;
u64 source_size;
u64 source_hash;
u32 module_count;
u32 variant;
};
struct Section {
u32 virtual_address;
u32 virtual_size;
u32 raw_address;
u32 raw_size;
};
struct ResourceEntry {
u32 id;
u32 offset;
bool is_directory;
bool is_named;
};
class ImageReader {
public:
explicit ImageReader(std::span<const u8> image_) : image{image_} {}
template <typename T>
[[nodiscard]] bool Read(size_t offset, T& out_value) const {
if (offset > image.size() || image.size() - offset < sizeof(T)) {
return false;
}
std::memcpy(&out_value, image.data() + offset, sizeof(T));
return true;
}
[[nodiscard]] bool Slice(size_t offset, size_t size, std::span<const u8>& out_slice) const {
if (offset > image.size() || image.size() - offset < size) {
return false;
}
out_slice = image.subspan(offset, size);
return true;
}
private:
std::span<const u8> image;
};
[[nodiscard]] std::optional<size_t> FindPeHeader(const ImageReader& reader) {
u16 dos_magic{};
if (!reader.Read(0, dos_magic) || dos_magic != DOS_MAGIC) {
return std::nullopt;
}
u32 pe_offset{};
if (!reader.Read(DOS_LFANEW_OFFSET, pe_offset)) {
return std::nullopt;
}
u32 pe_signature{};
if (!reader.Read(pe_offset, pe_signature) || pe_signature != PE_SIGNATURE) {
return std::nullopt;
}
return static_cast<size_t>(pe_offset);
}
[[nodiscard]] std::optional<size_t> FindDataDirectory(const ImageReader& reader,
size_t optional_header_offset) {
u16 optional_magic{};
if (!reader.Read(optional_header_offset, optional_magic)) {
return std::nullopt;
}
switch (optional_magic) {
case PE32_MAGIC:
return optional_header_offset + DATA_DIRECTORY_OFFSET_PE32;
case PE32_PLUS_MAGIC:
return optional_header_offset + DATA_DIRECTORY_OFFSET_PE32_PLUS;
default:
return std::nullopt;
}
}
[[nodiscard]] bool ReadSections(const ImageReader& reader, size_t pe_offset,
std::vector<Section>& out_sections) {
u16 section_count{};
u16 optional_header_size{};
if (!reader.Read(pe_offset + 4 + 2, section_count) ||
!reader.Read(pe_offset + 4 + OPTIONAL_HEADER_SIZE_OFFSET, optional_header_size)) {
return false;
}
const size_t table_offset = pe_offset + 4 + COFF_HEADER_SIZE + optional_header_size;
out_sections.reserve(section_count);
for (size_t i = 0; i < section_count; ++i) {
const size_t offset = table_offset + i * SECTION_HEADER_SIZE;
Section section{};
if (!reader.Read(offset + 8, section.virtual_size) ||
!reader.Read(offset + 12, section.virtual_address) ||
!reader.Read(offset + 16, section.raw_size) ||
!reader.Read(offset + 20, section.raw_address)) {
return false;
}
out_sections.push_back(section);
}
return true;
}
[[nodiscard]] std::optional<size_t> RvaToFileOffset(std::span<const Section> sections, u32 rva) {
for (const Section& section : sections) {
const u32 span = std::max(section.virtual_size, section.raw_size);
if (span == 0 || rva < section.virtual_address) {
continue;
}
const u32 relative = rva - section.virtual_address;
if (relative < span) {
return static_cast<size_t>(section.raw_address) + relative;
}
}
return std::nullopt;
}
[[nodiscard]] bool ReadResourceEntries(const ImageReader& reader, size_t directory_offset,
std::vector<ResourceEntry>& out_entries) {
u16 named_count{};
u16 id_count{};
if (!reader.Read(directory_offset + RESOURCE_NAMED_COUNT_OFFSET, named_count) ||
!reader.Read(directory_offset + RESOURCE_ID_COUNT_OFFSET, id_count)) {
return false;
}
const size_t total = size_t{named_count} + size_t{id_count};
out_entries.clear();
out_entries.reserve(total);
for (size_t i = 0; i < total; ++i) {
const size_t offset = directory_offset + RESOURCE_DIRECTORY_SIZE + i * RESOURCE_ENTRY_SIZE;
u32 name{};
u32 data{};
if (!reader.Read(offset, name) || !reader.Read(offset + 4, data)) {
return false;
}
out_entries.push_back(ResourceEntry{
.id = name & ~RESOURCE_SUBDIRECTORY_FLAG,
.offset = data & ~RESOURCE_SUBDIRECTORY_FLAG,
.is_directory = (data & RESOURCE_SUBDIRECTORY_FLAG) != 0,
.is_named = (name & RESOURCE_SUBDIRECTORY_FLAG) != 0,
});
}
return true;
}
[[nodiscard]] bool ReadResourceLeaf(const ImageReader& reader, std::span<const Section> sections,
size_t leaf_offset, std::span<const u8>& out_data) {
u32 data_rva{};
u32 data_size{};
if (!reader.Read(leaf_offset, data_rva) || !reader.Read(leaf_offset + 4, data_size) ||
data_size == 0) {
return false;
}
const std::optional<size_t> data_offset = RvaToFileOffset(sections, data_rva);
if (!data_offset) {
return false;
}
return reader.Slice(*data_offset, data_size, out_data);
}
using ResourceSpans = std::map<u32, std::span<const u8>>;
[[nodiscard]] bool CollectRcData(const ImageReader& reader, std::span<const Section> sections,
size_t resource_base, ResourceSpans& out_resources) {
std::vector<ResourceEntry> type_entries;
if (!ReadResourceEntries(reader, resource_base, type_entries)) {
return false;
}
for (const ResourceEntry& type_entry : type_entries) {
if (type_entry.is_named || type_entry.id != RESOURCE_TYPE_RCDATA ||
!type_entry.is_directory) {
continue;
}
std::vector<ResourceEntry> name_entries;
if (!ReadResourceEntries(reader, resource_base + type_entry.offset, name_entries)) {
return false;
}
for (const ResourceEntry& name_entry : name_entries) {
if (name_entry.is_named || !name_entry.is_directory) {
continue;
}
std::vector<ResourceEntry> language_entries;
if (!ReadResourceEntries(reader, resource_base + name_entry.offset, language_entries)) {
return false;
}
for (const ResourceEntry& language_entry : language_entries) {
if (language_entry.is_directory) {
continue;
}
std::span<const u8> data;
if (!ReadResourceLeaf(reader, sections, resource_base + language_entry.offset,
data)) {
continue;
}
out_resources.insert_or_assign(name_entry.id, data);
break;
}
}
}
return true;
}
[[nodiscard]] std::vector<u32> PerformanceShaderIds() {
std::vector<u32> ids{MIPMAPS_SHADER_ID, GENERATE_SHADER_ID};
for (u32 id = PERFORMANCE_SHADER_ID_FIRST; id <= PERFORMANCE_SHADER_ID_LAST; ++id) {
ids.push_back(id);
}
return ids;
}
template <typename Map>
[[nodiscard]] bool HasPerformanceShaders(const Map& resources) {
const std::vector<u32> ids = PerformanceShaderIds();
return std::ranges::all_of(ids, [&](u32 id) { return resources.contains(id); });
}
[[nodiscard]] u32 VariantOffset(ShaderVariant variant) {
return variant == ShaderVariant::NativeFp16 ? PerformanceShader::NATIVE_FP16_OFFSET
: PerformanceShader::NATIVE_FP32_OFFSET;
}
template <typename Map>
[[nodiscard]] bool HasNativeVariant(const Map& resources, ShaderVariant variant) {
const u32 offset = VariantOffset(variant);
return std::ranges::all_of(PerformanceShaderIds(), [&](u32 id) {
const auto hit = resources.find(id + offset);
return hit != resources.end() && IsSpirvModule(hit->second);
});
}
[[nodiscard]] std::optional<ShaderVariant> SelectVariant(const ResourceSpans& resources,
bool allow_fp16, bool prefer_fp16) {
if (prefer_fp16 && HasNativeVariant(resources, ShaderVariant::NativeFp16)) {
return ShaderVariant::NativeFp16;
}
if (HasNativeVariant(resources, ShaderVariant::NativeFp32)) {
return ShaderVariant::NativeFp32;
}
if (allow_fp16 && HasNativeVariant(resources, ShaderVariant::NativeFp16)) {
return ShaderVariant::NativeFp16;
}
return std::nullopt;
}
[[nodiscard]] LosslessStatus TranslateAll(const ResourceSpans& resources,
ShaderModules& out_modules,
ShaderVariant variant) {
const u32 offset = VariantOffset(variant);
out_modules.clear();
for (const u32 id : PerformanceShaderIds()) {
const auto hit = resources.find(id + offset);
if (hit == resources.end()) {
return LosslessStatus::MissingShaders;
}
std::vector<u32> adopted = AdoptSpirvModule(hit->second);
if (adopted.empty()) {
return LosslessStatus::TranslationFailed;
}
out_modules.emplace(id, std::move(adopted));
}
return LosslessStatus::Ok;
}
[[nodiscard]] bool WriteShaderCache(const std::filesystem::path& path, const CacheHeader& header,
const ShaderModules& modules) {
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write,
Common::FS::FileType::BinaryFile};
if (!file.IsOpen() || file.Write(header) != 1) {
return false;
}
for (const auto& [id, words] : modules) {
const u32 word_count = static_cast<u32>(words.size());
if (file.Write(id) != 1 || file.Write(word_count) != 1 ||
file.Write(words) != words.size()) {
return false;
}
}
return file.Flush();
}
[[nodiscard]] bool ReadShaderCache(const std::filesystem::path& path, u64 source_size,
u64 source_hash, u32 variant, ShaderModules& out_modules) {
if (!Common::FS::Exists(path)) {
return false;
}
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
CacheHeader header{};
if (!file.IsOpen() || file.Read(header) != 1) {
return false;
}
if (header.magic != CACHE_MAGIC || header.version != CACHE_VERSION ||
header.source_size != source_size || header.source_hash != source_hash ||
header.variant != variant) {
return false;
}
out_modules.clear();
for (u32 i = 0; i < header.module_count; ++i) {
u32 id{};
u32 word_count{};
if (file.Read(id) != 1 || file.Read(word_count) != 1 || word_count == 0) {
return false;
}
std::vector<u32> words(word_count);
if (file.Read(words) != words.size()) {
return false;
}
out_modules.emplace(id, std::move(words));
}
return HasPerformanceShaders(out_modules);
}
[[nodiscard]] LosslessStatus ReadImageFile(const std::filesystem::path& path,
std::vector<u8>& out_image) {
if (!Common::FS::Exists(path)) {
return LosslessStatus::NotInstalled;
}
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
if (!file.IsOpen()) {
return LosslessStatus::UnreadableFile;
}
out_image.resize(static_cast<size_t>(file.GetSize()));
if (out_image.empty() || file.Read(out_image) != out_image.size()) {
return LosslessStatus::UnreadableFile;
}
return LosslessStatus::Ok;
}
[[nodiscard]] LosslessStatus ParseShaderSpans(std::span<const u8> image,
ResourceSpans& out_resources) {
const ImageReader reader{image};
const std::optional<size_t> pe_offset = FindPeHeader(reader);
if (!pe_offset) {
return LosslessStatus::NotPortableExecutable;
}
const std::optional<size_t> data_directory =
FindDataDirectory(reader, *pe_offset + 4 + COFF_HEADER_SIZE);
if (!data_directory) {
return LosslessStatus::NotPortableExecutable;
}
std::vector<Section> sections;
if (!ReadSections(reader, *pe_offset, sections)) {
return LosslessStatus::NotPortableExecutable;
}
u32 resource_rva{};
if (!reader.Read(*data_directory + RESOURCE_DATA_DIRECTORY_INDEX * DATA_DIRECTORY_ENTRY_SIZE,
resource_rva) ||
resource_rva == 0) {
return LosslessStatus::MissingShaders;
}
const std::optional<size_t> resource_base = RvaToFileOffset(sections, resource_rva);
if (!resource_base) {
return LosslessStatus::NotPortableExecutable;
}
out_resources.clear();
if (!CollectRcData(reader, sections, *resource_base, out_resources)) {
return LosslessStatus::MissingShaders;
}
return HasPerformanceShaders(out_resources) ? LosslessStatus::Ok
: LosslessStatus::MissingShaders;
}
} // Anonymous namespace
std::filesystem::path GetLosslessDllPath() {
return Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / LOSSLESS_DLL_FILE;
}
std::filesystem::path GetShaderCachePath() {
return Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / LOSSLESS_CACHE_FILE;
}
LosslessStatus ReadShaderResources(const std::filesystem::path& path,
ShaderResources& out_resources) {
std::vector<u8> image;
const LosslessStatus read_status = ReadImageFile(path, image);
if (read_status != LosslessStatus::Ok) {
return read_status;
}
ResourceSpans spans;
const LosslessStatus parse_status = ParseShaderSpans(image, spans);
if (parse_status != LosslessStatus::Ok) {
return parse_status;
}
out_resources.clear();
for (const auto& [id, data] : spans) {
out_resources.emplace(id, std::vector<u8>{data.begin(), data.end()});
}
return LosslessStatus::Ok;
}
LosslessStatus ValidateLosslessDll(const std::filesystem::path& path) {
std::vector<u8> image;
const LosslessStatus read_status = ReadImageFile(path, image);
if (read_status != LosslessStatus::Ok) {
return read_status;
}
ResourceSpans spans;
return ParseShaderSpans(image, spans);
}
LosslessStatus GetInstalledLosslessStatus() {
return ValidateLosslessDll(GetLosslessDllPath());
}
LosslessStatus LoadShaderModules(ShaderModules& out_modules, bool allow_fp16, bool prefer_fp16) {
std::vector<u8> image;
const LosslessStatus read_status = ReadImageFile(GetLosslessDllPath(), image);
if (read_status != LosslessStatus::Ok) {
return read_status;
}
const u64 source_size = image.size();
const u64 source_hash =
Common::CityHash64(reinterpret_cast<const char*>(image.data()), image.size());
const std::filesystem::path cache_path = GetShaderCachePath();
ResourceSpans spans;
const LosslessStatus parse_status = ParseShaderSpans(image, spans);
if (parse_status != LosslessStatus::Ok) {
return parse_status;
}
const std::optional<ShaderVariant> variant = SelectVariant(spans, allow_fp16, prefer_fp16);
if (!variant) {
return LosslessStatus::MissingShaders;
}
if (ReadShaderCache(cache_path, source_size, source_hash, static_cast<u32>(*variant),
out_modules)) {
return LosslessStatus::Ok;
}
const LosslessStatus translate_status = TranslateAll(spans, out_modules, *variant);
if (translate_status != LosslessStatus::Ok) {
return translate_status;
}
const CacheHeader header{
.magic = CACHE_MAGIC,
.version = CACHE_VERSION,
.source_size = source_size,
.source_hash = source_hash,
.module_count = static_cast<u32>(out_modules.size()),
.variant = static_cast<u32>(*variant),
};
if (!WriteShaderCache(cache_path, header, out_modules)) {
void(Common::FS::RemoveFile(cache_path));
return LosslessStatus::CacheUnusable;
}
return LosslessStatus::Ok;
}
LosslessStatus BuildShaderCache() {
ShaderModules modules;
return LoadShaderModules(modules, true);
}
bool RemoveInstalledLosslessDll() {
const std::filesystem::path cache_path = GetShaderCachePath();
if (Common::FS::Exists(cache_path)) {
void(Common::FS::RemoveFile(cache_path));
}
const std::filesystem::path path = GetLosslessDllPath();
if (!Common::FS::Exists(path)) {
return true;
}
return Common::FS::RemoveFile(path);
}
} // namespace VideoCore::FrameGen
+67
View File
@@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <filesystem>
#include <map>
#include <vector>
#include "common/common_types.h"
namespace VideoCore::FrameGen {
enum class LosslessStatus : u32 {
Ok,
NotInstalled,
UnreadableFile,
NotPortableExecutable,
MissingShaders,
TranslationFailed,
CacheUnusable,
};
using ShaderResources = std::map<u32, std::vector<u8>>;
using ShaderModules = std::map<u32, std::vector<u32>>;
enum class ShaderVariant : u32 {
NativeFp32 = 1,
NativeFp16 = 2,
};
namespace PerformanceShader {
constexpr u32 MIPMAPS = 255;
constexpr u32 GENERATE = 256;
constexpr std::array<u32, 4> ALPHA{290, 291, 292, 293};
constexpr std::array<u32, 5> BETA{298, 299, 300, 301, 302};
constexpr std::array<u32, 5> GAMMA{280, 282, 283, 284, 285};
constexpr std::array<u32, 10> DELTA{280, 286, 287, 288, 289, 281, 294, 295, 296, 297};
constexpr u32 NATIVE_FP16_OFFSET = 49;
constexpr u32 NATIVE_FP32_OFFSET = 98;
} // namespace PerformanceShader
[[nodiscard]] std::filesystem::path GetLosslessDllPath();
[[nodiscard]] std::filesystem::path GetShaderCachePath();
[[nodiscard]] LosslessStatus ReadShaderResources(const std::filesystem::path& path,
ShaderResources& out_resources);
[[nodiscard]] LosslessStatus ValidateLosslessDll(const std::filesystem::path& path);
[[nodiscard]] LosslessStatus GetInstalledLosslessStatus();
[[nodiscard]] LosslessStatus BuildShaderCache();
[[nodiscard]] LosslessStatus LoadShaderModules(ShaderModules& out_modules,
bool allow_fp16 = false,
bool prefer_fp16 = false);
bool RemoveInstalledLosslessDll();
} // namespace VideoCore::FrameGen
@@ -0,0 +1,93 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cstring>
#include <map>
#include <tuple>
#include "video_core/frame_gen/lsfg_translate.h"
namespace VideoCore::FrameGen {
namespace {
constexpr u32 SPIRV_MAGIC = 0x07230203;
constexpr u32 SPIRV_WORD_COUNT_SHIFT = 16;
constexpr u32 SPIRV_OPCODE_MASK = 0xffff;
constexpr u32 SPIRV_OP_FUNCTION = 54;
constexpr u32 SPIRV_OP_DECORATE = 71;
constexpr u32 SPIRV_DECORATION_BINDING = 33;
constexpr u32 SPIRV_DECORATION_DESCRIPTOR_SET = 34;
constexpr u32 DECORATION_LITERAL_WORD = 3;
constexpr size_t SPIRV_HEADER_WORDS = 5;
void RenumberBindingsInOrder(std::vector<u32>& words) {
struct Slot {
u32 set;
u32 binding;
size_t literal_offset;
};
std::map<u32, u32> sets;
std::vector<Slot> slots;
size_t offset = SPIRV_HEADER_WORDS;
while (offset + 1 <= words.size()) {
const u32 length = words[offset] >> SPIRV_WORD_COUNT_SHIFT;
const u32 opcode = words[offset] & SPIRV_OPCODE_MASK;
if (length == 0 || offset + length > words.size()) {
return;
}
if (opcode == SPIRV_OP_FUNCTION) {
break;
}
if (opcode == SPIRV_OP_DECORATE && length >= 4) {
if (words[offset + 2] == SPIRV_DECORATION_DESCRIPTOR_SET) {
sets[words[offset + 1]] = words[offset + 3];
} else if (words[offset + 2] == SPIRV_DECORATION_BINDING) {
slots.push_back(Slot{0, words[offset + 3], offset + DECORATION_LITERAL_WORD});
}
}
offset += length;
}
for (Slot& slot : slots) {
const auto hit = sets.find(words[slot.literal_offset - 2]);
slot.set = hit == sets.end() ? 0 : hit->second;
}
std::ranges::stable_sort(slots, [](const Slot& lhs, const Slot& rhs) {
return std::tie(lhs.set, lhs.binding) < std::tie(rhs.set, rhs.binding);
});
for (size_t i = 0; i < slots.size(); ++i) {
words[slots[i].literal_offset] = static_cast<u32>(i);
}
}
} // Anonymous namespace
bool IsSpirvModule(std::span<const u8> blob) {
if (blob.size() < SPIRV_HEADER_WORDS * sizeof(u32) || blob.size() % sizeof(u32) != 0) {
return false;
}
u32 magic{};
std::memcpy(&magic, blob.data(), sizeof(magic));
return magic == SPIRV_MAGIC;
}
std::vector<u32> AdoptSpirvModule(std::span<const u8> blob) {
if (!IsSpirvModule(blob)) {
return {};
}
std::vector<u32> words(blob.size() / sizeof(u32));
std::memcpy(words.data(), blob.data(), blob.size());
RenumberBindingsInOrder(words);
return words;
}
} // namespace VideoCore::FrameGen
+17
View File
@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <span>
#include <vector>
#include "common/common_types.h"
namespace VideoCore::FrameGen {
[[nodiscard]] bool IsSpirvModule(std::span<const u8> blob);
[[nodiscard]] std::vector<u32> AdoptSpirvModule(std::span<const u8> blob);
} // namespace VideoCore::FrameGen
@@ -17,11 +17,14 @@ set(SHADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d_buffer.comp
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_stencil_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_bcn.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_buffer.comp
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d24s8.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d32f.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_d32f_to_abgr8.frag
@@ -30,8 +33,12 @@ set(SHADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/convert_float_to_depth.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.comp
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa_depth.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa_depth_stencil.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.comp
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa_depth.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa_depth_stencil.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_s8d24_to_abgr8.frag
${CMAKE_CURRENT_SOURCE_DIR}/full_screen_triangle.vert
${CMAKE_CURRENT_SOURCE_DIR}/fxaa.frag
@@ -163,6 +170,44 @@ foreach(SOURCE_FILE IN ITEMS ${SHADER_FILES})
endif()
endforeach()
# Integer variants of the MSAA conversion shaders. They only differ from the float
# source in the sampler and output types, so they are generated from it via defines.
set(SHADER_TYPE_VARIANTS
"convert_msaa_to_non_msaa.frag|sint|isampler2DMS|ivec4"
"convert_msaa_to_non_msaa.frag|uint|usampler2DMS|uvec4"
"convert_non_msaa_to_msaa.frag|sint|isampler2D|ivec4"
"convert_non_msaa_to_msaa.frag|uint|usampler2D|uvec4"
)
foreach(VARIANT IN ITEMS ${SHADER_TYPE_VARIANTS})
string(REPLACE "|" ";" VARIANT_PARTS ${VARIANT})
list(GET VARIANT_PARTS 0 VARIANT_FILENAME)
list(GET VARIANT_PARTS 1 VARIANT_SUFFIX)
list(GET VARIANT_PARTS 2 VARIANT_SAMPLER)
list(GET VARIANT_PARTS 3 VARIANT_TEXEL)
set(VARIANT_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/${VARIANT_FILENAME})
get_filename_component(VARIANT_STEM ${VARIANT_FILENAME} NAME_WE)
get_filename_component(VARIANT_EXT ${VARIANT_FILENAME} EXT)
string(REPLACE "." "" VARIANT_EXT ${VARIANT_EXT})
set(VARIANT_NAME ${VARIANT_STEM}_${VARIANT_SUFFIX}_${VARIANT_EXT})
string(TOUPPER ${VARIANT_NAME}_SPV VARIANT_VARIABLE_NAME)
set(VARIANT_HEADER_FILE ${SHADER_DIR}/${VARIANT_NAME}_spv.h)
add_custom_command(
OUTPUT
${VARIANT_HEADER_FILE}
COMMAND
${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS}
-DSAMPLER_TYPE=${VARIANT_SAMPLER} -DTEXEL_TYPE=${VARIANT_TEXEL}
--variable-name ${VARIANT_VARIABLE_NAME} -o ${VARIANT_HEADER_FILE} ${VARIANT_SOURCE}
--target-env ${SPIR_V_VERSION}
MAIN_DEPENDENCY
${VARIANT_SOURCE}
)
set(SHADER_HEADERS ${SHADER_HEADERS} ${VARIANT_HEADER_FILE})
endforeach()
foreach(FILEPATH IN ITEMS ${FIDELITYFX_FILES})
get_filename_component(FILENAME ${FILEPATH} NAME)
string(REPLACE "." "_" HEADER_NAME ${FILENAME})
@@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 450 core
layout(binding = 0) uniform sampler2D depth_tex;
layout(location = 0) in vec2 texcoord;
void main() {
gl_FragDepth = textureLod(depth_tex, texcoord, 0).r;
}
@@ -8,5 +8,5 @@ layout(binding = 0) uniform sampler2DMS depth_tex;
layout(location = 0) in vec2 texcoord;
void main() {
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), 0).r;
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), gl_SampleID).r;
}
@@ -10,6 +10,6 @@ layout(binding = 1) uniform usampler2DMS stencil_tex;
layout(location = 0) in vec2 texcoord;
void main() {
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), 0).r;
gl_FragStencilRefARB = int(texelFetch(stencil_tex, ivec2(texcoord), 0).r);
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), gl_SampleID).r;
gl_FragStencilRefARB = int(texelFetch(stencil_tex, ivec2(texcoord), gl_SampleID).r);
}
@@ -0,0 +1,104 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 430
#extension GL_EXT_shader_16bit_storage : require
#extension GL_EXT_shader_8bit_storage : require
#define BINDING_INPUT_BUFFER 0
#define BINDING_OUTPUT_BUFFER 1
layout(push_constant) uniform PushConstants {
uvec3 dim;
uint bytes_per_block_log2;
uvec3 origin;
uint layer_stride;
uint block_size;
uint x_shift;
uint block_height;
uint block_height_mask;
} pc;
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
layout(binding = BINDING_OUTPUT_BUFFER, std430) writeonly buffer OutputBuffer {
uint out_u32[];
};
layout(local_size_x = 16, local_size_y = 8, local_size_z = 1) in;
const uint GOB_SIZE_X = 64;
const uint GOB_SIZE_Y = 8;
const uint GOB_SIZE_X_SHIFT = 6;
const uint GOB_SIZE_Y_SHIFT = 3;
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
uint SwizzleTable(uint pos) {
const uint t[8] = uint[](
0x12100200, 0x13110301, 0x16140604, 0x17150705,
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
);
const uint i = pos >> 4;
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
return (h << 4) | (pos & 0xf);
}
uint SwizzleOffset(uvec2 pos) {
pos = pos & SWIZZLE_MASK;
return SwizzleTable(pos.y * 64u + pos.x);
}
uvec4 ReadTexel(uint offset) {
switch (pc.bytes_per_block_log2) {
case 2u:
return uvec4(u32data[offset / 4u], 0u, 0u, 0u);
case 3u:
return uvec4(u64data[offset / 8u], 0u, 0u);
case 4u:
return u128data[offset / 16u];
}
return uvec4(0u);
}
void main() {
uvec3 coord = gl_GlobalInvocationID;
if (coord.x >= pc.dim.x || coord.y >= pc.dim.y || coord.z >= pc.dim.z) {
return;
}
uvec3 pos = coord + pc.origin;
pos.x <<= pc.bytes_per_block_log2;
uint swizzle = SwizzleOffset(pos.xy);
uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
uint offset = 0u;
offset += pos.z * pc.layer_stride;
offset += (block_y >> pc.block_height) * pc.block_size;
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
offset += swizzle;
uvec4 texel = ReadTexel(offset);
uint words = 1u << (pc.bytes_per_block_log2 - 2u);
uint linear_index = coord.x + coord.y * pc.dim.x + coord.z * pc.dim.x * pc.dim.y;
uint out_idx = linear_index * words;
out_u32[out_idx] = texel.x;
if (words > 1u) {
out_u32[out_idx + 1u] = texel.y;
}
if (words > 2u) {
out_u32[out_idx + 2u] = texel.z;
out_u32[out_idx + 3u] = texel.w;
}
}
@@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 430
#define BINDING_INPUT_BUFFER 0
#define BINDING_OUTPUT_BUFFER 1
layout(push_constant) uniform PushConstants {
uvec3 dim;
uint bytes_per_block_log2;
uvec3 origin;
uint slice_size;
uint block_size;
uint x_shift;
uint block_height;
uint block_height_mask;
uint block_depth;
uint block_depth_mask;
} pc;
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
layout(binding = BINDING_OUTPUT_BUFFER, std430) writeonly buffer OutputBuffer {
uint out_u32[];
};
layout(local_size_x = 8, local_size_y = 8, local_size_z = 4) in;
const uint GOB_SIZE_X = 64;
const uint GOB_SIZE_Y = 8;
const uint GOB_SIZE_X_SHIFT = 6;
const uint GOB_SIZE_Y_SHIFT = 3;
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
uint SwizzleTable(uint pos) {
const uint t[8] = uint[](
0x12100200, 0x13110301, 0x16140604, 0x17150705,
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
);
const uint i = pos >> 4;
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
return (h << 4) | (pos & 0xf);
}
uint SwizzleOffset(uvec2 pos) {
pos = pos & SWIZZLE_MASK;
return SwizzleTable(pos.y * 64u + pos.x);
}
uvec4 ReadTexel(uint offset) {
switch (pc.bytes_per_block_log2) {
case 2u:
return uvec4(u32data[offset / 4u], 0u, 0u, 0u);
case 3u:
return uvec4(u64data[offset / 8u], 0u, 0u);
case 4u:
return u128data[offset / 16u];
}
return uvec4(0u);
}
void main() {
uvec3 coord = gl_GlobalInvocationID;
if (coord.x >= pc.dim.x || coord.y >= pc.dim.y || coord.z >= pc.dim.z) {
return;
}
uvec3 pos = coord + pc.origin;
pos.x <<= pc.bytes_per_block_log2;
uint swizzle = SwizzleOffset(pos.xy);
uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
uint offset = 0u;
offset += (pos.z >> pc.block_depth) * pc.slice_size;
offset += (pos.z & pc.block_depth_mask) << (GOB_SIZE_SHIFT + pc.block_height);
offset += (block_y >> pc.block_height) * pc.block_size;
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
offset += swizzle;
uvec4 texel = ReadTexel(offset);
uint words = 1u << (pc.bytes_per_block_log2 - 2u);
uint linear_index = coord.x + coord.y * pc.dim.x + coord.z * pc.dim.x * pc.dim.y;
uint out_idx = linear_index * words;
out_u32[out_idx] = texel.x;
if (words > 1u) {
out_u32[out_idx + 1u] = texel.y;
}
if (words > 2u) {
out_u32[out_idx + 2u] = texel.z;
out_u32[out_idx + 3u] = texel.w;
}
}

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