mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-04 19:36:27 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 93cd28e44f | |||
| aa8d2000f4 | |||
| 821765189a | |||
| f24b14e7cf | |||
| 1dcc574591 | |||
| 5c20f244c9 | |||
| aa4e494c08 |
@@ -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
|
|
||||||
@@ -63,3 +63,6 @@ artifacts
|
|||||||
/install*
|
/install*
|
||||||
vulkansdk*.exe
|
vulkansdk*.exe
|
||||||
*.tar.zst
|
*.tar.zst
|
||||||
|
|
||||||
|
# Build-time generated external sources
|
||||||
|
externals/generated
|
||||||
|
|||||||
@@ -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}
|
|
||||||
@@ -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)
|
|
||||||
+10
-24
@@ -84,6 +84,8 @@ option(ENABLE_WERROR "Enable -Werror diagnostics" ON)
|
|||||||
# Lossless Scaling frame generation. Only Android.
|
# Lossless Scaling frame generation. Only Android.
|
||||||
cmake_dependent_option(ENABLE_LSFG "Enable Lossless Scaling frame generation" ON "ANDROID" OFF)
|
cmake_dependent_option(ENABLE_LSFG "Enable Lossless Scaling frame generation" ON "ANDROID" OFF)
|
||||||
|
|
||||||
|
option(ENABLE_RESHADE "Enable ReShade FX post-processing effects" ON)
|
||||||
|
|
||||||
# non-linux bundled qt are static
|
# non-linux bundled qt are static
|
||||||
if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX))
|
if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX))
|
||||||
set(YUZU_STATIC_BUILD ON)
|
set(YUZU_STATIC_BUILD ON)
|
||||||
@@ -214,8 +216,6 @@ if(MSVC)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system
|
# TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system
|
||||||
cmake_dependent_option(YUZU_USE_BUNDLED_SDL3 "Download bundled SDL3 build" "${MSVC}" "NOT ANDROID" OFF)
|
|
||||||
|
|
||||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||||
|
|
||||||
set(EXT_DEFAULT OFF)
|
set(EXT_DEFAULT OFF)
|
||||||
@@ -223,6 +223,8 @@ if (MSVC OR ANDROID)
|
|||||||
set(EXT_DEFAULT ON)
|
set(EXT_DEFAULT ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
option(YUZU_USE_BUNDLED_SDL3 "Download bundled SDL3 build" ${EXT_DEFAULT})
|
||||||
|
|
||||||
# ffmpeg
|
# ffmpeg
|
||||||
option(YUZU_USE_BUNDLED_FFMPEG "Download bundled FFmpeg" ${EXT_DEFAULT})
|
option(YUZU_USE_BUNDLED_FFMPEG "Download bundled FFmpeg" ${EXT_DEFAULT})
|
||||||
cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external source" "${SOLARIS}" "NOT WIN32 AND NOT ANDROID" OFF)
|
cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external source" "${SOLARIS}" "NOT WIN32 AND NOT ANDROID" OFF)
|
||||||
@@ -238,7 +240,7 @@ option(YUZU_USE_BUNDLED_SIRIT "Download bundled sirit" ${BUNDLED_SIRIT_DEFAULT})
|
|||||||
# FreeBSD 15+ has libusb, versions below should disable it
|
# FreeBSD 15+ has libusb, versions below should disable it
|
||||||
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR LINUX OR FREEBSD OR APPLE" OFF)
|
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR LINUX OR FREEBSD OR APPLE" OFF)
|
||||||
|
|
||||||
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT (WIN32 AND ARCHITECTURE_arm64) AND NOT APPLE" OFF)
|
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT (WIN32 AND ARCHITECTURE_arm64) AND NOT APPLE AND NOT ANDROID" OFF)
|
||||||
mark_as_advanced(FORCE ENABLE_OPENGL)
|
mark_as_advanced(FORCE ENABLE_OPENGL)
|
||||||
|
|
||||||
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||||
@@ -377,15 +379,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
|
|||||||
# Prefer the -pthread flag on Linux.
|
# Prefer the -pthread flag on Linux.
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||||
find_package(Threads REQUIRED)
|
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 (EMSCRIPTEN)
|
|
||||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthread>)
|
|
||||||
add_link_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthread>)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_package(RenderDoc MODULE)
|
find_package(RenderDoc MODULE)
|
||||||
|
|
||||||
@@ -414,12 +407,7 @@ set(BUILD_TESTING OFF)
|
|||||||
set(ENABLE_TESTING OFF)
|
set(ENABLE_TESTING OFF)
|
||||||
|
|
||||||
# boost
|
# boost
|
||||||
if (EMSCRIPTEN)
|
set(BOOST_INCLUDE_LIBRARIES algorithm icl pool container heap asio headers process filesystem crc variant)
|
||||||
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()
|
|
||||||
|
|
||||||
AddJsonPackage(boost)
|
AddJsonPackage(boost)
|
||||||
|
|
||||||
@@ -440,12 +428,10 @@ if (Boost_ADDED)
|
|||||||
|
|
||||||
target_compile_options(boost_heap INTERFACE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shadow>)
|
target_compile_options(boost_heap INTERFACE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shadow>)
|
||||||
target_compile_options(boost_icl 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)
|
target_compile_options(boost_asio INTERFACE
|
||||||
if (TARGET boost_asio)
|
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-conversion>
|
||||||
target_compile_options(boost_asio INTERFACE
|
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-fallthrough>
|
||||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-conversion>
|
)
|
||||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-fallthrough>)
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
+8
-11
@@ -115,7 +115,7 @@
|
|||||||
"find_args": "MODULE GLOBAL",
|
"find_args": "MODULE GLOBAL",
|
||||||
"hash": "159ed94965018f2a371d45a3bfc1961e5fb1549e501ded70a6b4532d7fe99d0579c18b5195aff6e35f96f399b426cea2650ec9fb75ef80d4c9edeccb51f2e6c9",
|
"hash": "159ed94965018f2a371d45a3bfc1961e5fb1549e501ded70a6b4532d7fe99d0579c18b5195aff6e35f96f399b426cea2650ec9fb75ef80d4c9edeccb51f2e6c9",
|
||||||
"options": [
|
"options": [
|
||||||
"HTTPLIB_REQUIRE_OPENSSL OFF",
|
"HTTPLIB_REQUIRE_OPENSSL ON",
|
||||||
"HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES ON"
|
"HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES ON"
|
||||||
],
|
],
|
||||||
"patches": [
|
"patches": [
|
||||||
@@ -186,19 +186,12 @@
|
|||||||
"repo": "eden-emulator/oaknut",
|
"repo": "eden-emulator/oaknut",
|
||||||
"version": "v2.0.3"
|
"version": "v2.0.3"
|
||||||
},
|
},
|
||||||
"oboe": {
|
|
||||||
"bundled": true,
|
|
||||||
"hash": "ce4011afe7345370d4ead3b891cd69a5ef224b129535783586c0ca75051d303ed446e6c7f10bde8da31fff58d6e307f1732a3ffd03b249f9ef1fd48fd4132715",
|
|
||||||
"repo": "google/oboe",
|
|
||||||
"version": "1.10.0"
|
|
||||||
},
|
|
||||||
"openssl": {
|
"openssl": {
|
||||||
"hash": "29002ce50cb95a4f4f1d0e9d3f684401fbd4eac34203dc2eef3b6334af5d44aa46bf788b63a6f5c139c383eafb7269ae87a58a9a3ad5912903b9773e545ccc0a",
|
"hash": "29002ce50cb95a4f4f1d0e9d3f684401fbd4eac34203dc2eef3b6334af5d44aa46bf788b63a6f5c139c383eafb7269ae87a58a9a3ad5912903b9773e545ccc0a",
|
||||||
"min_version": "3.0.0",
|
"min_version": "3.0.0",
|
||||||
"package": "OpenSSL",
|
"package": "OpenSSL",
|
||||||
"patches": [
|
"patches": [
|
||||||
"0001-add-bundled-cert.patch",
|
"0001-add-bundled-cert.patch"
|
||||||
"0002-wasm-support.patch"
|
|
||||||
],
|
],
|
||||||
"repo": "openssl/openssl",
|
"repo": "openssl/openssl",
|
||||||
"version": "openssl-3.6.2"
|
"version": "openssl-3.6.2"
|
||||||
@@ -220,8 +213,7 @@
|
|||||||
"0001-cpmutil-compat.patch",
|
"0001-cpmutil-compat.patch",
|
||||||
"0002-use-ccache.patch",
|
"0002-use-ccache.patch",
|
||||||
"0003-use-cmake-compiler-flags.patch",
|
"0003-use-cmake-compiler-flags.patch",
|
||||||
"0004-use-shell-wrapper.patch",
|
"0004-use-shell-wrapper.patch"
|
||||||
"0005-wasm-support.patch"
|
|
||||||
],
|
],
|
||||||
"repo": "jimmy-park/openssl-cmake",
|
"repo": "jimmy-park/openssl-cmake",
|
||||||
"version": "3.6.2"
|
"version": "3.6.2"
|
||||||
@@ -254,6 +246,11 @@
|
|||||||
"repo": "stachenov/quazip",
|
"repo": "stachenov/quazip",
|
||||||
"version": "2e95c9001b"
|
"version": "2e95c9001b"
|
||||||
},
|
},
|
||||||
|
"reshade": {
|
||||||
|
"hash": "a1bd3fcf135fb6d018c1831ae45a8942d9777d0418b55e1189921e2ab775d1b53b0a9808924e09ef1c3e68ea11d69b3bcbebc7f4b59de14f6deec2dc24531d5e",
|
||||||
|
"repo": "crosire/reshade",
|
||||||
|
"version": "v6.7.3"
|
||||||
|
},
|
||||||
"sdl3": {
|
"sdl3": {
|
||||||
"hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3",
|
"hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3",
|
||||||
"min_version": "3.2.10",
|
"min_version": "3.2.10",
|
||||||
|
|||||||
Vendored
+62
@@ -0,0 +1,62 @@
|
|||||||
|
texture EdenBackBufferTex : COLOR;
|
||||||
|
sampler EdenBackBuffer { Texture = EdenBackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Saturation <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Saturation";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Brightness <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Brightness";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Contrast <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Contrast";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Gamma <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Gamma";
|
||||||
|
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
void VS_Eden(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(0.0, 0.0);
|
||||||
|
if (id == 2)
|
||||||
|
{
|
||||||
|
uv.x = 2.0;
|
||||||
|
}
|
||||||
|
if (id == 1)
|
||||||
|
{
|
||||||
|
uv.y = 2.0;
|
||||||
|
}
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_ColorGrade(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float3 rgb = tex2D(EdenBackBuffer, uv).rgb;
|
||||||
|
|
||||||
|
float luma = dot(rgb, float3(0.2126, 0.7152, 0.0722));
|
||||||
|
rgb = lerp(float3(luma, luma, luma), rgb, Saturation);
|
||||||
|
rgb *= Brightness;
|
||||||
|
rgb = (rgb - 0.5) * Contrast + 0.5;
|
||||||
|
rgb = pow(max(rgb, 0.0), 1.0 / max(Gamma, 0.0001));
|
||||||
|
|
||||||
|
return float4(saturate(rgb), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique EdenColorGrade
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_Eden;
|
||||||
|
PixelShader = PS_ColorGrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+45
@@ -0,0 +1,45 @@
|
|||||||
|
texture EdenBackBufferTex : COLOR;
|
||||||
|
sampler EdenBackBuffer { Texture = EdenBackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Amount <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Amount";
|
||||||
|
ui_min = 0.0; ui_max = 3.0; ui_step = 0.01;
|
||||||
|
> = 0.6;
|
||||||
|
|
||||||
|
void VS_Eden(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(0.0, 0.0);
|
||||||
|
if (id == 2)
|
||||||
|
{
|
||||||
|
uv.x = 2.0;
|
||||||
|
}
|
||||||
|
if (id == 1)
|
||||||
|
{
|
||||||
|
uv.y = 2.0;
|
||||||
|
}
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_Sharpen(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
||||||
|
|
||||||
|
float3 centre = tex2D(EdenBackBuffer, uv).rgb;
|
||||||
|
float3 blur = tex2D(EdenBackBuffer, uv + float2(-texel.x, 0.0)).rgb;
|
||||||
|
blur += tex2D(EdenBackBuffer, uv + float2(texel.x, 0.0)).rgb;
|
||||||
|
blur += tex2D(EdenBackBuffer, uv + float2(0.0, -texel.y)).rgb;
|
||||||
|
blur += tex2D(EdenBackBuffer, uv + float2(0.0, texel.y)).rgb;
|
||||||
|
blur *= 0.25;
|
||||||
|
|
||||||
|
return float4(centre + (centre - blur) * Amount, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique EdenSharpen
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_Eden;
|
||||||
|
PixelShader = PS_Sharpen;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+50
@@ -0,0 +1,50 @@
|
|||||||
|
texture EdenBackBufferTex : COLOR;
|
||||||
|
sampler EdenBackBuffer { Texture = EdenBackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Strength <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Strength";
|
||||||
|
ui_tooltip = "How dark the corners become.";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 0.6;
|
||||||
|
|
||||||
|
uniform float Aspect <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Aspect";
|
||||||
|
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
void VS_Eden(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(0.0, 0.0);
|
||||||
|
if (id == 2)
|
||||||
|
{
|
||||||
|
uv.x = 2.0;
|
||||||
|
}
|
||||||
|
if (id == 1)
|
||||||
|
{
|
||||||
|
uv.y = 2.0;
|
||||||
|
}
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_Vignette(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float2 diff = uv - 0.5;
|
||||||
|
diff.x *= Aspect;
|
||||||
|
diff.y /= max(Aspect, 0.0001);
|
||||||
|
|
||||||
|
float falloff = 1.0 - min(1.0, Strength * dot(diff, diff) * 2.0);
|
||||||
|
float3 rgb = tex2D(EdenBackBuffer, uv).rgb;
|
||||||
|
|
||||||
|
return float4(rgb * falloff, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique EdenVignette
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_Eden;
|
||||||
|
PixelShader = PS_Vignette;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
- [NetBSD](#netbsd)
|
- [NetBSD](#netbsd)
|
||||||
- [MSYS2](#msys2)
|
- [MSYS2](#msys2)
|
||||||
- [RedoxOS](#redoxos)
|
- [RedoxOS](#redoxos)
|
||||||
- [WebAssembly](#webassembly)
|
|
||||||
<!-- /TOC -->
|
<!-- /TOC -->
|
||||||
|
|
||||||
## Arch Linux
|
## Arch Linux
|
||||||
@@ -243,19 +242,3 @@ find ./*/ -name "*.dll" | while read -r dll; do deps "$dll"; done
|
|||||||
The package install may randomly hang at times, in which case it has to be restarted. ALWAYS do a `sudo pkg update` or the chances of it hanging will be close to 90%. If "multiple" installs fail at once, try installing 1 by 1 the packages.
|
The package install may randomly hang at times, in which case it has to be restarted. ALWAYS do a `sudo pkg update` or the chances of it hanging will be close to 90%. If "multiple" installs fail at once, try installing 1 by 1 the packages.
|
||||||
|
|
||||||
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.
|
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.
|
|
||||||
|
|||||||
@@ -361,14 +361,6 @@ pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel b
|
|||||||
|
|
||||||
[Caveats](./Caveats.md#haikuos).
|
[Caveats](./Caveats.md#haikuos).
|
||||||
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary>WebAssembly</summary>
|
|
||||||
|
|
||||||
Emscripten: The default installation should provide enough.
|
|
||||||
|
|
||||||
[Caveats](./Caveats.md#wasm).
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>RedoxOS</summary>
|
<summary>RedoxOS</summary>
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ The following options are desktop only.
|
|||||||
|
|
||||||
- `ENABLE_LIBUSB` (ON) Enable the use of the libusb input backend (HIGHLY RECOMMENDED)
|
- `ENABLE_LIBUSB` (ON) Enable the use of the libusb input backend (HIGHLY RECOMMENDED)
|
||||||
- `ENABLE_OPENGL` (ON) Enable the OpenGL graphics backend
|
- `ENABLE_OPENGL` (ON) Enable the OpenGL graphics backend
|
||||||
- Unavailable on Windows/ARM64
|
- Unavailable on Windows/ARM64 and on Android
|
||||||
- You probably shouldn't turn this off.
|
- You probably shouldn't turn this off.
|
||||||
|
|
||||||
### Qt
|
### Qt
|
||||||
|
|||||||
Vendored
+70
-43
@@ -136,48 +136,49 @@ if(ENABLE_CUBEB)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT ANDROID)
|
if (NOT YUZU_USE_BUNDLED_SDL3)
|
||||||
if (NOT YUZU_USE_BUNDLED_SDL3)
|
if (NOT WIN32)
|
||||||
if (NOT WIN32)
|
# Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers
|
||||||
# Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers
|
# Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095)
|
||||||
# Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095)
|
# Yuzu-cmd also needs: Video (depends on Loadso/Dlopen)
|
||||||
# Yuzu-cmd also needs: Video (depends on Loadso/Dlopen)
|
# CPUinfo also required for SDL Audio, at least until 2.28.0 (see https://github.com/libsdl-org/SDL/issues/7809)
|
||||||
# CPUinfo also required for SDL Audio, at least until 2.28.0 (see https://github.com/libsdl-org/SDL/issues/7809)
|
set(SDL_UNUSED_SUBSYSTEMS
|
||||||
set(SDL_UNUSED_SUBSYSTEMS
|
File Filesystem
|
||||||
File Filesystem
|
Locale Power Render)
|
||||||
Locale Power Render)
|
foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS})
|
||||||
foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS})
|
string(TOUPPER ${_SUB} _OPT)
|
||||||
string(TOUPPER ${_SUB} _OPT)
|
set(SDL_${_OPT} OFF)
|
||||||
set(SDL_${_OPT} OFF)
|
endforeach()
|
||||||
endforeach()
|
|
||||||
|
|
||||||
set(HIDAPI ON)
|
set(HIDAPI ON)
|
||||||
endif()
|
|
||||||
|
|
||||||
if (APPLE)
|
|
||||||
set(SDL_FILE ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
AddJsonPackage(sdl3)
|
|
||||||
else()
|
|
||||||
message(STATUS "Using bundled SDL3")
|
|
||||||
AddJsonPackage(sdl3-ci)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Normalize SDL3 link target across package variants.
|
if (APPLE)
|
||||||
# Some SDL3 packages export only SDL3::SDL3-shared or SDL3::SDL3-static.
|
set(SDL_FILE ON)
|
||||||
if (NOT TARGET SDL3::SDL3)
|
endif()
|
||||||
if (TARGET SDL3::SDL3-shared)
|
|
||||||
add_library(SDL3::SDL3 ALIAS SDL3::SDL3-shared)
|
AddJsonPackage(sdl3)
|
||||||
elseif (TARGET SDL3::SDL3-static)
|
else()
|
||||||
add_library(SDL3::SDL3 ALIAS SDL3::SDL3-static)
|
message(STATUS "Using bundled SDL3")
|
||||||
else()
|
AddJsonPackage(sdl3-ci)
|
||||||
message(FATAL_ERROR "SDL3 package found, but no usable SDL3 target was exported")
|
|
||||||
endif()
|
# copy sdl java sources for consumption by Gradle
|
||||||
|
if (ANDROID)
|
||||||
|
file(COPY ${SDL3_SOURCE_DIR}/java DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/generated/sdl)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(BUILD_SHARED_LIBS OFF)
|
# Normalize SDL3 link target across package variants.
|
||||||
|
# Some SDL3 packages export only SDL3::SDL3-shared or SDL3::SDL3-static.
|
||||||
|
if (NOT TARGET SDL3::SDL3)
|
||||||
|
if (TARGET SDL3::SDL3-shared)
|
||||||
|
add_library(SDL3::SDL3 ALIAS SDL3::SDL3-shared)
|
||||||
|
elseif (TARGET SDL3::SDL3-static)
|
||||||
|
add_library(SDL3::SDL3 ALIAS SDL3::SDL3-static)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "SDL3 package found, but no usable SDL3 target was exported")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# SPIRV Headers
|
# SPIRV Headers
|
||||||
AddJsonPackage(spirv-headers)
|
AddJsonPackage(spirv-headers)
|
||||||
@@ -194,6 +195,39 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# reshadefx
|
||||||
|
if (ENABLE_RESHADE)
|
||||||
|
AddJsonPackage(NAME reshade DOWNLOAD_ONLY)
|
||||||
|
|
||||||
|
set(RESHADEFX_SHIM_DIR ${CMAKE_CURRENT_BINARY_DIR}/reshadefx_shim)
|
||||||
|
file(WRITE ${RESHADEFX_SHIM_DIR}/spirv.hpp "#include <spirv/unified1/spirv.hpp>\n")
|
||||||
|
file(WRITE ${RESHADEFX_SHIM_DIR}/GLSL.std.450.h "#include <spirv/unified1/GLSL.std.450.h>\n")
|
||||||
|
|
||||||
|
add_library(reshadefx STATIC
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_codegen_spirv.cpp
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_expression.cpp
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_lexer.cpp
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_parser_exp.cpp
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_parser_stmt.cpp
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_preprocessor.cpp
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_symbol_table.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(reshadefx SYSTEM PUBLIC ${reshade_SOURCE_DIR}/source)
|
||||||
|
target_include_directories(reshadefx PRIVATE ${RESHADEFX_SHIM_DIR})
|
||||||
|
target_link_libraries(reshadefx PUBLIC SPIRV-Headers::SPIRV-Headers)
|
||||||
|
|
||||||
|
if (NOT MSVC)
|
||||||
|
target_compile_options(reshadefx PRIVATE -w)
|
||||||
|
else()
|
||||||
|
target_compile_options(reshadefx PRIVATE /w)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (NOT TARGET reshadefx::reshadefx)
|
||||||
|
add_library(reshadefx::reshadefx ALIAS reshadefx)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Catch2
|
# Catch2
|
||||||
if (YUZU_TESTS OR DYNARMIC_TESTS)
|
if (YUZU_TESTS OR DYNARMIC_TESTS)
|
||||||
AddJsonPackage(catch2)
|
AddJsonPackage(catch2)
|
||||||
@@ -385,13 +419,6 @@ if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# oboe
|
|
||||||
if (ANDROID)
|
|
||||||
AddJsonPackage(oboe)
|
|
||||||
|
|
||||||
add_library(oboe::oboe ALIAS oboe)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
# moltenvk
|
# moltenvk
|
||||||
if (NOT YUZU_USE_BUNDLED_MOLTENVK)
|
if (NOT YUZU_USE_BUNDLED_MOLTENVK)
|
||||||
|
|||||||
+5
-9
@@ -94,10 +94,6 @@ function(detect_architecture_symbols)
|
|||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
# arches here are put in a sane default order of importance
|
# 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
|
# notably, amd64, arm64, and riscv (in order) are BY FAR the most common
|
||||||
# mips is pretty popular in embedded
|
# mips is pretty popular in embedded
|
||||||
# ppc64 is pretty popular in supercomputing
|
# ppc64 is pretty popular in supercomputing
|
||||||
@@ -105,11 +101,6 @@ endfunction()
|
|||||||
# ia64 exists
|
# ia64 exists
|
||||||
# the rest exist, but are probably less popular than ia64
|
# the rest exist, but are probably less popular than ia64
|
||||||
|
|
||||||
detect_architecture_symbols(
|
|
||||||
ARCH wasm
|
|
||||||
SYMBOLS
|
|
||||||
"__EMSCRIPTEN__")
|
|
||||||
|
|
||||||
detect_architecture_symbols(
|
detect_architecture_symbols(
|
||||||
ARCH arm64
|
ARCH arm64
|
||||||
SYMBOLS
|
SYMBOLS
|
||||||
@@ -212,6 +203,11 @@ detect_architecture_symbols(
|
|||||||
"__loongarch__"
|
"__loongarch__"
|
||||||
"__loongarch64")
|
"__loongarch64")
|
||||||
|
|
||||||
|
detect_architecture_symbols(
|
||||||
|
ARCH wasm
|
||||||
|
SYMBOLS
|
||||||
|
"__EMSCRIPTEN__")
|
||||||
|
|
||||||
# "generic" target
|
# "generic" target
|
||||||
# If you have reached this point, you're on some as-of-yet unsupported architecture.
|
# If you have reached this point, you're on some as-of-yet unsupported architecture.
|
||||||
# See the docs up above for known unsupported architectures
|
# See the docs up above for known unsupported architectures
|
||||||
|
|||||||
-25
@@ -20,9 +20,6 @@ elseif (${CMAKE_SYSTEM_NAME} STREQUAL "managarm")
|
|||||||
set(MANAGARM ON)
|
set(MANAGARM ON)
|
||||||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Haiku")
|
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Haiku")
|
||||||
set(HAIKUOS ON)
|
set(HAIKUOS ON)
|
||||||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
|
|
||||||
set(EMSCRIPTEN ON)
|
|
||||||
message(WARNING "${CMAKE_LIBRARY_ARCHITECTURE} support is highly experimental!!!")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# BSD
|
# BSD
|
||||||
@@ -100,25 +97,3 @@ if (MINGW)
|
|||||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE
|
set(CMAKE_EXE_LINKER_FLAGS_RELEASE
|
||||||
"${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${MINGW_FLAGS}")
|
"${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${MINGW_FLAGS}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (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 (FREEBSD OR DRAGONFLYBSD)
|
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/local/lib")
|
|
||||||
endif()
|
|
||||||
|
|||||||
Vendored
+19
-54
@@ -35,37 +35,26 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG)
|
|||||||
elseif (NOT (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES CMAKE_SYSTEM_PROCESSOR
|
elseif (NOT (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES CMAKE_SYSTEM_PROCESSOR
|
||||||
AND CMAKE_HOST_SYSTEM_NAME MATCHES CMAKE_SYSTEM_NAME))
|
AND CMAKE_HOST_SYSTEM_NAME MATCHES CMAKE_SYSTEM_NAME))
|
||||||
string(TOLOWER "${CMAKE_SYSTEM_NAME}" FFmpeg_SYSTEM_NAME)
|
string(TOLOWER "${CMAKE_SYSTEM_NAME}" FFmpeg_SYSTEM_NAME)
|
||||||
# All of these platforms are supported by ffmpeg as native build OSes
|
if (FFmpeg_SYSTEM_NAME STREQUAL "openorbis" OR FFmpeg_SYSTEM_NAME STREQUAL "managarm")
|
||||||
# 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 (NETBSD OR SOLARIS OR FREEBSD OR OPENBSD OR DRAGONFLYBSD OR HAIKU
|
|
||||||
OR LINUX OR MSYS OR WIN32 OR ANDROID OR APPLE))
|
|
||||||
set(FFmpeg_SYSTEM_NAME "none")
|
set(FFmpeg_SYSTEM_NAME "none")
|
||||||
endif()
|
endif()
|
||||||
# TODO: Can we really do better? Auto-detection? Something clever?
|
# TODO: Can we really do better? Auto-detection? Something clever?
|
||||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
|
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
|
||||||
--enable-cross-compile
|
--enable-cross-compile
|
||||||
--arch="${CMAKE_SYSTEM_PROCESSOR}"
|
--arch="${CMAKE_SYSTEM_PROCESSOR}"
|
||||||
--target-os="${FFmpeg_SYSTEM_NAME}")
|
--target-os="${FFmpeg_SYSTEM_NAME}"
|
||||||
# Emscripten doesn't use CMAKE_SYSROOT I'm afraid, but it does well use EMSCRIPTEN_SYSROOT
|
--sysroot="${CMAKE_SYSROOT}"
|
||||||
if (EMSCRIPTEN)
|
)
|
||||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --sysroot="${EMSCRIPTEN_SYSROOT}")
|
|
||||||
else()
|
|
||||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --sysroot="${CMAKE_SYSROOT}")
|
|
||||||
endif()
|
|
||||||
if (DEFINED FFmpeg_CROSS_PREFIX)
|
if (DEFINED FFmpeg_CROSS_PREFIX)
|
||||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --cross-prefix="${FFmpeg_CROSS_PREFIX}")
|
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --cross-prefix="${FFmpeg_CROSS_PREFIX}")
|
||||||
else()
|
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()
|
endif()
|
||||||
set(FFmpeg_IS_CROSS_COMPILING TRUE)
|
set(FFmpeg_IS_CROSS_COMPILING TRUE)
|
||||||
endif()
|
endif()
|
||||||
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
|
# Doesn't support VA-API, don't go thru the embarrassment of trying to enable it
|
||||||
list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi)
|
list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi)
|
||||||
elseif (ANDROID)
|
elseif (ANDROID)
|
||||||
@@ -173,7 +162,8 @@ if (OPENORBIS)
|
|||||||
-lSceUserService
|
-lSceUserService
|
||||||
-lSceSysmodule
|
-lSceSysmodule
|
||||||
-lSceNet
|
-lSceNet
|
||||||
-lSceLibcInternal)
|
-lSceLibcInternal
|
||||||
|
)
|
||||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
|
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
|
||||||
--disable-pthreads
|
--disable-pthreads
|
||||||
--extra-cflags=${CMAKE_SYSROOT}/usr/include
|
--extra-cflags=${CMAKE_SYSROOT}/usr/include
|
||||||
@@ -184,18 +174,8 @@ elseif (MANAGARM)
|
|||||||
# Required for proper stuff
|
# Required for proper stuff
|
||||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
|
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
|
||||||
--disable-pthreads
|
--disable-pthreads
|
||||||
--extra-libs="${FFmpeg_CROSS_COMPILE_LIBS}")
|
--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})
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (YUZU_USE_BUNDLED_FFMPEG)
|
if (YUZU_USE_BUNDLED_FFMPEG)
|
||||||
@@ -225,7 +205,6 @@ else()
|
|||||||
# Build FFmpeg from externals
|
# Build FFmpeg from externals
|
||||||
message(STATUS "Using FFmpeg from externals")
|
message(STATUS "Using FFmpeg from externals")
|
||||||
|
|
||||||
set(FFmpeg_LD ${CMAKE_LINKER})
|
|
||||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86_64|amd64)")
|
if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86_64|amd64)")
|
||||||
# FFmpeg has source that requires one of nasm or yasm to assemble it.
|
# 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.
|
# REQUIRED throws an error if not found here during configuration rather than during compilation.
|
||||||
@@ -234,12 +213,6 @@ else()
|
|||||||
message(FATAL_ERROR "One of either `nasm` or `yasm` not found but is required.")
|
message(FATAL_ERROR "One of either `nasm` or `yasm` not found but is required.")
|
||||||
endif()
|
endif()
|
||||||
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)
|
find_program(AUTOCONF autoconf)
|
||||||
if ("${AUTOCONF}" STREQUAL "AUTOCONF-NOTFOUND")
|
if ("${AUTOCONF}" STREQUAL "AUTOCONF-NOTFOUND")
|
||||||
@@ -273,13 +246,7 @@ else()
|
|||||||
CACHE PATH "Paths to FFmpeg libraries" FORCE)
|
CACHE PATH "Paths to FFmpeg libraries" FORCE)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# Some SDKs (especially wasm) require us, actually, WANT us to use their "emconfigure"
|
find_program(BASH_PROGRAM bash REQUIRED)
|
||||||
# otherwise they will cry a billion tears
|
|
||||||
if (EMSCRIPTEN)
|
|
||||||
find_program(FFmpeg_CONFIGURE_WRAPPER emconfigure REQUIRED)
|
|
||||||
else()
|
|
||||||
find_program(FFmpeg_CONFIGURE_WRAPPER bash REQUIRED)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# `configure` parameters builds only exactly what yuzu needs from FFmpeg
|
# `configure` parameters builds only exactly what yuzu needs from FFmpeg
|
||||||
# `--disable-vdpau` is needed to avoid linking issues
|
# `--disable-vdpau` is needed to avoid linking issues
|
||||||
@@ -289,7 +256,7 @@ else()
|
|||||||
OUTPUT
|
OUTPUT
|
||||||
${FFmpeg_MAKEFILE}
|
${FFmpeg_MAKEFILE}
|
||||||
COMMAND
|
COMMAND
|
||||||
${FFmpeg_CONFIGURE_WRAPPER} ${FFmpeg_PREFIX}/configure
|
${BASH_PROGRAM} ${FFmpeg_PREFIX}/configure
|
||||||
--disable-avdevice
|
--disable-avdevice
|
||||||
--disable-avformat
|
--disable-avformat
|
||||||
--disable-doc
|
--disable-doc
|
||||||
@@ -298,10 +265,6 @@ else()
|
|||||||
--disable-ffprobe
|
--disable-ffprobe
|
||||||
--disable-network
|
--disable-network
|
||||||
--disable-swresample
|
--disable-swresample
|
||||||
--disable-autodetect
|
|
||||||
--disable-runtime-cpudetect
|
|
||||||
--disable-debug
|
|
||||||
--disable-programs
|
|
||||||
--enable-decoder=h264
|
--enable-decoder=h264
|
||||||
--enable-decoder=vp8
|
--enable-decoder=vp8
|
||||||
--enable-decoder=vp9
|
--enable-decoder=vp9
|
||||||
@@ -309,7 +272,7 @@ else()
|
|||||||
--enable-pic
|
--enable-pic
|
||||||
--cc=${FFmpeg_CC}
|
--cc=${FFmpeg_CC}
|
||||||
--cxx=${FFmpeg_CXX}
|
--cxx=${FFmpeg_CXX}
|
||||||
--ld=${FFmpeg_LD}
|
--ld=${CMAKE_LINKER}
|
||||||
--extra-cflags=${CMAKE_C_FLAGS}
|
--extra-cflags=${CMAKE_C_FLAGS}
|
||||||
--extra-cxxflags=${CMAKE_CXX_FLAGS}
|
--extra-cxxflags=${CMAKE_CXX_FLAGS}
|
||||||
--extra-ldflags=${CMAKE_C_LINK_FLAGS}
|
--extra-ldflags=${CMAKE_C_LINK_FLAGS}
|
||||||
@@ -325,7 +288,11 @@ else()
|
|||||||
|
|
||||||
# Workaround for Ubuntu 18.04's older version of make not being able to call make as a child
|
# 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.
|
# 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})
|
set(FFmpeg_BUILD_LIBRARIES ${FFmpeg_LIBRARIES})
|
||||||
|
|
||||||
@@ -333,8 +300,6 @@ else()
|
|||||||
if (LINUX OR ANDROID OR APPLE OR WIN32 OR FREEBSD)
|
if (LINUX OR ANDROID OR APPLE OR WIN32 OR FREEBSD)
|
||||||
set(FFmpeg_MAKE_ARGS -j${SYSTEM_THREADS})
|
set(FFmpeg_MAKE_ARGS -j${SYSTEM_THREADS})
|
||||||
else()
|
else()
|
||||||
# No GNU make implies that this system may be highly non-GNU
|
|
||||||
find_program(MAKE make required)
|
|
||||||
set(FFmpeg_MAKE_ARGS "")
|
set(FFmpeg_MAKE_ARGS "")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -342,7 +307,7 @@ else()
|
|||||||
OUTPUT
|
OUTPUT
|
||||||
${FFmpeg_BUILD_LIBRARIES}
|
${FFmpeg_BUILD_LIBRARIES}
|
||||||
COMMAND
|
COMMAND
|
||||||
${MAKE} ${FFmpeg_MAKE_ARGS}
|
gmake ${FFmpeg_MAKE_ARGS}
|
||||||
WORKING_DIRECTORY
|
WORKING_DIRECTORY
|
||||||
${FFmpeg_BUILD_DIR}
|
${FFmpeg_BUILD_DIR}
|
||||||
)
|
)
|
||||||
|
|||||||
Vendored
-2
@@ -14,9 +14,7 @@
|
|||||||
namespace Tz {
|
namespace Tz {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
#ifndef EINVAL
|
|
||||||
#define EINVAL 22
|
#define EINVAL 22
|
||||||
#endif
|
|
||||||
|
|
||||||
static Rule gmtmem{};
|
static Rule gmtmem{};
|
||||||
static Rule* const gmtptr = &gmtmem;
|
static Rule* const gmtptr = &gmtmem;
|
||||||
|
|||||||
+2
-5
@@ -177,6 +177,7 @@ else()
|
|||||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-invalid-offsetof>
|
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-invalid-offsetof>
|
||||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-unused-parameter>
|
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-unused-parameter>
|
||||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-missing-field-initializers>)
|
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-missing-field-initializers>)
|
||||||
|
|
||||||
if (CXX_CLANG OR CXX_ICC OR CXX_APPLE) # Clang, AppleClang, or Intel C++
|
if (CXX_CLANG OR CXX_ICC OR CXX_APPLE) # Clang, AppleClang, or Intel C++
|
||||||
if (NOT MSVC)
|
if (NOT MSVC)
|
||||||
add_compile_options(
|
add_compile_options(
|
||||||
@@ -190,11 +191,7 @@ else()
|
|||||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-nullability-completeness>)
|
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-nullability-completeness>)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ARCHITECTURE_wasm)
|
if (ARCHITECTURE_x86_64)
|
||||||
# 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)
|
|
||||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mcx16>)
|
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mcx16>)
|
||||||
if (LINUX OR FREEBSD)
|
if (LINUX OR FREEBSD)
|
||||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mtls-dialect=gnu2>)
|
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mtls-dialect=gnu2>)
|
||||||
|
|||||||
@@ -272,6 +272,13 @@ android {
|
|||||||
resValue("string", "app_name_suffixed", "$currentName$suffix")
|
resValue("string", "app_name_suffixed", "$currentName$suffix")
|
||||||
resValue("string", "app_name", "Eden$suffix")
|
resValue("string", "app_name", "Eden$suffix")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
named("main") {
|
||||||
|
java.srcDir("${edenDir}/externals/generated/sdl/java")
|
||||||
|
kotlin.srcDir("${edenDir}/externals/generated/sdl/java")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
idea {
|
idea {
|
||||||
|
|||||||
Vendored
+3
@@ -9,6 +9,9 @@
|
|||||||
-keep class org.ini4j.spi.IniBuilder
|
-keep class org.ini4j.spi.IniBuilder
|
||||||
-keep class org.ini4j.spi.IniFormatter
|
-keep class org.ini4j.spi.IniFormatter
|
||||||
|
|
||||||
|
-keep class org.libsdl.app.** { *; }
|
||||||
|
-keepclassmembers class org.libsdl.app.** { *; }
|
||||||
|
|
||||||
# Suppress warnings for R8
|
# Suppress warnings for R8
|
||||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import android.widget.TextView
|
|||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import org.libsdl.app.SDL
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||||
import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment
|
import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment
|
||||||
@@ -52,6 +53,8 @@ object NativeLibrary {
|
|||||||
init {
|
init {
|
||||||
try {
|
try {
|
||||||
System.loadLibrary("yuzu-android")
|
System.loadLibrary("yuzu-android")
|
||||||
|
SDL.setupJNI()
|
||||||
|
initJvm()
|
||||||
} catch (ex: UnsatisfiedLinkError) {
|
} catch (ex: UnsatisfiedLinkError) {
|
||||||
error("[NativeLibrary] $ex")
|
error("[NativeLibrary] $ex")
|
||||||
}
|
}
|
||||||
@@ -367,6 +370,7 @@ object NativeLibrary {
|
|||||||
NetPlayManager.clearChat()
|
NetPlayManager.clearChat()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
external fun initJvm()
|
||||||
external fun initMultiplayer()
|
external fun initMultiplayer()
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
|
|||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.features.settings.model
|
||||||
|
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
abstract class FxUniformSetting(
|
||||||
|
protected val index: Int,
|
||||||
|
protected val uniform: NativePostProcessing.Uniform,
|
||||||
|
protected val component: Int
|
||||||
|
) : AbstractSetting {
|
||||||
|
override val key: String
|
||||||
|
get() = "fx_${index}_${uniform.name}_$component"
|
||||||
|
|
||||||
|
override val isRuntimeModifiable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override val pairedSettingKey: String
|
||||||
|
get() = ""
|
||||||
|
|
||||||
|
override val isSwitchable: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
|
override val isSaveable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override var global: Boolean
|
||||||
|
get() = true
|
||||||
|
set(_) {}
|
||||||
|
|
||||||
|
protected fun currentValue(): Float {
|
||||||
|
if (NativePostProcessing.hasValue(index, uniform.name)) {
|
||||||
|
return NativePostProcessing.getValue(index, uniform.name, component)
|
||||||
|
}
|
||||||
|
return uniform.defaultAt(component)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun commit(value: Float) {
|
||||||
|
NativePostProcessing.setValue(index, uniform.name, component, value)
|
||||||
|
NativePostProcessing.store()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset() = commit(uniform.defaultAt(component))
|
||||||
|
}
|
||||||
|
|
||||||
|
class FxUniformSliderSetting(
|
||||||
|
index: Int,
|
||||||
|
uniform: NativePostProcessing.Uniform,
|
||||||
|
component: Int
|
||||||
|
) : FxUniformSetting(index, uniform, component), AbstractIntSetting {
|
||||||
|
override val defaultValue: Any
|
||||||
|
get() = ((uniform.defaultAt(component) - uniform.min) / uniform.step).roundToInt()
|
||||||
|
|
||||||
|
override fun getInt(needsGlobal: Boolean): Int =
|
||||||
|
((currentValue() - uniform.min) / uniform.step).roundToInt()
|
||||||
|
|
||||||
|
override fun setInt(value: Int) = commit(uniform.min + value * uniform.step)
|
||||||
|
|
||||||
|
override fun getValueAsString(needsGlobal: Boolean): String {
|
||||||
|
if (uniform.kind == NativePostProcessing.KIND_FLOAT) {
|
||||||
|
return String.format("%.3f", currentValue())
|
||||||
|
}
|
||||||
|
return currentValue().roundToInt().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FxUniformChoiceSetting(
|
||||||
|
index: Int,
|
||||||
|
uniform: NativePostProcessing.Uniform,
|
||||||
|
component: Int
|
||||||
|
) : FxUniformSetting(index, uniform, component), AbstractIntSetting {
|
||||||
|
override val defaultValue: Any
|
||||||
|
get() = uniform.defaultAt(component).roundToInt()
|
||||||
|
|
||||||
|
override fun getInt(needsGlobal: Boolean): Int = currentValue().roundToInt()
|
||||||
|
|
||||||
|
override fun setInt(value: Int) = commit(value.toFloat())
|
||||||
|
|
||||||
|
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
class FxUniformBooleanSetting(
|
||||||
|
index: Int,
|
||||||
|
uniform: NativePostProcessing.Uniform,
|
||||||
|
component: Int
|
||||||
|
) : FxUniformSetting(index, uniform, component), AbstractBooleanSetting {
|
||||||
|
override val defaultValue: Any
|
||||||
|
get() = uniform.defaultAt(component) != 0f
|
||||||
|
|
||||||
|
override fun getBoolean(needsGlobal: Boolean): Boolean = currentValue() != 0f
|
||||||
|
|
||||||
|
override fun setBoolean(value: Boolean) {
|
||||||
|
if (value) {
|
||||||
|
commit(1f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
commit(0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getValueAsString(needsGlobal: Boolean): String = getBoolean().toString()
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ object Settings {
|
|||||||
SECTION_SYSTEM(R.string.preferences_system),
|
SECTION_SYSTEM(R.string.preferences_system),
|
||||||
SECTION_RENDERER(R.string.preferences_graphics),
|
SECTION_RENDERER(R.string.preferences_graphics),
|
||||||
SECTION_FRAME_GEN(R.string.frame_gen),
|
SECTION_FRAME_GEN(R.string.frame_gen),
|
||||||
|
SECTION_POST_PROCESSING(R.string.post_processing),
|
||||||
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
|
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
|
||||||
SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
|
SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
|
||||||
SECTION_SOC_OVERLAY(R.string.soc_overlay_options),
|
SECTION_SOC_OVERLAY(R.string.soc_overlay_options),
|
||||||
|
|||||||
+1
@@ -13,6 +13,7 @@ enum class StringSetting(override val key: String) : AbstractStringSetting {
|
|||||||
DEVICE_NAME("device_name"),
|
DEVICE_NAME("device_name"),
|
||||||
LOG_FILTER("log_filter"),
|
LOG_FILTER("log_filter"),
|
||||||
PROGRAM_ARGS("program_args"),
|
PROGRAM_ARGS("program_args"),
|
||||||
|
POST_SHADER_CHAIN("post_shader_chain"),
|
||||||
|
|
||||||
WEB_TOKEN("eden_token"),
|
WEB_TOKEN("eden_token"),
|
||||||
WEB_USERNAME("eden_username")
|
WEB_USERNAME("eden_username")
|
||||||
|
|||||||
+218
@@ -18,6 +18,9 @@ import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.FxUniformBooleanSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.FxUniformChoiceSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.FxUniformSliderSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
|
import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||||
@@ -30,6 +33,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.*
|
|||||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
||||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||||
import org.yuzu.yuzu_emu.utils.FullscreenHelper
|
import org.yuzu.yuzu_emu.utils.FullscreenHelper
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
@@ -163,6 +167,7 @@ class SettingsFragmentPresenter(
|
|||||||
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||||
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||||
MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl)
|
MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl)
|
||||||
|
MenuTag.SECTION_POST_PROCESSING -> addPostProcessingSettings(sl)
|
||||||
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
|
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
|
||||||
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
|
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
|
||||||
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
|
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
|
||||||
@@ -190,6 +195,219 @@ class SettingsFragmentPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addPostProcessingSettings(sl: ArrayList<SettingsItem>) {
|
||||||
|
val usable = NativePostProcessing.catalog().filter { it.valid }
|
||||||
|
|
||||||
|
sl.apply {
|
||||||
|
add(
|
||||||
|
RunnableSetting(
|
||||||
|
titleId = R.string.post_processing_reload,
|
||||||
|
descriptionId = R.string.post_processing_reload_description,
|
||||||
|
isRunnable = true
|
||||||
|
) {
|
||||||
|
NativePostProcessing.reload()
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (usable.isEmpty()) {
|
||||||
|
add(
|
||||||
|
RunnableSetting(
|
||||||
|
titleId = R.string.post_processing_empty,
|
||||||
|
descriptionString = NativePostProcessing.getShaderDirectory(),
|
||||||
|
isRunnable = false
|
||||||
|
) {}
|
||||||
|
)
|
||||||
|
return@apply
|
||||||
|
}
|
||||||
|
|
||||||
|
val labels = mutableListOf<String>()
|
||||||
|
val files = mutableListOf<String>()
|
||||||
|
val techniques = mutableListOf<String>()
|
||||||
|
for (effect in usable) {
|
||||||
|
for (technique in effect.techniques) {
|
||||||
|
if (effect.techniques.size == 1) {
|
||||||
|
labels.add(effect.name)
|
||||||
|
} else {
|
||||||
|
labels.add(effect.name + " \u00b7 " + technique)
|
||||||
|
}
|
||||||
|
files.add(effect.file)
|
||||||
|
techniques.add(technique)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val chain = NativePostProcessing.chain()
|
||||||
|
for (index in chain.indices) {
|
||||||
|
val entry = chain[index]
|
||||||
|
val effect = usable.firstOrNull { it.file == entry.file }
|
||||||
|
|
||||||
|
var header = entry.file
|
||||||
|
if (effect != null) {
|
||||||
|
header = effect.name
|
||||||
|
}
|
||||||
|
add(HeaderSetting(titleString = header))
|
||||||
|
|
||||||
|
add(
|
||||||
|
IntSingleChoiceSetting(
|
||||||
|
buildSlotSelector(index, entry, files, techniques),
|
||||||
|
titleId = R.string.post_processing_effect,
|
||||||
|
choices = labels.toTypedArray(),
|
||||||
|
values = labels.indices.toList().toTypedArray()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (effect != null) {
|
||||||
|
for (uniform in effect.uniforms) {
|
||||||
|
addUniform(this, index, uniform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index > 0) {
|
||||||
|
add(
|
||||||
|
RunnableSetting(
|
||||||
|
titleId = R.string.post_processing_move_up,
|
||||||
|
isRunnable = true
|
||||||
|
) {
|
||||||
|
NativePostProcessing.move(index, -1)
|
||||||
|
NativePostProcessing.store()
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (index < chain.size - 1) {
|
||||||
|
add(
|
||||||
|
RunnableSetting(
|
||||||
|
titleId = R.string.post_processing_move_down,
|
||||||
|
isRunnable = true
|
||||||
|
) {
|
||||||
|
NativePostProcessing.move(index, 1)
|
||||||
|
NativePostProcessing.store()
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
add(
|
||||||
|
RunnableSetting(
|
||||||
|
titleId = R.string.post_processing_reset,
|
||||||
|
isRunnable = true
|
||||||
|
) {
|
||||||
|
NativePostProcessing.resetValues(index)
|
||||||
|
NativePostProcessing.store()
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
add(
|
||||||
|
RunnableSetting(
|
||||||
|
titleId = R.string.post_processing_remove,
|
||||||
|
isRunnable = true
|
||||||
|
) {
|
||||||
|
NativePostProcessing.remove(index)
|
||||||
|
NativePostProcessing.store()
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
add(
|
||||||
|
RunnableSetting(
|
||||||
|
titleId = R.string.post_processing_add,
|
||||||
|
isRunnable = true
|
||||||
|
) {
|
||||||
|
NativePostProcessing.append(files[0], techniques[0])
|
||||||
|
NativePostProcessing.store()
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildSlotSelector(
|
||||||
|
index: Int,
|
||||||
|
entry: NativePostProcessing.ChainEntry,
|
||||||
|
files: List<String>,
|
||||||
|
techniques: List<String>
|
||||||
|
): AbstractIntSetting = object : AbstractIntSetting {
|
||||||
|
override val key = "fx_slot_$index"
|
||||||
|
|
||||||
|
override fun getInt(needsGlobal: Boolean): Int {
|
||||||
|
for (i in files.indices) {
|
||||||
|
if (files[i] == entry.file && techniques[i] == entry.technique) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setInt(value: Int) {
|
||||||
|
NativePostProcessing.replace(index, files[value], techniques[value])
|
||||||
|
NativePostProcessing.store()
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val defaultValue = 0
|
||||||
|
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
|
||||||
|
override fun reset() {}
|
||||||
|
override val isRuntimeModifiable = true
|
||||||
|
override val pairedSettingKey = ""
|
||||||
|
override val isSwitchable = false
|
||||||
|
override val isSaveable = true
|
||||||
|
override var global: Boolean
|
||||||
|
get() = true
|
||||||
|
set(_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addUniform(
|
||||||
|
sl: ArrayList<SettingsItem>,
|
||||||
|
index: Int,
|
||||||
|
uniform: NativePostProcessing.Uniform
|
||||||
|
) {
|
||||||
|
if (uniform.uiType == NativePostProcessing.UI_CHECKBOX ||
|
||||||
|
uniform.kind == NativePostProcessing.KIND_BOOL
|
||||||
|
) {
|
||||||
|
sl.add(
|
||||||
|
SwitchSetting(
|
||||||
|
FxUniformBooleanSetting(index, uniform, 0),
|
||||||
|
titleString = uniform.label,
|
||||||
|
descriptionString = uniform.tooltip
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uniform.items.isNotEmpty() &&
|
||||||
|
(uniform.uiType == NativePostProcessing.UI_COMBO ||
|
||||||
|
uniform.uiType == NativePostProcessing.UI_RADIO)
|
||||||
|
) {
|
||||||
|
sl.add(
|
||||||
|
IntSingleChoiceSetting(
|
||||||
|
FxUniformChoiceSetting(index, uniform, 0),
|
||||||
|
titleString = uniform.label,
|
||||||
|
descriptionString = uniform.tooltip,
|
||||||
|
choices = uniform.items.toTypedArray(),
|
||||||
|
values = uniform.items.indices.toList().toTypedArray()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (component in 0 until uniform.components) {
|
||||||
|
var title = uniform.label
|
||||||
|
if (uniform.components > 1) {
|
||||||
|
title = uniform.label + " [" + component + "]"
|
||||||
|
}
|
||||||
|
sl.add(
|
||||||
|
SliderSetting(
|
||||||
|
FxUniformSliderSetting(index, uniform, component),
|
||||||
|
titleString = title,
|
||||||
|
descriptionString = uniform.tooltip,
|
||||||
|
min = 0,
|
||||||
|
max = uniform.steps
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(
|
add(
|
||||||
|
|||||||
@@ -383,6 +383,20 @@ class GamePropertiesFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
add(
|
||||||
|
SubmenuProperty(
|
||||||
|
R.string.post_processing,
|
||||||
|
R.string.post_processing_per_game_description,
|
||||||
|
R.drawable.ic_post_processing,
|
||||||
|
action = {
|
||||||
|
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||||
|
args.game,
|
||||||
|
Settings.MenuTag.SECTION_POST_PROCESSING
|
||||||
|
)
|
||||||
|
binding.root.findNavController().navigate(action)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if (GpuDriverHelper.isAdrenoGpu()) {
|
if (GpuDriverHelper.isAdrenoGpu()) {
|
||||||
add(
|
add(
|
||||||
|
|||||||
@@ -171,6 +171,20 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
add(
|
||||||
|
HomeSetting(
|
||||||
|
R.string.post_processing,
|
||||||
|
R.string.post_processing_description,
|
||||||
|
R.drawable.ic_post_processing,
|
||||||
|
{
|
||||||
|
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||||
|
null,
|
||||||
|
Settings.MenuTag.SECTION_POST_PROCESSING
|
||||||
|
)
|
||||||
|
binding.root.findNavController().navigate(action)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
add(
|
add(
|
||||||
HomeSetting(
|
HomeSetting(
|
||||||
R.string.lossless_scaling,
|
R.string.lossless_scaling,
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
object NativePostProcessing {
|
||||||
|
const val KIND_BOOL = 0
|
||||||
|
const val KIND_INT = 1
|
||||||
|
const val KIND_FLOAT = 2
|
||||||
|
|
||||||
|
const val UI_HIDDEN = 0
|
||||||
|
const val UI_SLIDER = 1
|
||||||
|
const val UI_DRAG = 2
|
||||||
|
const val UI_COMBO = 3
|
||||||
|
const val UI_RADIO = 4
|
||||||
|
const val UI_CHECKBOX = 5
|
||||||
|
const val UI_COLOR = 6
|
||||||
|
const val UI_INPUT_BOX = 7
|
||||||
|
|
||||||
|
external fun getCatalogJson(): String
|
||||||
|
|
||||||
|
external fun getChainJson(): String
|
||||||
|
|
||||||
|
external fun append(file: String, technique: String)
|
||||||
|
|
||||||
|
external fun replace(index: Int, file: String, technique: String)
|
||||||
|
|
||||||
|
external fun remove(index: Int)
|
||||||
|
|
||||||
|
external fun move(index: Int, delta: Int)
|
||||||
|
|
||||||
|
external fun resetValues(index: Int)
|
||||||
|
|
||||||
|
external fun getValue(index: Int, uniform: String, component: Int): Float
|
||||||
|
|
||||||
|
external fun hasValue(index: Int, uniform: String): Boolean
|
||||||
|
|
||||||
|
external fun setValue(index: Int, uniform: String, component: Int, value: Float)
|
||||||
|
|
||||||
|
external fun store()
|
||||||
|
|
||||||
|
external fun reload()
|
||||||
|
|
||||||
|
external fun getShaderDirectory(): String
|
||||||
|
|
||||||
|
data class Uniform(
|
||||||
|
val name: String,
|
||||||
|
val label: String,
|
||||||
|
val tooltip: String,
|
||||||
|
val category: String,
|
||||||
|
val kind: Int,
|
||||||
|
val uiType: Int,
|
||||||
|
val components: Int,
|
||||||
|
val min: Float,
|
||||||
|
val max: Float,
|
||||||
|
val step: Float,
|
||||||
|
val items: List<String>,
|
||||||
|
val defaults: List<Float>
|
||||||
|
) {
|
||||||
|
val steps: Int
|
||||||
|
get() {
|
||||||
|
val span = max - min
|
||||||
|
if (step <= 0f) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
val count = Math.round(span / step)
|
||||||
|
if (count < 1) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defaultAt(component: Int): Float {
|
||||||
|
if (component < defaults.size) {
|
||||||
|
return defaults[component]
|
||||||
|
}
|
||||||
|
return 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Effect(
|
||||||
|
val file: String,
|
||||||
|
val name: String,
|
||||||
|
val error: String,
|
||||||
|
val techniques: List<String>,
|
||||||
|
val uniforms: List<Uniform>
|
||||||
|
) {
|
||||||
|
val valid: Boolean
|
||||||
|
get() = error.isEmpty() && techniques.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ChainEntry(val file: String, val technique: String)
|
||||||
|
|
||||||
|
fun catalog(): List<Effect> {
|
||||||
|
val out = mutableListOf<Effect>()
|
||||||
|
val array = JSONArray(getCatalogJson())
|
||||||
|
for (i in 0 until array.length()) {
|
||||||
|
val obj = array.getJSONObject(i)
|
||||||
|
out.add(
|
||||||
|
Effect(
|
||||||
|
file = obj.optString("file"),
|
||||||
|
name = obj.optString("name"),
|
||||||
|
error = obj.optString("error"),
|
||||||
|
techniques = obj.optJSONArray("techniques").toStringList(),
|
||||||
|
uniforms = obj.optJSONArray("uniforms").toUniformList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
fun chain(): List<ChainEntry> {
|
||||||
|
val out = mutableListOf<ChainEntry>()
|
||||||
|
val array = JSONArray(getChainJson())
|
||||||
|
for (i in 0 until array.length()) {
|
||||||
|
val obj = array.getJSONObject(i)
|
||||||
|
out.add(ChainEntry(obj.optString("file"), obj.optString("technique")))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findEffect(file: String): Effect? = catalog().firstOrNull { it.file == file }
|
||||||
|
|
||||||
|
private fun JSONArray?.toStringList(): List<String> {
|
||||||
|
if (this == null) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
val out = mutableListOf<String>()
|
||||||
|
for (i in 0 until length()) {
|
||||||
|
out.add(optString(i))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JSONArray?.toFloatList(): List<Float> {
|
||||||
|
if (this == null) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
val out = mutableListOf<Float>()
|
||||||
|
for (i in 0 until length()) {
|
||||||
|
out.add(optDouble(i, 0.0).toFloat())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JSONArray?.toUniformList(): List<Uniform> {
|
||||||
|
if (this == null) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
val out = mutableListOf<Uniform>()
|
||||||
|
for (i in 0 until length()) {
|
||||||
|
val obj: JSONObject = optJSONObject(i) ?: continue
|
||||||
|
out.add(
|
||||||
|
Uniform(
|
||||||
|
name = obj.optString("name"),
|
||||||
|
label = obj.optString("label"),
|
||||||
|
tooltip = obj.optString("tooltip"),
|
||||||
|
category = obj.optString("category"),
|
||||||
|
kind = obj.optInt("kind", KIND_FLOAT),
|
||||||
|
uiType = obj.optInt("uiType", UI_HIDDEN),
|
||||||
|
components = obj.optInt("components", 1),
|
||||||
|
min = obj.optDouble("min", 0.0).toFloat(),
|
||||||
|
max = obj.optDouble("max", 1.0).toFloat(),
|
||||||
|
step = obj.optDouble("step", 0.01).toFloat(),
|
||||||
|
items = obj.optJSONArray("items").toStringList(),
|
||||||
|
defaults = obj.optJSONArray("defaults").toFloatList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,12 +17,13 @@ add_library(yuzu-android SHARED
|
|||||||
android_config.cpp
|
android_config.cpp
|
||||||
android_config.h
|
android_config.h
|
||||||
native_input.cpp
|
native_input.cpp
|
||||||
|
native_post_processing.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
||||||
|
|
||||||
target_link_libraries(yuzu-android PRIVATE audio_core common core input_common frontend_common video_core)
|
target_link_libraries(yuzu-android PRIVATE audio_core common core input_common frontend_common video_core)
|
||||||
target_link_libraries(yuzu-android PRIVATE android camera2ndk EGL glad jnigraphics log)
|
target_link_libraries(yuzu-android PRIVATE android camera2ndk EGL glad jnigraphics log GLESv2)
|
||||||
if (ARCHITECTURE_arm64)
|
if (ARCHITECTURE_arm64)
|
||||||
target_link_libraries(yuzu-android PRIVATE adrenotools)
|
target_link_libraries(yuzu-android PRIVATE adrenotools)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -1730,6 +1730,15 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_loadAmiibo(JNIEnv* env, jobject jobj,
|
|||||||
return static_cast<jint>(info);
|
return static_cast<jint>(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" JNIEXPORT void JNICALL
|
||||||
|
Java_org_yuzu_yuzu_1emu_NativeLibrary_initJvm(JNIEnv *env, jclass clazz) {
|
||||||
|
JavaVM *vm;
|
||||||
|
if (env->GetJavaVM(&vm) != JNI_OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Common::Android::Initialize(vm, env);
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_org_yuzu_yuzu_1emu_NativeLibrary_initMultiplayer(
|
Java_org_yuzu_yuzu_1emu_NativeLibrary_initMultiplayer(
|
||||||
JNIEnv* env, [[maybe_unused]] jobject obj) {
|
JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include "common/android/android_common.h"
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
#include "video_core/post_processing/fx_chain.h"
|
||||||
|
#include "video_core/post_processing/fx_effect.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
nlohmann::json SerializeUniform(const VideoCore::FxUniformDesc& uniform) {
|
||||||
|
nlohmann::json out;
|
||||||
|
out["name"] = uniform.name;
|
||||||
|
out["label"] = uniform.label;
|
||||||
|
out["tooltip"] = uniform.tooltip;
|
||||||
|
out["category"] = uniform.category;
|
||||||
|
out["kind"] = static_cast<int>(uniform.kind);
|
||||||
|
out["uiType"] = static_cast<int>(uniform.ui_type);
|
||||||
|
out["components"] = uniform.components;
|
||||||
|
out["min"] = uniform.ui_min;
|
||||||
|
out["max"] = uniform.ui_max;
|
||||||
|
out["step"] = uniform.ui_step;
|
||||||
|
out["items"] = uniform.items;
|
||||||
|
|
||||||
|
nlohmann::json defaults = nlohmann::json::array();
|
||||||
|
for (u32 i = 0; i < uniform.components; ++i) {
|
||||||
|
defaults.push_back(uniform.default_value[i]);
|
||||||
|
}
|
||||||
|
out["defaults"] = defaults;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<f32, 4> DefaultValueOf(size_t index, const std::string& uniform) {
|
||||||
|
const auto entries = VideoCore::FxChain::Instance().Entries();
|
||||||
|
if (index >= entries.size()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const VideoCore::FxEffectDesc* effect = VideoCore::FindFxEffect(entries[index].file);
|
||||||
|
if (effect == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const VideoCore::FxUniformDesc* desc = VideoCore::FindFxUniform(*effect, uniform);
|
||||||
|
if (desc == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return desc->default_value;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getCatalogJson(JNIEnv* env,
|
||||||
|
jobject obj) {
|
||||||
|
nlohmann::json out = nlohmann::json::array();
|
||||||
|
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
for (const auto& effect : VideoCore::GetFxCatalog()) {
|
||||||
|
nlohmann::json entry;
|
||||||
|
entry["file"] = effect.file;
|
||||||
|
entry["name"] = effect.name;
|
||||||
|
entry["error"] = effect.error;
|
||||||
|
entry["techniques"] = effect.techniques;
|
||||||
|
|
||||||
|
nlohmann::json uniforms = nlohmann::json::array();
|
||||||
|
for (const auto& uniform : effect.uniforms) {
|
||||||
|
uniforms.push_back(SerializeUniform(uniform));
|
||||||
|
}
|
||||||
|
entry["uniforms"] = uniforms;
|
||||||
|
|
||||||
|
out.push_back(entry);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return Common::Android::ToJString(env, out.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getChainJson(JNIEnv* env, jobject obj) {
|
||||||
|
nlohmann::json out = nlohmann::json::array();
|
||||||
|
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
for (const auto& entry : VideoCore::FxChain::Instance().Entries()) {
|
||||||
|
nlohmann::json item;
|
||||||
|
item["file"] = entry.file;
|
||||||
|
item["technique"] = entry.technique;
|
||||||
|
|
||||||
|
nlohmann::json values = nlohmann::json::object();
|
||||||
|
for (const auto& [name, value] : entry.values) {
|
||||||
|
values[name] = {value[0], value[1], value[2], value[3]};
|
||||||
|
}
|
||||||
|
item["values"] = values;
|
||||||
|
|
||||||
|
out.push_back(item);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return Common::Android::ToJString(env, out.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_append(JNIEnv* env, jobject obj,
|
||||||
|
jstring jfile, jstring jtechnique) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::FxChain::Instance().Append(Common::Android::GetJString(env, jfile),
|
||||||
|
Common::Android::GetJString(env, jtechnique));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_replace(JNIEnv* env, jobject obj,
|
||||||
|
jint index, jstring jfile,
|
||||||
|
jstring jtechnique) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::FxChain::Instance().Replace(static_cast<size_t>(index),
|
||||||
|
Common::Android::GetJString(env, jfile),
|
||||||
|
Common::Android::GetJString(env, jtechnique));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_remove(JNIEnv* env, jobject obj,
|
||||||
|
jint index) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::FxChain::Instance().Remove(static_cast<size_t>(index));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_move(JNIEnv* env, jobject obj, jint index,
|
||||||
|
jint delta) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::FxChain::Instance().Move(static_cast<size_t>(index), delta);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_resetValues(JNIEnv* env, jobject obj,
|
||||||
|
jint index) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::FxChain::Instance().ResetValues(static_cast<size_t>(index));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
jfloat Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getValue(JNIEnv* env, jobject obj,
|
||||||
|
jint index, jstring juniform,
|
||||||
|
jint component) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
if (component < 0 || component >= 4) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
const auto value = VideoCore::FxChain::Instance().GetValue(
|
||||||
|
static_cast<size_t>(index), Common::Android::GetJString(env, juniform));
|
||||||
|
return value[static_cast<size_t>(component)];
|
||||||
|
#else
|
||||||
|
return 0.0f;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_hasValue(JNIEnv* env, jobject obj,
|
||||||
|
jint index,
|
||||||
|
jstring juniform) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
return static_cast<jboolean>(VideoCore::FxChain::Instance().HasValue(
|
||||||
|
static_cast<size_t>(index), Common::Android::GetJString(env, juniform)));
|
||||||
|
#else
|
||||||
|
return static_cast<jboolean>(false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_setValue(JNIEnv* env, jobject obj,
|
||||||
|
jint index, jstring juniform,
|
||||||
|
jint component, jfloat value) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
if (component < 0 || component >= 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::string uniform = Common::Android::GetJString(env, juniform);
|
||||||
|
auto& chain = VideoCore::FxChain::Instance();
|
||||||
|
const auto slot = static_cast<size_t>(index);
|
||||||
|
|
||||||
|
auto current = chain.GetValue(slot, uniform);
|
||||||
|
if (!chain.HasValue(slot, uniform)) {
|
||||||
|
current = DefaultValueOf(slot, uniform);
|
||||||
|
}
|
||||||
|
|
||||||
|
current[static_cast<size_t>(component)] = value;
|
||||||
|
chain.SetValue(slot, uniform, current);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_store(JNIEnv* env, jobject obj) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::FxChain::Instance().StoreToSettings();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_reload(JNIEnv* env, jobject obj) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::ReloadFxCatalog();
|
||||||
|
VideoCore::FxChain::Instance().DropUnknownEntries();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getShaderDirectory(JNIEnv* env,
|
||||||
|
jobject obj) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
return Common::Android::ToJString(env, VideoCore::GetFxRootDirectory().string());
|
||||||
|
#else
|
||||||
|
return Common::Android::ToJString(env, "");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<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:fillType="evenOdd"
|
||||||
|
android:pathData="M8.5,3 L19.4,3 Q21,3 21,4.6 L21,16.5 L19.4,16.5 L19.4,4.6 L8.5,4.6 Z M5,7 L15,7 Q17,7 17,9 L17,19 Q17,21 15,21 L5,21 Q3,21 3,19 L3,9 Q3,7 5,7 Z M5.2,8.6 L14.8,8.6 Q15.4,8.6 15.4,9.2 L15.4,18.8 Q15.4,19.4 14.8,19.4 L5.2,19.4 Q4.6,19.4 4.6,18.8 L4.6,9.2 Q4.6,8.6 5.2,8.6 Z M10,10.9 A3.1,3.1 0 0 1 10,17.1 Z"/>
|
||||||
|
</vector>
|
||||||
@@ -800,7 +800,6 @@
|
|||||||
<string name="theme_mode_dark">تاریک</string>
|
<string name="theme_mode_dark">تاریک</string>
|
||||||
|
|
||||||
<!-- Audio output engines -->
|
<!-- Audio output engines -->
|
||||||
<string name="oboe">oboe</string>
|
|
||||||
<string name="cubeb">cubeb</string>
|
<string name="cubeb">cubeb</string>
|
||||||
|
|
||||||
<!-- Anisotropic filtering options -->
|
<!-- Anisotropic filtering options -->
|
||||||
|
|||||||
@@ -465,13 +465,13 @@
|
|||||||
|
|
||||||
<string-array name="outputEngineEntries">
|
<string-array name="outputEngineEntries">
|
||||||
<item>@string/auto</item>
|
<item>@string/auto</item>
|
||||||
<item>@string/oboe</item>
|
<item>@string/sdl3</item>
|
||||||
<item>@string/cubeb</item>
|
<item>@string/cubeb</item>
|
||||||
<item>@string/string_null</item>
|
<item>@string/string_null</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<integer-array name="outputEngineValues">
|
<integer-array name="outputEngineValues">
|
||||||
<item>0</item>
|
<item>0</item>
|
||||||
<item>4</item>
|
<item>2</item>
|
||||||
<item>1</item>
|
<item>1</item>
|
||||||
<item>3</item>
|
<item>3</item>
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|||||||
@@ -298,6 +298,18 @@
|
|||||||
<string name="gpu_driver_fetcher">GPU driver fetcher</string>
|
<string name="gpu_driver_fetcher">GPU driver fetcher</string>
|
||||||
<string name="gpu_driver_manager">GPU driver manager</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="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
||||||
|
<string name="post_processing">Post-Processing Effects</string>
|
||||||
|
<string name="post_processing_description">ReShade FX effects applied after rendering</string>
|
||||||
|
<string name="post_processing_per_game_description">Configure the effect chain for this game</string>
|
||||||
|
<string name="post_processing_effect">Effect</string>
|
||||||
|
<string name="post_processing_add">Add effect</string>
|
||||||
|
<string name="post_processing_remove">Remove</string>
|
||||||
|
<string name="post_processing_move_up">Move up</string>
|
||||||
|
<string name="post_processing_move_down">Move down</string>
|
||||||
|
<string name="post_processing_reset">Reset to defaults</string>
|
||||||
|
<string name="post_processing_reload">Reload from disk</string>
|
||||||
|
<string name="post_processing_reload_description">Rescan the shader folder for .fx files</string>
|
||||||
|
<string name="post_processing_empty">No effects found. Place .fx files in this folder:</string>
|
||||||
<string name="frame_gen">Frame generation</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_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_description">Insert interpolated frames between rendered ones using Lossless Scaling. Forces FIFO presentation while enabled.</string>
|
||||||
@@ -1240,7 +1252,7 @@
|
|||||||
<string name="theme_mode_dark">Dark</string>
|
<string name="theme_mode_dark">Dark</string>
|
||||||
|
|
||||||
<!-- Audio output engines -->
|
<!-- Audio output engines -->
|
||||||
<string name="oboe" translatable="false">oboe</string>
|
<string name="sdl3" translatable="false">SDL3</string>
|
||||||
<string name="cubeb" translatable="false">cubeb</string>
|
<string name="cubeb" translatable="false">cubeb</string>
|
||||||
|
|
||||||
<!-- Anisotropic filtering options -->
|
<!-- Anisotropic filtering options -->
|
||||||
|
|||||||
@@ -238,20 +238,11 @@ if (ENABLE_CUBEB)
|
|||||||
target_compile_definitions(audio_core PRIVATE HAVE_CUBEB=1)
|
target_compile_definitions(audio_core PRIVATE HAVE_CUBEB=1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ANDROID)
|
target_sources(audio_core PRIVATE
|
||||||
target_sources(audio_core PRIVATE
|
sink/sdl3_sink.cpp
|
||||||
sink/oboe_sink.cpp
|
sink/sdl3_sink.h)
|
||||||
sink/oboe_sink.h)
|
|
||||||
|
|
||||||
target_link_libraries(audio_core PRIVATE oboe)
|
target_link_libraries(audio_core PRIVATE SDL3::SDL3)
|
||||||
target_compile_definitions(audio_core PUBLIC HAVE_OBOE)
|
target_compile_definitions(audio_core PRIVATE HAVE_SDL3)
|
||||||
else()
|
|
||||||
target_sources(audio_core PRIVATE
|
|
||||||
sink/sdl3_sink.cpp
|
|
||||||
sink/sdl3_sink.h)
|
|
||||||
|
|
||||||
target_link_libraries(audio_core PRIVATE SDL3::SDL3)
|
|
||||||
target_compile_definitions(audio_core PRIVATE HAVE_SDL3)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
create_target_directory_groups(audio_core)
|
create_target_directory_groups(audio_core)
|
||||||
|
|||||||
@@ -1,230 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include <span>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <oboe/Oboe.h>
|
|
||||||
|
|
||||||
#include "audio_core/common/common.h"
|
|
||||||
#include "audio_core/sink/oboe_sink.h"
|
|
||||||
#include "audio_core/sink/sink_stream.h"
|
|
||||||
#include "common/logging.h"
|
|
||||||
#include "common/scope_exit.h"
|
|
||||||
#include "core/core.h"
|
|
||||||
|
|
||||||
namespace AudioCore::Sink {
|
|
||||||
|
|
||||||
class OboeSinkStream final : public SinkStream,
|
|
||||||
public oboe::AudioStreamDataCallback,
|
|
||||||
public oboe::AudioStreamErrorCallback {
|
|
||||||
public:
|
|
||||||
explicit OboeSinkStream(Core::System& system_, StreamType type_, const std::string& name_,
|
|
||||||
u32 system_channels_)
|
|
||||||
: SinkStream(system_, type_) {
|
|
||||||
name = name_;
|
|
||||||
system_channels = system_channels_;
|
|
||||||
|
|
||||||
this->OpenStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
~OboeSinkStream() override {
|
|
||||||
LOG_INFO(Audio_Sink, "Destroyed Oboe stream");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Finalize() override {
|
|
||||||
this->Stop();
|
|
||||||
m_stream.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Start(bool resume = false) override {
|
|
||||||
if (!m_stream || !paused) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
paused = false;
|
|
||||||
|
|
||||||
if (m_stream->start() != oboe::Result::OK) {
|
|
||||||
LOG_CRITICAL(Audio_Sink, "Error starting Oboe stream");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Stop() override {
|
|
||||||
if (!m_stream || paused) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->SignalPause();
|
|
||||||
|
|
||||||
if (m_stream->stop() != oboe::Result::OK) {
|
|
||||||
LOG_CRITICAL(Audio_Sink, "Error stopping Oboe stream");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
static s32 QueryChannelCount(oboe::Direction direction) {
|
|
||||||
std::shared_ptr<oboe::AudioStream> temp_stream;
|
|
||||||
oboe::AudioStreamBuilder builder;
|
|
||||||
|
|
||||||
const auto result = ConfigureBuilder(builder, direction)->openStream(temp_stream);
|
|
||||||
if (result == oboe::Result::OK) {
|
|
||||||
return temp_stream->getChannelCount() >= 6 ? 6 : 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_ERROR(Audio_Sink, "Failed to open {} stream. Using default channel count 2",
|
|
||||||
direction == oboe::Direction::Output ? "output" : "input");
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
oboe::DataCallbackResult onAudioReady(oboe::AudioStream*, void* audio_data,
|
|
||||||
s32 num_buffer_frames) override {
|
|
||||||
const size_t num_channels = this->GetDeviceChannels();
|
|
||||||
const size_t frame_size = num_channels;
|
|
||||||
const size_t num_frames = static_cast<size_t>(num_buffer_frames);
|
|
||||||
|
|
||||||
if (type == StreamType::In) {
|
|
||||||
std::span<const s16> input_buffer{reinterpret_cast<const s16*>(audio_data),
|
|
||||||
num_frames * frame_size};
|
|
||||||
this->ProcessAudioIn(input_buffer, num_frames);
|
|
||||||
} else {
|
|
||||||
std::span<s16> output_buffer{reinterpret_cast<s16*>(audio_data),
|
|
||||||
num_frames * frame_size};
|
|
||||||
this->ProcessAudioOutAndRender(output_buffer, num_frames);
|
|
||||||
}
|
|
||||||
|
|
||||||
return oboe::DataCallbackResult::Continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onErrorAfterClose(oboe::AudioStream*, oboe::Result) override {
|
|
||||||
LOG_INFO(Audio_Sink, "Audio stream closed, reinitializing");
|
|
||||||
|
|
||||||
if (this->OpenStream()) {
|
|
||||||
m_stream->start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static oboe::AudioStreamBuilder* ConfigureBuilder(oboe::AudioStreamBuilder& builder,
|
|
||||||
oboe::Direction direction) {
|
|
||||||
// TODO: investigate callback delay issues when using AAudio
|
|
||||||
return builder.setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
|
||||||
->setAudioApi(oboe::AudioApi::OpenSLES)
|
|
||||||
->setDirection(direction)
|
|
||||||
->setSampleRate(TargetSampleRate)
|
|
||||||
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
|
|
||||||
->setFormat(oboe::AudioFormat::I16)
|
|
||||||
->setFormatConversionAllowed(true)
|
|
||||||
->setUsage(oboe::Usage::Game)
|
|
||||||
->setBufferCapacityInFrames(TargetSampleCount * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OpenStream() {
|
|
||||||
const auto direction = [&]() {
|
|
||||||
switch (type) {
|
|
||||||
case StreamType::In:
|
|
||||||
return oboe::Direction::Input;
|
|
||||||
case StreamType::Out:
|
|
||||||
case StreamType::Render:
|
|
||||||
return oboe::Direction::Output;
|
|
||||||
default:
|
|
||||||
ASSERT(false);
|
|
||||||
return oboe::Direction::Output;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
|
|
||||||
const auto expected_channels = QueryChannelCount(direction);
|
|
||||||
const auto expected_mask = [&]() {
|
|
||||||
switch (expected_channels) {
|
|
||||||
case 1:
|
|
||||||
return oboe::ChannelMask::Mono;
|
|
||||||
case 2:
|
|
||||||
return oboe::ChannelMask::Stereo;
|
|
||||||
case 6:
|
|
||||||
return oboe::ChannelMask::CM5Point1;
|
|
||||||
default:
|
|
||||||
ASSERT(false);
|
|
||||||
return oboe::ChannelMask::Unspecified;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
|
|
||||||
oboe::AudioStreamBuilder builder;
|
|
||||||
const auto result = ConfigureBuilder(builder, direction)
|
|
||||||
->setChannelCount(expected_channels)
|
|
||||||
->setChannelMask(expected_mask)
|
|
||||||
->setChannelConversionAllowed(true)
|
|
||||||
->setDataCallback(this)
|
|
||||||
->setErrorCallback(this)
|
|
||||||
->openStream(m_stream);
|
|
||||||
ASSERT(result == oboe::Result::OK);
|
|
||||||
return result == oboe::Result::OK && this->SetStreamProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SetStreamProperties() {
|
|
||||||
ASSERT(m_stream);
|
|
||||||
|
|
||||||
m_stream->setBufferSizeInFrames(TargetSampleCount * 2);
|
|
||||||
device_channels = m_stream->getChannelCount();
|
|
||||||
|
|
||||||
const auto sample_rate = m_stream->getSampleRate();
|
|
||||||
const auto buffer_capacity = m_stream->getBufferCapacityInFrames();
|
|
||||||
const auto stream_backend =
|
|
||||||
m_stream->getAudioApi() == oboe::AudioApi::AAudio ? "AAudio" : "OpenSLES";
|
|
||||||
|
|
||||||
LOG_INFO(Audio_Sink, "Opened Oboe {} stream with {} channels sample rate {} capacity {}",
|
|
||||||
stream_backend, device_channels, sample_rate, buffer_capacity);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<oboe::AudioStream> m_stream{};
|
|
||||||
};
|
|
||||||
|
|
||||||
OboeSink::OboeSink() {
|
|
||||||
// TODO: This is not generally knowable
|
|
||||||
// The channel count is distinct based on direction and can change
|
|
||||||
device_channels = OboeSinkStream::QueryChannelCount(oboe::Direction::Output);
|
|
||||||
}
|
|
||||||
|
|
||||||
OboeSink::~OboeSink() = default;
|
|
||||||
|
|
||||||
SinkStream* OboeSink::AcquireSinkStream(Core::System& system, u32 system_channels,
|
|
||||||
const std::string& name, StreamType type) {
|
|
||||||
SinkStreamPtr& stream = sink_streams.emplace_back(
|
|
||||||
std::make_unique<OboeSinkStream>(system, type, name, system_channels));
|
|
||||||
|
|
||||||
return stream.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OboeSink::CloseStream(SinkStream* to_remove) {
|
|
||||||
sink_streams.remove_if([&](auto& stream) { return stream.get() == to_remove; });
|
|
||||||
}
|
|
||||||
|
|
||||||
void OboeSink::CloseStreams() {
|
|
||||||
sink_streams.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
f32 OboeSink::GetDeviceVolume() const {
|
|
||||||
if (sink_streams.empty()) {
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sink_streams.front()->GetDeviceVolume();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OboeSink::SetDeviceVolume(f32 volume) {
|
|
||||||
for (auto& stream : sink_streams) {
|
|
||||||
stream->SetDeviceVolume(volume);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OboeSink::SetSystemVolume(f32 volume) {
|
|
||||||
for (auto& stream : sink_streams) {
|
|
||||||
stream->SetSystemVolume(volume);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace AudioCore::Sink
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <list>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "audio_core/sink/sink.h"
|
|
||||||
|
|
||||||
namespace Core {
|
|
||||||
class System;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace AudioCore::Sink {
|
|
||||||
class SinkStream;
|
|
||||||
|
|
||||||
class OboeSink final : public Sink {
|
|
||||||
public:
|
|
||||||
explicit OboeSink();
|
|
||||||
~OboeSink() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new sink stream.
|
|
||||||
*
|
|
||||||
* @param system - Core system.
|
|
||||||
* @param system_channels - Number of channels the audio system expects.
|
|
||||||
* May differ from the device's channel count.
|
|
||||||
* @param name - Name of this stream.
|
|
||||||
* @param type - Type of this stream, render/in/out.
|
|
||||||
*
|
|
||||||
* @return A pointer to the created SinkStream
|
|
||||||
*/
|
|
||||||
SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
|
|
||||||
const std::string& name, StreamType type) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close a given stream.
|
|
||||||
*
|
|
||||||
* @param stream - The stream to close.
|
|
||||||
*/
|
|
||||||
void CloseStream(SinkStream* stream) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close all streams.
|
|
||||||
*/
|
|
||||||
void CloseStreams() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the device volume. Set from calls to the IAudioDevice service.
|
|
||||||
*
|
|
||||||
* @return Volume of the device.
|
|
||||||
*/
|
|
||||||
f32 GetDeviceVolume() const override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the device volume. Set from calls to the IAudioDevice service.
|
|
||||||
*
|
|
||||||
* @param volume - New volume of the device.
|
|
||||||
*/
|
|
||||||
void SetDeviceVolume(f32 volume) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the system volume. Comes from the audio system using this stream.
|
|
||||||
*
|
|
||||||
* @param volume - New volume of the system.
|
|
||||||
*/
|
|
||||||
void SetSystemVolume(f32 volume) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// List of streams managed by this sink
|
|
||||||
std::list<SinkStreamPtr> sink_streams{};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace AudioCore::Sink
|
|
||||||
@@ -10,9 +10,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "audio_core/sink/sink_details.h"
|
#include "audio_core/sink/sink_details.h"
|
||||||
#ifdef HAVE_OBOE
|
|
||||||
#include "audio_core/sink/oboe_sink.h"
|
|
||||||
#endif
|
|
||||||
#ifdef HAVE_CUBEB
|
#ifdef HAVE_CUBEB
|
||||||
#include "audio_core/sink/cubeb_sink.h"
|
#include "audio_core/sink/cubeb_sink.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -43,24 +40,8 @@ struct SinkDetails {
|
|||||||
/// SuitableFn is_suitable; // REVERTED FOR LatencyFn latency ABOVE - DIABLO 3 FIX
|
/// SuitableFn is_suitable; // REVERTED FOR LatencyFn latency ABOVE - DIABLO 3 FIX
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOTE TO PROBABLY FIX LATER FOR ANDROID - the return value "0u" for the first HAVE_OBOE
|
|
||||||
// section below was just copied from the null section so there's a somewhat valid value
|
|
||||||
// being returned, since the previous "true" value probably isn't compatible with the
|
|
||||||
// previous EA-3833 code. (HAVE_OBOE was introduced in a later release.) Eventually need
|
|
||||||
// to change "0u" for something else directly from the oboe_sink.cpp functions.
|
|
||||||
|
|
||||||
// sink_details is ordered in terms of desirability, with the best choice at the top.
|
// sink_details is ordered in terms of desirability, with the best choice at the top.
|
||||||
constexpr SinkDetails sink_details[] = {
|
constexpr SinkDetails sink_details[] = {
|
||||||
#ifdef HAVE_OBOE
|
|
||||||
SinkDetails{
|
|
||||||
Settings::AudioEngine::Oboe,
|
|
||||||
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
|
||||||
return std::make_unique<OboeSink>();
|
|
||||||
},
|
|
||||||
[](bool capture) { return std::vector<std::string>{"Default"}; },
|
|
||||||
[]() { return 0u; },
|
|
||||||
},
|
|
||||||
#endif
|
|
||||||
#ifdef HAVE_CUBEB
|
#ifdef HAVE_CUBEB
|
||||||
SinkDetails{
|
SinkDetails{
|
||||||
Settings::AudioEngine::Cubeb,
|
Settings::AudioEngine::Cubeb,
|
||||||
|
|||||||
@@ -236,11 +236,7 @@ else()
|
|||||||
target_link_libraries(common PUBLIC Boost::headers)
|
target_link_libraries(common PUBLIC Boost::headers)
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(common PRIVATE OpenSSL::SSL)
|
target_link_libraries(common PRIVATE OpenSSL::SSL)
|
||||||
target_link_libraries(common PUBLIC Boost::filesystem httplib::httplib nlohmann_json::nlohmann_json)
|
target_link_libraries(common PUBLIC Boost::filesystem Boost::context httplib::httplib nlohmann_json::nlohmann_json)
|
||||||
if (NOT EMSCRIPTEN)
|
|
||||||
# Emscripten is: "bring your own implementation", boost doesn't add upstream support sadly
|
|
||||||
target_link_libraries(common PRIVATE Boost::context)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (lz4_ADDED)
|
if (lz4_ADDED)
|
||||||
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)
|
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "android_common.h"
|
#include "android_common.h"
|
||||||
@@ -51,7 +51,7 @@ jstring ToJString(JNIEnv* env, std::u16string_view str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double GetJDouble(JNIEnv* env, jobject jdouble) {
|
double GetJDouble(JNIEnv* env, jobject jdouble) {
|
||||||
return env->GetDoubleField(jdouble, GetDoubleValueField());
|
return env->CallDoubleMethod(jdouble, GetDoubleValueMethod());
|
||||||
}
|
}
|
||||||
|
|
||||||
jobject ToJDouble(JNIEnv* env, double value) {
|
jobject ToJDouble(JNIEnv* env, double value) {
|
||||||
@@ -59,7 +59,7 @@ jobject ToJDouble(JNIEnv* env, double value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s32 GetJInteger(JNIEnv* env, jobject jinteger) {
|
s32 GetJInteger(JNIEnv* env, jobject jinteger) {
|
||||||
return env->GetIntField(jinteger, GetIntegerValueField());
|
return env->CallIntMethod(jinteger, GetIntegerValueMethod());
|
||||||
}
|
}
|
||||||
|
|
||||||
jobject ToJInteger(JNIEnv* env, s32 value) {
|
jobject ToJInteger(JNIEnv* env, s32 value) {
|
||||||
@@ -67,7 +67,7 @@ jobject ToJInteger(JNIEnv* env, s32 value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool GetJBoolean(JNIEnv* env, jobject jboolean) {
|
bool GetJBoolean(JNIEnv* env, jobject jboolean) {
|
||||||
return env->GetBooleanField(jboolean, GetBooleanValueField());
|
return env->CallBooleanMethod(jboolean, GetBooleanValueMethod());
|
||||||
}
|
}
|
||||||
|
|
||||||
jobject ToJBoolean(JNIEnv* env, bool value) {
|
jobject ToJBoolean(JNIEnv* env, bool value) {
|
||||||
|
|||||||
+188
-192
@@ -63,15 +63,15 @@ static jfieldID s_patch_title_id_field;
|
|||||||
|
|
||||||
static jclass s_double_class;
|
static jclass s_double_class;
|
||||||
static jmethodID s_double_constructor;
|
static jmethodID s_double_constructor;
|
||||||
static jfieldID s_double_value_field;
|
static jmethodID s_double_value_method;
|
||||||
|
|
||||||
static jclass s_integer_class;
|
static jclass s_integer_class;
|
||||||
static jmethodID s_integer_constructor;
|
static jmethodID s_integer_constructor;
|
||||||
static jfieldID s_integer_value_field;
|
static jmethodID s_integer_value_method;
|
||||||
|
|
||||||
static jclass s_boolean_class;
|
static jclass s_boolean_class;
|
||||||
static jmethodID s_boolean_constructor;
|
static jmethodID s_boolean_constructor;
|
||||||
static jfieldID s_boolean_value_field;
|
static jmethodID s_boolean_value_method;
|
||||||
|
|
||||||
static jclass s_player_input_class;
|
static jclass s_player_input_class;
|
||||||
static jmethodID s_player_input_constructor;
|
static jmethodID s_player_input_constructor;
|
||||||
@@ -103,7 +103,6 @@ static jmethodID s_clear_chat;
|
|||||||
static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
|
static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
|
||||||
|
|
||||||
namespace Common::Android {
|
namespace Common::Android {
|
||||||
|
|
||||||
JNIEnv *GetEnvForThread() {
|
JNIEnv *GetEnvForThread() {
|
||||||
thread_local static struct OwnedEnv {
|
thread_local static struct OwnedEnv {
|
||||||
OwnedEnv() {
|
OwnedEnv() {
|
||||||
@@ -299,8 +298,8 @@ namespace Common::Android {
|
|||||||
return s_double_constructor;
|
return s_double_constructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
jfieldID GetDoubleValueField() {
|
jmethodID GetDoubleValueMethod() {
|
||||||
return s_double_value_field;
|
return s_double_value_method;
|
||||||
}
|
}
|
||||||
|
|
||||||
jclass GetIntegerClass() {
|
jclass GetIntegerClass() {
|
||||||
@@ -311,8 +310,8 @@ namespace Common::Android {
|
|||||||
return s_integer_constructor;
|
return s_integer_constructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
jfieldID GetIntegerValueField() {
|
jmethodID GetIntegerValueMethod() {
|
||||||
return s_integer_value_field;
|
return s_integer_value_method;
|
||||||
}
|
}
|
||||||
|
|
||||||
jclass GetBooleanClass() {
|
jclass GetBooleanClass() {
|
||||||
@@ -323,8 +322,8 @@ namespace Common::Android {
|
|||||||
return s_boolean_constructor;
|
return s_boolean_constructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
jfieldID GetBooleanValueField() {
|
jmethodID GetBooleanValueMethod() {
|
||||||
return s_boolean_value_field;
|
return s_boolean_value_method;
|
||||||
}
|
}
|
||||||
|
|
||||||
jclass GetPlayerInputClass() {
|
jclass GetPlayerInputClass() {
|
||||||
@@ -429,188 +428,6 @@ namespace Common::Android {
|
|||||||
|
|
||||||
jint InitFFmpegOnLoad(JavaVM *vm);
|
jint InitFFmpegOnLoad(JavaVM *vm);
|
||||||
|
|
||||||
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
|
||||||
s_java_vm = vm;
|
|
||||||
InitFFmpegOnLoad(vm);
|
|
||||||
|
|
||||||
JNIEnv *env;
|
|
||||||
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION) != JNI_OK)
|
|
||||||
return JNI_ERR;
|
|
||||||
|
|
||||||
// Initialize Java classes
|
|
||||||
const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary");
|
|
||||||
s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class));
|
|
||||||
s_disk_cache_progress_class = reinterpret_cast<jclass>(env->NewGlobalRef(
|
|
||||||
env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress")));
|
|
||||||
s_load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass(
|
|
||||||
"org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage")));
|
|
||||||
|
|
||||||
const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir");
|
|
||||||
s_game_dir_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_dir_class));
|
|
||||||
s_game_dir_constructor = env->GetMethodID(game_dir_class, "<init>",
|
|
||||||
"(Ljava/lang/String;Z)V");
|
|
||||||
env->DeleteLocalRef(game_dir_class);
|
|
||||||
|
|
||||||
// Initialize methods
|
|
||||||
s_exit_emulation_activity =
|
|
||||||
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
|
|
||||||
s_disk_cache_load_progress =
|
|
||||||
env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V");
|
|
||||||
s_copy_to_storage = env->GetStaticMethodID(s_native_library_class, "copyFileToStorage",
|
|
||||||
"(Ljava/lang/String;Ljava/lang/String;)Z");
|
|
||||||
s_file_exists = env->GetStaticMethodID(s_native_library_class, "exists",
|
|
||||||
"(Ljava/lang/String;)Z");
|
|
||||||
s_file_extension = env->GetStaticMethodID(s_native_library_class, "getFileExtension",
|
|
||||||
"(Ljava/lang/String;)Ljava/lang/String;");
|
|
||||||
s_on_emulation_started =
|
|
||||||
env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V");
|
|
||||||
s_on_emulation_stopped =
|
|
||||||
env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V");
|
|
||||||
s_on_program_changed =
|
|
||||||
env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V");
|
|
||||||
|
|
||||||
const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game");
|
|
||||||
s_game_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_class));
|
|
||||||
s_game_constructor = env->GetMethodID(game_class, "<init>",
|
|
||||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
|
|
||||||
"String;Ljava/lang/String;Ljava/lang/String;Z)V");
|
|
||||||
s_game_title_field = env->GetFieldID(game_class, "title", "Ljava/lang/String;");
|
|
||||||
s_game_path_field = env->GetFieldID(game_class, "path", "Ljava/lang/String;");
|
|
||||||
s_game_program_id_field = env->GetFieldID(game_class, "programId", "Ljava/lang/String;");
|
|
||||||
s_game_developer_field = env->GetFieldID(game_class, "developer", "Ljava/lang/String;");
|
|
||||||
s_game_version_field = env->GetFieldID(game_class, "version", "Ljava/lang/String;");
|
|
||||||
s_game_is_homebrew_field = env->GetFieldID(game_class, "isHomebrew", "Z");
|
|
||||||
env->DeleteLocalRef(game_class);
|
|
||||||
|
|
||||||
const jclass string_class = env->FindClass("java/lang/String");
|
|
||||||
s_string_class = reinterpret_cast<jclass>(env->NewGlobalRef(string_class));
|
|
||||||
env->DeleteLocalRef(string_class);
|
|
||||||
|
|
||||||
const jclass pair_class = env->FindClass("kotlin/Pair");
|
|
||||||
s_pair_class = reinterpret_cast<jclass>(env->NewGlobalRef(pair_class));
|
|
||||||
s_pair_constructor =
|
|
||||||
env->GetMethodID(pair_class, "<init>", "(Ljava/lang/Object;Ljava/lang/Object;)V");
|
|
||||||
s_pair_first_field = env->GetFieldID(pair_class, "first", "Ljava/lang/Object;");
|
|
||||||
s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;");
|
|
||||||
env->DeleteLocalRef(pair_class);
|
|
||||||
|
|
||||||
const jclass overlay_control_data_class =
|
|
||||||
env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData");
|
|
||||||
s_overlay_control_data_class =
|
|
||||||
reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
|
|
||||||
s_overlay_control_data_constructor =
|
|
||||||
env->GetMethodID(overlay_control_data_class, "<init>",
|
|
||||||
"(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;F)V");
|
|
||||||
s_overlay_control_data_id_field =
|
|
||||||
env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
|
|
||||||
s_overlay_control_data_enabled_field =
|
|
||||||
env->GetFieldID(overlay_control_data_class, "enabled", "Z");
|
|
||||||
s_overlay_control_data_landscape_position_field =
|
|
||||||
env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;");
|
|
||||||
s_overlay_control_data_portrait_position_field =
|
|
||||||
env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
|
|
||||||
s_overlay_control_data_foldable_position_field =
|
|
||||||
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
|
|
||||||
s_overlay_control_data_individual_scale_field =
|
|
||||||
env->GetFieldID(overlay_control_data_class, "individualScale", "F");
|
|
||||||
env->DeleteLocalRef(overlay_control_data_class);
|
|
||||||
|
|
||||||
const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch");
|
|
||||||
s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
|
|
||||||
s_patch_constructor = env->GetMethodID(
|
|
||||||
patch_class, "<init>",
|
|
||||||
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JI)V");
|
|
||||||
s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z");
|
|
||||||
s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
|
|
||||||
s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;");
|
|
||||||
s_patch_type_field = env->GetFieldID(patch_class, "type", "I");
|
|
||||||
s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;");
|
|
||||||
s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;");
|
|
||||||
env->DeleteLocalRef(patch_class);
|
|
||||||
|
|
||||||
const jclass double_class = env->FindClass("java/lang/Double");
|
|
||||||
s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class));
|
|
||||||
s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V");
|
|
||||||
s_double_value_field = env->GetFieldID(double_class, "value", "D");
|
|
||||||
env->DeleteLocalRef(double_class);
|
|
||||||
|
|
||||||
const jclass int_class = env->FindClass("java/lang/Integer");
|
|
||||||
s_integer_class = reinterpret_cast<jclass>(env->NewGlobalRef(int_class));
|
|
||||||
s_integer_constructor = env->GetMethodID(int_class, "<init>", "(I)V");
|
|
||||||
s_integer_value_field = env->GetFieldID(int_class, "value", "I");
|
|
||||||
env->DeleteLocalRef(int_class);
|
|
||||||
|
|
||||||
const jclass boolean_class = env->FindClass("java/lang/Boolean");
|
|
||||||
s_boolean_class = reinterpret_cast<jclass>(env->NewGlobalRef(boolean_class));
|
|
||||||
s_boolean_constructor = env->GetMethodID(boolean_class, "<init>", "(Z)V");
|
|
||||||
s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z");
|
|
||||||
env->DeleteLocalRef(boolean_class);
|
|
||||||
|
|
||||||
const jclass player_input_class =
|
|
||||||
env->FindClass("org/yuzu/yuzu_emu/features/input/model/PlayerInput");
|
|
||||||
s_player_input_class = reinterpret_cast<jclass>(env->NewGlobalRef(player_input_class));
|
|
||||||
s_player_input_constructor = env->GetMethodID(
|
|
||||||
player_input_class, "<init>",
|
|
||||||
"(Z[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;ZIJJJJLjava/lang/String;Z)V");
|
|
||||||
s_player_input_connected_field = env->GetFieldID(player_input_class, "connected", "Z");
|
|
||||||
s_player_input_buttons_field =
|
|
||||||
env->GetFieldID(player_input_class, "buttons", "[Ljava/lang/String;");
|
|
||||||
s_player_input_analogs_field =
|
|
||||||
env->GetFieldID(player_input_class, "analogs", "[Ljava/lang/String;");
|
|
||||||
s_player_input_motions_field =
|
|
||||||
env->GetFieldID(player_input_class, "motions", "[Ljava/lang/String;");
|
|
||||||
s_player_input_vibration_enabled_field =
|
|
||||||
env->GetFieldID(player_input_class, "vibrationEnabled", "Z");
|
|
||||||
s_player_input_vibration_strength_field =
|
|
||||||
env->GetFieldID(player_input_class, "vibrationStrength", "I");
|
|
||||||
s_player_input_body_color_left_field =
|
|
||||||
env->GetFieldID(player_input_class, "bodyColorLeft", "J");
|
|
||||||
s_player_input_body_color_right_field =
|
|
||||||
env->GetFieldID(player_input_class, "bodyColorRight", "J");
|
|
||||||
s_player_input_button_color_left_field =
|
|
||||||
env->GetFieldID(player_input_class, "buttonColorLeft", "J");
|
|
||||||
s_player_input_button_color_right_field =
|
|
||||||
env->GetFieldID(player_input_class, "buttonColorRight", "J");
|
|
||||||
s_player_input_profile_name_field =
|
|
||||||
env->GetFieldID(player_input_class, "profileName", "Ljava/lang/String;");
|
|
||||||
s_player_input_use_system_vibrator_field =
|
|
||||||
env->GetFieldID(player_input_class, "useSystemVibrator", "Z");
|
|
||||||
env->DeleteLocalRef(player_input_class);
|
|
||||||
|
|
||||||
const jclass yuzu_input_device_interface =
|
|
||||||
env->FindClass("org/yuzu/yuzu_emu/features/input/YuzuInputDevice");
|
|
||||||
s_yuzu_input_device_interface =
|
|
||||||
reinterpret_cast<jclass>(env->NewGlobalRef(yuzu_input_device_interface));
|
|
||||||
s_yuzu_input_device_get_name =
|
|
||||||
env->GetMethodID(yuzu_input_device_interface, "getName", "()Ljava/lang/String;");
|
|
||||||
s_yuzu_input_device_get_guid =
|
|
||||||
env->GetMethodID(yuzu_input_device_interface, "getGUID", "()Ljava/lang/String;");
|
|
||||||
s_yuzu_input_device_get_port = env->GetMethodID(yuzu_input_device_interface, "getPort",
|
|
||||||
"()I");
|
|
||||||
s_yuzu_input_device_get_supports_vibration =
|
|
||||||
env->GetMethodID(yuzu_input_device_interface, "getSupportsVibration", "()Z");
|
|
||||||
s_yuzu_input_device_vibrate = env->GetMethodID(yuzu_input_device_interface, "vibrate",
|
|
||||||
"(F)V");
|
|
||||||
s_yuzu_input_device_get_axes =
|
|
||||||
env->GetMethodID(yuzu_input_device_interface, "getAxes", "()[Ljava/lang/Integer;");
|
|
||||||
s_yuzu_input_device_has_keys =
|
|
||||||
env->GetMethodID(yuzu_input_device_interface, "hasKeys", "([I)[Z");
|
|
||||||
env->DeleteLocalRef(yuzu_input_device_interface);
|
|
||||||
s_add_netplay_message = env->GetStaticMethodID(s_native_library_class, "addNetPlayMessage",
|
|
||||||
"(ILjava/lang/String;)V");
|
|
||||||
s_clear_chat = env->GetStaticMethodID(s_native_library_class, "clearChat", "()V");
|
|
||||||
|
|
||||||
|
|
||||||
// Initialize Android Storage
|
|
||||||
Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
|
|
||||||
|
|
||||||
// Initialize applets
|
|
||||||
Common::Android::SoftwareKeyboard::InitJNI(env);
|
|
||||||
Common::Android::WebBrowser::InitJNI(env);
|
|
||||||
|
|
||||||
return JNI_VERSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
void JNI_OnUnload(JavaVM *vm, void *reserved) {
|
void JNI_OnUnload(JavaVM *vm, void *reserved) {
|
||||||
JNIEnv *env;
|
JNIEnv *env;
|
||||||
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION) != JNI_OK) {
|
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION) != JNI_OK) {
|
||||||
@@ -645,4 +462,183 @@ namespace Common::Android {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void Initialize(JavaVM* vm, JNIEnv *env) {
|
||||||
|
s_java_vm = vm;
|
||||||
|
InitFFmpegOnLoad(vm);
|
||||||
|
|
||||||
|
if (env->ExceptionCheck()) {
|
||||||
|
env->ExceptionClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Java classes
|
||||||
|
const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary");
|
||||||
|
s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class));
|
||||||
|
s_disk_cache_progress_class = reinterpret_cast<jclass>(env->NewGlobalRef(
|
||||||
|
env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress")));
|
||||||
|
s_load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass(
|
||||||
|
"org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage")));
|
||||||
|
|
||||||
|
const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir");
|
||||||
|
s_game_dir_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_dir_class));
|
||||||
|
s_game_dir_constructor = env->GetMethodID(game_dir_class, "<init>",
|
||||||
|
"(Ljava/lang/String;Z)V");
|
||||||
|
env->DeleteLocalRef(game_dir_class);
|
||||||
|
|
||||||
|
// Initialize methods
|
||||||
|
s_exit_emulation_activity =
|
||||||
|
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
|
||||||
|
s_disk_cache_load_progress =
|
||||||
|
env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V");
|
||||||
|
s_copy_to_storage = env->GetStaticMethodID(s_native_library_class, "copyFileToStorage",
|
||||||
|
"(Ljava/lang/String;Ljava/lang/String;)Z");
|
||||||
|
s_file_exists = env->GetStaticMethodID(s_native_library_class, "exists",
|
||||||
|
"(Ljava/lang/String;)Z");
|
||||||
|
s_file_extension = env->GetStaticMethodID(s_native_library_class, "getFileExtension",
|
||||||
|
"(Ljava/lang/String;)Ljava/lang/String;");
|
||||||
|
s_on_emulation_started =
|
||||||
|
env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V");
|
||||||
|
s_on_emulation_stopped =
|
||||||
|
env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V");
|
||||||
|
s_on_program_changed =
|
||||||
|
env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V");
|
||||||
|
|
||||||
|
const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game");
|
||||||
|
s_game_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_class));
|
||||||
|
s_game_constructor = env->GetMethodID(game_class, "<init>",
|
||||||
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
|
||||||
|
"String;Ljava/lang/String;Ljava/lang/String;Z)V");
|
||||||
|
s_game_title_field = env->GetFieldID(game_class, "title", "Ljava/lang/String;");
|
||||||
|
s_game_path_field = env->GetFieldID(game_class, "path", "Ljava/lang/String;");
|
||||||
|
s_game_program_id_field = env->GetFieldID(game_class, "programId", "Ljava/lang/String;");
|
||||||
|
s_game_developer_field = env->GetFieldID(game_class, "developer", "Ljava/lang/String;");
|
||||||
|
s_game_version_field = env->GetFieldID(game_class, "version", "Ljava/lang/String;");
|
||||||
|
s_game_is_homebrew_field = env->GetFieldID(game_class, "isHomebrew", "Z");
|
||||||
|
env->DeleteLocalRef(game_class);
|
||||||
|
|
||||||
|
const jclass string_class = env->FindClass("java/lang/String");
|
||||||
|
s_string_class = reinterpret_cast<jclass>(env->NewGlobalRef(string_class));
|
||||||
|
env->DeleteLocalRef(string_class);
|
||||||
|
|
||||||
|
const jclass pair_class = env->FindClass("kotlin/Pair");
|
||||||
|
s_pair_class = reinterpret_cast<jclass>(env->NewGlobalRef(pair_class));
|
||||||
|
s_pair_constructor =
|
||||||
|
env->GetMethodID(pair_class, "<init>", "(Ljava/lang/Object;Ljava/lang/Object;)V");
|
||||||
|
s_pair_first_field = env->GetFieldID(pair_class, "first", "Ljava/lang/Object;");
|
||||||
|
s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;");
|
||||||
|
env->DeleteLocalRef(pair_class);
|
||||||
|
|
||||||
|
const jclass overlay_control_data_class =
|
||||||
|
env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData");
|
||||||
|
s_overlay_control_data_class =
|
||||||
|
reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
|
||||||
|
s_overlay_control_data_constructor =
|
||||||
|
env->GetMethodID(overlay_control_data_class, "<init>",
|
||||||
|
"(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;F)V");
|
||||||
|
s_overlay_control_data_id_field =
|
||||||
|
env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
|
||||||
|
s_overlay_control_data_enabled_field =
|
||||||
|
env->GetFieldID(overlay_control_data_class, "enabled", "Z");
|
||||||
|
s_overlay_control_data_landscape_position_field =
|
||||||
|
env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;");
|
||||||
|
s_overlay_control_data_portrait_position_field =
|
||||||
|
env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
|
||||||
|
s_overlay_control_data_foldable_position_field =
|
||||||
|
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
|
||||||
|
s_overlay_control_data_individual_scale_field =
|
||||||
|
env->GetFieldID(overlay_control_data_class, "individualScale", "F");
|
||||||
|
env->DeleteLocalRef(overlay_control_data_class);
|
||||||
|
|
||||||
|
const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch");
|
||||||
|
s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
|
||||||
|
s_patch_constructor = env->GetMethodID(
|
||||||
|
patch_class, "<init>",
|
||||||
|
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JI)V");
|
||||||
|
s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z");
|
||||||
|
s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
|
||||||
|
s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;");
|
||||||
|
s_patch_type_field = env->GetFieldID(patch_class, "type", "I");
|
||||||
|
s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;");
|
||||||
|
s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;");
|
||||||
|
env->DeleteLocalRef(patch_class);
|
||||||
|
|
||||||
|
const jclass double_class = env->FindClass("java/lang/Double");
|
||||||
|
s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class));
|
||||||
|
s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V");
|
||||||
|
s_double_value_method = env->GetMethodID(double_class, "doubleValue", "()D");
|
||||||
|
env->DeleteLocalRef(double_class);
|
||||||
|
|
||||||
|
const jclass int_class = env->FindClass("java/lang/Integer");
|
||||||
|
s_integer_class = reinterpret_cast<jclass>(env->NewGlobalRef(int_class));
|
||||||
|
s_integer_constructor = env->GetMethodID(int_class, "<init>", "(I)V");
|
||||||
|
s_integer_value_method = env->GetMethodID(int_class, "intValue", "()I");
|
||||||
|
env->DeleteLocalRef(int_class);
|
||||||
|
|
||||||
|
const jclass boolean_class = env->FindClass("java/lang/Boolean");
|
||||||
|
s_boolean_class = reinterpret_cast<jclass>(env->NewGlobalRef(boolean_class));
|
||||||
|
s_boolean_constructor = env->GetMethodID(boolean_class, "<init>", "(Z)V");
|
||||||
|
s_boolean_value_method = env->GetMethodID(boolean_class, "booleanValue", "()Z");
|
||||||
|
env->DeleteLocalRef(boolean_class);
|
||||||
|
|
||||||
|
const jclass player_input_class =
|
||||||
|
env->FindClass("org/yuzu/yuzu_emu/features/input/model/PlayerInput");
|
||||||
|
s_player_input_class = reinterpret_cast<jclass>(env->NewGlobalRef(player_input_class));
|
||||||
|
s_player_input_constructor = env->GetMethodID(
|
||||||
|
player_input_class, "<init>",
|
||||||
|
"(Z[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;ZIJJJJLjava/lang/String;Z)V");
|
||||||
|
s_player_input_connected_field = env->GetFieldID(player_input_class, "connected", "Z");
|
||||||
|
s_player_input_buttons_field =
|
||||||
|
env->GetFieldID(player_input_class, "buttons", "[Ljava/lang/String;");
|
||||||
|
s_player_input_analogs_field =
|
||||||
|
env->GetFieldID(player_input_class, "analogs", "[Ljava/lang/String;");
|
||||||
|
s_player_input_motions_field =
|
||||||
|
env->GetFieldID(player_input_class, "motions", "[Ljava/lang/String;");
|
||||||
|
s_player_input_vibration_enabled_field =
|
||||||
|
env->GetFieldID(player_input_class, "vibrationEnabled", "Z");
|
||||||
|
s_player_input_vibration_strength_field =
|
||||||
|
env->GetFieldID(player_input_class, "vibrationStrength", "I");
|
||||||
|
s_player_input_body_color_left_field =
|
||||||
|
env->GetFieldID(player_input_class, "bodyColorLeft", "J");
|
||||||
|
s_player_input_body_color_right_field =
|
||||||
|
env->GetFieldID(player_input_class, "bodyColorRight", "J");
|
||||||
|
s_player_input_button_color_left_field =
|
||||||
|
env->GetFieldID(player_input_class, "buttonColorLeft", "J");
|
||||||
|
s_player_input_button_color_right_field =
|
||||||
|
env->GetFieldID(player_input_class, "buttonColorRight", "J");
|
||||||
|
s_player_input_profile_name_field =
|
||||||
|
env->GetFieldID(player_input_class, "profileName", "Ljava/lang/String;");
|
||||||
|
s_player_input_use_system_vibrator_field =
|
||||||
|
env->GetFieldID(player_input_class, "useSystemVibrator", "Z");
|
||||||
|
env->DeleteLocalRef(player_input_class);
|
||||||
|
|
||||||
|
const jclass yuzu_input_device_interface =
|
||||||
|
env->FindClass("org/yuzu/yuzu_emu/features/input/YuzuInputDevice");
|
||||||
|
s_yuzu_input_device_interface =
|
||||||
|
reinterpret_cast<jclass>(env->NewGlobalRef(yuzu_input_device_interface));
|
||||||
|
s_yuzu_input_device_get_name =
|
||||||
|
env->GetMethodID(yuzu_input_device_interface, "getName", "()Ljava/lang/String;");
|
||||||
|
s_yuzu_input_device_get_guid =
|
||||||
|
env->GetMethodID(yuzu_input_device_interface, "getGUID", "()Ljava/lang/String;");
|
||||||
|
s_yuzu_input_device_get_port = env->GetMethodID(yuzu_input_device_interface, "getPort",
|
||||||
|
"()I");
|
||||||
|
s_yuzu_input_device_get_supports_vibration =
|
||||||
|
env->GetMethodID(yuzu_input_device_interface, "getSupportsVibration", "()Z");
|
||||||
|
s_yuzu_input_device_vibrate = env->GetMethodID(yuzu_input_device_interface, "vibrate",
|
||||||
|
"(F)V");
|
||||||
|
s_yuzu_input_device_get_axes =
|
||||||
|
env->GetMethodID(yuzu_input_device_interface, "getAxes", "()[Ljava/lang/Integer;");
|
||||||
|
s_yuzu_input_device_has_keys =
|
||||||
|
env->GetMethodID(yuzu_input_device_interface, "hasKeys", "([I)[Z");
|
||||||
|
env->DeleteLocalRef(yuzu_input_device_interface);
|
||||||
|
s_add_netplay_message = env->GetStaticMethodID(s_native_library_class, "addNetPlayMessage",
|
||||||
|
"(ILjava/lang/String;)V");
|
||||||
|
s_clear_chat = env->GetStaticMethodID(s_native_library_class, "clearChat", "()V");
|
||||||
|
|
||||||
|
// Initialize Android Storage
|
||||||
|
Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
|
||||||
|
|
||||||
|
// Initialize applets
|
||||||
|
Common::Android::SoftwareKeyboard::InitJNI(env);
|
||||||
|
Common::Android::WebBrowser::InitJNI(env);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Common::Android
|
} // namespace Common::Android
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Common::Android {
|
namespace Common::Android {
|
||||||
|
|
||||||
|
void Initialize(JavaVM* vm, JNIEnv *env);
|
||||||
JNIEnv* GetEnvForThread();
|
JNIEnv* GetEnvForThread();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,15 +81,15 @@ jfieldID GetPatchTitleIdField();
|
|||||||
|
|
||||||
jclass GetDoubleClass();
|
jclass GetDoubleClass();
|
||||||
jmethodID GetDoubleConstructor();
|
jmethodID GetDoubleConstructor();
|
||||||
jfieldID GetDoubleValueField();
|
jmethodID GetDoubleValueMethod();
|
||||||
|
|
||||||
jclass GetIntegerClass();
|
jclass GetIntegerClass();
|
||||||
jmethodID GetIntegerConstructor();
|
jmethodID GetIntegerConstructor();
|
||||||
jfieldID GetIntegerValueField();
|
jmethodID GetIntegerValueMethod();
|
||||||
|
|
||||||
jclass GetBooleanClass();
|
jclass GetBooleanClass();
|
||||||
jmethodID GetBooleanConstructor();
|
jmethodID GetBooleanConstructor();
|
||||||
jfieldID GetBooleanValueField();
|
jmethodID GetBooleanValueMethod();
|
||||||
|
|
||||||
jclass GetPlayerInputClass();
|
jclass GetPlayerInputClass();
|
||||||
jmethodID GetPlayerInputConstructor();
|
jmethodID GetPlayerInputConstructor();
|
||||||
|
|||||||
+11
-84
@@ -11,12 +11,7 @@
|
|||||||
#include "common/fiber.h"
|
#include "common/fiber.h"
|
||||||
#include "common/virtual_buffer.h"
|
#include "common/virtual_buffer.h"
|
||||||
|
|
||||||
#ifdef __EMSCRIPTEN__
|
|
||||||
#include <emscripten.h>
|
|
||||||
#include <emscripten/fiber.h>
|
|
||||||
#else
|
|
||||||
#include <boost/context/detail/fcontext.hpp>
|
#include <boost/context/detail/fcontext.hpp>
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
@@ -27,103 +22,36 @@ constexpr size_t DEFAULT_STACK_SIZE = 512 * 4096;
|
|||||||
#endif
|
#endif
|
||||||
constexpr u32 CANARY_VALUE = 0xDEADBEEF;
|
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 {
|
struct Fiber::FiberImpl {
|
||||||
FiberImpl() {}
|
FiberImpl() {}
|
||||||
|
|
||||||
u32 canary_1 = CANARY_VALUE;
|
u32 canary_1 = CANARY_VALUE;
|
||||||
std::array<u8, DEFAULT_STACK_SIZE> stack{};
|
std::array<u8, DEFAULT_STACK_SIZE> stack{};
|
||||||
|
std::array<u8, DEFAULT_STACK_SIZE> rewind_stack{};
|
||||||
u32 canary_2 = CANARY_VALUE;
|
u32 canary_2 = CANARY_VALUE;
|
||||||
|
|
||||||
boost::context::detail::fcontext_t context{};
|
boost::context::detail::fcontext_t context{};
|
||||||
|
boost::context::detail::fcontext_t rewind_context{};
|
||||||
|
|
||||||
std::mutex guard;
|
std::mutex guard;
|
||||||
std::function<void()> entry_point;
|
std::function<void()> entry_point;
|
||||||
|
std::function<void()> rewind_point;
|
||||||
std::shared_ptr<Fiber> previous_fiber;
|
std::shared_ptr<Fiber> previous_fiber;
|
||||||
|
|
||||||
u8* stack_limit = nullptr;
|
u8* stack_limit = nullptr;
|
||||||
|
u8* rewind_stack_limit = nullptr;
|
||||||
bool is_thread_fiber = false;
|
bool is_thread_fiber = false;
|
||||||
bool released = 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>()} {
|
Fiber::Fiber(std::function<void()>&& entry_point_func) : impl{std::make_unique<FiberImpl>()} {
|
||||||
impl->entry_point = std::move(entry_point_func);
|
impl->entry_point = std::move(entry_point_func);
|
||||||
impl->stack_limit = impl->stack.data();
|
impl->stack_limit = impl->stack.data();
|
||||||
|
impl->rewind_stack_limit = impl->rewind_stack.data();
|
||||||
u8* stack_base = impl->stack_limit + DEFAULT_STACK_SIZE;
|
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 {
|
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);
|
auto* fiber = static_cast<Fiber*>(transfer.data);
|
||||||
@@ -144,7 +72,7 @@ Fiber::~Fiber() {
|
|||||||
if (!impl->released) {
|
if (!impl->released) {
|
||||||
// Make sure the Fiber is not being used
|
// Make sure the Fiber is not being used
|
||||||
const bool locked = impl->guard.try_lock();
|
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) {
|
if (locked) {
|
||||||
impl->guard.unlock();
|
impl->guard.unlock();
|
||||||
}
|
}
|
||||||
@@ -152,7 +80,7 @@ Fiber::~Fiber() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Fiber::Exit() {
|
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) {
|
if (impl->is_thread_fiber) {
|
||||||
impl->guard.unlock();
|
impl->guard.unlock();
|
||||||
impl->released = true;
|
impl->released = true;
|
||||||
@@ -182,6 +110,5 @@ std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
|
|||||||
fiber->impl->is_thread_fiber = true;
|
fiber->impl->is_thread_fiber = true;
|
||||||
return fiber;
|
return fiber;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
@@ -45,6 +45,7 @@ public:
|
|||||||
/// Fiber 'from' must be the currently running fiber.
|
/// Fiber 'from' must be the currently running fiber.
|
||||||
static void YieldTo(std::weak_ptr<Fiber> weak_from, Fiber& to);
|
static void YieldTo(std::weak_ptr<Fiber> weak_from, Fiber& to);
|
||||||
[[nodiscard]] static std::shared_ptr<Fiber> ThreadToFiber();
|
[[nodiscard]] static std::shared_ptr<Fiber> ThreadToFiber();
|
||||||
|
void SetRewindPoint(std::function<void()>&& rewind_func);
|
||||||
/// Only call from main thread's fiber
|
/// Only call from main thread's fiber
|
||||||
void Exit();
|
void Exit();
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#define LOSSLESS_DIR "lossless"
|
#define LOSSLESS_DIR "lossless"
|
||||||
#define NAND_DIR "nand"
|
#define NAND_DIR "nand"
|
||||||
#define PLAY_TIME_DIR "play_time"
|
#define PLAY_TIME_DIR "play_time"
|
||||||
|
#define POST_SHADER_DIR "post_shaders"
|
||||||
#define SCREENSHOTS_DIR "screenshots"
|
#define SCREENSHOTS_DIR "screenshots"
|
||||||
#define SDMC_DIR "sdmc"
|
#define SDMC_DIR "sdmc"
|
||||||
#define SHADER_DIR "shader"
|
#define SHADER_DIR "shader"
|
||||||
|
|||||||
@@ -136,11 +136,6 @@ public:
|
|||||||
eden_path = GetDataDirectory("XDG_DATA_HOME") / EDEN_DIR;
|
eden_path = GetDataDirectory("XDG_DATA_HOME") / EDEN_DIR;
|
||||||
eden_path_cache = GetDataDirectory("XDG_CACHE_HOME") / EDEN_DIR;
|
eden_path_cache = GetDataDirectory("XDG_CACHE_HOME") / EDEN_DIR;
|
||||||
eden_path_config = GetDataDirectory("XDG_CONFIG_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 {
|
} else {
|
||||||
eden_path_cache = eden_path / CACHE_DIR;
|
eden_path_cache = eden_path / CACHE_DIR;
|
||||||
eden_path_config = eden_path / CONFIG_DIR;
|
eden_path_config = eden_path / CONFIG_DIR;
|
||||||
@@ -165,6 +160,7 @@ public:
|
|||||||
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
|
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
|
||||||
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
|
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
|
||||||
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
|
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
|
||||||
|
GenerateEdenPath(EdenPath::PostShaderDir, eden_path / POST_SHADER_DIR);
|
||||||
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
|
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
|
||||||
GenerateEdenPath(EdenPath::ScreenshotsDir, eden_path / SCREENSHOTS_DIR);
|
GenerateEdenPath(EdenPath::ScreenshotsDir, eden_path / SCREENSHOTS_DIR);
|
||||||
GenerateEdenPath(EdenPath::SDMCDir, eden_path / SDMC_DIR);
|
GenerateEdenPath(EdenPath::SDMCDir, eden_path / SDMC_DIR);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ enum class EdenPath {
|
|||||||
LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
|
LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
|
||||||
NANDDir, // Where the emulated NAND is stored.
|
NANDDir, // Where the emulated NAND is stored.
|
||||||
PlayTimeDir, // Where play time data is stored.
|
PlayTimeDir, // Where play time data is stored.
|
||||||
|
PostShaderDir, // Where user post-processing shaders are stored.
|
||||||
SaveDir, // Where save data is stored.
|
SaveDir, // Where save data is stored.
|
||||||
ScreenshotsDir, // Where yuzu screenshots are stored.
|
ScreenshotsDir, // Where yuzu screenshots are stored.
|
||||||
SDMCDir, // Where the emulated SDMC is stored.
|
SDMCDir, // Where the emulated SDMC is stored.
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ private:
|
|||||||
::Common::unordered_map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset
|
::Common::unordered_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
|
// None of the luxuries of POSIX, all of the suffering
|
||||||
// For managarm: see https://github.com/managarm/managarm/issues/1370
|
// For managarm: see https://github.com/managarm/managarm/issues/1370
|
||||||
#else // ^^^ Windows ^^^ vvv POSIX vvv
|
#else // ^^^ Windows ^^^ vvv POSIX vvv
|
||||||
@@ -689,7 +689,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
|
|||||||
: backing_size(backing_size_)
|
: backing_size(backing_size_)
|
||||||
, virtual_size(virtual_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");
|
LOG_WARNING(HW_Memory, "Platform doesn't support fastmem");
|
||||||
fallback_buffer.emplace(backing_size);
|
fallback_buffer.emplace(backing_size);
|
||||||
backing_base = fallback_buffer->data();
|
backing_base = fallback_buffer->data();
|
||||||
@@ -723,7 +723,7 @@ HostMemory::HostMemory(HostMemory&&) noexcept = default;
|
|||||||
HostMemory& HostMemory::operator=(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) {
|
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(virtual_offset % PageAlignment == 0);
|
||||||
ASSERT(host_offset % PageAlignment == 0);
|
ASSERT(host_offset % PageAlignment == 0);
|
||||||
ASSERT(length % 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) {
|
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(virtual_offset % PageAlignment == 0);
|
||||||
ASSERT(length % PageAlignment == 0);
|
ASSERT(length % PageAlignment == 0);
|
||||||
ASSERT(virtual_offset + length <= virtual_size);
|
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) {
|
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(virtual_offset % PageAlignment == 0);
|
||||||
ASSERT(length % PageAlignment == 0);
|
ASSERT(length % PageAlignment == 0);
|
||||||
ASSERT(virtual_offset + length <= virtual_size);
|
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() {
|
void HostMemory::EnableDirectMappedAddress() {
|
||||||
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
|
#if !(defined(__OPENORBIS__) || defined(__managarm__))
|
||||||
if (impl) {
|
if (impl) {
|
||||||
impl->EnableDirectMappedAddress();
|
impl->EnableDirectMappedAddress();
|
||||||
virtual_size += reinterpret_cast<uintptr_t>(virtual_base);
|
virtual_size += reinterpret_cast<uintptr_t>(virtual_base);
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ private:
|
|||||||
size_t backing_size{};
|
size_t backing_size{};
|
||||||
size_t virtual_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
|
// Low level handler for the platform dependent memory routines
|
||||||
class Impl;
|
class Impl;
|
||||||
std::unique_ptr<Impl> impl;
|
std::unique_ptr<Impl> impl;
|
||||||
|
|||||||
@@ -388,6 +388,14 @@ struct Values {
|
|||||||
true,
|
true,
|
||||||
true};
|
true};
|
||||||
|
|
||||||
|
SwitchableSetting<std::string> post_shader_chain{linkage,
|
||||||
|
std::string(),
|
||||||
|
"post_shader_chain",
|
||||||
|
Category::Renderer,
|
||||||
|
Specialization::Default,
|
||||||
|
true,
|
||||||
|
true};
|
||||||
|
|
||||||
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
|
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
|
||||||
Specialization::Default, true, false};
|
Specialization::Default, true, false};
|
||||||
|
|
||||||
|
|||||||
@@ -92,14 +92,14 @@ struct EnumMetadata {
|
|||||||
// AudioEngine must be specified discretely due to having existing but slightly different
|
// AudioEngine must be specified discretely due to having existing but slightly different
|
||||||
// canonicalizations
|
// canonicalizations
|
||||||
// TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id
|
// TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id
|
||||||
enum class AudioEngine : u32 { Auto, Cubeb, Sdl3, Null, Oboe, };
|
enum class AudioEngine : u32 { Auto, Cubeb, Sdl3, Null, };
|
||||||
template<>
|
template<>
|
||||||
inline std::vector<std::pair<std::string_view, AudioEngine>> EnumMetadata<AudioEngine>::Canonicalizations() {
|
inline std::vector<std::pair<std::string_view, AudioEngine>> EnumMetadata<AudioEngine>::Canonicalizations() {
|
||||||
return {
|
return {
|
||||||
{"auto", AudioEngine::Auto},
|
{"auto", AudioEngine::Auto},
|
||||||
{"cubeb", AudioEngine::Cubeb},
|
{"cubeb", AudioEngine::Cubeb},
|
||||||
{"sdl3", AudioEngine::Sdl3},
|
{"sdl3", AudioEngine::Sdl3},
|
||||||
{"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe},
|
{"null", AudioEngine::Null},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/// @brief This is just a sufficiently large number that is more than the number of other enums declared here
|
/// @brief This is just a sufficiently large number that is more than the number of other enums declared here
|
||||||
@@ -113,7 +113,7 @@ inline AudioEngine EnumMetadata<AudioEngine>::GetFirst() {
|
|||||||
}
|
}
|
||||||
template<>
|
template<>
|
||||||
inline AudioEngine EnumMetadata<AudioEngine>::GetLast() {
|
inline AudioEngine EnumMetadata<AudioEngine>::GetLast() {
|
||||||
return AudioEngine::Oboe;
|
return AudioEngine::Null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ENUM(AudioMode, Mono, Stereo, Surround);
|
ENUM(AudioMode, Mono, Stereo, Surround);
|
||||||
|
|||||||
@@ -490,8 +490,6 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
|||||||
LOG_DEBUG(Common, "Could not set thread nice value to {}: {}", nice_value,
|
LOG_DEBUG(Common, "Could not set thread nice value to {}: {}", nice_value,
|
||||||
GetLastErrorMsg());
|
GetLastErrorMsg());
|
||||||
}
|
}
|
||||||
#elif defined(__EMSCRIPTEN__)
|
|
||||||
// TODO: set priority?
|
|
||||||
#else
|
#else
|
||||||
const s32 max_prio = sched_get_priority_max(SCHED_OTHER);
|
const s32 max_prio = sched_get_priority_max(SCHED_OTHER);
|
||||||
const s32 min_prio = sched_get_priority_min(SCHED_OTHER);
|
const s32 min_prio = sched_get_priority_min(SCHED_OTHER);
|
||||||
@@ -536,8 +534,6 @@ void SetCurrentThreadName(const char* name) {
|
|||||||
// See for reference
|
// See for reference
|
||||||
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
|
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
|
||||||
(void)name;
|
(void)name;
|
||||||
#elif defined(__EMSCRIPTEN__)
|
|
||||||
// TODO: set thread name?
|
|
||||||
#else
|
#else
|
||||||
pthread_setname_np(pthread_self(), name);
|
pthread_setname_np(pthread_self(), name);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+5
-12
@@ -38,6 +38,10 @@ add_library(core STATIC
|
|||||||
debugger/debugger.cpp
|
debugger/debugger.cpp
|
||||||
debugger/debugger.h
|
debugger/debugger.h
|
||||||
debugger/debugger_interface.h
|
debugger/debugger_interface.h
|
||||||
|
debugger/gdbstub.cpp
|
||||||
|
debugger/gdbstub.h
|
||||||
|
debugger/gdbstub_arch.cpp
|
||||||
|
debugger/gdbstub_arch.h
|
||||||
device_memory.cpp
|
device_memory.cpp
|
||||||
device_memory.h
|
device_memory.h
|
||||||
device_memory_manager.h
|
device_memory_manager.h
|
||||||
@@ -1162,14 +1166,6 @@ add_library(core STATIC
|
|||||||
tools/freezer.h
|
tools/freezer.h
|
||||||
tools/renderdoc.cpp
|
tools/renderdoc.cpp
|
||||||
tools/renderdoc.h)
|
tools/renderdoc.h)
|
||||||
if (NOT 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)
|
if (ENABLE_WIFI_SCAN)
|
||||||
target_sources(core PRIVATE internal_network/wifi_scanner.cpp)
|
target_sources(core PRIVATE internal_network/wifi_scanner.cpp)
|
||||||
@@ -1213,10 +1209,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)
|
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
|
||||||
|
|
||||||
if (BOOST_NO_HEADERS)
|
if (BOOST_NO_HEADERS)
|
||||||
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::crc)
|
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc)
|
||||||
if (NOT EMSCRIPTEN)
|
|
||||||
target_link_libraries(core PUBLIC Boost::asio Boost::process)
|
|
||||||
endif()
|
|
||||||
else()
|
else()
|
||||||
target_link_libraries(core PUBLIC Boost::headers)
|
target_link_libraries(core PUBLIC Boost::headers)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -6,20 +6,19 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifndef __EMSCRIPTEN__
|
|
||||||
#include <dynarmic/interface/halt_reason.h>
|
#include <dynarmic/interface/halt_reason.h>
|
||||||
#endif
|
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
#ifndef __EMSCRIPTEN__
|
|
||||||
constexpr Dynarmic::HaltReason StepThread = Dynarmic::HaltReason::Step;
|
constexpr Dynarmic::HaltReason StepThread = Dynarmic::HaltReason::Step;
|
||||||
constexpr Dynarmic::HaltReason DataAbort = Dynarmic::HaltReason::MemoryAbort;
|
constexpr Dynarmic::HaltReason DataAbort = Dynarmic::HaltReason::MemoryAbort;
|
||||||
constexpr Dynarmic::HaltReason BreakLoop = Dynarmic::HaltReason::UserDefined2;
|
constexpr Dynarmic::HaltReason BreakLoop = Dynarmic::HaltReason::UserDefined2;
|
||||||
constexpr Dynarmic::HaltReason SupervisorCall = Dynarmic::HaltReason::UserDefined3;
|
constexpr Dynarmic::HaltReason SupervisorCall = Dynarmic::HaltReason::UserDefined3;
|
||||||
constexpr Dynarmic::HaltReason InstructionBreakpoint = Dynarmic::HaltReason::UserDefined4;
|
constexpr Dynarmic::HaltReason InstructionBreakpoint = Dynarmic::HaltReason::UserDefined4;
|
||||||
constexpr Dynarmic::HaltReason PrefetchAbort = Dynarmic::HaltReason::UserDefined6;
|
constexpr Dynarmic::HaltReason PrefetchAbort = Dynarmic::HaltReason::UserDefined6;
|
||||||
|
|
||||||
constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) {
|
constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) {
|
||||||
static_assert(u64(HaltReason::StepThread) == u64(StepThread));
|
static_assert(u64(HaltReason::StepThread) == u64(StepThread));
|
||||||
static_assert(u64(HaltReason::DataAbort) == u64(DataAbort));
|
static_assert(u64(HaltReason::DataAbort) == u64(DataAbort));
|
||||||
@@ -29,6 +28,5 @@ constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) {
|
|||||||
static_assert(u64(HaltReason::PrefetchAbort) == u64(PrefetchAbort));
|
static_assert(u64(HaltReason::PrefetchAbort) == u64(PrefetchAbort));
|
||||||
return HaltReason(hr);
|
return HaltReason(hr);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|||||||
@@ -6,54 +6,30 @@
|
|||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
|
|
||||||
// TODO: gdb stub compat with emscripten?
|
|
||||||
#else
|
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <boost/version.hpp>
|
#include <boost/version.hpp>
|
||||||
|
|
||||||
#if BOOST_VERSION > 108400 && (!defined(_WINDOWS) && !defined(__ANDROID__)) || defined(YUZU_BOOST_v1)
|
#if BOOST_VERSION > 108400 && (!defined(_WINDOWS) && !defined(__ANDROID__)) || defined(YUZU_BOOST_v1)
|
||||||
#define USE_BOOST_v1
|
#define USE_BOOST_v1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_BOOST_v1
|
#ifdef USE_BOOST_v1
|
||||||
#include <boost/process/v1/async_pipe.hpp>
|
#include <boost/process/v1/async_pipe.hpp>
|
||||||
#else
|
#else
|
||||||
#include <boost/process/async_pipe.hpp>
|
#include <boost/process/async_pipe.hpp>
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "common/logging.h"
|
#include "common/logging.h"
|
||||||
#include "common/polyfill_thread.h"
|
#include "common/polyfill_thread.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/debugger/debugger.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/debugger_interface.h"
|
||||||
#include "core/debugger/gdbstub.h"
|
#include "core/debugger/gdbstub.h"
|
||||||
#endif
|
|
||||||
#include "core/hle/kernel/global_scheduler_context.h"
|
#include "core/hle/kernel/global_scheduler_context.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
#include "core/hle/kernel/k_scheduler.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>
|
template <typename Readable, typename Buffer, typename Callback>
|
||||||
static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
|
static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
|
||||||
static_assert(std::is_trivial_v<Buffer>);
|
static_assert(std::is_trivial_v<Buffer>);
|
||||||
@@ -382,7 +358,7 @@ private:
|
|||||||
|
|
||||||
ConnectionState(boost::asio::ip::tcp::socket&& client_socket_, async_pipe signal_pipe_, Kernel::KernelCore& kernel)
|
ConnectionState(boost::asio::ip::tcp::socket&& client_socket_, async_pipe signal_pipe_, Kernel::KernelCore& kernel)
|
||||||
: client_socket{std::move(client_socket_)}
|
: client_socket{std::move(client_socket_)}
|
||||||
, signal_pipe{signal_pipe_}
|
, signal_pipe{std::move(signal_pipe_)}
|
||||||
, active_thread{kernel, nullptr}
|
, active_thread{kernel, nullptr}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@@ -424,4 +400,3 @@ void Debugger::NotifyShutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -7,14 +7,8 @@
|
|||||||
#include <random>
|
#include <random>
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
|
|
||||||
#include "core/arm/exclusive_monitor.h"
|
|
||||||
#ifndef __EMSCRIPTEN__
|
|
||||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||||
#include "core/arm/dynarmic/dynarmic_exclusive_monitor.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/core.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
#include "core/hle/kernel/k_scoped_resource_reservation.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_thread_queue.h"
|
||||||
#include "core/hle/kernel/k_worker_task_manager.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
|
#ifdef HAS_NCE
|
||||||
#include "core/arm/nce/arm_nce.h"
|
#include "core/arm/nce/arm_nce.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -1025,6 +1021,9 @@ Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) {
|
|||||||
|
|
||||||
// Suspend for debug, if we should.
|
// Suspend for debug, if we should.
|
||||||
if (kernel.System().DebuggerEnabled()) {
|
if (kernel.System().DebuggerEnabled()) {
|
||||||
|
LOG_INFO(Debug_GDBStub,
|
||||||
|
"GDB stub enabled; suspending guest process until a debugger continues execution on port {}",
|
||||||
|
Settings::values.gdbstub_port.GetValue());
|
||||||
main_thread->RequestSuspend(kernel, SuspendType::Debug);
|
main_thread->RequestSuspend(kernel, SuspendType::Debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1304,11 +1303,9 @@ void KProcess::LoadModule(KernelCore& kernel, CodeSet code_set, KProcessAddress
|
|||||||
}
|
}
|
||||||
|
|
||||||
void KProcess::InitializeInterfaces(KernelCore& kernel) {
|
void KProcess::InitializeInterfaces(KernelCore& kernel) {
|
||||||
#ifdef __EMSCRIPTEN__
|
|
||||||
ASSERT(false && "unimplemented");
|
|
||||||
#else
|
|
||||||
m_exclusive_monitor =
|
m_exclusive_monitor =
|
||||||
Core::MakeExclusiveMonitor(this->GetMemory(), Core::Hardware::NUM_CPU_CORES);
|
Core::MakeExclusiveMonitor(this->GetMemory(), Core::Hardware::NUM_CPU_CORES);
|
||||||
|
|
||||||
#ifdef HAS_NCE
|
#ifdef HAS_NCE
|
||||||
if (this->IsApplication() && Settings::IsNceEnabled()) {
|
if (this->IsApplication() && Settings::IsNceEnabled()) {
|
||||||
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++)
|
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++)
|
||||||
@@ -1328,7 +1325,6 @@ void KProcess::InitializeInterfaces(KernelCore& kernel) {
|
|||||||
static_cast<Core::DynarmicExclusiveMonitor&>(*m_exclusive_monitor), i);
|
static_cast<Core::DynarmicExclusiveMonitor&>(*m_exclusive_monitor), i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KProcess::InsertWatchpoint(KernelCore& kernel, KProcessAddress addr, u64 size, DebugWatchpointType type) {
|
bool KProcess::InsertWatchpoint(KernelCore& kernel, KProcessAddress addr, u64 size, DebugWatchpointType type) {
|
||||||
|
|||||||
@@ -88,22 +88,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
|
// 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
|
// 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},
|
{"audio", &Audio::LoopProcess},
|
||||||
{"FS", &FileSystem::LoopProcess},
|
{"FS", &FileSystem::LoopProcess},
|
||||||
{"ldn", &LDN::LoopProcess},
|
{"ldn", &LDN::LoopProcess},
|
||||||
{"nvservices", &Nvidia::LoopProcess},
|
{"nvservices", &Nvidia::LoopProcess},
|
||||||
{"bsdsocket", &Sockets::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(std::string(e.first), [&system, f = e.second] { f(system); }).detach();
|
||||||
kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach();
|
kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach();
|
||||||
#endif
|
|
||||||
// Avoid cold clones of lambdas -- succintly
|
// Avoid cold clones of lambdas -- succintly
|
||||||
for (auto const& e : std::vector<std::pair<std::string_view, void (*)(Core::System&)>>{
|
for (auto const& e : std::vector<std::pair<std::string_view, void (*)(Core::System&)>>{
|
||||||
{"sm", &SM::LoopProcess},
|
{"sm", &SM::LoopProcess},
|
||||||
@@ -127,10 +120,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
|||||||
{"glue", &Glue::LoopProcess},
|
{"glue", &Glue::LoopProcess},
|
||||||
{"grc", &GRC::LoopProcess},
|
{"grc", &GRC::LoopProcess},
|
||||||
{"hid", &HID::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},
|
{"jit", &JIT::LoopProcess},
|
||||||
#endif
|
|
||||||
{"lbl", &LBL::LoopProcess},
|
{"lbl", &LBL::LoopProcess},
|
||||||
{"Loader", &LDR::LoopProcess},
|
{"Loader", &LDR::LoopProcess},
|
||||||
{"LogManager.Prod", &LM::LoopProcess},
|
{"LogManager.Prod", &LM::LoopProcess},
|
||||||
|
|||||||
@@ -28,10 +28,7 @@ enum class NpadMcuState : u32 {
|
|||||||
struct NpadMcuHolder {
|
struct NpadMcuHolder {
|
||||||
NpadMcuState state;
|
NpadMcuState state;
|
||||||
INSERT_PADDING_BYTES(0x4);
|
INSERT_PADDING_BYTES(0x4);
|
||||||
union {
|
IAbstractedPad* abstracted_pad;
|
||||||
IAbstractedPad* abstracted_pad;
|
|
||||||
u64 abstracted_pad_raw;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
static_assert(sizeof(NpadMcuHolder) == 0x10, "NpadMcuHolder is an invalid size");
|
static_assert(sizeof(NpadMcuHolder) == 0x10, "NpadMcuHolder is an invalid size");
|
||||||
|
|
||||||
|
|||||||
@@ -33,15 +33,9 @@ public:
|
|||||||
bool is_created{};
|
bool is_created{};
|
||||||
bool is_mapped{};
|
bool is_mapped{};
|
||||||
INSERT_PADDING_BYTES(0x5);
|
INSERT_PADDING_BYTES(0x5);
|
||||||
union {
|
Kernel::KSharedMemory* shared_memory;
|
||||||
Kernel::KSharedMemory* shared_memory = nullptr;
|
|
||||||
u64 shared_memory_raw;
|
|
||||||
};
|
|
||||||
INSERT_PADDING_BYTES(0x38);
|
INSERT_PADDING_BYTES(0x38);
|
||||||
union {
|
SharedMemoryFormat* address = nullptr;
|
||||||
SharedMemoryFormat* address = nullptr;
|
|
||||||
u64 address_raw;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
// Correct size is 0x50 bytes
|
// Correct size is 0x50 bytes
|
||||||
static_assert(sizeof(SharedMemoryHolder) == 0x50, "SharedMemoryHolder is an invalid size");
|
static_assert(sizeof(SharedMemoryHolder) == 0x50, "SharedMemoryHolder is an invalid size");
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ add_library(input_common STATIC
|
|||||||
drivers/tas_input.h
|
drivers/tas_input.h
|
||||||
drivers/touch_screen.cpp
|
drivers/touch_screen.cpp
|
||||||
drivers/touch_screen.h
|
drivers/touch_screen.h
|
||||||
|
drivers/udp_client.cpp
|
||||||
|
drivers/udp_client.h
|
||||||
drivers/virtual_amiibo.cpp
|
drivers/virtual_amiibo.cpp
|
||||||
drivers/virtual_amiibo.h
|
drivers/virtual_amiibo.h
|
||||||
drivers/virtual_gamepad.cpp
|
drivers/virtual_gamepad.cpp
|
||||||
@@ -32,12 +34,8 @@ add_library(input_common STATIC
|
|||||||
input_poller.cpp
|
input_poller.cpp
|
||||||
input_poller.h
|
input_poller.h
|
||||||
main.cpp
|
main.cpp
|
||||||
main.h)
|
main.h
|
||||||
if (NOT EMSCRIPTEN)
|
)
|
||||||
target_sources(input_common PRIVATE
|
|
||||||
drivers/udp_client.cpp
|
|
||||||
drivers/udp_client.h)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
target_compile_options(input_common PRIVATE
|
target_compile_options(input_common PRIVATE
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ std::set<s32> Android::GetDeviceAxes(JNIEnv* env, jobject& j_device) const {
|
|||||||
std::set<s32> axes;
|
std::set<s32> axes;
|
||||||
for (int i = 0; i < env->GetArrayLength(j_axes); ++i) {
|
for (int i = 0; i < env->GetArrayLength(j_axes); ++i) {
|
||||||
jobject axis = env->GetObjectArrayElement(j_axes, i);
|
jobject axis = env->GetObjectArrayElement(j_axes, i);
|
||||||
axes.insert(env->GetIntField(axis, Common::Android::GetIntegerValueField()));
|
axes.insert(env->CallIntMethod(axis, Common::Android::GetIntegerValueMethod()));
|
||||||
}
|
}
|
||||||
return axes;
|
return axes;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "input_common/drivers/mouse.h"
|
#include "input_common/drivers/mouse.h"
|
||||||
#include "input_common/drivers/tas_input.h"
|
#include "input_common/drivers/tas_input.h"
|
||||||
#include "input_common/drivers/touch_screen.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_amiibo.h"
|
||||||
#include "input_common/drivers/virtual_gamepad.h"
|
#include "input_common/drivers/virtual_gamepad.h"
|
||||||
#include "input_common/helpers/stick_from_buttons.h"
|
#include "input_common/helpers/stick_from_buttons.h"
|
||||||
@@ -20,9 +21,6 @@
|
|||||||
#include "input_common/input_mapping.h"
|
#include "input_common/input_mapping.h"
|
||||||
#include "input_common/input_poller.h"
|
#include "input_common/input_poller.h"
|
||||||
#include "input_common/main.h"
|
#include "input_common/main.h"
|
||||||
#ifndef __EMSCRIPTEN__
|
|
||||||
#include "input_common/drivers/udp_client.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef ENABLE_LIBUSB
|
#ifdef ENABLE_LIBUSB
|
||||||
#include "input_common/drivers/gc_adapter.h"
|
#include "input_common/drivers/gc_adapter.h"
|
||||||
@@ -84,9 +82,7 @@ struct InputSubsystem::Impl {
|
|||||||
#ifdef ENABLE_LIBUSB
|
#ifdef ENABLE_LIBUSB
|
||||||
RegisterEngine("gcpad", gcadapter);
|
RegisterEngine("gcpad", gcadapter);
|
||||||
#endif
|
#endif
|
||||||
#ifndef __EMSCRIPTEN__
|
|
||||||
RegisterEngine("cemuhookudp", udp_client);
|
RegisterEngine("cemuhookudp", udp_client);
|
||||||
#endif
|
|
||||||
RegisterEngine("tas", tas_input);
|
RegisterEngine("tas", tas_input);
|
||||||
RegisterEngine("camera", camera);
|
RegisterEngine("camera", camera);
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
@@ -120,9 +116,7 @@ struct InputSubsystem::Impl {
|
|||||||
#ifdef ENABLE_LIBUSB
|
#ifdef ENABLE_LIBUSB
|
||||||
UnregisterEngine(gcadapter);
|
UnregisterEngine(gcadapter);
|
||||||
#endif
|
#endif
|
||||||
#ifndef __EMSCRIPTEN__
|
|
||||||
UnregisterEngine(udp_client);
|
UnregisterEngine(udp_client);
|
||||||
#endif
|
|
||||||
UnregisterEngine(tas_input);
|
UnregisterEngine(tas_input);
|
||||||
UnregisterEngine(camera);
|
UnregisterEngine(camera);
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
@@ -158,10 +152,8 @@ struct InputSubsystem::Impl {
|
|||||||
auto gcadapter_devices = gcadapter->GetInputDevices();
|
auto gcadapter_devices = gcadapter->GetInputDevices();
|
||||||
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
|
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
|
||||||
#endif
|
#endif
|
||||||
#ifndef __EMSCRIPTEN__
|
|
||||||
auto udp_devices = udp_client->GetInputDevices();
|
auto udp_devices = udp_client->GetInputDevices();
|
||||||
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
|
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
|
||||||
#endif
|
|
||||||
#ifdef HAVE_SDL3
|
#ifdef HAVE_SDL3
|
||||||
auto joycon_devices = joycon->GetInputDevices();
|
auto joycon_devices = joycon->GetInputDevices();
|
||||||
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
|
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
|
||||||
@@ -194,11 +186,9 @@ struct InputSubsystem::Impl {
|
|||||||
return gcadapter;
|
return gcadapter;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifndef __EMSCRIPTEN__
|
|
||||||
if (engine == udp_client->GetEngineName()) {
|
if (engine == udp_client->GetEngineName()) {
|
||||||
return udp_client;
|
return udp_client;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
#ifdef HAVE_SDL3
|
#ifdef HAVE_SDL3
|
||||||
if (engine == sdl->GetEngineName()) {
|
if (engine == sdl->GetEngineName()) {
|
||||||
return sdl;
|
return sdl;
|
||||||
@@ -281,11 +271,9 @@ struct InputSubsystem::Impl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifndef __EMSCRIPTEN__
|
|
||||||
if (engine == udp_client->GetEngineName()) {
|
if (engine == udp_client->GetEngineName()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
if (engine == tas_input->GetEngineName()) {
|
if (engine == tas_input->GetEngineName()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -312,9 +300,7 @@ struct InputSubsystem::Impl {
|
|||||||
#ifdef ENABLE_LIBUSB
|
#ifdef ENABLE_LIBUSB
|
||||||
gcadapter->BeginConfiguration();
|
gcadapter->BeginConfiguration();
|
||||||
#endif
|
#endif
|
||||||
#ifndef __EMSCRIPTEN__
|
|
||||||
udp_client->BeginConfiguration();
|
udp_client->BeginConfiguration();
|
||||||
#endif
|
|
||||||
#ifdef HAVE_SDL3
|
#ifdef HAVE_SDL3
|
||||||
sdl->BeginConfiguration();
|
sdl->BeginConfiguration();
|
||||||
joycon->BeginConfiguration();
|
joycon->BeginConfiguration();
|
||||||
@@ -330,9 +316,7 @@ struct InputSubsystem::Impl {
|
|||||||
#ifdef ENABLE_LIBUSB
|
#ifdef ENABLE_LIBUSB
|
||||||
gcadapter->EndConfiguration();
|
gcadapter->EndConfiguration();
|
||||||
#endif
|
#endif
|
||||||
#ifndef __EMSCRIPTEN__
|
|
||||||
udp_client->EndConfiguration();
|
udp_client->EndConfiguration();
|
||||||
#endif
|
|
||||||
#ifdef HAVE_SDL3
|
#ifdef HAVE_SDL3
|
||||||
sdl->EndConfiguration();
|
sdl->EndConfiguration();
|
||||||
joycon->EndConfiguration();
|
joycon->EndConfiguration();
|
||||||
@@ -357,9 +341,7 @@ struct InputSubsystem::Impl {
|
|||||||
std::shared_ptr<Mouse> mouse;
|
std::shared_ptr<Mouse> mouse;
|
||||||
std::shared_ptr<TouchScreen> touch_screen;
|
std::shared_ptr<TouchScreen> touch_screen;
|
||||||
std::shared_ptr<TasInput::Tas> tas_input;
|
std::shared_ptr<TasInput::Tas> tas_input;
|
||||||
#ifndef __EMSCRIPTEN__
|
|
||||||
std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
|
std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
|
||||||
#endif
|
|
||||||
std::shared_ptr<Camera> camera;
|
std::shared_ptr<Camera> camera;
|
||||||
std::shared_ptr<VirtualAmiibo> virtual_amiibo;
|
std::shared_ptr<VirtualAmiibo> virtual_amiibo;
|
||||||
std::shared_ptr<VirtualGamepad> virtual_gamepad;
|
std::shared_ptr<VirtualGamepad> virtual_gamepad;
|
||||||
@@ -488,9 +470,7 @@ bool InputSubsystem::IsStickInverted(const Common::ParamPackage& params) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void InputSubsystem::ReloadInputDevices() {
|
void InputSubsystem::ReloadInputDevices() {
|
||||||
#ifndef __EMSCRIPTEN__
|
|
||||||
impl->udp_client.get()->ReloadSockets();
|
impl->udp_client.get()->ReloadSockets();
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputSubsystem::BeginMapping(Polling::InputType type) {
|
void InputSubsystem::BeginMapping(Polling::InputType type) {
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
|||||||
INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"),
|
INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"),
|
||||||
tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA "
|
tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA "
|
||||||
"can produce a more stable picture in lower resolutions."));
|
"can produce a more stable picture in lower resolutions."));
|
||||||
|
INSERT(Settings, post_shader_chain, QString(), QString());
|
||||||
INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"),
|
INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"),
|
||||||
tr("The method used to render the window in fullscreen.\nBorderless offers the best "
|
tr("The method used to render the window in fullscreen.\nBorderless offers the best "
|
||||||
"compatibility with the on-screen keyboard that some games request for "
|
"compatibility with the on-screen keyboard that some games request for "
|
||||||
|
|||||||
@@ -257,7 +257,8 @@ else()
|
|||||||
# Bracket depth determines maximum size of a fold expression in Clang since 9c9974c3ccb6.
|
# Bracket depth determines maximum size of a fold expression in Clang since 9c9974c3ccb6.
|
||||||
# And this in turns limits the size of a std::array.
|
# And this in turns limits the size of a std::array.
|
||||||
$<$<CXX_COMPILER_ID:Clang>:-fbracket-depth=1024>
|
$<$<CXX_COMPILER_ID:Clang>:-fbracket-depth=1024>
|
||||||
$<$<CXX_COMPILER_ID:AppleClang>:-fbracket-depth=1024>)
|
$<$<CXX_COMPILER_ID:AppleClang>:-fbracket-depth=1024>
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
create_target_directory_groups(shader_recompiler)
|
create_target_directory_groups(shader_recompiler)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ struct CbufWordKey {
|
|||||||
|
|
||||||
struct CbufWordKeyHash {
|
struct CbufWordKeyHash {
|
||||||
constexpr size_t operator()(const CbufWordKey& k) const noexcept {
|
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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -160,14 +157,16 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
|
|||||||
|
|
||||||
unsigned Swizzle(u64 insn) {
|
unsigned Swizzle(u64 insn) {
|
||||||
const Encoding texs{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 (texs.dest_reg_b == IR::Reg::RZ) {
|
||||||
if (encoding >= RG_LUT.size())
|
if (encoding >= RG_LUT.size()) {
|
||||||
throw NotImplementedException("Illegal RG encoding {}", encoding);
|
throw NotImplementedException("Illegal RG encoding {}", encoding);
|
||||||
|
}
|
||||||
return RG_LUT[encoding];
|
return RG_LUT[encoding];
|
||||||
} else {
|
} else {
|
||||||
if (encoding >= RGBA_LUT.size())
|
if (encoding >= RGBA_LUT.size()) {
|
||||||
throw NotImplementedException("Illegal RGBA encoding {}", encoding);
|
throw NotImplementedException("Illegal RGBA encoding {}", encoding);
|
||||||
|
}
|
||||||
return RGBA_LUT[encoding];
|
return RGBA_LUT[encoding];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -145,14 +142,16 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
|
|||||||
|
|
||||||
unsigned Swizzle(u64 insn) {
|
unsigned Swizzle(u64 insn) {
|
||||||
const Encoding tlds{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 (tlds.dest_reg_b == IR::Reg::RZ) {
|
||||||
if (encoding >= RG_LUT.size())
|
if (encoding >= RG_LUT.size()) {
|
||||||
throw NotImplementedException("Illegal RG encoding {}", encoding);
|
throw NotImplementedException("Illegal RG encoding {}", encoding);
|
||||||
|
}
|
||||||
return RG_LUT[encoding];
|
return RG_LUT[encoding];
|
||||||
} else {
|
} else {
|
||||||
if (encoding >= RGBA_LUT.size())
|
if (encoding >= RGBA_LUT.size()) {
|
||||||
throw NotImplementedException("Illegal RGBA encoding {}", encoding);
|
throw NotImplementedException("Illegal RGBA encoding {}", encoding);
|
||||||
|
}
|
||||||
return RGBA_LUT[encoding];
|
return RGBA_LUT[encoding];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -299,6 +299,21 @@ if (ENABLE_LSFG)
|
|||||||
target_compile_definitions(video_core PUBLIC HAS_LSFG)
|
target_compile_definitions(video_core PUBLIC HAS_LSFG)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (ENABLE_RESHADE)
|
||||||
|
target_sources(video_core PRIVATE
|
||||||
|
post_processing/fx_chain.cpp
|
||||||
|
post_processing/fx_chain.h
|
||||||
|
post_processing/fx_compile.cpp
|
||||||
|
post_processing/fx_compile.h
|
||||||
|
post_processing/fx_effect.cpp
|
||||||
|
post_processing/fx_effect.h
|
||||||
|
renderer_vulkan/present/post_process.cpp
|
||||||
|
renderer_vulkan/present/post_process.h
|
||||||
|
)
|
||||||
|
target_link_libraries(video_core PRIVATE reshadefx::reshadefx)
|
||||||
|
target_compile_definitions(video_core PUBLIC HAS_RESHADE)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (ENABLE_OPENGL)
|
if (ENABLE_OPENGL)
|
||||||
target_sources(video_core PRIVATE
|
target_sources(video_core PRIVATE
|
||||||
renderer_opengl/present/filters.cpp
|
renderer_opengl/present/filters.cpp
|
||||||
|
|||||||
@@ -0,0 +1,293 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "video_core/post_processing/fx_chain.h"
|
||||||
|
#include "video_core/post_processing/fx_effect.h"
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::vector<std::string_view> Split(std::string_view value, char separator) {
|
||||||
|
std::vector<std::string_view> out;
|
||||||
|
size_t start = 0;
|
||||||
|
while (start <= value.size()) {
|
||||||
|
size_t end = value.find(separator, start);
|
||||||
|
if (end == std::string_view::npos) {
|
||||||
|
end = value.size();
|
||||||
|
}
|
||||||
|
out.push_back(value.substr(start, end - start));
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSerializableName(std::string_view value) {
|
||||||
|
return value.find_first_of(";|,=") == std::string_view::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
std::vector<FxChainEntry> ParseFxChain(std::string_view value) {
|
||||||
|
std::vector<FxChainEntry> parsed;
|
||||||
|
|
||||||
|
for (const std::string_view record : Split(value, ';')) {
|
||||||
|
if (record.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto fields = Split(record, '|');
|
||||||
|
if (fields.size() < 2 || fields[0].empty() || fields[1].empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FxChainEntry entry;
|
||||||
|
entry.file = std::string(fields[0]);
|
||||||
|
entry.technique = std::string(fields[1]);
|
||||||
|
|
||||||
|
if (fields.size() >= 3) {
|
||||||
|
for (const std::string_view assignment : Split(fields[2], ',')) {
|
||||||
|
const size_t equals = assignment.find('=');
|
||||||
|
if (equals == std::string_view::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::string name(assignment.substr(0, equals));
|
||||||
|
if (name.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<f32, 4> components{};
|
||||||
|
size_t index = 0;
|
||||||
|
for (const std::string_view piece : Split(assignment.substr(equals + 1), '/')) {
|
||||||
|
if (index >= components.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const std::string text(piece);
|
||||||
|
if (!text.empty()) {
|
||||||
|
components[index] = std::strtof(text.c_str(), nullptr);
|
||||||
|
}
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
entry.values.emplace(name, components);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.push_back(std::move(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SerializeFxChain(std::span<const FxChainEntry> entries) {
|
||||||
|
std::string out;
|
||||||
|
|
||||||
|
for (const auto& entry : entries) {
|
||||||
|
if (!IsSerializableName(entry.file) || !IsSerializableName(entry.technique)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!out.empty()) {
|
||||||
|
out += ';';
|
||||||
|
}
|
||||||
|
out += entry.file;
|
||||||
|
out += '|';
|
||||||
|
out += entry.technique;
|
||||||
|
out += '|';
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
for (const auto& [name, value] : entry.values) {
|
||||||
|
if (!IsSerializableName(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!first) {
|
||||||
|
out += ',';
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
out += name;
|
||||||
|
out += '=';
|
||||||
|
for (size_t i = 0; i < value.size(); ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
out += '/';
|
||||||
|
}
|
||||||
|
out += fmt::format("{}", value[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
FxChain& FxChain::Instance() {
|
||||||
|
static FxChain instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
FxChainSnapshot FxChain::Snapshot() const {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
return FxChainSnapshot{
|
||||||
|
.entries = entries,
|
||||||
|
.generation = generation.load(std::memory_order_relaxed),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<FxChainEntry> FxChain::Entries() const {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t FxChain::Size() const {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
return entries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FxChain::Append(std::string_view file, std::string_view technique) {
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
FxChainEntry entry;
|
||||||
|
entry.file = std::string(file);
|
||||||
|
entry.technique = std::string(technique);
|
||||||
|
entries.push_back(std::move(entry));
|
||||||
|
}
|
||||||
|
generation.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FxChain::Replace(size_t index, std::string_view file, std::string_view technique) {
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
if (index >= entries.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (entries[index].file == file && entries[index].technique == technique) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FxChainEntry entry;
|
||||||
|
entry.file = std::string(file);
|
||||||
|
entry.technique = std::string(technique);
|
||||||
|
entries[index] = std::move(entry);
|
||||||
|
}
|
||||||
|
generation.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FxChain::Remove(size_t index) {
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
if (index >= entries.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
entries.erase(entries.begin() + static_cast<std::ptrdiff_t>(index));
|
||||||
|
}
|
||||||
|
generation.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FxChain::Move(size_t index, int delta) {
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
if (index >= entries.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::ptrdiff_t target = static_cast<std::ptrdiff_t>(index) + delta;
|
||||||
|
if (target < 0 || target >= static_cast<std::ptrdiff_t>(entries.size())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::swap(entries[index], entries[static_cast<size_t>(target)]);
|
||||||
|
}
|
||||||
|
generation.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FxChain::Clear() {
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
entries.clear();
|
||||||
|
}
|
||||||
|
generation.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FxChain::SetValue(size_t index, std::string_view uniform, const std::array<f32, 4>& value) {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
if (index >= entries.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
entries[index].values[std::string(uniform)] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<f32, 4> FxChain::GetValue(size_t index, std::string_view uniform) const {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
if (index >= entries.size()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto it = entries[index].values.find(std::string(uniform));
|
||||||
|
if (it == entries[index].values.end()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string, std::array<f32, 4>> FxChain::EntryValues(size_t index) const {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
if (index >= entries.size()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return entries[index].values;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FxChain::HasValue(size_t index, std::string_view uniform) const {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
if (index >= entries.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return entries[index].values.contains(std::string(uniform));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FxChain::ResetValues(size_t index) {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
if (index >= entries.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
entries[index].values.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FxChain::LoadFromSettings() {
|
||||||
|
auto loaded = ParseFxChain(Settings::values.post_shader_chain.GetValue());
|
||||||
|
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
entries = std::move(loaded);
|
||||||
|
generation.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FxChain::StoreToSettings() const {
|
||||||
|
std::string serialized;
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
serialized = SerializeFxChain(entries);
|
||||||
|
}
|
||||||
|
Settings::values.post_shader_chain.SetValue(serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FxChain::DropUnknownEntries() {
|
||||||
|
bool changed = false;
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
const auto removed = std::remove_if(entries.begin(), entries.end(), [](const FxChainEntry& entry) {
|
||||||
|
const FxEffectDesc* effect = FindFxEffect(entry.file);
|
||||||
|
if (effect == nullptr || !effect->Valid()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return std::find(effect->techniques.begin(), effect->techniques.end(),
|
||||||
|
entry.technique) == effect->techniques.end();
|
||||||
|
});
|
||||||
|
if (removed != entries.end()) {
|
||||||
|
entries.erase(removed, entries.end());
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
generation.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace VideoCore
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
|
||||||
|
struct FxChainEntry {
|
||||||
|
std::string file;
|
||||||
|
std::string technique;
|
||||||
|
std::map<std::string, std::array<f32, 4>> values;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FxChainSnapshot {
|
||||||
|
std::vector<FxChainEntry> entries;
|
||||||
|
u64 generation{};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<FxChainEntry> ParseFxChain(std::string_view value);
|
||||||
|
|
||||||
|
std::string SerializeFxChain(std::span<const FxChainEntry> entries);
|
||||||
|
|
||||||
|
class FxChain {
|
||||||
|
public:
|
||||||
|
static FxChain& Instance();
|
||||||
|
|
||||||
|
FxChainSnapshot Snapshot() const;
|
||||||
|
|
||||||
|
std::vector<FxChainEntry> Entries() const;
|
||||||
|
|
||||||
|
size_t Size() const;
|
||||||
|
|
||||||
|
void Append(std::string_view file, std::string_view technique);
|
||||||
|
|
||||||
|
void Replace(size_t index, std::string_view file, std::string_view technique);
|
||||||
|
|
||||||
|
void Remove(size_t index);
|
||||||
|
|
||||||
|
void Move(size_t index, int delta);
|
||||||
|
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
void SetValue(size_t index, std::string_view uniform, const std::array<f32, 4>& value);
|
||||||
|
|
||||||
|
std::array<f32, 4> GetValue(size_t index, std::string_view uniform) const;
|
||||||
|
|
||||||
|
std::map<std::string, std::array<f32, 4>> EntryValues(size_t index) const;
|
||||||
|
|
||||||
|
bool HasValue(size_t index, std::string_view uniform) const;
|
||||||
|
|
||||||
|
void ResetValues(size_t index);
|
||||||
|
|
||||||
|
void LoadFromSettings();
|
||||||
|
|
||||||
|
void StoreToSettings() const;
|
||||||
|
|
||||||
|
void DropUnknownEntries();
|
||||||
|
|
||||||
|
private:
|
||||||
|
FxChain() = default;
|
||||||
|
|
||||||
|
mutable std::mutex mutex;
|
||||||
|
std::vector<FxChainEntry> entries;
|
||||||
|
std::atomic<u64> generation{1};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace VideoCore
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "effect_codegen.hpp"
|
||||||
|
#include "effect_parser.hpp"
|
||||||
|
#include "effect_preprocessor.hpp"
|
||||||
|
|
||||||
|
#include "common/fs/fs.h"
|
||||||
|
#include "common/fs/fs_util.h"
|
||||||
|
#include "video_core/post_processing/fx_compile.h"
|
||||||
|
#include "video_core/post_processing/fx_effect.h"
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
|
||||||
|
FxCompileResult CompileFxEffect(const std::filesystem::path& path, u32 width, u32 height,
|
||||||
|
u32 color_bit_depth) {
|
||||||
|
FxCompileResult result;
|
||||||
|
|
||||||
|
if (!Common::FS::Exists(path)) {
|
||||||
|
result.error = "Effect file not found: " + Common::FS::PathToUTF8String(path);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
reshadefx::preprocessor preprocessor;
|
||||||
|
preprocessor.add_macro_definition("__RESHADE__", "50000");
|
||||||
|
preprocessor.add_macro_definition("__RESHADE_PERFORMANCE_MODE__", "1");
|
||||||
|
preprocessor.add_macro_definition("__RENDERER__", "0x20000");
|
||||||
|
preprocessor.add_macro_definition("__VENDOR__", "0");
|
||||||
|
preprocessor.add_macro_definition("__DEVICE__", "0");
|
||||||
|
preprocessor.add_macro_definition("__APPLICATION__", "0");
|
||||||
|
preprocessor.add_macro_definition("BUFFER_WIDTH", std::to_string(width));
|
||||||
|
preprocessor.add_macro_definition("BUFFER_HEIGHT", std::to_string(height));
|
||||||
|
preprocessor.add_macro_definition("BUFFER_RCP_WIDTH", "(1.0 / BUFFER_WIDTH)");
|
||||||
|
preprocessor.add_macro_definition("BUFFER_RCP_HEIGHT", "(1.0 / BUFFER_HEIGHT)");
|
||||||
|
preprocessor.add_macro_definition("BUFFER_COLOR_DEPTH", std::to_string(color_bit_depth));
|
||||||
|
preprocessor.add_macro_definition("BUFFER_COLOR_BIT_DEPTH", std::to_string(color_bit_depth));
|
||||||
|
|
||||||
|
for (const auto& include : GetFxIncludePaths(path)) {
|
||||||
|
preprocessor.add_include_path(include);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preprocessor.append_file(path)) {
|
||||||
|
result.error = preprocessor.errors();
|
||||||
|
if (result.error.empty()) {
|
||||||
|
result.error = "Failed to preprocess " + Common::FS::PathToUTF8String(path);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<reshadefx::codegen> backend(
|
||||||
|
reshadefx::create_codegen_spirv(true, false, false, false, true));
|
||||||
|
|
||||||
|
reshadefx::parser parser;
|
||||||
|
if (!parser.parse(preprocessor.output(), backend.get())) {
|
||||||
|
result.error = parser.errors();
|
||||||
|
if (result.error.empty()) {
|
||||||
|
result.error = "Failed to parse " + Common::FS::PathToUTF8String(path);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.module = backend->module();
|
||||||
|
|
||||||
|
std::set<std::string> wanted;
|
||||||
|
for (const auto& technique : result.module.techniques) {
|
||||||
|
for (const auto& pass : technique.passes) {
|
||||||
|
if (!pass.vs_entry_point.empty()) {
|
||||||
|
wanted.insert(pass.vs_entry_point);
|
||||||
|
}
|
||||||
|
if (!pass.ps_entry_point.empty()) {
|
||||||
|
wanted.insert(pass.ps_entry_point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& name : wanted) {
|
||||||
|
std::string binary;
|
||||||
|
std::string assembly;
|
||||||
|
std::string errors;
|
||||||
|
if (!backend->assemble_code_for_entry_point(name, binary, assembly, errors)) {
|
||||||
|
result.error = "Failed to assemble entry point '" + name + "': " + errors;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (binary.size() % sizeof(u32) != 0) {
|
||||||
|
result.error = "Entry point '" + name + "' produced a malformed SPIR-V module";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u32> words(binary.size() / sizeof(u32));
|
||||||
|
std::memcpy(words.data(), binary.data(), binary.size());
|
||||||
|
result.entry_points.emplace(name, std::move(words));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.entry_points.empty()) {
|
||||||
|
result.error = "Effect declares no usable entry points";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace VideoCore
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "effect_module.hpp"
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
|
||||||
|
struct FxCompileResult {
|
||||||
|
reshadefx::effect_module module;
|
||||||
|
std::map<std::string, std::vector<u32>> entry_points;
|
||||||
|
std::string error;
|
||||||
|
|
||||||
|
bool Succeeded() const {
|
||||||
|
return error.empty() && !entry_points.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FxCompileResult CompileFxEffect(const std::filesystem::path& path, u32 width, u32 height,
|
||||||
|
u32 color_bit_depth);
|
||||||
|
|
||||||
|
} // namespace VideoCore
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "common/fs/fs.h"
|
||||||
|
#include "common/fs/fs_util.h"
|
||||||
|
#include "common/fs/path_util.h"
|
||||||
|
#include "common/logging.h"
|
||||||
|
#include "video_core/post_processing/fx_compile.h"
|
||||||
|
#include "video_core/post_processing/fx_effect.h"
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr u32 CATALOG_PROBE_WIDTH = 1280;
|
||||||
|
constexpr u32 CATALOG_PROBE_HEIGHT = 720;
|
||||||
|
constexpr u32 CATALOG_PROBE_DEPTH = 8;
|
||||||
|
|
||||||
|
std::vector<FxEffectDesc> catalog;
|
||||||
|
|
||||||
|
const reshadefx::annotation* FindAnnotation(const std::vector<reshadefx::annotation>& annotations,
|
||||||
|
std::string_view name) {
|
||||||
|
const auto it = std::find_if(annotations.begin(), annotations.end(),
|
||||||
|
[&](const reshadefx::annotation& a) { return a.name == name; });
|
||||||
|
if (it == annotations.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &*it;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AnnotationString(const std::vector<reshadefx::annotation>& annotations,
|
||||||
|
std::string_view name) {
|
||||||
|
const reshadefx::annotation* a = FindAnnotation(annotations, name);
|
||||||
|
if (a == nullptr) {
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
return a->value.string_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnnotationFloat(const std::vector<reshadefx::annotation>& annotations, std::string_view name,
|
||||||
|
f32& out) {
|
||||||
|
const reshadefx::annotation* a = FindAnnotation(annotations, name);
|
||||||
|
if (a == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (a->type.is_floating_point()) {
|
||||||
|
out = a->value.as_float[0];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a->type.is_integral()) {
|
||||||
|
out = static_cast<f32>(a->value.as_int[0]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FxUiType ParseUiType(std::string_view value) {
|
||||||
|
if (value == "slider") {
|
||||||
|
return FxUiType::Slider;
|
||||||
|
}
|
||||||
|
if (value == "drag") {
|
||||||
|
return FxUiType::Drag;
|
||||||
|
}
|
||||||
|
if (value == "combo") {
|
||||||
|
return FxUiType::Combo;
|
||||||
|
}
|
||||||
|
if (value == "radio") {
|
||||||
|
return FxUiType::Radio;
|
||||||
|
}
|
||||||
|
if (value == "check" || value == "checkbox") {
|
||||||
|
return FxUiType::CheckBox;
|
||||||
|
}
|
||||||
|
if (value == "color") {
|
||||||
|
return FxUiType::Color;
|
||||||
|
}
|
||||||
|
if (value == "input") {
|
||||||
|
return FxUiType::InputBox;
|
||||||
|
}
|
||||||
|
return FxUiType::Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> SplitItems(const std::string& items) {
|
||||||
|
std::vector<std::string> out;
|
||||||
|
std::string current;
|
||||||
|
for (const char c : items) {
|
||||||
|
if (c == '\0') {
|
||||||
|
out.push_back(current);
|
||||||
|
current.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
current += c;
|
||||||
|
}
|
||||||
|
if (!current.empty()) {
|
||||||
|
out.push_back(current);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
FxUniformDesc DescribeUniform(const reshadefx::uniform& info) {
|
||||||
|
FxUniformDesc desc;
|
||||||
|
desc.name = info.name;
|
||||||
|
desc.components = std::min<u32>(info.type.components(), 4);
|
||||||
|
|
||||||
|
if (info.type.is_boolean()) {
|
||||||
|
desc.kind = FxUniformKind::Boolean;
|
||||||
|
} else if (info.type.is_integral()) {
|
||||||
|
desc.kind = FxUniformKind::Integer;
|
||||||
|
} else {
|
||||||
|
desc.kind = FxUniformKind::Floating;
|
||||||
|
}
|
||||||
|
|
||||||
|
desc.label = AnnotationString(info.annotations, "ui_label");
|
||||||
|
if (desc.label.empty()) {
|
||||||
|
desc.label = info.name;
|
||||||
|
}
|
||||||
|
desc.tooltip = AnnotationString(info.annotations, "ui_tooltip");
|
||||||
|
desc.category = AnnotationString(info.annotations, "ui_category");
|
||||||
|
desc.ui_type = ParseUiType(AnnotationString(info.annotations, "ui_type"));
|
||||||
|
desc.items = SplitItems(AnnotationString(info.annotations, "ui_items"));
|
||||||
|
|
||||||
|
if (desc.kind == FxUniformKind::Boolean) {
|
||||||
|
desc.ui_min = 0.0f;
|
||||||
|
desc.ui_max = 1.0f;
|
||||||
|
desc.ui_step = 1.0f;
|
||||||
|
} else if (desc.kind == FxUniformKind::Integer) {
|
||||||
|
desc.ui_min = 0.0f;
|
||||||
|
desc.ui_max = 100.0f;
|
||||||
|
desc.ui_step = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void(AnnotationFloat(info.annotations, "ui_min", desc.ui_min));
|
||||||
|
void(AnnotationFloat(info.annotations, "ui_max", desc.ui_max));
|
||||||
|
void(AnnotationFloat(info.annotations, "ui_step", desc.ui_step));
|
||||||
|
|
||||||
|
if (desc.ui_step <= 0.0f) {
|
||||||
|
desc.ui_step = 0.01f;
|
||||||
|
if (desc.kind != FxUniformKind::Floating) {
|
||||||
|
desc.ui_step = 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (desc.ui_max < desc.ui_min) {
|
||||||
|
std::swap(desc.ui_min, desc.ui_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.has_initializer_value) {
|
||||||
|
for (u32 i = 0; i < desc.components; ++i) {
|
||||||
|
if (desc.kind == FxUniformKind::Floating) {
|
||||||
|
desc.default_value[i] = info.initializer_value.as_float[i];
|
||||||
|
} else {
|
||||||
|
desc.default_value[i] = static_cast<f32>(info.initializer_value.as_int[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
FxEffectDesc DescribeEffect(const std::filesystem::path& path, const std::filesystem::path& root) {
|
||||||
|
FxEffectDesc desc;
|
||||||
|
desc.file = Common::FS::PathToUTF8String(std::filesystem::relative(path, root));
|
||||||
|
desc.name = Common::FS::PathToUTF8String(path.stem());
|
||||||
|
|
||||||
|
const auto compiled =
|
||||||
|
CompileFxEffect(path, CATALOG_PROBE_WIDTH, CATALOG_PROBE_HEIGHT, CATALOG_PROBE_DEPTH);
|
||||||
|
if (!compiled.Succeeded()) {
|
||||||
|
desc.error = compiled.error;
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& technique : compiled.module.techniques) {
|
||||||
|
desc.techniques.push_back(technique.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& uniform : compiled.module.uniforms) {
|
||||||
|
FxUniformDesc uniform_desc = DescribeUniform(uniform);
|
||||||
|
if (uniform_desc.ui_type == FxUiType::Hidden) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
desc.uniforms.push_back(std::move(uniform_desc));
|
||||||
|
}
|
||||||
|
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
std::filesystem::path GetFxRootDirectory() {
|
||||||
|
return Common::FS::GetEdenPath(Common::FS::EdenPath::PostShaderDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::filesystem::path> GetFxIncludePaths(const std::filesystem::path& effect_path) {
|
||||||
|
const auto root = GetFxRootDirectory();
|
||||||
|
std::vector<std::filesystem::path> paths;
|
||||||
|
paths.push_back(effect_path.parent_path());
|
||||||
|
paths.push_back(root);
|
||||||
|
paths.push_back(root / "Shaders");
|
||||||
|
|
||||||
|
const auto last = std::unique(paths.begin(), paths.end());
|
||||||
|
paths.erase(last, paths.end());
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path ResolveFxTexturePath(const std::filesystem::path& effect_path,
|
||||||
|
std::string_view source) {
|
||||||
|
const auto root = GetFxRootDirectory();
|
||||||
|
const std::filesystem::path name{source};
|
||||||
|
|
||||||
|
const std::array candidates{
|
||||||
|
effect_path.parent_path() / name,
|
||||||
|
root / "Textures" / name,
|
||||||
|
root / name,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& candidate : candidates) {
|
||||||
|
if (Common::FS::Exists(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::filesystem::path();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReloadFxCatalog() {
|
||||||
|
catalog.clear();
|
||||||
|
|
||||||
|
const auto root = GetFxRootDirectory();
|
||||||
|
if (!Common::FS::Exists(root)) {
|
||||||
|
void(Common::FS::CreateDirs(root));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::filesystem::path> effect_files;
|
||||||
|
Common::FS::IterateDirEntriesRecursively(
|
||||||
|
root,
|
||||||
|
[&](const std::filesystem::directory_entry& entry) {
|
||||||
|
if (entry.path().extension() == ".fx") {
|
||||||
|
effect_files.push_back(entry.path());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
Common::FS::DirEntryFilter::File);
|
||||||
|
|
||||||
|
std::sort(effect_files.begin(), effect_files.end());
|
||||||
|
|
||||||
|
for (const auto& file : effect_files) {
|
||||||
|
FxEffectDesc desc = DescribeEffect(file, root);
|
||||||
|
if (!desc.error.empty()) {
|
||||||
|
LOG_WARNING(Render, "Post-processing effect '{}' failed to compile:\n{}", desc.file,
|
||||||
|
desc.error);
|
||||||
|
}
|
||||||
|
catalog.push_back(std::move(desc));
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t usable = std::count_if(catalog.begin(), catalog.end(),
|
||||||
|
[](const FxEffectDesc& d) { return d.Valid(); });
|
||||||
|
LOG_INFO(Render, "Loaded {} post-processing effects ({} usable)", catalog.size(), usable);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<FxEffectDesc>& GetFxCatalog() {
|
||||||
|
return catalog;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FxEffectDesc* FindFxEffect(std::string_view file) {
|
||||||
|
const auto it = std::find_if(catalog.begin(), catalog.end(),
|
||||||
|
[&](const FxEffectDesc& d) { return d.file == file; });
|
||||||
|
if (it == catalog.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &*it;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FxUniformDesc* FindFxUniform(const FxEffectDesc& effect, std::string_view name) {
|
||||||
|
const auto it = std::find_if(effect.uniforms.begin(), effect.uniforms.end(),
|
||||||
|
[&](const FxUniformDesc& u) { return u.name == name; });
|
||||||
|
if (it == effect.uniforms.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &*it;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace VideoCore
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
|
||||||
|
enum class FxUniformKind {
|
||||||
|
Boolean,
|
||||||
|
Integer,
|
||||||
|
Floating,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class FxUiType {
|
||||||
|
Hidden,
|
||||||
|
Slider,
|
||||||
|
Drag,
|
||||||
|
Combo,
|
||||||
|
Radio,
|
||||||
|
CheckBox,
|
||||||
|
Color,
|
||||||
|
InputBox,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FxUniformDesc {
|
||||||
|
std::string name;
|
||||||
|
std::string label;
|
||||||
|
std::string tooltip;
|
||||||
|
std::string category;
|
||||||
|
FxUniformKind kind{FxUniformKind::Floating};
|
||||||
|
u32 components{1};
|
||||||
|
FxUiType ui_type{FxUiType::Hidden};
|
||||||
|
f32 ui_min{0.0f};
|
||||||
|
f32 ui_max{1.0f};
|
||||||
|
f32 ui_step{0.01f};
|
||||||
|
std::vector<std::string> items;
|
||||||
|
std::array<f32, 4> default_value{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FxEffectDesc {
|
||||||
|
std::string file;
|
||||||
|
std::string name;
|
||||||
|
std::vector<std::string> techniques;
|
||||||
|
std::vector<FxUniformDesc> uniforms;
|
||||||
|
std::string error;
|
||||||
|
|
||||||
|
bool Valid() const {
|
||||||
|
return error.empty() && !techniques.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::filesystem::path GetFxRootDirectory();
|
||||||
|
|
||||||
|
std::vector<std::filesystem::path> GetFxIncludePaths(const std::filesystem::path& effect_path);
|
||||||
|
|
||||||
|
std::filesystem::path ResolveFxTexturePath(const std::filesystem::path& effect_path,
|
||||||
|
std::string_view source);
|
||||||
|
|
||||||
|
void ReloadFxCatalog();
|
||||||
|
|
||||||
|
const std::vector<FxEffectDesc>& GetFxCatalog();
|
||||||
|
|
||||||
|
const FxEffectDesc* FindFxEffect(std::string_view file);
|
||||||
|
|
||||||
|
const FxUniformDesc* FindFxUniform(const FxEffectDesc& effect, std::string_view name);
|
||||||
|
|
||||||
|
} // namespace VideoCore
|
||||||
@@ -18,6 +18,10 @@
|
|||||||
#include "video_core/renderer_vulkan/present/sgsr.h"
|
#include "video_core/renderer_vulkan/present/sgsr.h"
|
||||||
#include "video_core/renderer_vulkan/present/fxaa.h"
|
#include "video_core/renderer_vulkan/present/fxaa.h"
|
||||||
#include "video_core/renderer_vulkan/present/layer.h"
|
#include "video_core/renderer_vulkan/present/layer.h"
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
#include "video_core/post_processing/fx_chain.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/post_process.h"
|
||||||
|
#endif
|
||||||
#include "video_core/renderer_vulkan/present/present_push_constants.h"
|
#include "video_core/renderer_vulkan/present/present_push_constants.h"
|
||||||
#include "video_core/renderer_vulkan/present/smaa.h"
|
#include "video_core/renderer_vulkan/present/smaa.h"
|
||||||
#include "video_core/renderer_vulkan/present/util.h"
|
#include "video_core/renderer_vulkan/present/util.h"
|
||||||
@@ -93,6 +97,9 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
|
|||||||
|
|
||||||
RefreshResources(device, framebuffer);
|
RefreshResources(device, framebuffer);
|
||||||
SetAntiAliasPass(device);
|
SetAntiAliasPass(device);
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
SetPostProcessPass(device);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Finish any pending renderpass
|
// Finish any pending renderpass
|
||||||
scheduler.RequestOutsideRenderPassOperationContext();
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
@@ -115,6 +122,12 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
|
|||||||
smaa->Draw(device, scheduler, image_index, &source_image, &source_image_view);
|
smaa->Draw(device, scheduler, image_index, &source_image, &source_image_view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
if (post_process.has_value()) {
|
||||||
|
post_process->Draw(device, scheduler, image_index, &source_image, &source_image_view);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
auto crop_rect = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height);
|
auto crop_rect = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height);
|
||||||
const VkExtent2D render_extent{
|
const VkExtent2D render_extent{
|
||||||
.width = scaled_width,
|
.width = scaled_width,
|
||||||
@@ -214,6 +227,40 @@ void Layer::SetAntiAliasPass(const Device& device) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
void Layer::SetPostProcessPass(const Device& device) {
|
||||||
|
const VkExtent2D render_area{
|
||||||
|
.width = Settings::values.resolution_info.ScaleUp(raw_width),
|
||||||
|
.height = Settings::values.resolution_info.ScaleUp(raw_height),
|
||||||
|
};
|
||||||
|
|
||||||
|
const u64 generation = VideoCore::FxChain::Instance().Snapshot().generation;
|
||||||
|
|
||||||
|
if (post_process_generation == generation && post_process_extent.width == render_area.width &&
|
||||||
|
post_process_extent.height == render_area.height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const u64 tick : resource_ticks) {
|
||||||
|
scheduler.Wait(tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
post_process_generation = generation;
|
||||||
|
post_process_extent = render_area;
|
||||||
|
post_process.reset();
|
||||||
|
|
||||||
|
if (VideoCore::FxChain::Instance().Size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
post_process.emplace(device, memory_allocator, scheduler, image_count, render_area);
|
||||||
|
|
||||||
|
if (post_process->Empty()) {
|
||||||
|
post_process.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void Layer::ReleaseRawImages() {
|
void Layer::ReleaseRawImages() {
|
||||||
for (const u64 tick : resource_ticks) {
|
for (const u64 tick : resource_ticks) {
|
||||||
scheduler.Wait(tick);
|
scheduler.Wait(tick);
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
#include "video_core/renderer_vulkan/present/fsr.h"
|
#include "video_core/renderer_vulkan/present/fsr.h"
|
||||||
#include "video_core/renderer_vulkan/present/sgsr.h"
|
#include "video_core/renderer_vulkan/present/sgsr.h"
|
||||||
#include "video_core/renderer_vulkan/present/fxaa.h"
|
#include "video_core/renderer_vulkan/present/fxaa.h"
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
#include "video_core/renderer_vulkan/present/post_process.h"
|
||||||
|
#endif
|
||||||
#include "video_core/renderer_vulkan/present/smaa.h"
|
#include "video_core/renderer_vulkan/present/smaa.h"
|
||||||
|
|
||||||
namespace Layout {
|
namespace Layout {
|
||||||
@@ -66,6 +69,9 @@ private:
|
|||||||
|
|
||||||
void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer);
|
void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer);
|
||||||
void SetAntiAliasPass(const Device& device);
|
void SetAntiAliasPass(const Device& device);
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
void SetPostProcessPass(const Device& device);
|
||||||
|
#endif
|
||||||
void ReleaseRawImages();
|
void ReleaseRawImages();
|
||||||
|
|
||||||
u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const;
|
u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const;
|
||||||
@@ -95,6 +101,11 @@ private:
|
|||||||
Settings::AntiAliasing anti_alias_setting{};
|
Settings::AntiAliasing anti_alias_setting{};
|
||||||
std::variant<std::monostate, FXAA, SMAA> anti_alias{};
|
std::variant<std::monostate, FXAA, SMAA> anti_alias{};
|
||||||
std::variant<std::monostate, SGSR, FSR> sr_filter{};
|
std::variant<std::monostate, SGSR, FSR> sr_filter{};
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
std::optional<PostProcessChain> post_process{};
|
||||||
|
u64 post_process_generation{};
|
||||||
|
VkExtent2D post_process_extent{};
|
||||||
|
#endif
|
||||||
std::vector<u64> resource_ticks{};
|
std::vector<u64> resource_ticks{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,899 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include "common/fs/fs.h"
|
||||||
|
#include "common/fs/fs_util.h"
|
||||||
|
#include "common/logging.h"
|
||||||
|
#include "video_core/post_processing/fx_chain.h"
|
||||||
|
#include "video_core/post_processing/fx_compile.h"
|
||||||
|
#include "video_core/post_processing/fx_effect.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/post_process.h"
|
||||||
|
#include "video_core/renderer_vulkan/present/util.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_device.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr VkFormat BACKBUFFER_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||||
|
constexpr size_t NO_TEXTURE = ~size_t{0};
|
||||||
|
|
||||||
|
VkFormat ToVkFormat(reshadefx::texture_format format) {
|
||||||
|
switch (format) {
|
||||||
|
case reshadefx::texture_format::r8:
|
||||||
|
return VK_FORMAT_R8_UNORM;
|
||||||
|
case reshadefx::texture_format::r16f:
|
||||||
|
return VK_FORMAT_R16_SFLOAT;
|
||||||
|
case reshadefx::texture_format::r32f:
|
||||||
|
return VK_FORMAT_R32_SFLOAT;
|
||||||
|
case reshadefx::texture_format::rg8:
|
||||||
|
return VK_FORMAT_R8G8_UNORM;
|
||||||
|
case reshadefx::texture_format::rg16:
|
||||||
|
return VK_FORMAT_R16G16_UNORM;
|
||||||
|
case reshadefx::texture_format::rg16f:
|
||||||
|
return VK_FORMAT_R16G16_SFLOAT;
|
||||||
|
case reshadefx::texture_format::rg32f:
|
||||||
|
return VK_FORMAT_R32G32_SFLOAT;
|
||||||
|
case reshadefx::texture_format::rgba8:
|
||||||
|
return VK_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
case reshadefx::texture_format::rgba16:
|
||||||
|
return VK_FORMAT_R16G16B16A16_UNORM;
|
||||||
|
case reshadefx::texture_format::rgba16f:
|
||||||
|
return VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||||
|
case reshadefx::texture_format::rgba32f:
|
||||||
|
return VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||||
|
case reshadefx::texture_format::rgb10a2:
|
||||||
|
return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
|
||||||
|
default:
|
||||||
|
return VK_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSamplerAddressMode ToAddressMode(reshadefx::texture_address_mode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case reshadefx::texture_address_mode::wrap:
|
||||||
|
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||||
|
case reshadefx::texture_address_mode::mirror:
|
||||||
|
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
|
||||||
|
case reshadefx::texture_address_mode::border:
|
||||||
|
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
||||||
|
case reshadefx::texture_address_mode::clamp:
|
||||||
|
default:
|
||||||
|
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkBlendFactor ToBlendFactor(reshadefx::blend_factor func) {
|
||||||
|
switch (func) {
|
||||||
|
case reshadefx::blend_factor::zero:
|
||||||
|
return VK_BLEND_FACTOR_ZERO;
|
||||||
|
case reshadefx::blend_factor::source_color:
|
||||||
|
return VK_BLEND_FACTOR_SRC_COLOR;
|
||||||
|
case reshadefx::blend_factor::source_alpha:
|
||||||
|
return VK_BLEND_FACTOR_SRC_ALPHA;
|
||||||
|
case reshadefx::blend_factor::one_minus_source_color:
|
||||||
|
return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
|
||||||
|
case reshadefx::blend_factor::one_minus_source_alpha:
|
||||||
|
return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||||
|
case reshadefx::blend_factor::dest_color:
|
||||||
|
return VK_BLEND_FACTOR_DST_COLOR;
|
||||||
|
case reshadefx::blend_factor::dest_alpha:
|
||||||
|
return VK_BLEND_FACTOR_DST_ALPHA;
|
||||||
|
case reshadefx::blend_factor::one_minus_dest_color:
|
||||||
|
return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
|
||||||
|
case reshadefx::blend_factor::one_minus_dest_alpha:
|
||||||
|
return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
|
||||||
|
case reshadefx::blend_factor::one:
|
||||||
|
default:
|
||||||
|
return VK_BLEND_FACTOR_ONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkBlendOp ToBlendOp(reshadefx::blend_op op) {
|
||||||
|
switch (op) {
|
||||||
|
case reshadefx::blend_op::subtract:
|
||||||
|
return VK_BLEND_OP_SUBTRACT;
|
||||||
|
case reshadefx::blend_op::reverse_subtract:
|
||||||
|
return VK_BLEND_OP_REVERSE_SUBTRACT;
|
||||||
|
case reshadefx::blend_op::min:
|
||||||
|
return VK_BLEND_OP_MIN;
|
||||||
|
case reshadefx::blend_op::max:
|
||||||
|
return VK_BLEND_OP_MAX;
|
||||||
|
case reshadefx::blend_op::add:
|
||||||
|
default:
|
||||||
|
return VK_BLEND_OP_ADD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPrimitiveTopology ToTopology(reshadefx::primitive_topology topology) {
|
||||||
|
switch (topology) {
|
||||||
|
case reshadefx::primitive_topology::point_list:
|
||||||
|
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||||
|
case reshadefx::primitive_topology::line_list:
|
||||||
|
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||||
|
case reshadefx::primitive_topology::line_strip:
|
||||||
|
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||||
|
case reshadefx::primitive_topology::triangle_strip:
|
||||||
|
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
||||||
|
case reshadefx::primitive_topology::triangle_list:
|
||||||
|
default:
|
||||||
|
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::RenderPass CreateFxRenderPass(const Device& device, VkFormat format, bool clear) {
|
||||||
|
VkAttachmentLoadOp load_op = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||||
|
VkImageLayout initial_layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||||
|
if (clear) {
|
||||||
|
load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||||
|
initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkAttachmentDescription attachment{
|
||||||
|
.flags = 0,
|
||||||
|
.format = format,
|
||||||
|
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||||
|
.loadOp = load_op,
|
||||||
|
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||||
|
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||||
|
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||||
|
.initialLayout = initial_layout,
|
||||||
|
.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const VkAttachmentReference reference{
|
||||||
|
.attachment = 0,
|
||||||
|
.layout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const VkSubpassDescription subpass{
|
||||||
|
.flags = 0,
|
||||||
|
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
|
.inputAttachmentCount = 0,
|
||||||
|
.pInputAttachments = nullptr,
|
||||||
|
.colorAttachmentCount = 1,
|
||||||
|
.pColorAttachments = &reference,
|
||||||
|
.pResolveAttachments = nullptr,
|
||||||
|
.pDepthStencilAttachment = nullptr,
|
||||||
|
.preserveAttachmentCount = 0,
|
||||||
|
.pPreserveAttachments = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
return device.GetLogical().CreateRenderPass(VkRenderPassCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.attachmentCount = 1,
|
||||||
|
.pAttachments = &attachment,
|
||||||
|
.subpassCount = 1,
|
||||||
|
.pSubpasses = &subpass,
|
||||||
|
.dependencyCount = 0,
|
||||||
|
.pDependencies = nullptr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Pipeline CreateFxPipeline(const Device& device, vk::RenderPass& renderpass,
|
||||||
|
vk::PipelineLayout& layout, VkShaderModule vertex_shader,
|
||||||
|
VkShaderModule fragment_shader,
|
||||||
|
const reshadefx::pass& pass) {
|
||||||
|
const std::array<VkPipelineShaderStageCreateInfo, 2> stages{{
|
||||||
|
{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.stage = VK_SHADER_STAGE_VERTEX_BIT,
|
||||||
|
.module = vertex_shader,
|
||||||
|
.pName = pass.vs_entry_point.c_str(),
|
||||||
|
.pSpecializationInfo = nullptr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
.module = fragment_shader,
|
||||||
|
.pName = pass.ps_entry_point.c_str(),
|
||||||
|
.pSpecializationInfo = nullptr,
|
||||||
|
},
|
||||||
|
}};
|
||||||
|
|
||||||
|
constexpr VkPipelineVertexInputStateCreateInfo vertex_input{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.vertexBindingDescriptionCount = 0,
|
||||||
|
.pVertexBindingDescriptions = nullptr,
|
||||||
|
.vertexAttributeDescriptionCount = 0,
|
||||||
|
.pVertexAttributeDescriptions = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
const VkPipelineInputAssemblyStateCreateInfo input_assembly{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.topology = ToTopology(pass.topology),
|
||||||
|
.primitiveRestartEnable = VK_FALSE,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr VkPipelineViewportStateCreateInfo viewport_state{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.viewportCount = 1,
|
||||||
|
.pViewports = nullptr,
|
||||||
|
.scissorCount = 1,
|
||||||
|
.pScissors = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr VkPipelineRasterizationStateCreateInfo rasterization{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.depthClampEnable = VK_FALSE,
|
||||||
|
.rasterizerDiscardEnable = VK_FALSE,
|
||||||
|
.polygonMode = VK_POLYGON_MODE_FILL,
|
||||||
|
.cullMode = VK_CULL_MODE_NONE,
|
||||||
|
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
||||||
|
.depthBiasEnable = VK_FALSE,
|
||||||
|
.depthBiasConstantFactor = 0.0f,
|
||||||
|
.depthBiasClamp = 0.0f,
|
||||||
|
.depthBiasSlopeFactor = 0.0f,
|
||||||
|
.lineWidth = 1.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr VkPipelineMultisampleStateCreateInfo multisampling{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
|
||||||
|
.sampleShadingEnable = VK_FALSE,
|
||||||
|
.minSampleShading = 0.0f,
|
||||||
|
.pSampleMask = nullptr,
|
||||||
|
.alphaToCoverageEnable = VK_FALSE,
|
||||||
|
.alphaToOneEnable = VK_FALSE,
|
||||||
|
};
|
||||||
|
|
||||||
|
VkBool32 blend_enable = VK_FALSE;
|
||||||
|
if (pass.blend_enable[0]) {
|
||||||
|
blend_enable = VK_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkPipelineColorBlendAttachmentState blending{
|
||||||
|
.blendEnable = blend_enable,
|
||||||
|
.srcColorBlendFactor = ToBlendFactor(pass.source_color_blend_factor[0]),
|
||||||
|
.dstColorBlendFactor = ToBlendFactor(pass.dest_color_blend_factor[0]),
|
||||||
|
.colorBlendOp = ToBlendOp(pass.color_blend_op[0]),
|
||||||
|
.srcAlphaBlendFactor = ToBlendFactor(pass.source_alpha_blend_factor[0]),
|
||||||
|
.dstAlphaBlendFactor = ToBlendFactor(pass.dest_alpha_blend_factor[0]),
|
||||||
|
.alphaBlendOp = ToBlendOp(pass.alpha_blend_op[0]),
|
||||||
|
.colorWriteMask = static_cast<VkColorComponentFlags>(pass.render_target_write_mask[0] & 0xF),
|
||||||
|
};
|
||||||
|
|
||||||
|
const VkPipelineColorBlendStateCreateInfo color_blend{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.logicOpEnable = VK_FALSE,
|
||||||
|
.logicOp = VK_LOGIC_OP_COPY,
|
||||||
|
.attachmentCount = 1,
|
||||||
|
.pAttachments = &blending,
|
||||||
|
.blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::array dynamic_states{
|
||||||
|
VK_DYNAMIC_STATE_VIEWPORT,
|
||||||
|
VK_DYNAMIC_STATE_SCISSOR,
|
||||||
|
};
|
||||||
|
|
||||||
|
const VkPipelineDynamicStateCreateInfo dynamic_state{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.dynamicStateCount = static_cast<u32>(dynamic_states.size()),
|
||||||
|
.pDynamicStates = dynamic_states.data(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.stageCount = static_cast<u32>(stages.size()),
|
||||||
|
.pStages = stages.data(),
|
||||||
|
.pVertexInputState = &vertex_input,
|
||||||
|
.pInputAssemblyState = &input_assembly,
|
||||||
|
.pTessellationState = nullptr,
|
||||||
|
.pViewportState = &viewport_state,
|
||||||
|
.pRasterizationState = &rasterization,
|
||||||
|
.pMultisampleState = &multisampling,
|
||||||
|
.pDepthStencilState = nullptr,
|
||||||
|
.pColorBlendState = &color_blend,
|
||||||
|
.pDynamicState = &dynamic_state,
|
||||||
|
.layout = *layout,
|
||||||
|
.renderPass = *renderpass,
|
||||||
|
.subpass = 0,
|
||||||
|
.basePipelineHandle = nullptr,
|
||||||
|
.basePipelineIndex = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
PostProcessChain::PostProcessChain(const Device& device, MemoryAllocator& allocator,
|
||||||
|
Scheduler& scheduler, size_t image_count, VkExtent2D extent)
|
||||||
|
: m_extent(extent)
|
||||||
|
, m_image_count(u32(image_count))
|
||||||
|
{
|
||||||
|
m_start = std::chrono::steady_clock::now();
|
||||||
|
m_previous = m_start;
|
||||||
|
|
||||||
|
CreatePingPongImages(device, allocator);
|
||||||
|
|
||||||
|
m_fallback_sampler = CreateWrappedSampler(device);
|
||||||
|
m_fallback_image = CreateWrappedImage(allocator, VkExtent2D{1, 1}, VK_FORMAT_R8G8B8A8_UNORM);
|
||||||
|
m_fallback_view = CreateWrappedImageView(device, m_fallback_image, VK_FORMAT_R8G8B8A8_UNORM);
|
||||||
|
|
||||||
|
if (!BuildEffects(device, allocator, scheduler)) {
|
||||||
|
m_effects.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PostProcessChain::~PostProcessChain() = default;
|
||||||
|
|
||||||
|
bool PostProcessChain::Empty() const {
|
||||||
|
return m_effects.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PostProcessChain::CreatePingPongImages(const Device& device, MemoryAllocator& allocator) {
|
||||||
|
m_frames.resize(m_image_count);
|
||||||
|
for (auto& frame : m_frames) {
|
||||||
|
for (size_t i = 0; i < frame.images.size(); ++i) {
|
||||||
|
frame.images[i] = CreateWrappedImage(allocator, m_extent, BACKBUFFER_FORMAT);
|
||||||
|
frame.views[i] = CreateWrappedImageView(device, frame.images[i], BACKBUFFER_FORMAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PostProcessChain::BuildEffects(const Device& device, MemoryAllocator& allocator,
|
||||||
|
Scheduler& scheduler) {
|
||||||
|
const auto snapshot = VideoCore::FxChain::Instance().Snapshot();
|
||||||
|
if (snapshot.entries.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto root = VideoCore::GetFxRootDirectory();
|
||||||
|
|
||||||
|
for (size_t entry_index = 0; entry_index < snapshot.entries.size(); ++entry_index) {
|
||||||
|
const auto& entry = snapshot.entries[entry_index];
|
||||||
|
const auto path = root / entry.file;
|
||||||
|
|
||||||
|
const auto compiled = VideoCore::CompileFxEffect(path, m_extent.width, m_extent.height, 8);
|
||||||
|
if (!compiled.Succeeded()) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Post-processing effect '{}' failed to compile:\n{}",
|
||||||
|
entry.file, compiled.error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& module = compiled.module;
|
||||||
|
|
||||||
|
const auto technique = std::find_if(
|
||||||
|
module.techniques.begin(), module.techniques.end(),
|
||||||
|
[&](const reshadefx::technique& t) { return t.name == entry.technique; });
|
||||||
|
if (technique == module.techniques.end()) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "Effect '{}' has no technique '{}'", entry.file,
|
||||||
|
entry.technique);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect effect;
|
||||||
|
effect.entry_index = entry_index;
|
||||||
|
effect.file = entry.file;
|
||||||
|
effect.uniform_size = module.total_uniform_size;
|
||||||
|
for (const auto& [name, words] : compiled.entry_points) {
|
||||||
|
effect.shaders.emplace(name, CreateWrappedShaderModule(device, words));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& texture : module.textures) {
|
||||||
|
Texture out;
|
||||||
|
out.name = texture.unique_name;
|
||||||
|
out.extent = VkExtent2D{texture.width, texture.height};
|
||||||
|
out.format = ToVkFormat(texture.format);
|
||||||
|
|
||||||
|
if (texture.semantic == "COLOR") {
|
||||||
|
out.is_backbuffer = true;
|
||||||
|
effect.textures.push_back(std::move(out));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (texture.semantic == "DEPTH") {
|
||||||
|
effect.textures.push_back(std::move(out));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.image = CreateWrappedImage(allocator, out.extent, out.format);
|
||||||
|
out.view = CreateWrappedImageView(device, out.image, out.format);
|
||||||
|
effect.textures.push_back(std::move(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& sampler : module.samplers) {
|
||||||
|
Sampler out;
|
||||||
|
out.texture_index = NO_TEXTURE;
|
||||||
|
for (size_t i = 0; i < effect.textures.size(); ++i) {
|
||||||
|
if (effect.textures[i].name == sampler.texture_name) {
|
||||||
|
out.texture_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkFilter mag_filter = VK_FILTER_LINEAR;
|
||||||
|
VkFilter min_filter = VK_FILTER_LINEAR;
|
||||||
|
VkSamplerMipmapMode mip_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||||
|
const u32 filter = static_cast<u32>(sampler.filter);
|
||||||
|
if ((filter & 0x10) == 0) {
|
||||||
|
min_filter = VK_FILTER_NEAREST;
|
||||||
|
}
|
||||||
|
if ((filter & 0x04) == 0) {
|
||||||
|
mag_filter = VK_FILTER_NEAREST;
|
||||||
|
}
|
||||||
|
if ((filter & 0x01) == 0) {
|
||||||
|
mip_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.sampler = device.GetLogical().CreateSampler(VkSamplerCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.magFilter = mag_filter,
|
||||||
|
.minFilter = min_filter,
|
||||||
|
.mipmapMode = mip_mode,
|
||||||
|
.addressModeU = ToAddressMode(sampler.address_u),
|
||||||
|
.addressModeV = ToAddressMode(sampler.address_v),
|
||||||
|
.addressModeW = ToAddressMode(sampler.address_w),
|
||||||
|
.mipLodBias = sampler.lod_bias,
|
||||||
|
.anisotropyEnable = VK_FALSE,
|
||||||
|
.maxAnisotropy = 1.0f,
|
||||||
|
.compareEnable = VK_FALSE,
|
||||||
|
.compareOp = VK_COMPARE_OP_NEVER,
|
||||||
|
.minLod = sampler.min_lod,
|
||||||
|
.maxLod = sampler.max_lod,
|
||||||
|
.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
|
||||||
|
.unnormalizedCoordinates = VK_FALSE,
|
||||||
|
});
|
||||||
|
|
||||||
|
effect.samplers.push_back(std::move(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& uniform : module.uniforms) {
|
||||||
|
UniformWrite write;
|
||||||
|
write.name = uniform.name;
|
||||||
|
write.offset = uniform.offset;
|
||||||
|
write.components = std::min<u32>(uniform.type.components(), 4);
|
||||||
|
write.kind = UniformKind::Floating;
|
||||||
|
if (uniform.type.is_boolean()) {
|
||||||
|
write.kind = UniformKind::Boolean;
|
||||||
|
} else if (uniform.type.is_integral()) {
|
||||||
|
write.kind = UniformKind::Integer;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& annotation : uniform.annotations) {
|
||||||
|
if (annotation.name != "source") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::string& source = annotation.value.string_data;
|
||||||
|
if (source == "frametime") {
|
||||||
|
write.source = UniformSource::FrameTime;
|
||||||
|
} else if (source == "framecount") {
|
||||||
|
write.source = UniformSource::FrameCount;
|
||||||
|
} else if (source == "timer") {
|
||||||
|
write.source = UniformSource::Timer;
|
||||||
|
} else if (source == "random") {
|
||||||
|
write.source = UniformSource::Random;
|
||||||
|
} else if (source == "pingpong") {
|
||||||
|
write.source = UniformSource::PingPong;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uniform.has_initializer_value) {
|
||||||
|
for (u32 i = 0; i < write.components; ++i) {
|
||||||
|
if (write.kind == UniformKind::Floating) {
|
||||||
|
write.fallback[i] = uniform.initializer_value.as_float[i];
|
||||||
|
} else {
|
||||||
|
write.fallback[i] = static_cast<f32>(uniform.initializer_value.as_int[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write.args = {0.0f, 1.0f, 1.0f, 0.0f};
|
||||||
|
for (const auto& annotation : uniform.annotations) {
|
||||||
|
if (annotation.name == "min" && annotation.type.is_floating_point()) {
|
||||||
|
write.args[0] = annotation.value.as_float[0];
|
||||||
|
}
|
||||||
|
if (annotation.name == "max" && annotation.type.is_floating_point()) {
|
||||||
|
write.args[1] = annotation.value.as_float[0];
|
||||||
|
}
|
||||||
|
if (annotation.name == "step" && annotation.type.is_floating_point()) {
|
||||||
|
write.args[2] = annotation.value.as_float[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write.state = write.args[0];
|
||||||
|
|
||||||
|
effect.uniforms.push_back(std::move(write));
|
||||||
|
}
|
||||||
|
|
||||||
|
effect.uniform_layout = CreateWrappedDescriptorSetLayout(
|
||||||
|
device, std::array{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||||
|
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||||
|
|
||||||
|
for (const auto& pass : technique->passes) {
|
||||||
|
Pass out;
|
||||||
|
out.num_vertices = pass.num_vertices;
|
||||||
|
out.clear = pass.clear_render_targets != 0;
|
||||||
|
out.target_texture = NO_TEXTURE;
|
||||||
|
out.extent = m_extent;
|
||||||
|
|
||||||
|
const std::string& target = pass.render_target_names[0];
|
||||||
|
if (target.empty()) {
|
||||||
|
out.writes_backbuffer = true;
|
||||||
|
} else {
|
||||||
|
for (size_t i = 0; i < effect.textures.size(); ++i) {
|
||||||
|
if (effect.textures[i].name == target) {
|
||||||
|
out.target_texture = i;
|
||||||
|
out.extent = effect.textures[i].extent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (out.target_texture == NO_TEXTURE) {
|
||||||
|
LOG_WARNING(Render_Vulkan, "Effect '{}' pass targets unknown texture '{}'",
|
||||||
|
entry.file, target);
|
||||||
|
out.writes_backbuffer = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pass.viewport_width != 0 && pass.viewport_height != 0) {
|
||||||
|
out.extent = VkExtent2D{pass.viewport_width, pass.viewport_height};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkFormat target_format = BACKBUFFER_FORMAT;
|
||||||
|
if (!out.writes_backbuffer) {
|
||||||
|
target_format = effect.textures[out.target_texture].format;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 binding_count = 0;
|
||||||
|
for (const auto& binding : pass.sampler_bindings) {
|
||||||
|
SamplerBinding entry_binding;
|
||||||
|
entry_binding.binding = binding.entry_point_binding;
|
||||||
|
entry_binding.sampler_index = binding.index;
|
||||||
|
out.sampler_bindings.push_back(entry_binding);
|
||||||
|
binding_count = std::max(binding_count, binding.entry_point_binding + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<VkDescriptorType> sampler_types(
|
||||||
|
std::max<size_t>(binding_count, 1), VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||||
|
out.sampler_layout = CreateWrappedDescriptorSetLayout(
|
||||||
|
device, sampler_types, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||||
|
|
||||||
|
const std::array set_layouts{*effect.uniform_layout, *out.sampler_layout};
|
||||||
|
out.pipeline_layout =
|
||||||
|
device.GetLogical().CreatePipelineLayout(VkPipelineLayoutCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.setLayoutCount = static_cast<u32>(set_layouts.size()),
|
||||||
|
.pSetLayouts = set_layouts.data(),
|
||||||
|
.pushConstantRangeCount = 0,
|
||||||
|
.pPushConstantRanges = nullptr,
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto vertex_shader = effect.shaders.find(pass.vs_entry_point);
|
||||||
|
const auto fragment_shader = effect.shaders.find(pass.ps_entry_point);
|
||||||
|
if (vertex_shader == effect.shaders.end() ||
|
||||||
|
fragment_shader == effect.shaders.end()) {
|
||||||
|
LOG_WARNING(Render_Vulkan, "Effect '{}' pass references a missing entry point",
|
||||||
|
entry.file);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.renderpass = CreateFxRenderPass(device, target_format, out.clear);
|
||||||
|
out.pipeline = CreateFxPipeline(device, out.renderpass, out.pipeline_layout,
|
||||||
|
*vertex_shader->second, *fragment_shader->second, pass);
|
||||||
|
|
||||||
|
if (out.writes_backbuffer) {
|
||||||
|
out.backbuffer_slot = static_cast<u32>(effect.backbuffer_pass_count % 2);
|
||||||
|
++effect.backbuffer_pass_count;
|
||||||
|
for (u32 image = 0; image < m_image_count; ++image) {
|
||||||
|
for (size_t slot = 0; slot < 2; ++slot) {
|
||||||
|
out.framebuffers.push_back(CreateWrappedFramebuffer(
|
||||||
|
device, out.renderpass, m_frames[image].views[slot], out.extent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.framebuffers.push_back(
|
||||||
|
CreateWrappedFramebuffer(device, out.renderpass,
|
||||||
|
effect.textures[out.target_texture].view, out.extent));
|
||||||
|
}
|
||||||
|
|
||||||
|
effect.passes.push_back(std::move(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effect.passes.empty()) {
|
||||||
|
LOG_WARNING(Render_Vulkan, "Effect '{}' technique '{}' has no passes", entry.file,
|
||||||
|
entry.technique);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 buffer_size = std::max<u32>(effect.uniform_size, 4);
|
||||||
|
for (u32 i = 0; i < m_image_count; ++i) {
|
||||||
|
effect.uniform_buffers.push_back(
|
||||||
|
CreateWrappedBuffer(allocator, buffer_size, MemoryUsage::Upload));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t sampler_descriptor_count = 0;
|
||||||
|
size_t sampler_set_count = 0;
|
||||||
|
for (const auto& pass : effect.passes) {
|
||||||
|
sampler_descriptor_count +=
|
||||||
|
m_image_count * std::max<size_t>(pass.sampler_bindings.size(), 1);
|
||||||
|
sampler_set_count += m_image_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
effect.descriptor_pool = CreateWrappedDescriptorPool(
|
||||||
|
device, m_image_count + sampler_descriptor_count, m_image_count + sampler_set_count,
|
||||||
|
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER});
|
||||||
|
|
||||||
|
const std::vector<VkDescriptorSetLayout> uniform_layouts(m_image_count,
|
||||||
|
*effect.uniform_layout);
|
||||||
|
effect.uniform_sets = CreateWrappedDescriptorSets(effect.descriptor_pool, uniform_layouts);
|
||||||
|
|
||||||
|
for (auto& pass : effect.passes) {
|
||||||
|
const std::vector<VkDescriptorSetLayout> layouts(m_image_count, *pass.sampler_layout);
|
||||||
|
pass.sampler_sets = CreateWrappedDescriptorSets(effect.descriptor_pool, layouts);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_effects.push_back(std::move(effect));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PostProcessChain::PrepareImages(const Device& device, Scheduler& scheduler) {
|
||||||
|
if (m_images_ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler.Record([this](vk::CommandBuffer cmdbuf) {
|
||||||
|
ClearColorImage(cmdbuf, *m_fallback_image);
|
||||||
|
for (auto& frame : m_frames) {
|
||||||
|
for (auto& image : frame.images) {
|
||||||
|
ClearColorImage(cmdbuf, *image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto& effect : m_effects) {
|
||||||
|
for (auto& texture : effect.textures) {
|
||||||
|
if (texture.image) {
|
||||||
|
ClearColorImage(cmdbuf, *texture.image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
scheduler.Finish();
|
||||||
|
|
||||||
|
m_images_ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PostProcessChain::UpdateUniforms(Effect& effect, size_t image_index, f32 delta_seconds) {
|
||||||
|
if (effect.uniform_size == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static thread_local std::mt19937 rng{std::random_device{}()};
|
||||||
|
|
||||||
|
std::vector<u8> staging(effect.uniform_size, 0);
|
||||||
|
const f32 elapsed =
|
||||||
|
std::chrono::duration<f32>(std::chrono::steady_clock::now() - m_start).count();
|
||||||
|
const auto overrides = VideoCore::FxChain::Instance().EntryValues(effect.entry_index);
|
||||||
|
|
||||||
|
for (auto& uniform : effect.uniforms) {
|
||||||
|
std::array<f32, 4> value = uniform.fallback;
|
||||||
|
|
||||||
|
const auto override = overrides.find(uniform.name);
|
||||||
|
if (override != overrides.end()) {
|
||||||
|
value = override->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (uniform.source) {
|
||||||
|
case UniformSource::FrameTime:
|
||||||
|
value[0] = delta_seconds * 1000.0f;
|
||||||
|
break;
|
||||||
|
case UniformSource::FrameCount:
|
||||||
|
value[0] = static_cast<f32>(m_frame_count);
|
||||||
|
break;
|
||||||
|
case UniformSource::Timer:
|
||||||
|
value[0] = elapsed * 1000.0f;
|
||||||
|
break;
|
||||||
|
case UniformSource::Random: {
|
||||||
|
const int low = static_cast<int>(uniform.args[0]);
|
||||||
|
int high = static_cast<int>(uniform.args[1]);
|
||||||
|
if (high <= low) {
|
||||||
|
high = low + 1;
|
||||||
|
}
|
||||||
|
std::uniform_int_distribution<int> dist(low, high);
|
||||||
|
value[0] = static_cast<f32>(dist(rng));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UniformSource::PingPong: {
|
||||||
|
const f32 min_value = uniform.args[0];
|
||||||
|
f32 max_value = uniform.args[1];
|
||||||
|
if (max_value <= min_value) {
|
||||||
|
max_value = min_value + 1.0f;
|
||||||
|
}
|
||||||
|
f32 step = uniform.args[2];
|
||||||
|
if (step == 0.0f) {
|
||||||
|
step = 1.0f;
|
||||||
|
}
|
||||||
|
uniform.state += uniform.direction * step * delta_seconds;
|
||||||
|
if (uniform.state >= max_value) {
|
||||||
|
uniform.state = max_value;
|
||||||
|
uniform.direction = -1.0f;
|
||||||
|
}
|
||||||
|
if (uniform.state <= min_value) {
|
||||||
|
uniform.state = min_value;
|
||||||
|
uniform.direction = 1.0f;
|
||||||
|
}
|
||||||
|
value[0] = uniform.state;
|
||||||
|
value[1] = uniform.direction;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UniformSource::Value:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 i = 0; i < uniform.components; ++i) {
|
||||||
|
const size_t offset = uniform.offset + i * sizeof(u32);
|
||||||
|
if (offset + sizeof(u32) > staging.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (uniform.kind == UniformKind::Floating) {
|
||||||
|
const f32 element = value[i];
|
||||||
|
std::memcpy(staging.data() + offset, &element, sizeof(f32));
|
||||||
|
} else {
|
||||||
|
const s32 element = static_cast<s32>(value[i]);
|
||||||
|
std::memcpy(staging.data() + offset, &element, sizeof(s32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::span<u8> mapped = effect.uniform_buffers[image_index].Mapped();
|
||||||
|
if (mapped.size() >= staging.size()) {
|
||||||
|
std::memcpy(mapped.data(), staging.data(), staging.size());
|
||||||
|
effect.uniform_buffers[image_index].Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PostProcessChain::UpdateDescriptors(const Device& device, Effect& effect, Pass& pass,
|
||||||
|
size_t image_index, VkImageView backbuffer_view) {
|
||||||
|
std::vector<VkDescriptorImageInfo> image_infos;
|
||||||
|
std::vector<VkWriteDescriptorSet> writes;
|
||||||
|
image_infos.reserve(pass.sampler_bindings.size() + 1);
|
||||||
|
|
||||||
|
const VkDescriptorSet sampler_set = pass.sampler_sets[image_index];
|
||||||
|
|
||||||
|
for (const auto& binding : pass.sampler_bindings) {
|
||||||
|
VkImageView view = *m_fallback_view;
|
||||||
|
VkSampler handle = *m_fallback_sampler;
|
||||||
|
|
||||||
|
if (binding.sampler_index < effect.samplers.size()) {
|
||||||
|
const Sampler& sampler = effect.samplers[binding.sampler_index];
|
||||||
|
if (sampler.sampler) {
|
||||||
|
handle = *sampler.sampler;
|
||||||
|
}
|
||||||
|
if (sampler.texture_index != NO_TEXTURE) {
|
||||||
|
const Texture& texture = effect.textures[sampler.texture_index];
|
||||||
|
if (texture.is_backbuffer) {
|
||||||
|
view = backbuffer_view;
|
||||||
|
} else if (texture.view) {
|
||||||
|
view = *texture.view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writes.push_back(
|
||||||
|
CreateWriteDescriptorSet(image_infos, handle, view, sampler_set, binding.binding));
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkDescriptorBufferInfo buffer_info{
|
||||||
|
.buffer = *effect.uniform_buffers[image_index],
|
||||||
|
.offset = 0,
|
||||||
|
.range = VK_WHOLE_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
writes.push_back(VkWriteDescriptorSet{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.dstSet = effect.uniform_sets[image_index],
|
||||||
|
.dstBinding = 0,
|
||||||
|
.dstArrayElement = 0,
|
||||||
|
.descriptorCount = 1,
|
||||||
|
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||||
|
.pImageInfo = nullptr,
|
||||||
|
.pBufferInfo = &buffer_info,
|
||||||
|
.pTexelBufferView = nullptr,
|
||||||
|
});
|
||||||
|
|
||||||
|
device.GetLogical().UpdateDescriptorSets(writes, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void PostProcessChain::Draw(const Device& device, Scheduler& scheduler, size_t image_index,
|
||||||
|
VkImage* inout_image, VkImageView* inout_image_view) {
|
||||||
|
if (m_effects.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrepareImages(device, scheduler);
|
||||||
|
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
const f32 delta_seconds = std::chrono::duration<f32>(now - m_previous).count();
|
||||||
|
m_previous = now;
|
||||||
|
++m_frame_count;
|
||||||
|
|
||||||
|
FrameImages& frame = m_frames[image_index];
|
||||||
|
VkImage current_image = *inout_image;
|
||||||
|
VkImageView current_view = *inout_image_view;
|
||||||
|
u32 slot = 0;
|
||||||
|
|
||||||
|
for (auto& effect : m_effects) {
|
||||||
|
UpdateUniforms(effect, image_index, delta_seconds);
|
||||||
|
|
||||||
|
for (size_t pass_index = 0; pass_index < effect.passes.size(); ++pass_index) {
|
||||||
|
Pass& pass = effect.passes[pass_index];
|
||||||
|
|
||||||
|
UpdateDescriptors(device, effect, pass, image_index, current_view);
|
||||||
|
|
||||||
|
VkFramebuffer framebuffer{};
|
||||||
|
VkImage target_image{};
|
||||||
|
if (pass.writes_backbuffer) {
|
||||||
|
const u32 target_slot = (slot + 1) % 2;
|
||||||
|
framebuffer = *pass.framebuffers[image_index * 2 + target_slot];
|
||||||
|
target_image = *frame.images[target_slot];
|
||||||
|
} else {
|
||||||
|
framebuffer = *pass.framebuffers[0];
|
||||||
|
target_image = *effect.textures[pass.target_texture].image;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkImage source_image = current_image;
|
||||||
|
const VkRenderPass renderpass = *pass.renderpass;
|
||||||
|
const VkPipeline pipeline = *pass.pipeline;
|
||||||
|
const VkPipelineLayout layout = *pass.pipeline_layout;
|
||||||
|
const VkDescriptorSet uniform_set = effect.uniform_sets[image_index];
|
||||||
|
const VkDescriptorSet sampler_set = pass.sampler_sets[image_index];
|
||||||
|
const VkExtent2D extent = pass.extent;
|
||||||
|
const u32 vertices = pass.num_vertices;
|
||||||
|
|
||||||
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
|
scheduler.Record([=](vk::CommandBuffer cmdbuf) {
|
||||||
|
TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL);
|
||||||
|
TransitionImageLayout(cmdbuf, target_image, VK_IMAGE_LAYOUT_GENERAL);
|
||||||
|
BeginRenderPass(cmdbuf, renderpass, framebuffer, extent);
|
||||||
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0,
|
||||||
|
std::array{uniform_set, sampler_set}, {});
|
||||||
|
cmdbuf.Draw(vertices, 1, 0, 0);
|
||||||
|
cmdbuf.EndRenderPass();
|
||||||
|
TransitionImageLayout(cmdbuf, target_image, VK_IMAGE_LAYOUT_GENERAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pass.writes_backbuffer) {
|
||||||
|
slot = (slot + 1) % 2;
|
||||||
|
current_image = *frame.images[slot];
|
||||||
|
current_view = *frame.views[slot];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*inout_image = current_image;
|
||||||
|
*inout_image_view = current_view;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
class Scheduler;
|
||||||
|
|
||||||
|
class PostProcessChain {
|
||||||
|
public:
|
||||||
|
explicit PostProcessChain(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler,
|
||||||
|
size_t image_count, VkExtent2D extent);
|
||||||
|
~PostProcessChain();
|
||||||
|
|
||||||
|
void Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage* inout_image,
|
||||||
|
VkImageView* inout_image_view);
|
||||||
|
|
||||||
|
bool Empty() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class UniformKind : u32 {
|
||||||
|
Boolean,
|
||||||
|
Integer,
|
||||||
|
Floating,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class UniformSource : u32 {
|
||||||
|
Value,
|
||||||
|
FrameTime,
|
||||||
|
FrameCount,
|
||||||
|
Timer,
|
||||||
|
Random,
|
||||||
|
PingPong,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UniformWrite {
|
||||||
|
std::string name;
|
||||||
|
u32 offset{};
|
||||||
|
u32 components{};
|
||||||
|
UniformKind kind{UniformKind::Floating};
|
||||||
|
UniformSource source{UniformSource::Value};
|
||||||
|
std::array<f32, 4> fallback{};
|
||||||
|
std::array<f32, 4> args{};
|
||||||
|
f32 state{};
|
||||||
|
f32 direction{1.0f};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Texture {
|
||||||
|
std::string name;
|
||||||
|
vk::Image image{};
|
||||||
|
vk::ImageView view{};
|
||||||
|
VkExtent2D extent{};
|
||||||
|
VkFormat format{};
|
||||||
|
bool is_backbuffer{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Sampler {
|
||||||
|
vk::Sampler sampler{};
|
||||||
|
size_t texture_index{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SamplerBinding {
|
||||||
|
u32 binding{};
|
||||||
|
size_t sampler_index{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Pass {
|
||||||
|
vk::RenderPass renderpass{};
|
||||||
|
vk::Pipeline pipeline{};
|
||||||
|
vk::DescriptorSetLayout sampler_layout{};
|
||||||
|
vk::PipelineLayout pipeline_layout{};
|
||||||
|
vk::DescriptorSets sampler_sets{};
|
||||||
|
std::vector<SamplerBinding> sampler_bindings{};
|
||||||
|
std::vector<vk::Framebuffer> framebuffers{};
|
||||||
|
size_t target_texture{};
|
||||||
|
VkExtent2D extent{};
|
||||||
|
u32 num_vertices{3};
|
||||||
|
bool clear{};
|
||||||
|
bool writes_backbuffer{};
|
||||||
|
u32 backbuffer_slot{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Effect {
|
||||||
|
size_t entry_index{};
|
||||||
|
std::string file{};
|
||||||
|
std::map<std::string, vk::ShaderModule> shaders{};
|
||||||
|
std::vector<Texture> textures{};
|
||||||
|
std::vector<Sampler> samplers{};
|
||||||
|
std::vector<Pass> passes{};
|
||||||
|
std::vector<UniformWrite> uniforms{};
|
||||||
|
u32 uniform_size{};
|
||||||
|
std::vector<vk::Buffer> uniform_buffers{};
|
||||||
|
vk::DescriptorSetLayout uniform_layout{};
|
||||||
|
vk::DescriptorPool descriptor_pool{};
|
||||||
|
vk::DescriptorSets uniform_sets{};
|
||||||
|
size_t backbuffer_pass_count{};
|
||||||
|
u32 backbuffer_slots{1};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FrameImages {
|
||||||
|
std::array<vk::Image, 2> images{};
|
||||||
|
std::array<vk::ImageView, 2> views{};
|
||||||
|
};
|
||||||
|
|
||||||
|
bool BuildEffects(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler);
|
||||||
|
void CreatePingPongImages(const Device& device, MemoryAllocator& allocator);
|
||||||
|
void PrepareImages(const Device& device, Scheduler& scheduler);
|
||||||
|
void UpdateUniforms(Effect& effect, size_t image_index, f32 delta_seconds);
|
||||||
|
void UpdateDescriptors(const Device& device, Effect& effect, Pass& pass, size_t image_index,
|
||||||
|
VkImageView backbuffer_view);
|
||||||
|
|
||||||
|
const VkExtent2D m_extent;
|
||||||
|
const u32 m_image_count;
|
||||||
|
|
||||||
|
std::vector<Effect> m_effects{};
|
||||||
|
std::vector<FrameImages> m_frames{};
|
||||||
|
|
||||||
|
vk::Sampler m_fallback_sampler{};
|
||||||
|
vk::Image m_fallback_image{};
|
||||||
|
vk::ImageView m_fallback_view{};
|
||||||
|
|
||||||
|
std::chrono::steady_clock::time_point m_start{};
|
||||||
|
std::chrono::steady_clock::time_point m_previous{};
|
||||||
|
u64 m_frame_count{};
|
||||||
|
bool m_images_ready{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
@@ -24,6 +24,10 @@
|
|||||||
#include "video_core/gpu.h"
|
#include "video_core/gpu.h"
|
||||||
#include "video_core/present.h"
|
#include "video_core/present.h"
|
||||||
#include "video_core/renderer_vulkan/present/util.h"
|
#include "video_core/renderer_vulkan/present/util.h"
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
#include "video_core/post_processing/fx_chain.h"
|
||||||
|
#include "video_core/post_processing/fx_effect.h"
|
||||||
|
#endif
|
||||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
#include "video_core/renderer_vulkan/vk_blit_screen.h"
|
#include "video_core/renderer_vulkan/vk_blit_screen.h"
|
||||||
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||||
@@ -183,6 +187,12 @@ try
|
|||||||
scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
|
scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::ReloadFxCatalog();
|
||||||
|
VideoCore::FxChain::Instance().LoadFromSettings();
|
||||||
|
VideoCore::FxChain::Instance().DropUnknownEntries();
|
||||||
|
#endif
|
||||||
|
|
||||||
Report();
|
Report();
|
||||||
} catch (const vk::Exception& exception) {
|
} catch (const vk::Exception& exception) {
|
||||||
LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what());
|
LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what());
|
||||||
|
|||||||
@@ -250,6 +250,12 @@ if (YUZU_CRASH_DUMPS)
|
|||||||
target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS)
|
target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (ENABLE_RESHADE)
|
||||||
|
target_sources(yuzu PRIVATE
|
||||||
|
configuration/configure_post_processing.cpp
|
||||||
|
configuration/configure_post_processing.h)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (CXX_CLANG)
|
if (CXX_CLANG)
|
||||||
target_compile_definitions(yuzu PRIVATE
|
target_compile_definitions(yuzu PRIVATE
|
||||||
$<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,15>:CANNOT_EXPLICITLY_INSTANTIATE>)
|
$<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,15>:CANNOT_EXPLICITLY_INSTANTIATE>)
|
||||||
|
|||||||
@@ -0,0 +1,378 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QSlider>
|
||||||
|
#include <QToolButton>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "common/fs/fs.h"
|
||||||
|
#include "common/fs/fs_util.h"
|
||||||
|
#include "video_core/post_processing/fx_chain.h"
|
||||||
|
#include "video_core/post_processing/fx_effect.h"
|
||||||
|
#include "yuzu/configuration/configure_post_processing.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::array<float, 4> CurrentValue(int index, const VideoCore::FxUniformDesc& uniform) {
|
||||||
|
auto& chain = VideoCore::FxChain::Instance();
|
||||||
|
if (chain.HasValue(static_cast<size_t>(index), uniform.name)) {
|
||||||
|
return chain.GetValue(static_cast<size_t>(index), uniform.name);
|
||||||
|
}
|
||||||
|
return uniform.default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SliderSteps(const VideoCore::FxUniformDesc& uniform) {
|
||||||
|
const float span = uniform.ui_max - uniform.ui_min;
|
||||||
|
const int steps = static_cast<int>(std::lround(span / uniform.ui_step));
|
||||||
|
if (steps < 1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FormatValue(const VideoCore::FxUniformDesc& uniform, float value) {
|
||||||
|
if (uniform.kind == VideoCore::FxUniformKind::Floating) {
|
||||||
|
return QString::number(value, 'f', 3);
|
||||||
|
}
|
||||||
|
return QString::number(static_cast<int>(std::lround(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SlotLabel(const VideoCore::FxEffectDesc& effect, const std::string& technique) {
|
||||||
|
const QString name = QString::fromStdString(effect.name);
|
||||||
|
if (effect.techniques.size() == 1) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return name + QStringLiteral(" · ") + QString::fromStdString(technique);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(parent) {
|
||||||
|
setWindowTitle(tr("Post-Processing Effects"));
|
||||||
|
setMinimumWidth(560);
|
||||||
|
setMinimumHeight(460);
|
||||||
|
|
||||||
|
auto* root = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
auto* description = new QLabel(
|
||||||
|
tr("ReShade FX effects are loaded from the post_shaders folder in the Eden data "
|
||||||
|
"directory. Changes apply immediately while a game is running."),
|
||||||
|
this);
|
||||||
|
description->setWordWrap(true);
|
||||||
|
root->addWidget(description);
|
||||||
|
|
||||||
|
auto* scroll = new QScrollArea(this);
|
||||||
|
scroll->setWidgetResizable(true);
|
||||||
|
slots_container = new QWidget(scroll);
|
||||||
|
slots_layout = new QVBoxLayout(slots_container);
|
||||||
|
slots_layout->setAlignment(Qt::AlignTop);
|
||||||
|
scroll->setWidget(slots_container);
|
||||||
|
root->addWidget(scroll, 1);
|
||||||
|
|
||||||
|
auto* actions = new QHBoxLayout();
|
||||||
|
|
||||||
|
auto* add_button = new QPushButton(tr("Add Effect"), this);
|
||||||
|
connect(add_button, &QPushButton::clicked, this, [this]() {
|
||||||
|
for (const auto& effect : VideoCore::GetFxCatalog()) {
|
||||||
|
if (!effect.Valid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
VideoCore::FxChain::Instance().Append(effect.file, effect.techniques.front());
|
||||||
|
ApplyStructuralChange();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
actions->addWidget(add_button);
|
||||||
|
|
||||||
|
auto* reload_button = new QPushButton(tr("Reload From Disk"), this);
|
||||||
|
connect(reload_button, &QPushButton::clicked, this, [this]() {
|
||||||
|
VideoCore::ReloadFxCatalog();
|
||||||
|
VideoCore::FxChain::Instance().DropUnknownEntries();
|
||||||
|
ApplyStructuralChange();
|
||||||
|
});
|
||||||
|
actions->addWidget(reload_button);
|
||||||
|
|
||||||
|
auto* open_button = new QPushButton(tr("Open Folder"), this);
|
||||||
|
connect(open_button, &QPushButton::clicked, this, []() {
|
||||||
|
const auto path = VideoCore::GetFxRootDirectory();
|
||||||
|
void(Common::FS::CreateDirs(path));
|
||||||
|
QDesktopServices::openUrl(
|
||||||
|
QUrl::fromLocalFile(QString::fromStdString(Common::FS::PathToUTF8String(path))));
|
||||||
|
});
|
||||||
|
actions->addWidget(open_button);
|
||||||
|
|
||||||
|
actions->addStretch();
|
||||||
|
root->addLayout(actions);
|
||||||
|
|
||||||
|
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, this);
|
||||||
|
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::close);
|
||||||
|
root->addWidget(buttons);
|
||||||
|
|
||||||
|
RebuildRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigurePostProcessing::~ConfigurePostProcessing() = default;
|
||||||
|
|
||||||
|
void ConfigurePostProcessing::ApplyStructuralChange() {
|
||||||
|
VideoCore::FxChain::Instance().StoreToSettings();
|
||||||
|
RebuildRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigurePostProcessing::PopulateEffectCombo(QComboBox* combo,
|
||||||
|
const VideoCore::FxChainEntry& entry) const {
|
||||||
|
combo->clear();
|
||||||
|
int selected = -1;
|
||||||
|
|
||||||
|
for (const auto& effect : VideoCore::GetFxCatalog()) {
|
||||||
|
if (!effect.Valid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const auto& technique : effect.techniques) {
|
||||||
|
const QString key = QString::fromStdString(effect.file + "|" + technique);
|
||||||
|
combo->addItem(SlotLabel(effect, technique), key);
|
||||||
|
if (effect.file == entry.file && technique == entry.technique) {
|
||||||
|
selected = combo->count() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected >= 0) {
|
||||||
|
combo->setCurrentIndex(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigurePostProcessing::BuildUniformWidget(QWidget* parent, QVBoxLayout* layout, int index,
|
||||||
|
const VideoCore::FxUniformDesc& uniform) {
|
||||||
|
const auto value = CurrentValue(index, uniform);
|
||||||
|
const QString label = QString::fromStdString(uniform.label);
|
||||||
|
|
||||||
|
if (uniform.ui_type == VideoCore::FxUiType::CheckBox) {
|
||||||
|
auto* box = new QCheckBox(label, parent);
|
||||||
|
box->setChecked(value[0] != 0.0f);
|
||||||
|
if (!uniform.tooltip.empty()) {
|
||||||
|
box->setToolTip(QString::fromStdString(uniform.tooltip));
|
||||||
|
}
|
||||||
|
const std::string name = uniform.name;
|
||||||
|
connect(box, &QCheckBox::toggled, this, [index, name](bool checked) {
|
||||||
|
std::array<float, 4> next{};
|
||||||
|
if (checked) {
|
||||||
|
next[0] = 1.0f;
|
||||||
|
}
|
||||||
|
VideoCore::FxChain::Instance().SetValue(static_cast<size_t>(index), name, next);
|
||||||
|
VideoCore::FxChain::Instance().StoreToSettings();
|
||||||
|
});
|
||||||
|
layout->addWidget(box);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uniform.ui_type == VideoCore::FxUiType::Combo ||
|
||||||
|
uniform.ui_type == VideoCore::FxUiType::Radio) {
|
||||||
|
auto* row = new QHBoxLayout();
|
||||||
|
row->addWidget(new QLabel(label, parent));
|
||||||
|
auto* combo = new QComboBox(parent);
|
||||||
|
for (size_t i = 0; i < uniform.items.size(); ++i) {
|
||||||
|
combo->addItem(QString::fromStdString(uniform.items[i]), static_cast<int>(i));
|
||||||
|
}
|
||||||
|
if (combo->count() == 0) {
|
||||||
|
combo->addItem(tr("Enabled"), 1);
|
||||||
|
combo->addItem(tr("Disabled"), 0);
|
||||||
|
}
|
||||||
|
const int current = static_cast<int>(std::lround(value[0]));
|
||||||
|
if (current >= 0 && current < combo->count()) {
|
||||||
|
combo->setCurrentIndex(current);
|
||||||
|
}
|
||||||
|
if (!uniform.tooltip.empty()) {
|
||||||
|
combo->setToolTip(QString::fromStdString(uniform.tooltip));
|
||||||
|
}
|
||||||
|
const std::string name = uniform.name;
|
||||||
|
connect(combo, &QComboBox::currentIndexChanged, this, [index, name](int selected) {
|
||||||
|
std::array<float, 4> next{};
|
||||||
|
next[0] = static_cast<float>(selected);
|
||||||
|
VideoCore::FxChain::Instance().SetValue(static_cast<size_t>(index), name, next);
|
||||||
|
VideoCore::FxChain::Instance().StoreToSettings();
|
||||||
|
});
|
||||||
|
row->addWidget(combo, 1);
|
||||||
|
layout->addLayout(row);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* grid = new QGridLayout();
|
||||||
|
for (unsigned component = 0; component < uniform.components; ++component) {
|
||||||
|
QString component_label = label;
|
||||||
|
if (uniform.components > 1) {
|
||||||
|
component_label = label + QStringLiteral(" [%1]").arg(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* name_label = new QLabel(component_label, parent);
|
||||||
|
auto* value_label = new QLabel(parent);
|
||||||
|
value_label->setMinimumWidth(64);
|
||||||
|
value_label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||||
|
value_label->setText(FormatValue(uniform, value[component]));
|
||||||
|
|
||||||
|
auto* slider = new QSlider(Qt::Horizontal, parent);
|
||||||
|
slider->setMinimum(0);
|
||||||
|
slider->setMaximum(SliderSteps(uniform));
|
||||||
|
slider->setValue(
|
||||||
|
static_cast<int>(std::lround((value[component] - uniform.ui_min) / uniform.ui_step)));
|
||||||
|
if (!uniform.tooltip.empty()) {
|
||||||
|
slider->setToolTip(QString::fromStdString(uniform.tooltip));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string name = uniform.name;
|
||||||
|
const auto desc = uniform;
|
||||||
|
connect(slider, &QSlider::valueChanged, this,
|
||||||
|
[index, name, desc, component, value_label](int steps) {
|
||||||
|
auto next = CurrentValue(index, desc);
|
||||||
|
next[component] = desc.ui_min + static_cast<float>(steps) * desc.ui_step;
|
||||||
|
VideoCore::FxChain::Instance().SetValue(static_cast<size_t>(index), name, next);
|
||||||
|
value_label->setText(FormatValue(desc, next[component]));
|
||||||
|
});
|
||||||
|
connect(slider, &QSlider::sliderReleased, this,
|
||||||
|
[]() { VideoCore::FxChain::Instance().StoreToSettings(); });
|
||||||
|
|
||||||
|
grid->addWidget(name_label, static_cast<int>(component), 0);
|
||||||
|
grid->addWidget(slider, static_cast<int>(component), 1);
|
||||||
|
grid->addWidget(value_label, static_cast<int>(component), 2);
|
||||||
|
}
|
||||||
|
layout->addLayout(grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget* ConfigurePostProcessing::BuildSlot(int index, const VideoCore::FxChainEntry& entry) {
|
||||||
|
auto* group = new QGroupBox(slots_container);
|
||||||
|
auto* layout = new QVBoxLayout(group);
|
||||||
|
|
||||||
|
auto* header = new QHBoxLayout();
|
||||||
|
|
||||||
|
auto* combo = new QComboBox(group);
|
||||||
|
PopulateEffectCombo(combo, entry);
|
||||||
|
connect(combo, &QComboBox::currentIndexChanged, this, [this, index, combo](int) {
|
||||||
|
const QString key = combo->currentData().toString();
|
||||||
|
const qsizetype separator = key.indexOf(QLatin1Char('|'));
|
||||||
|
if (separator < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VideoCore::FxChain::Instance().Replace(static_cast<size_t>(index),
|
||||||
|
key.left(separator).toStdString(),
|
||||||
|
key.mid(separator + 1).toStdString());
|
||||||
|
ApplyStructuralChange();
|
||||||
|
});
|
||||||
|
header->addWidget(combo, 1);
|
||||||
|
|
||||||
|
auto* up_button = new QToolButton(group);
|
||||||
|
up_button->setText(QStringLiteral("▲"));
|
||||||
|
up_button->setEnabled(index > 0);
|
||||||
|
connect(up_button, &QToolButton::clicked, this, [this, index]() {
|
||||||
|
VideoCore::FxChain::Instance().Move(static_cast<size_t>(index), -1);
|
||||||
|
ApplyStructuralChange();
|
||||||
|
});
|
||||||
|
header->addWidget(up_button);
|
||||||
|
|
||||||
|
auto* down_button = new QToolButton(group);
|
||||||
|
down_button->setText(QStringLiteral("▼"));
|
||||||
|
down_button->setEnabled(static_cast<size_t>(index) + 1 < VideoCore::FxChain::Instance().Size());
|
||||||
|
connect(down_button, &QToolButton::clicked, this, [this, index]() {
|
||||||
|
VideoCore::FxChain::Instance().Move(static_cast<size_t>(index), 1);
|
||||||
|
ApplyStructuralChange();
|
||||||
|
});
|
||||||
|
header->addWidget(down_button);
|
||||||
|
|
||||||
|
auto* reset_button = new QToolButton(group);
|
||||||
|
reset_button->setText(QStringLiteral("⟲"));
|
||||||
|
reset_button->setToolTip(tr("Reset to defaults"));
|
||||||
|
connect(reset_button, &QToolButton::clicked, this, [this, index]() {
|
||||||
|
VideoCore::FxChain::Instance().ResetValues(static_cast<size_t>(index));
|
||||||
|
ApplyStructuralChange();
|
||||||
|
});
|
||||||
|
header->addWidget(reset_button);
|
||||||
|
|
||||||
|
auto* remove_button = new QToolButton(group);
|
||||||
|
remove_button->setText(QStringLiteral("✕"));
|
||||||
|
connect(remove_button, &QToolButton::clicked, this, [this, index]() {
|
||||||
|
VideoCore::FxChain::Instance().Remove(static_cast<size_t>(index));
|
||||||
|
ApplyStructuralChange();
|
||||||
|
});
|
||||||
|
header->addWidget(remove_button);
|
||||||
|
|
||||||
|
layout->addLayout(header);
|
||||||
|
|
||||||
|
const VideoCore::FxEffectDesc* effect = VideoCore::FindFxEffect(entry.file);
|
||||||
|
if (effect == nullptr) {
|
||||||
|
auto* missing =
|
||||||
|
new QLabel(tr("Effect '%1' was not found.").arg(QString::fromStdString(entry.file)),
|
||||||
|
group);
|
||||||
|
missing->setWordWrap(true);
|
||||||
|
layout->addWidget(missing);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!effect->error.empty()) {
|
||||||
|
auto* failed = new QLabel(
|
||||||
|
tr("Effect failed to compile:\n%1").arg(QString::fromStdString(effect->error)), group);
|
||||||
|
failed->setWordWrap(true);
|
||||||
|
layout->addWidget(failed);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string current_category;
|
||||||
|
for (const auto& uniform : effect->uniforms) {
|
||||||
|
if (uniform.category != current_category) {
|
||||||
|
current_category = uniform.category;
|
||||||
|
if (!current_category.empty()) {
|
||||||
|
auto* category = new QLabel(QString::fromStdString(current_category), group);
|
||||||
|
category->setStyleSheet(QStringLiteral("font-weight: bold;"));
|
||||||
|
layout->addWidget(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BuildUniformWidget(group, layout, index, uniform);
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigurePostProcessing::RebuildRows() {
|
||||||
|
QLayoutItem* item = nullptr;
|
||||||
|
while ((item = slots_layout->takeAt(0)) != nullptr) {
|
||||||
|
if (item->widget() != nullptr) {
|
||||||
|
item->widget()->deleteLater();
|
||||||
|
}
|
||||||
|
delete item;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_usable = false;
|
||||||
|
for (const auto& effect : VideoCore::GetFxCatalog()) {
|
||||||
|
if (effect.Valid()) {
|
||||||
|
has_usable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!has_usable) {
|
||||||
|
auto* empty = new QLabel(
|
||||||
|
tr("No usable ReShade FX effects were found. Place .fx files in the post_shaders "
|
||||||
|
"folder."),
|
||||||
|
slots_container);
|
||||||
|
empty->setWordWrap(true);
|
||||||
|
slots_layout->addWidget(empty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto entries = VideoCore::FxChain::Instance().Entries();
|
||||||
|
for (size_t i = 0; i < entries.size(); ++i) {
|
||||||
|
slots_layout->addWidget(BuildSlot(static_cast<int>(i), entries[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
class QComboBox;
|
||||||
|
class QVBoxLayout;
|
||||||
|
class QWidget;
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
struct FxChainEntry;
|
||||||
|
struct FxEffectDesc;
|
||||||
|
struct FxUniformDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfigurePostProcessing : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ConfigurePostProcessing(QWidget* parent = nullptr);
|
||||||
|
~ConfigurePostProcessing() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void RebuildRows();
|
||||||
|
QWidget* BuildSlot(int index, const VideoCore::FxChainEntry& entry);
|
||||||
|
void BuildUniformWidget(QWidget* parent, QVBoxLayout* layout, int index,
|
||||||
|
const VideoCore::FxUniformDesc& uniform);
|
||||||
|
void PopulateEffectCombo(QComboBox* combo, const VideoCore::FxChainEntry& entry) const;
|
||||||
|
void ApplyStructuralChange();
|
||||||
|
|
||||||
|
QVBoxLayout* slots_layout{};
|
||||||
|
QWidget* slots_container{};
|
||||||
|
};
|
||||||
@@ -148,6 +148,7 @@
|
|||||||
<addaction name="action_Show_Filter_Bar"/>
|
<addaction name="action_Show_Filter_Bar"/>
|
||||||
<addaction name="action_Show_Status_Bar"/>
|
<addaction name="action_Show_Status_Bar"/>
|
||||||
<addaction name="action_Show_Performance_Overlay"/>
|
<addaction name="action_Show_Performance_Overlay"/>
|
||||||
|
<addaction name="action_Post_Processing_Shaders"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="menu_Reset_Window_Size"/>
|
<addaction name="menu_Reset_Window_Size"/>
|
||||||
<addaction name="menu_View_Debugging"/>
|
<addaction name="menu_View_Debugging"/>
|
||||||
@@ -610,6 +611,11 @@
|
|||||||
<string>Show &Performance Overlay</string>
|
<string>Show &Performance Overlay</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="action_Post_Processing_Shaders">
|
||||||
|
<property name="text">
|
||||||
|
<string>Post-Processing &Shaders...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="action_Carousel_View">
|
<action name="action_Carousel_View">
|
||||||
<property name="checkable">
|
<property name="checkable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
|||||||
@@ -13,6 +13,9 @@
|
|||||||
#include "common/settings_enums.h"
|
#include "common/settings_enums.h"
|
||||||
#include "frontend_common/settings_generator.h"
|
#include "frontend_common/settings_generator.h"
|
||||||
#include "render/performance_overlay.h"
|
#include "render/performance_overlay.h"
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
#include "configuration/configure_post_processing.h"
|
||||||
|
#endif
|
||||||
#include "updater/update_dialog.h"
|
#include "updater/update_dialog.h"
|
||||||
|
|
||||||
#include "common/fs/ryujinx_compat.h"
|
#include "common/fs/ryujinx_compat.h"
|
||||||
@@ -1518,6 +1521,11 @@ void MainWindow::ConnectMenuEvents() {
|
|||||||
connect_menu(ui->action_Show_Filter_Bar, &MainWindow::OnToggleFilterBar);
|
connect_menu(ui->action_Show_Filter_Bar, &MainWindow::OnToggleFilterBar);
|
||||||
connect_menu(ui->action_Show_Status_Bar, &MainWindow::OnToggleStatusBar);
|
connect_menu(ui->action_Show_Status_Bar, &MainWindow::OnToggleStatusBar);
|
||||||
connect_menu(ui->action_Show_Performance_Overlay, &MainWindow::OnTogglePerfOverlay);
|
connect_menu(ui->action_Show_Performance_Overlay, &MainWindow::OnTogglePerfOverlay);
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
connect_menu(ui->action_Post_Processing_Shaders, &MainWindow::OnPostProcessingShaders);
|
||||||
|
#else
|
||||||
|
ui->action_Post_Processing_Shaders->setVisible(false);
|
||||||
|
#endif
|
||||||
|
|
||||||
connect_menu(ui->action_Reset_Window_Size_720, &MainWindow::ResetWindowSize720);
|
connect_menu(ui->action_Reset_Window_Size_720, &MainWindow::ResetWindowSize720);
|
||||||
connect_menu(ui->action_Reset_Window_Size_900, &MainWindow::ResetWindowSize900);
|
connect_menu(ui->action_Reset_Window_Size_900, &MainWindow::ResetWindowSize900);
|
||||||
@@ -3900,6 +3908,22 @@ void MainWindow::OnTogglePerfOverlay() {
|
|||||||
perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked());
|
perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
void MainWindow::OnPostProcessingShaders() {
|
||||||
|
if (post_processing_dialog == nullptr) {
|
||||||
|
post_processing_dialog = new ConfigurePostProcessing(this);
|
||||||
|
connect(post_processing_dialog, &QDialog::finished, post_processing_dialog, [this]() {
|
||||||
|
post_processing_dialog->deleteLater();
|
||||||
|
post_processing_dialog = nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
post_processing_dialog->show();
|
||||||
|
post_processing_dialog->raise();
|
||||||
|
post_processing_dialog->activateWindow();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void MainWindow::OnGameListRefresh() {
|
void MainWindow::OnGameListRefresh() {
|
||||||
// Resets metadata cache and reloads
|
// Resets metadata cache and reloads
|
||||||
QtCommon::Game::ResetMetadata(false);
|
QtCommon::Game::ResetMetadata(false);
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ class QSlider;
|
|||||||
class QHBoxLayout;
|
class QHBoxLayout;
|
||||||
class WaitTreeWidget;
|
class WaitTreeWidget;
|
||||||
class PerformanceOverlay;
|
class PerformanceOverlay;
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
class ConfigurePostProcessing;
|
||||||
|
#endif
|
||||||
enum class GameListOpenTarget;
|
enum class GameListOpenTarget;
|
||||||
enum class DumpRomFSTarget;
|
enum class DumpRomFSTarget;
|
||||||
class GameListPlaceholder;
|
class GameListPlaceholder;
|
||||||
@@ -392,6 +395,9 @@ private slots:
|
|||||||
void OnToggleFilterBar();
|
void OnToggleFilterBar();
|
||||||
void OnToggleStatusBar();
|
void OnToggleStatusBar();
|
||||||
void OnTogglePerfOverlay();
|
void OnTogglePerfOverlay();
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
void OnPostProcessingShaders();
|
||||||
|
#endif
|
||||||
void OnGameListRefresh();
|
void OnGameListRefresh();
|
||||||
void InitializeHotkeys();
|
void InitializeHotkeys();
|
||||||
void ToggleFullscreen();
|
void ToggleFullscreen();
|
||||||
@@ -496,6 +502,9 @@ private:
|
|||||||
QTimer shutdown_timer;
|
QTimer shutdown_timer;
|
||||||
OverlayDialog* shutdown_dialog{};
|
OverlayDialog* shutdown_dialog{};
|
||||||
PerformanceOverlay* perf_overlay = nullptr;
|
PerformanceOverlay* perf_overlay = nullptr;
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
ConfigurePostProcessing* post_processing_dialog = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
GameListPlaceholder* game_list_placeholder = nullptr;
|
GameListPlaceholder* game_list_placeholder = nullptr;
|
||||||
|
|
||||||
|
|||||||
@@ -75,15 +75,3 @@ if (NOT MSVC)
|
|||||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-unused-parameter>
|
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-unused-parameter>
|
||||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-missing-field-initializers>)
|
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-missing-field-initializers>)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (EMSCRIPTEN)
|
|
||||||
# 10GB is required at max... yikes!
|
|
||||||
target_link_options(yuzu-cmd PRIVATE
|
|
||||||
-sALLOW_MEMORY_GROWTH=1
|
|
||||||
-sINITIAL_MEMORY=33554432
|
|
||||||
-sMAXIMUM_MEMORY=10737418240
|
|
||||||
-sGLOBAL_BASE=16777216
|
|
||||||
-sEXPORTED_RUNTIME_METHODS=['FS']
|
|
||||||
-sPTHREAD_POOL_SIZE_STRICT=0
|
|
||||||
-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency)
|
|
||||||
endif()
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ Tools for Eden and other subprojects. When adding new scripts please use `#!/bin
|
|||||||
- `find-unused-strings.sh`: Find any unused strings in the Android app (XML -> Kotlin).
|
- `find-unused-strings.sh`: Find any unused strings in the Android app (XML -> Kotlin).
|
||||||
- `cpp-lint.sh`: Homemade dumb C++ linter.
|
- `cpp-lint.sh`: Homemade dumb C++ linter.
|
||||||
- `fuzzsettings.cpp`: Fuzz settings files.
|
- `fuzzsettings.cpp`: Fuzz settings files.
|
||||||
- `miniserver.js`: Make a quick server that serves a page with the WASM on it, takes a single argument which is the path to the build directory containing *both* `eden-cli.js` and `eden-cli.wasm`. Run via `node.js`, `wasmtime` isn't supported.
|
|
||||||
|
|
||||||
## Android
|
## Android
|
||||||
|
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
import { createServer } from 'http';
|
|
||||||
import { readFile } from 'fs';
|
|
||||||
import { join } from 'path';
|
|
||||||
console.log(`dont forget to run: "npm --global install @jdmichaud/dwarf-2-sourcemap" for better debugging!`);
|
|
||||||
const server = createServer((req, res) => {
|
|
||||||
console.log(`get ${req.url}`);
|
|
||||||
if (req.url === '/') {
|
|
||||||
// https://developer.mozilla.org/en-US/docs/WebAssembly/Guides/Loading_and_running
|
|
||||||
// If your browser doesn't support fetch... HAHA GET FUCKED
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'text/html',
|
|
||||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
||||||
'Cross-Origin-Embedder-Policy': 'require-corp'
|
|
||||||
});
|
|
||||||
res.end(`<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>eden-cli</title>
|
|
||||||
</head>
|
|
||||||
<body style="margin:0;padding:0;background-color:black;color:white;font-family:Monospace,Tahoma,Arial;">
|
|
||||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:2px;width:100%;height:100vh;">
|
|
||||||
<canvas id="canvas" oncontextmenu="event.preventDefault()" style="width:100%;height:100%;background-color:gray;"></canvas>
|
|
||||||
<div id="tty-stdout"></div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
var Module = { //do not prepend var
|
|
||||||
mainScriptUrlOrBlob: 'eden-cli.js',
|
|
||||||
arguments: ['--null-render', '--singlecore', '--filter', '*:Trace', '/home/web_user/game.nro'],
|
|
||||||
canvas: document.getElementById('canvas'),
|
|
||||||
print: (e) => {
|
|
||||||
e = e.replace('[1;31m', '<span style="color:red;font-weight:bold;">');
|
|
||||||
e = e.replace('[0;37m', '<span style="color:white;font-weight:bold;">');
|
|
||||||
e = e.replace('[1;35m', '<span style="color:pink;font-weight:bold;">');
|
|
||||||
e = e.replace('[1;33m', '<span style="color:yellow;font-weight:bold;">');
|
|
||||||
e = e.replace('[0;36m', '<span style="color:white;font-weight:bold;">');
|
|
||||||
e = e.replace('[0m', '</span>');
|
|
||||||
document.getElementById('tty-stdout').innerHTML += \`\${e}</br>\`;
|
|
||||||
},
|
|
||||||
printErr: (e) => {
|
|
||||||
document.getElementById('tty-stdout').innerHTML += \`<span style="color:red">\${e}</span></br>\`;
|
|
||||||
},
|
|
||||||
// not a wasm func but idc
|
|
||||||
printInternal: (e) => {
|
|
||||||
document.getElementById('tty-stdout').innerHTML += \`<span style="color:white">Internal WASM: \${e}</span></br>\`;
|
|
||||||
},
|
|
||||||
preInit: [
|
|
||||||
() => {
|
|
||||||
// copy just most relevant :)
|
|
||||||
Module.FS.mkdir('/home/web_user/.local');
|
|
||||||
Module.FS.mkdir('/home/web_user/.local/share');
|
|
||||||
Module.FS.mkdir('/home/web_user/.local/share/eden');
|
|
||||||
Module.FS.mkdir('/home/web_user/.config');
|
|
||||||
Module.FS.mkdir('/home/web_user/.config/eden');
|
|
||||||
Module.FS.createDataFile('/home/web_user', 'game.nro', gameNroFileBuffer, true, false, true);
|
|
||||||
}
|
|
||||||
],
|
|
||||||
onRuntimeInitialized: () => { Module.printInternal("runtime ok"); },
|
|
||||||
setStatus: (e) => { Module.printInternal(e); },
|
|
||||||
monitorRunDependencies: (e) => { Module.printInternal("monitor deps: " + e); },
|
|
||||||
__wasm_call_ctors: () => { Module.printInternal("ctors beep"); },
|
|
||||||
};
|
|
||||||
var gameNroFileBuffer = {};
|
|
||||||
Module.printInternal(\`Atomics: \${window.Atomics}, SharedArrayBuffer: \${window.SharedArrayBuffer}\`);
|
|
||||||
Module.printInternal("trying to load script (if it hangs here check console)");
|
|
||||||
fetch('game.nro').then((resp) => {
|
|
||||||
if (!resp.ok)
|
|
||||||
throw Error(\`\${resp.status}\`);
|
|
||||||
return resp.bytes();
|
|
||||||
}).then((buffer) => {
|
|
||||||
gameNroFileBuffer = buffer;
|
|
||||||
// load the thingy AFTER loading the nro
|
|
||||||
Module.printInternal(\`loading from ${build_dir}/\${Module.mainScriptUrlOrBlob}\`);
|
|
||||||
var script = document.createElement('script');
|
|
||||||
script.src = '/eden-cli.js';
|
|
||||||
script.onload = (e) => Module.printInternal(\`loaded WASMy script \${e}!!\`);
|
|
||||||
document.head.appendChild(script);
|
|
||||||
}).catch(Module.printErr);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>`);
|
|
||||||
} else if (req.url === '/eden-cli.js') {
|
|
||||||
readFile(join(build_dir, 'eden-cli.js'), (err, content) => {
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'application/javascript',
|
|
||||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
||||||
'Cross-Origin-Embedder-Policy': 'require-corp'
|
|
||||||
});
|
|
||||||
res.end(content, 'utf-8');
|
|
||||||
});
|
|
||||||
} else if (req.url === '/eden-cli.wasm') {
|
|
||||||
readFile(join(build_dir, 'eden-cli.wasm'), (err, content) => {
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'application/wasm',
|
|
||||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
||||||
'Cross-Origin-Embedder-Policy': 'require-corp'
|
|
||||||
});
|
|
||||||
res.end(content);
|
|
||||||
});
|
|
||||||
} else if (req.url === '/game.nro') {
|
|
||||||
readFile(nro_file, (err, content) => {
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'application/octet-stream',
|
|
||||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
||||||
'Cross-Origin-Embedder-Policy': 'require-corp'
|
|
||||||
});
|
|
||||||
res.end(content);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.writeHead(404, {});
|
|
||||||
res.end('', 'utf-8');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const build_dir = process.argv[2];
|
|
||||||
const nro_file = process.argv[3];
|
|
||||||
if (typeof build_dir == "undefined" || typeof nro_file == "undefined") {
|
|
||||||
console.log(`Usage: ${process.argv[0]} ${process.argv[1]} [build directory] [NRO file]`);
|
|
||||||
} else {
|
|
||||||
server.listen(2210, () => {
|
|
||||||
console.log(`${process.argv[0]} ${process.argv[1]} http://localhost:2210`);
|
|
||||||
console.log(`build dir = ${build_dir}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user