mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-31 10:36:56 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 237fb283f8 | |||
| a098e17e5c | |||
| b3e4033ce9 | |||
| 26cb153f14 | |||
| ce8ec956bc | |||
| 56ea54c380 | |||
| e0facbe8a7 | |||
| 9c395241ca | |||
| e00e9faf12 | |||
| 526a367ffa | |||
| 7bd119a5b1 | |||
| cc3f8cc4ca | |||
| b6fb3300b0 | |||
| 2d8ee311dc | |||
| 2b7fac48cd | |||
| 411809818b | |||
| 67b3730059 | |||
| 4a96cc9694 | |||
| b3bc1d8a40 | |||
| 2da1418d77 | |||
| a74d16a8fa | |||
| 60943e8b9e | |||
| 911bab04f7 | |||
| cc04e1c536 | |||
| e7a931d06b |
@@ -0,0 +1,92 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,40 @@
|
||||
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}
|
||||
@@ -0,0 +1,112 @@
|
||||
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)
|
||||
+21
-5
@@ -375,6 +375,15 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
|
||||
# Prefer the -pthread flag on Linux.
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
# It is absolutely promordial to enable on Emscripten
|
||||
# Not only this allows to use std::thread and std::jthread without exceptions
|
||||
# but it also fixes several issues related to MT operations.
|
||||
# ...and CMake doesn't include it by default even when we specify
|
||||
# that we prefer the pthread flag; why is that? I don't know.
|
||||
if (PLATFORM_EMSCRIPTEN)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthread>)
|
||||
add_link_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthread>)
|
||||
endif()
|
||||
|
||||
find_package(RenderDoc MODULE)
|
||||
|
||||
@@ -403,7 +412,12 @@ set(BUILD_TESTING OFF)
|
||||
set(ENABLE_TESTING OFF)
|
||||
|
||||
# boost
|
||||
set(BOOST_INCLUDE_LIBRARIES algorithm icl pool container heap asio headers process filesystem crc variant)
|
||||
if (PLATFORM_EMSCRIPTEN)
|
||||
set(BOOST_INCLUDE_LIBRARIES algorithm icl pool container heap headers filesystem crc variant)
|
||||
set(BOOST_CONTAINER_HEADER_ONLY ON)
|
||||
else()
|
||||
set(BOOST_INCLUDE_LIBRARIES algorithm icl pool container heap asio headers process filesystem crc variant)
|
||||
endif()
|
||||
|
||||
AddJsonPackage(boost)
|
||||
|
||||
@@ -424,10 +438,12 @@ if (Boost_ADDED)
|
||||
|
||||
target_compile_options(boost_heap INTERFACE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shadow>)
|
||||
target_compile_options(boost_icl INTERFACE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shadow>)
|
||||
target_compile_options(boost_asio INTERFACE
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-conversion>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-fallthrough>
|
||||
)
|
||||
# May not exist (i.e emscripten)
|
||||
if (TARGET boost_asio)
|
||||
target_compile_options(boost_asio INTERFACE
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-conversion>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-fallthrough>)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
+5
-3
@@ -108,7 +108,7 @@
|
||||
"find_args": "MODULE GLOBAL",
|
||||
"hash": "159ed94965018f2a371d45a3bfc1961e5fb1549e501ded70a6b4532d7fe99d0579c18b5195aff6e35f96f399b426cea2650ec9fb75ef80d4c9edeccb51f2e6c9",
|
||||
"options": [
|
||||
"HTTPLIB_REQUIRE_OPENSSL ON",
|
||||
"HTTPLIB_REQUIRE_OPENSSL OFF",
|
||||
"HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES ON"
|
||||
],
|
||||
"patches": [
|
||||
@@ -190,7 +190,8 @@
|
||||
"min_version": "3.0.0",
|
||||
"package": "OpenSSL",
|
||||
"patches": [
|
||||
"0001-add-bundled-cert.patch"
|
||||
"0001-add-bundled-cert.patch",
|
||||
"0002-wasm-support.patch"
|
||||
],
|
||||
"repo": "openssl/openssl",
|
||||
"version": "openssl-3.6.2"
|
||||
@@ -213,7 +214,8 @@
|
||||
"0001-cpmutil-compat.patch",
|
||||
"0002-use-ccache.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",
|
||||
"version": "3.6.2"
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
- [NetBSD](#netbsd)
|
||||
- [MSYS2](#msys2)
|
||||
- [RedoxOS](#redoxos)
|
||||
- [WebAssembly](#webassembly)
|
||||
- [Windows](#windows)
|
||||
- [Windows 7, Windows 8 and Windows 8.1](#windows-7-windows-8-and-windows-81)
|
||||
- [Windows Vista and below](#windows-vista-and-below)
|
||||
@@ -245,6 +246,22 @@ The package install may randomly hang at times, in which case it has to be resta
|
||||
|
||||
When CMake invokes certain file syscalls - it may sometimes cause crashes or corruptions on the (kernel?) address space - so reboot the system if there is a "hang" in CMake.
|
||||
|
||||
## WebAssembly
|
||||
|
||||
**It doesn't run on a browser yet.**
|
||||
|
||||
WebAssembly or "WASM" for short is a *mainly 32-bit* virtual "architecture" which we only bootstrap on 64-bit only. This means not only the program runs 2x slower than it would due to using JS BigInt, it also means we need to go out of our way to enable proper 64-bit support via `-sMEMORY64=1`, however once again, some Emscripten quirks force us to specify `-s MEMORY64` and `-sMEMORY64=1` at the same time: see the [CI build script](../.ci/wasm/build.sh).
|
||||
|
||||
The WebAssembly target is very heavy on resources and requires at least 4 times the normal amount of resources that a native build would. Additionally, the only supported environment is Firefox at the moment, node.js and `wasmtime are not supported (PRs welcome!)
|
||||
|
||||
If running under Firefox and you hit "out of memory" on dev console, close the entire tab, then open it back again, see [this issue](https://github.com/emscripten-core/emscripten/issues/8126).
|
||||
|
||||
To run the binary (after building) you should be fine with `node ./eden-cli.js`. For obvious reasons no Qt frontend is available on WASM, support for Vulkan is done charily via [llvmpipe2wasm](https://github.com/Devsh-Graphics-Programming/llvmpipe2wasm).
|
||||
|
||||
If you run into the error "acorn.js can't be found" check [this associated issue](https://github.com/emscripten-core/emscripten/issues/13368), the fix in short is `npm --global install acorn`. On FreeBSD you could run `npm` under root, or you could do the sane thing and do `sudo chown -R $USER /usr/local/lib/node_modules/ /usr/local/bin/` (remember to restore permissions afterwards!) unless you wish to run `npm` under root which is generally a bad idea.
|
||||
|
||||
2026-06-09: As of writing, no Dynarmic-based JIT is possible on this target, full interpreted emulation is the only reasonable option. While there is some efforts on making a JIT like [here](https://github.com/wingo/wasm-jit) or [here](https://wingolog.org/archives/2022/08/18/just-in-time-code-generation-within-webassembly), the result is so latency expensive we're better off using an interpreter instead.
|
||||
|
||||
## Windows
|
||||
|
||||
### Windows 7, Windows 8 and Windows 8.1
|
||||
|
||||
@@ -359,6 +359,14 @@ pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel b
|
||||
|
||||
[Caveats](./Caveats.md#haikuos).
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>WebAssembly</summary>
|
||||
|
||||
Emscripten: The default installation should provide enough.
|
||||
|
||||
[Caveats](./Caveats.md#wasm).
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>RedoxOS</summary>
|
||||
|
||||
+9
-5
@@ -94,6 +94,10 @@ function(detect_architecture_symbols)
|
||||
endfunction()
|
||||
|
||||
# arches here are put in a sane default order of importance
|
||||
# EXCEPT FOR WASM, which must be probed for FIRST, because some genius
|
||||
# decided to also allow the host architecture to be defined when building
|
||||
# for the emscripten target, absolutely lovely detail.
|
||||
#
|
||||
# notably, amd64, arm64, and riscv (in order) are BY FAR the most common
|
||||
# mips is pretty popular in embedded
|
||||
# ppc64 is pretty popular in supercomputing
|
||||
@@ -101,6 +105,11 @@ endfunction()
|
||||
# ia64 exists
|
||||
# the rest exist, but are probably less popular than ia64
|
||||
|
||||
detect_architecture_symbols(
|
||||
ARCH wasm
|
||||
SYMBOLS
|
||||
"__EMSCRIPTEN__")
|
||||
|
||||
detect_architecture_symbols(
|
||||
ARCH arm64
|
||||
SYMBOLS
|
||||
@@ -203,11 +212,6 @@ detect_architecture_symbols(
|
||||
"__loongarch__"
|
||||
"__loongarch64")
|
||||
|
||||
detect_architecture_symbols(
|
||||
ARCH wasm
|
||||
SYMBOLS
|
||||
"__EMSCRIPTEN__")
|
||||
|
||||
# "generic" target
|
||||
# If you have reached this point, you're on some as-of-yet unsupported architecture.
|
||||
# See the docs up above for known unsupported architectures
|
||||
|
||||
+25
@@ -20,6 +20,9 @@ elseif (${CMAKE_SYSTEM_NAME} STREQUAL "managarm")
|
||||
set(MANAGARM ON)
|
||||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Haiku")
|
||||
set(HAIKUOS ON)
|
||||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
|
||||
set(EMSCRIPTEN ON)
|
||||
message(WARNING "${CMAKE_LIBRARY_ARCHITECTURE} support is highly experimental!!!")
|
||||
endif()
|
||||
|
||||
# BSD
|
||||
@@ -97,3 +100,25 @@ if (MINGW)
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE
|
||||
"${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${MINGW_FLAGS}")
|
||||
endif()
|
||||
|
||||
if (PLATFORM_EMSCRIPTEN)
|
||||
set(EMSCRIPTEN_C_FLAGS "-s MEMORY64 -m64 -pipe -sMEMORY64=1")
|
||||
set(EMSCRIPTEN_LINK_FLAGS "-sMEMORY64=1 -m64 -Wl,-mwasm64 -sASYNCIFY=1")
|
||||
|
||||
# This prevents FFmpeg and other libraries from assuming it's the host's CPU
|
||||
# Additionally some Emscripten installs may not be very good... generally
|
||||
set(EMSCRIPTEN_SYSTEM_PROCESSOR wasm)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EMSCRIPTEN_C_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${EMSCRIPTEN_C_FLAGS}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${EMSCRIPTEN_LINK_FLAGS}")
|
||||
set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} ${EMSCRIPTEN_LINK_FLAGS}")
|
||||
set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} ${EMSCRIPTEN_LINK_FLAGS}")
|
||||
|
||||
unset(EMSCRIPTEN_C_FLAGS)
|
||||
unset(EMSCRIPTEN_LINK_FLAGS)
|
||||
endif()
|
||||
|
||||
# awesome
|
||||
if (PLATFORM_FREEBSD OR PLATFORM_DRAGONFLYBSD)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/local/lib")
|
||||
endif()
|
||||
|
||||
Vendored
+56
-19
@@ -37,26 +37,39 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG)
|
||||
elseif (NOT (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES CMAKE_SYSTEM_PROCESSOR
|
||||
AND CMAKE_HOST_SYSTEM_NAME MATCHES CMAKE_SYSTEM_NAME))
|
||||
string(TOLOWER "${CMAKE_SYSTEM_NAME}" FFmpeg_SYSTEM_NAME)
|
||||
if (FFmpeg_SYSTEM_NAME STREQUAL "openorbis" OR FFmpeg_SYSTEM_NAME STREQUAL "managarm")
|
||||
# All of these platforms are supported by ffmpeg as native build OSes
|
||||
# anything else (like Redox or Managarm or PS4) is NOT natively supported
|
||||
# hence, assume the "unix like" is just "none" for the sake of OUR sanity.
|
||||
# If YOUR OS/platform has actual native support:
|
||||
# 1. fucking congrats
|
||||
# 2. feel free to add it on the condition below
|
||||
if (NOT (PLATFORM_NETBSD OR PLATFORM_SUN OR PLATFORM_FREEBSD
|
||||
OR PLATFORM_OPENBSD OR PLATFORM_DRAGONFLYBSD OR PLATFORM_HAIKU
|
||||
OR PLATFORM_LINUX OR PLATFORM_MSYS OR WIN32 OR ANDROID OR APPLE))
|
||||
set(FFmpeg_SYSTEM_NAME "none")
|
||||
endif()
|
||||
# TODO: Can we really do better? Auto-detection? Something clever?
|
||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
|
||||
--enable-cross-compile
|
||||
--arch="${CMAKE_SYSTEM_PROCESSOR}"
|
||||
--target-os="${FFmpeg_SYSTEM_NAME}"
|
||||
--sysroot="${CMAKE_SYSROOT}"
|
||||
)
|
||||
--target-os="${FFmpeg_SYSTEM_NAME}")
|
||||
if (PLATFORM_EMSCRIPTEN)
|
||||
# funniest trolling from emscripten, such a classic!
|
||||
# maybe I should PR so they use CMAKE_SYSROOT... y'know?
|
||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --sysroot="${EMSCRIPTEN_SYSROOT}")
|
||||
else()
|
||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --sysroot="${CMAKE_SYSROOT}")
|
||||
endif()
|
||||
if (DEFINED FFmpeg_CROSS_PREFIX)
|
||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --cross-prefix="${FFmpeg_CROSS_PREFIX}")
|
||||
else()
|
||||
message(WARNING "Please set FFmpeg_CROSS_PREFIX to your cross toolchain prefix, for example: \${CMAKE_STAGING_PREFIX}/bin/${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}-")
|
||||
message(WARNING "Please set FFmpeg_CROSS_PREFIX to your cross toolchain prefix, for example: ${CMAKE_STAGING_PREFIX}/bin/${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}")
|
||||
endif()
|
||||
set(FFmpeg_IS_CROSS_COMPILING TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (OPENORBIS OR MANAGARM)
|
||||
if (OPENORBIS OR MANAGARM OR EMSCRIPTEN)
|
||||
# Doesn't support VA-API, don't go thru the embarrassment of trying to enable it
|
||||
list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi)
|
||||
elseif (ANDROID)
|
||||
@@ -164,8 +177,7 @@ if (OPENORBIS)
|
||||
-lSceUserService
|
||||
-lSceSysmodule
|
||||
-lSceNet
|
||||
-lSceLibcInternal
|
||||
)
|
||||
-lSceLibcInternal)
|
||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
|
||||
--disable-pthreads
|
||||
--extra-cflags=${CMAKE_SYSROOT}/usr/include
|
||||
@@ -176,8 +188,18 @@ elseif (MANAGARM)
|
||||
# Required for proper stuff
|
||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
|
||||
--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()
|
||||
|
||||
if (YUZU_USE_BUNDLED_FFMPEG)
|
||||
@@ -207,6 +229,7 @@ else()
|
||||
# Build FFmpeg from externals
|
||||
message(STATUS "Using FFmpeg from externals")
|
||||
|
||||
set(FFmpeg_LD ${CMAKE_LINKER})
|
||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86_64|amd64)")
|
||||
# FFmpeg has source that requires one of nasm or yasm to assemble it.
|
||||
# REQUIRED throws an error if not found here during configuration rather than during compilation.
|
||||
@@ -215,6 +238,12 @@ else()
|
||||
message(FATAL_ERROR "One of either `nasm` or `yasm` not found but is required.")
|
||||
endif()
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
||||
# TODO: this is 100% redundant but we need it because CMAKE_LINKER can resolve to ld.lld
|
||||
# which is NOT compatible
|
||||
find_program(EMCC NAMES emcc REQUIRED)
|
||||
set(FFmpeg_LD ${EMCC})
|
||||
endif()
|
||||
|
||||
find_program(AUTOCONF autoconf)
|
||||
if ("${AUTOCONF}" STREQUAL "AUTOCONF-NOTFOUND")
|
||||
@@ -248,7 +277,13 @@ else()
|
||||
CACHE PATH "Paths to FFmpeg libraries" FORCE)
|
||||
endforeach()
|
||||
|
||||
find_program(BASH_PROGRAM bash REQUIRED)
|
||||
# Some SDKs (especially wasm) require us, actually, WANT us to use their "emconfigure"
|
||||
# otherwise they will cry a billion tears
|
||||
if (PLATFORM_EMSCRIPTEN)
|
||||
find_program(FFmpeg_CONFIGURE_WRAPPER emconfigure REQUIRED)
|
||||
else()
|
||||
find_program(FFmpeg_CONFIGURE_WRAPPER bash REQUIRED)
|
||||
endif()
|
||||
|
||||
# `configure` parameters builds only exactly what yuzu needs from FFmpeg
|
||||
# `--disable-vdpau` is needed to avoid linking issues
|
||||
@@ -258,7 +293,7 @@ else()
|
||||
OUTPUT
|
||||
${FFmpeg_MAKEFILE}
|
||||
COMMAND
|
||||
${BASH_PROGRAM} ${FFmpeg_PREFIX}/configure
|
||||
${FFmpeg_CONFIGURE_WRAPPER} ${FFmpeg_PREFIX}/configure
|
||||
--disable-avdevice
|
||||
--disable-avformat
|
||||
--disable-doc
|
||||
@@ -267,6 +302,10 @@ else()
|
||||
--disable-ffprobe
|
||||
--disable-network
|
||||
--disable-swresample
|
||||
--disable-autodetect
|
||||
--disable-runtime-cpudetect
|
||||
--disable-debug
|
||||
--disable-programs
|
||||
--enable-decoder=h264
|
||||
--enable-decoder=vp8
|
||||
--enable-decoder=vp9
|
||||
@@ -274,7 +313,7 @@ else()
|
||||
--enable-pic
|
||||
--cc=${FFmpeg_CC}
|
||||
--cxx=${FFmpeg_CXX}
|
||||
--ld=${CMAKE_LINKER}
|
||||
--ld=${FFmpeg_LD}
|
||||
--extra-cflags=${CMAKE_C_FLAGS}
|
||||
--extra-cxxflags=${CMAKE_CXX_FLAGS}
|
||||
--extra-ldflags=${CMAKE_C_LINK_FLAGS}
|
||||
@@ -290,11 +329,7 @@ else()
|
||||
|
||||
# Workaround for Ubuntu 18.04's older version of make not being able to call make as a child
|
||||
# with context of the jobserver. Also helps ninja users.
|
||||
execute_process(
|
||||
COMMAND
|
||||
nproc
|
||||
OUTPUT_VARIABLE
|
||||
SYSTEM_THREADS)
|
||||
cmake_host_system_information(RESULT SYSTEM_THREADS QUERY NUMBER_OF_LOGICAL_CORES)
|
||||
|
||||
set(FFmpeg_BUILD_LIBRARIES ${FFmpeg_LIBRARIES})
|
||||
|
||||
@@ -302,6 +337,8 @@ else()
|
||||
if (LINUX OR ANDROID OR APPLE OR WIN32 OR FREEBSD)
|
||||
set(FFmpeg_MAKE_ARGS -j${SYSTEM_THREADS})
|
||||
else()
|
||||
# No GNU make implies that this system may be highly non-GNU
|
||||
find_program(MAKE make required)
|
||||
set(FFmpeg_MAKE_ARGS "")
|
||||
endif()
|
||||
|
||||
@@ -309,7 +346,7 @@ else()
|
||||
OUTPUT
|
||||
${FFmpeg_BUILD_LIBRARIES}
|
||||
COMMAND
|
||||
gmake ${FFmpeg_MAKE_ARGS}
|
||||
${MAKE} ${FFmpeg_MAKE_ARGS}
|
||||
WORKING_DIRECTORY
|
||||
${FFmpeg_BUILD_DIR}
|
||||
)
|
||||
|
||||
Vendored
+2
@@ -14,7 +14,9 @@
|
||||
namespace Tz {
|
||||
|
||||
namespace {
|
||||
#ifndef EINVAL
|
||||
#define EINVAL 22
|
||||
#endif
|
||||
|
||||
static Rule gmtmem{};
|
||||
static Rule* const gmtptr = &gmtmem;
|
||||
|
||||
+5
-2
@@ -177,7 +177,6 @@ else()
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-invalid-offsetof>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-unused-parameter>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-missing-field-initializers>)
|
||||
|
||||
if (CXX_CLANG OR CXX_ICC OR CXX_APPLE) # Clang, AppleClang, or Intel C++
|
||||
if (NOT MSVC)
|
||||
add_compile_options(
|
||||
@@ -191,7 +190,11 @@ else()
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-nullability-completeness>)
|
||||
endif()
|
||||
|
||||
if (ARCHITECTURE_x86_64)
|
||||
if (ARCHITECTURE_wasm)
|
||||
# we are evil but fmt is even more evil
|
||||
add_compile_options(
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shorten-64-to-32>)
|
||||
elseif (ARCHITECTURE_x86_64)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mcx16>)
|
||||
if (LINUX OR FREEBSD)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mtls-dialect=gnu2>)
|
||||
|
||||
-1
@@ -586,7 +586,6 @@ abstract class SettingsItem(
|
||||
IntSetting.FSR_SHARPENING_SLIDER,
|
||||
titleId = R.string.fsr_sharpness,
|
||||
descriptionId = R.string.fsr_sharpness_description,
|
||||
max = 200,
|
||||
units = "%"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1182,7 +1182,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
container,
|
||||
IntSetting.FSR_SHARPENING_SLIDER,
|
||||
minValue = 0,
|
||||
maxValue = 200,
|
||||
maxValue = 100,
|
||||
units = "%"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -235,7 +235,11 @@ else()
|
||||
target_link_libraries(common PUBLIC Boost::headers)
|
||||
endif()
|
||||
target_link_libraries(common PRIVATE OpenSSL::SSL)
|
||||
target_link_libraries(common PUBLIC Boost::filesystem Boost::context httplib::httplib nlohmann_json::nlohmann_json)
|
||||
target_link_libraries(common PUBLIC Boost::filesystem httplib::httplib nlohmann_json::nlohmann_json)
|
||||
if (NOT PLATFORM_EMSCRIPTEN)
|
||||
# Emscripten is: "bring your own implementation", boost doesn't add upstream support sadly
|
||||
target_link_libraries(common PRIVATE Boost::context)
|
||||
endif()
|
||||
|
||||
if (lz4_ADDED)
|
||||
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)
|
||||
|
||||
+85
-12
@@ -11,7 +11,12 @@
|
||||
#include "common/fiber.h"
|
||||
#include "common/virtual_buffer.h"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/fiber.h>
|
||||
#else
|
||||
#include <boost/context/detail/fcontext.hpp>
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -22,36 +27,103 @@ constexpr size_t DEFAULT_STACK_SIZE = 512 * 4096;
|
||||
#endif
|
||||
constexpr u32 CANARY_VALUE = 0xDEADBEEF;
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
struct Fiber::FiberImpl {
|
||||
FiberImpl() {}
|
||||
|
||||
u32 canary_1 = CANARY_VALUE;
|
||||
std::array<u8, DEFAULT_STACK_SIZE> stack{};
|
||||
std::array<u8, DEFAULT_STACK_SIZE> rewind_stack{};
|
||||
std::array<u8, DEFAULT_STACK_SIZE> astack{};
|
||||
u32 canary_2 = CANARY_VALUE;
|
||||
|
||||
boost::context::detail::fcontext_t context{};
|
||||
boost::context::detail::fcontext_t rewind_context{};
|
||||
emscripten_fiber_t* context{nullptr};
|
||||
|
||||
std::mutex guard;
|
||||
std::function<void()> entry_point;
|
||||
std::function<void()> rewind_point;
|
||||
std::shared_ptr<Fiber> previous_fiber;
|
||||
|
||||
u8* stack_limit = nullptr;
|
||||
u8* rewind_stack_limit = nullptr;
|
||||
bool is_thread_fiber = false;
|
||||
bool released = false;
|
||||
};
|
||||
|
||||
void Fiber::SetRewindPoint(std::function<void()>&& rewind_func) {
|
||||
impl->rewind_point = std::move(rewind_func);
|
||||
Fiber::Fiber(std::function<void()>&& entry_point_func) : impl{std::make_unique<FiberImpl>()} {
|
||||
impl->entry_point = std::move(entry_point_func);
|
||||
emscripten_fiber_init(impl->context, [](void *user_data) -> void {
|
||||
auto* fiber = static_cast<Fiber*>(user_data);
|
||||
ASSERT(fiber && fiber->impl && fiber->impl->previous_fiber && fiber->impl->previous_fiber->impl);
|
||||
ASSERT(fiber->impl->canary_1 == CANARY_VALUE);
|
||||
ASSERT(fiber->impl->canary_2 == CANARY_VALUE);
|
||||
fiber->impl->previous_fiber->impl->context = fiber->impl->context;
|
||||
fiber->impl->previous_fiber->impl->guard.unlock();
|
||||
fiber->impl->previous_fiber.reset();
|
||||
fiber->impl->entry_point();
|
||||
UNREACHABLE();
|
||||
}, impl.get(), impl->stack.data(), impl->stack.size(), impl->astack.data(), impl->astack.size());
|
||||
}
|
||||
|
||||
Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {}
|
||||
|
||||
Fiber::~Fiber() {
|
||||
if (!impl->released) {
|
||||
// Make sure the Fiber is not being used
|
||||
const bool locked = impl->guard.try_lock();
|
||||
ASSERT(locked && "Destroying a fiber that's still running");
|
||||
if (locked) {
|
||||
impl->guard.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Fiber::Exit() {
|
||||
ASSERT(impl->is_thread_fiber && "Exiting non main thread fiber");
|
||||
if (impl->is_thread_fiber) {
|
||||
impl->guard.unlock();
|
||||
impl->released = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Fiber::YieldTo(std::weak_ptr<Fiber> weak_from, Fiber& to) {
|
||||
to.impl->guard.lock();
|
||||
to.impl->previous_fiber = weak_from.lock();
|
||||
emscripten_fiber_swap(to.impl->context, to.impl->previous_fiber->impl->context);
|
||||
// "from" might no longer be valid if the thread was killed
|
||||
if (auto from = weak_from.lock()) {
|
||||
if (from->impl->previous_fiber == nullptr) {
|
||||
ASSERT(false && "previous_fiber is nullptr!");
|
||||
} else {
|
||||
from->impl->previous_fiber->impl->guard.unlock();
|
||||
from->impl->previous_fiber.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
|
||||
std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
|
||||
fiber->impl->guard.lock();
|
||||
fiber->impl->is_thread_fiber = true;
|
||||
return fiber;
|
||||
}
|
||||
#else
|
||||
struct Fiber::FiberImpl {
|
||||
FiberImpl() {}
|
||||
|
||||
u32 canary_1 = CANARY_VALUE;
|
||||
std::array<u8, DEFAULT_STACK_SIZE> stack{};
|
||||
u32 canary_2 = CANARY_VALUE;
|
||||
|
||||
boost::context::detail::fcontext_t context{};
|
||||
|
||||
std::mutex guard;
|
||||
std::function<void()> entry_point;
|
||||
std::shared_ptr<Fiber> previous_fiber;
|
||||
|
||||
u8* stack_limit = nullptr;
|
||||
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);
|
||||
impl->stack_limit = impl->stack.data();
|
||||
impl->rewind_stack_limit = impl->rewind_stack.data();
|
||||
u8* stack_base = impl->stack_limit + DEFAULT_STACK_SIZE;
|
||||
impl->context = boost::context::detail::make_fcontext(stack_base, impl->stack.size(), [](boost::context::detail::transfer_t transfer) -> void {
|
||||
auto* fiber = static_cast<Fiber*>(transfer.data);
|
||||
@@ -72,7 +144,7 @@ Fiber::~Fiber() {
|
||||
if (!impl->released) {
|
||||
// Make sure the Fiber is not being used
|
||||
const bool locked = impl->guard.try_lock();
|
||||
ASSERT_MSG(locked, "Destroying a fiber that's still running");
|
||||
ASSERT(locked && "Destroying a fiber that's still running");
|
||||
if (locked) {
|
||||
impl->guard.unlock();
|
||||
}
|
||||
@@ -80,7 +152,7 @@ Fiber::~Fiber() {
|
||||
}
|
||||
|
||||
void Fiber::Exit() {
|
||||
ASSERT_MSG(impl->is_thread_fiber, "Exiting non main thread fiber");
|
||||
ASSERT(impl->is_thread_fiber && "Exiting non main thread fiber");
|
||||
if (impl->is_thread_fiber) {
|
||||
impl->guard.unlock();
|
||||
impl->released = true;
|
||||
@@ -110,5 +182,6 @@ std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
|
||||
fiber->impl->is_thread_fiber = true;
|
||||
return fiber;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Common
|
||||
|
||||
+1
-2
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
@@ -45,7 +45,6 @@ public:
|
||||
/// Fiber 'from' must be the currently running fiber.
|
||||
static void YieldTo(std::weak_ptr<Fiber> weak_from, Fiber& to);
|
||||
[[nodiscard]] static std::shared_ptr<Fiber> ThreadToFiber();
|
||||
void SetRewindPoint(std::function<void()>&& rewind_func);
|
||||
/// Only call from main thread's fiber
|
||||
void Exit();
|
||||
private:
|
||||
|
||||
@@ -136,6 +136,11 @@ public:
|
||||
eden_path = GetDataDirectory("XDG_DATA_HOME") / EDEN_DIR;
|
||||
eden_path_cache = GetDataDirectory("XDG_CACHE_HOME") / EDEN_DIR;
|
||||
eden_path_config = GetDataDirectory("XDG_CONFIG_HOME") / EDEN_DIR;
|
||||
#if defined(__EMSCRIPTEN__) || defined(__wasi__) || defined(__managarm__)
|
||||
// folders MAY not exist in this distrobution/OS
|
||||
CreateParentDir(GetDataDirectory("XDG_CONFIG_HOME"));
|
||||
CreateParentDir(GetDataDirectory("XDG_CACHE_HOME"));
|
||||
#endif
|
||||
} else {
|
||||
eden_path_cache = eden_path / CACHE_DIR;
|
||||
eden_path_config = eden_path / CONFIG_DIR;
|
||||
|
||||
@@ -394,7 +394,7 @@ private:
|
||||
ankerl::unordered_dense::map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset
|
||||
};
|
||||
|
||||
#elif defined(__OPENORBIS__) || defined(__managarm__)
|
||||
#elif defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__)
|
||||
// None of the luxuries of POSIX, all of the suffering
|
||||
// For managarm: see https://github.com/managarm/managarm/issues/1370
|
||||
#else // ^^^ Windows ^^^ vvv POSIX vvv
|
||||
@@ -689,7 +689,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
|
||||
: backing_size(backing_size_)
|
||||
, virtual_size(virtual_size_)
|
||||
{
|
||||
#if defined(__OPENORBIS__) || defined(__managarm__)
|
||||
#if defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__)
|
||||
LOG_WARNING(HW_Memory, "Platform doesn't support fastmem");
|
||||
fallback_buffer.emplace(backing_size);
|
||||
backing_base = fallback_buffer->data();
|
||||
@@ -723,7 +723,7 @@ HostMemory::HostMemory(HostMemory&&) noexcept = default;
|
||||
HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default;
|
||||
|
||||
void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms, bool separate_heap) {
|
||||
#if !(defined(__OPENORBIS__) || defined(__managarm__))
|
||||
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
|
||||
ASSERT(virtual_offset % PageAlignment == 0);
|
||||
ASSERT(host_offset % PageAlignment == 0);
|
||||
ASSERT(length % PageAlignment == 0);
|
||||
@@ -737,7 +737,7 @@ void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length, M
|
||||
}
|
||||
|
||||
void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap) {
|
||||
#if !(defined(__OPENORBIS__) || defined(__managarm__))
|
||||
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
|
||||
ASSERT(virtual_offset % PageAlignment == 0);
|
||||
ASSERT(length % PageAlignment == 0);
|
||||
ASSERT(virtual_offset + length <= virtual_size);
|
||||
@@ -749,7 +749,7 @@ void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap)
|
||||
}
|
||||
|
||||
void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission perm) {
|
||||
#if !(defined(__OPENORBIS__) || defined(__managarm__))
|
||||
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
|
||||
ASSERT(virtual_offset % PageAlignment == 0);
|
||||
ASSERT(length % PageAlignment == 0);
|
||||
ASSERT(virtual_offset + length <= virtual_size);
|
||||
@@ -768,7 +768,7 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f
|
||||
}
|
||||
|
||||
void HostMemory::EnableDirectMappedAddress() {
|
||||
#if !(defined(__OPENORBIS__) || defined(__managarm__))
|
||||
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
|
||||
if (impl) {
|
||||
impl->EnableDirectMappedAddress();
|
||||
virtual_size += reinterpret_cast<uintptr_t>(virtual_base);
|
||||
|
||||
@@ -77,7 +77,7 @@ private:
|
||||
size_t backing_size{};
|
||||
size_t virtual_size{};
|
||||
|
||||
#if !(defined(__OPENORBIS__) || defined(__managarm__))
|
||||
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
|
||||
// Low level handler for the platform dependent memory routines
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
|
||||
@@ -490,6 +490,8 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||
LOG_DEBUG(Common, "Could not set thread nice value to {}: {}", nice_value,
|
||||
GetLastErrorMsg());
|
||||
}
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
// TODO: set priority?
|
||||
#else
|
||||
const s32 max_prio = sched_get_priority_max(SCHED_OTHER);
|
||||
const s32 min_prio = sched_get_priority_min(SCHED_OTHER);
|
||||
@@ -534,6 +536,8 @@ void SetCurrentThreadName(const char* name) {
|
||||
// See for reference
|
||||
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
|
||||
(void)name;
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
// TODO: set thread name?
|
||||
#else
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
#endif
|
||||
|
||||
+12
-5
@@ -38,10 +38,6 @@ add_library(core STATIC
|
||||
debugger/debugger.cpp
|
||||
debugger/debugger.h
|
||||
debugger/debugger_interface.h
|
||||
debugger/gdbstub.cpp
|
||||
debugger/gdbstub.h
|
||||
debugger/gdbstub_arch.cpp
|
||||
debugger/gdbstub_arch.h
|
||||
device_memory.cpp
|
||||
device_memory.h
|
||||
device_memory_manager.h
|
||||
@@ -1170,6 +1166,14 @@ add_library(core STATIC
|
||||
tools/freezer.h
|
||||
tools/renderdoc.cpp
|
||||
tools/renderdoc.h)
|
||||
if (NOT PLATFORM_EMSCRIPTEN)
|
||||
# incompatible with wasm's async model
|
||||
target_sources(core PRIVATE
|
||||
debugger/gdbstub.cpp
|
||||
debugger/gdbstub.h
|
||||
debugger/gdbstub_arch.cpp
|
||||
debugger/gdbstub_arch.h)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WIFI_SCAN)
|
||||
target_sources(core PRIVATE internal_network/wifi_scanner.cpp)
|
||||
@@ -1213,7 +1217,10 @@ target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS})
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
|
||||
|
||||
if (BOOST_NO_HEADERS)
|
||||
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc)
|
||||
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::crc)
|
||||
if (NOT PLATFORM_EMSCRIPTEN)
|
||||
target_link_libraries(core PUBLIC Boost::asio Boost::process)
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(core PUBLIC Boost::headers)
|
||||
endif()
|
||||
|
||||
@@ -6,19 +6,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#include <dynarmic/interface/halt_reason.h>
|
||||
|
||||
#endif
|
||||
#include "core/arm/arm_interface.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
constexpr Dynarmic::HaltReason StepThread = Dynarmic::HaltReason::Step;
|
||||
constexpr Dynarmic::HaltReason DataAbort = Dynarmic::HaltReason::MemoryAbort;
|
||||
constexpr Dynarmic::HaltReason BreakLoop = Dynarmic::HaltReason::UserDefined2;
|
||||
constexpr Dynarmic::HaltReason SupervisorCall = Dynarmic::HaltReason::UserDefined3;
|
||||
constexpr Dynarmic::HaltReason InstructionBreakpoint = Dynarmic::HaltReason::UserDefined4;
|
||||
constexpr Dynarmic::HaltReason PrefetchAbort = Dynarmic::HaltReason::UserDefined6;
|
||||
|
||||
constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) {
|
||||
static_assert(u64(HaltReason::StepThread) == u64(StepThread));
|
||||
static_assert(u64(HaltReason::DataAbort) == u64(DataAbort));
|
||||
@@ -28,5 +29,6 @@ constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) {
|
||||
static_assert(u64(HaltReason::PrefetchAbort) == u64(PrefetchAbort));
|
||||
return HaltReason(hr);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -6,30 +6,54 @@
|
||||
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
|
||||
// TODO: gdb stub compat with emscripten?
|
||||
#else
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/version.hpp>
|
||||
|
||||
#if BOOST_VERSION > 108400 && (!defined(_WINDOWS) && !defined(__ANDROID__)) || defined(YUZU_BOOST_v1)
|
||||
#define USE_BOOST_v1
|
||||
#endif
|
||||
|
||||
#ifdef USE_BOOST_v1
|
||||
#include <boost/process/v1/async_pipe.hpp>
|
||||
#else
|
||||
#include <boost/process/async_pipe.hpp>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/debugger/debugger.h"
|
||||
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
|
||||
// TODO: gdbstub with emscripten?
|
||||
#else
|
||||
#include "core/debugger/debugger_interface.h"
|
||||
#include "core/debugger/gdbstub.h"
|
||||
#endif
|
||||
#include "core/hle/kernel/global_scheduler_context.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
|
||||
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
|
||||
namespace Core {
|
||||
// Dummy
|
||||
class DebuggerImpl {
|
||||
char pad;
|
||||
};
|
||||
Debugger::Debugger(Core::System& system, u16 port) {}
|
||||
Debugger::~Debugger() = default;
|
||||
bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
|
||||
return false;
|
||||
}
|
||||
bool Debugger::NotifyThreadWatchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) {
|
||||
return false;
|
||||
}
|
||||
void Debugger::NotifyShutdown() {}
|
||||
} // namespace Core
|
||||
#else
|
||||
template <typename Readable, typename Buffer, typename Callback>
|
||||
static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
|
||||
static_assert(std::is_trivial_v<Buffer>);
|
||||
@@ -400,3 +424,4 @@ void Debugger::NotifyShutdown() {
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
#endif
|
||||
|
||||
@@ -7,8 +7,14 @@
|
||||
#include <random>
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_32.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_64.h"
|
||||
#endif
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_scoped_resource_reservation.h"
|
||||
@@ -18,8 +24,6 @@
|
||||
#include "core/hle/kernel/k_thread_queue.h"
|
||||
#include "core/hle/kernel/k_worker_task_manager.h"
|
||||
|
||||
#include "core/arm/dynarmic/arm_dynarmic_32.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_64.h"
|
||||
#ifdef HAS_NCE
|
||||
#include "core/arm/nce/arm_nce.h"
|
||||
#endif
|
||||
@@ -1300,9 +1304,11 @@ void KProcess::LoadModule(KernelCore& kernel, CodeSet code_set, KProcessAddress
|
||||
}
|
||||
|
||||
void KProcess::InitializeInterfaces(KernelCore& kernel) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
ASSERT(false && "unimplemented");
|
||||
#else
|
||||
m_exclusive_monitor =
|
||||
Core::MakeExclusiveMonitor(this->GetMemory(), Core::Hardware::NUM_CPU_CORES);
|
||||
|
||||
#ifdef HAS_NCE
|
||||
if (this->IsApplication() && Settings::IsNceEnabled()) {
|
||||
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++)
|
||||
@@ -1322,6 +1328,7 @@ void KProcess::InitializeInterfaces(KernelCore& kernel) {
|
||||
static_cast<Core::DynarmicExclusiveMonitor&>(*m_exclusive_monitor), i);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool KProcess::InsertWatchpoint(KernelCore& kernel, KProcessAddress addr, u64 size, DebugWatchpointType type) {
|
||||
|
||||
@@ -86,15 +86,22 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
||||
|
||||
// BEGONE cold clones of lambdas, for I have merged you all into a SINGLE lambda instead of
|
||||
// spamming lambdas like it's some kind of lambda calculus class
|
||||
for (auto const& e : std::vector<std::pair<std::string_view, void (*)(Core::System&)>>{
|
||||
std::vector<std::pair<std::string_view, void (*)(Core::System&)>> rt_services{
|
||||
{"audio", &Audio::LoopProcess},
|
||||
{"FS", &FileSystem::LoopProcess},
|
||||
{"ldn", &LDN::LoopProcess},
|
||||
{"nvservices", &Nvidia::LoopProcess},
|
||||
{"bsdsocket", &Sockets::LoopProcess},
|
||||
})
|
||||
};
|
||||
#if defined(__EMSCRIPTEN__) || defined(__wasi__) || defined(__OPENORBIS__)
|
||||
for (auto const& e : rt_services)
|
||||
kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); });
|
||||
kernel.RunOnGuestCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); });
|
||||
#else
|
||||
for (auto const& e : rt_services)
|
||||
kernel.RunOnHostCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); }).detach();
|
||||
kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach();
|
||||
kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach();
|
||||
#endif
|
||||
// Avoid cold clones of lambdas -- succintly
|
||||
for (auto const& e : std::vector<std::pair<std::string_view, void (*)(Core::System&)>>{
|
||||
{"sm", &SM::LoopProcess},
|
||||
@@ -118,7 +125,10 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
||||
{"glue", &Glue::LoopProcess},
|
||||
{"grc", &GRC::LoopProcess},
|
||||
{"hid", &HID::LoopProcess},
|
||||
// Must match with src/core/CMakeLists.txt for target_source of jit.cpp
|
||||
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64) || defined(ARCHITECTURE_riscv64) || defined(ARCHITECTURE_loongarch64)
|
||||
{"jit", &JIT::LoopProcess},
|
||||
#endif
|
||||
{"lbl", &LBL::LoopProcess},
|
||||
{"Loader", &LDR::LoopProcess},
|
||||
{"LogManager.Prod", &LM::LoopProcess},
|
||||
|
||||
@@ -28,7 +28,10 @@ enum class NpadMcuState : u32 {
|
||||
struct NpadMcuHolder {
|
||||
NpadMcuState state;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
IAbstractedPad* abstracted_pad;
|
||||
union {
|
||||
IAbstractedPad* abstracted_pad;
|
||||
u64 abstracted_pad_raw;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(NpadMcuHolder) == 0x10, "NpadMcuHolder is an invalid size");
|
||||
|
||||
|
||||
@@ -33,9 +33,15 @@ public:
|
||||
bool is_created{};
|
||||
bool is_mapped{};
|
||||
INSERT_PADDING_BYTES(0x5);
|
||||
Kernel::KSharedMemory* shared_memory;
|
||||
union {
|
||||
Kernel::KSharedMemory* shared_memory = nullptr;
|
||||
u64 shared_memory_raw;
|
||||
};
|
||||
INSERT_PADDING_BYTES(0x38);
|
||||
SharedMemoryFormat* address = nullptr;
|
||||
union {
|
||||
SharedMemoryFormat* address = nullptr;
|
||||
u64 address_raw;
|
||||
};
|
||||
};
|
||||
// Correct size is 0x50 bytes
|
||||
static_assert(sizeof(SharedMemoryHolder) == 0x50, "SharedMemoryHolder is an invalid size");
|
||||
|
||||
@@ -15,8 +15,6 @@ add_library(input_common STATIC
|
||||
drivers/tas_input.h
|
||||
drivers/touch_screen.cpp
|
||||
drivers/touch_screen.h
|
||||
drivers/udp_client.cpp
|
||||
drivers/udp_client.h
|
||||
drivers/virtual_amiibo.cpp
|
||||
drivers/virtual_amiibo.h
|
||||
drivers/virtual_gamepad.cpp
|
||||
@@ -34,8 +32,12 @@ add_library(input_common STATIC
|
||||
input_poller.cpp
|
||||
input_poller.h
|
||||
main.cpp
|
||||
main.h
|
||||
)
|
||||
main.h)
|
||||
if (NOT PLATFORM_EMSCRIPTEN)
|
||||
target_sources(input_common PRIVATE
|
||||
drivers/udp_client.cpp
|
||||
drivers/udp_client.h)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
target_compile_options(input_common PRIVATE
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "input_common/drivers/mouse.h"
|
||||
#include "input_common/drivers/tas_input.h"
|
||||
#include "input_common/drivers/touch_screen.h"
|
||||
#include "input_common/drivers/udp_client.h"
|
||||
#include "input_common/drivers/virtual_amiibo.h"
|
||||
#include "input_common/drivers/virtual_gamepad.h"
|
||||
#include "input_common/helpers/stick_from_buttons.h"
|
||||
@@ -21,6 +20,9 @@
|
||||
#include "input_common/input_mapping.h"
|
||||
#include "input_common/input_poller.h"
|
||||
#include "input_common/main.h"
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#include "input_common/drivers/udp_client.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LIBUSB
|
||||
#include "input_common/drivers/gc_adapter.h"
|
||||
@@ -82,7 +84,9 @@ struct InputSubsystem::Impl {
|
||||
#ifdef ENABLE_LIBUSB
|
||||
RegisterEngine("gcpad", gcadapter);
|
||||
#endif
|
||||
#ifndef __EMSCRIPTEN__
|
||||
RegisterEngine("cemuhookudp", udp_client);
|
||||
#endif
|
||||
RegisterEngine("tas", tas_input);
|
||||
RegisterEngine("camera", camera);
|
||||
#ifdef __ANDROID__
|
||||
@@ -116,7 +120,9 @@ struct InputSubsystem::Impl {
|
||||
#ifdef ENABLE_LIBUSB
|
||||
UnregisterEngine(gcadapter);
|
||||
#endif
|
||||
#ifndef __EMSCRIPTEN__
|
||||
UnregisterEngine(udp_client);
|
||||
#endif
|
||||
UnregisterEngine(tas_input);
|
||||
UnregisterEngine(camera);
|
||||
#ifdef __ANDROID__
|
||||
@@ -152,8 +158,10 @@ struct InputSubsystem::Impl {
|
||||
auto gcadapter_devices = gcadapter->GetInputDevices();
|
||||
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
|
||||
#endif
|
||||
#ifndef __EMSCRIPTEN__
|
||||
auto udp_devices = udp_client->GetInputDevices();
|
||||
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
|
||||
#endif
|
||||
#ifdef HAVE_SDL3
|
||||
auto joycon_devices = joycon->GetInputDevices();
|
||||
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
|
||||
@@ -186,9 +194,11 @@ struct InputSubsystem::Impl {
|
||||
return gcadapter;
|
||||
}
|
||||
#endif
|
||||
#ifndef __EMSCRIPTEN__
|
||||
if (engine == udp_client->GetEngineName()) {
|
||||
return udp_client;
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_SDL3
|
||||
if (engine == sdl->GetEngineName()) {
|
||||
return sdl;
|
||||
@@ -271,9 +281,11 @@ struct InputSubsystem::Impl {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifndef __EMSCRIPTEN__
|
||||
if (engine == udp_client->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
if (engine == tas_input->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
@@ -300,7 +312,9 @@ struct InputSubsystem::Impl {
|
||||
#ifdef ENABLE_LIBUSB
|
||||
gcadapter->BeginConfiguration();
|
||||
#endif
|
||||
#ifndef __EMSCRIPTEN__
|
||||
udp_client->BeginConfiguration();
|
||||
#endif
|
||||
#ifdef HAVE_SDL3
|
||||
sdl->BeginConfiguration();
|
||||
joycon->BeginConfiguration();
|
||||
@@ -316,7 +330,9 @@ struct InputSubsystem::Impl {
|
||||
#ifdef ENABLE_LIBUSB
|
||||
gcadapter->EndConfiguration();
|
||||
#endif
|
||||
#ifndef __EMSCRIPTEN__
|
||||
udp_client->EndConfiguration();
|
||||
#endif
|
||||
#ifdef HAVE_SDL3
|
||||
sdl->EndConfiguration();
|
||||
joycon->EndConfiguration();
|
||||
@@ -341,7 +357,9 @@ struct InputSubsystem::Impl {
|
||||
std::shared_ptr<Mouse> mouse;
|
||||
std::shared_ptr<TouchScreen> touch_screen;
|
||||
std::shared_ptr<TasInput::Tas> tas_input;
|
||||
#ifndef __EMSCRIPTEN__
|
||||
std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
|
||||
#endif
|
||||
std::shared_ptr<Camera> camera;
|
||||
std::shared_ptr<VirtualAmiibo> virtual_amiibo;
|
||||
std::shared_ptr<VirtualGamepad> virtual_gamepad;
|
||||
@@ -470,7 +488,9 @@ bool InputSubsystem::IsStickInverted(const Common::ParamPackage& params) const {
|
||||
}
|
||||
|
||||
void InputSubsystem::ReloadInputDevices() {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
impl->udp_client.get()->ReloadSockets();
|
||||
#endif
|
||||
}
|
||||
|
||||
void InputSubsystem::BeginMapping(Polling::InputType type) {
|
||||
|
||||
@@ -257,8 +257,7 @@ else()
|
||||
# Bracket depth determines maximum size of a fold expression in Clang since 9c9974c3ccb6.
|
||||
# And this in turns limits the size of a std::array.
|
||||
$<$<CXX_COMPILER_ID:Clang>:-fbracket-depth=1024>
|
||||
$<$<CXX_COMPILER_ID:AppleClang>:-fbracket-depth=1024>
|
||||
)
|
||||
$<$<CXX_COMPILER_ID:AppleClang>:-fbracket-depth=1024>)
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(shader_recompiler)
|
||||
|
||||
@@ -31,7 +31,7 @@ struct CbufWordKey {
|
||||
|
||||
struct CbufWordKeyHash {
|
||||
constexpr size_t operator()(const CbufWordKey& k) const noexcept {
|
||||
return (size_t(k.index) << 32) ^ k.offset;
|
||||
return size_t((u64(k.index) << 32) ^ u64(k.offset));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -157,16 +160,14 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
|
||||
|
||||
unsigned Swizzle(u64 insn) {
|
||||
const Encoding texs{insn};
|
||||
const size_t encoding{texs.swizzle};
|
||||
u8 const encoding = u8(texs.swizzle);
|
||||
if (texs.dest_reg_b == IR::Reg::RZ) {
|
||||
if (encoding >= RG_LUT.size()) {
|
||||
if (encoding >= RG_LUT.size())
|
||||
throw NotImplementedException("Illegal RG encoding {}", encoding);
|
||||
}
|
||||
return RG_LUT[encoding];
|
||||
} else {
|
||||
if (encoding >= RGBA_LUT.size()) {
|
||||
if (encoding >= RGBA_LUT.size())
|
||||
throw NotImplementedException("Illegal RGBA encoding {}", encoding);
|
||||
}
|
||||
return RGBA_LUT[encoding];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -142,16 +145,14 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
|
||||
|
||||
unsigned Swizzle(u64 insn) {
|
||||
const Encoding tlds{insn};
|
||||
const size_t encoding{tlds.swizzle};
|
||||
u8 const encoding = u8(tlds.swizzle);
|
||||
if (tlds.dest_reg_b == IR::Reg::RZ) {
|
||||
if (encoding >= RG_LUT.size()) {
|
||||
if (encoding >= RG_LUT.size())
|
||||
throw NotImplementedException("Illegal RG encoding {}", encoding);
|
||||
}
|
||||
return RG_LUT[encoding];
|
||||
} else {
|
||||
if (encoding >= RGBA_LUT.size()) {
|
||||
if (encoding >= RGBA_LUT.size())
|
||||
throw NotImplementedException("Illegal RGBA encoding {}", encoding);
|
||||
}
|
||||
return RGBA_LUT[encoding];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
|
||||
@@ -810,46 +809,46 @@ void BufferCache<P>::BindHostVertexBuffers() {
|
||||
|
||||
if (use_optimized_vertex_buffers) {
|
||||
auto& flags = maxwell3d->dirty.flags;
|
||||
const u32 enabled_mask = enabled_vertex_buffers_mask;
|
||||
bool any_dirty = false;
|
||||
u32 pending_mask = enabled_mask;
|
||||
while (pending_mask != 0) {
|
||||
const u32 index = std::countr_zero(pending_mask);
|
||||
pending_mask &= (pending_mask - 1);
|
||||
u32 enabled_mask = enabled_vertex_buffers_mask;
|
||||
HostBindings<Buffer> bindings{};
|
||||
u32 last_index = (std::numeric_limits<u32>::max)();
|
||||
const auto flush_bindings = [&]() {
|
||||
if (bindings.buffers.empty()) {
|
||||
return;
|
||||
}
|
||||
bindings.max_index = bindings.min_index + static_cast<u32>(bindings.buffers.size());
|
||||
runtime.BindVertexBuffers(bindings);
|
||||
bindings = HostBindings<Buffer>{};
|
||||
last_index = (std::numeric_limits<u32>::max)();
|
||||
};
|
||||
while (enabled_mask != 0) {
|
||||
const u32 index = std::countr_zero(enabled_mask);
|
||||
enabled_mask &= (enabled_mask - 1);
|
||||
const Binding& binding = VertexBufferSlot(index);
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer, binding.buffer_id);
|
||||
SynchronizeBuffer(buffer, binding.device_addr, binding.size);
|
||||
any_dirty |= flags[Dirty::VertexBuffer0 + index];
|
||||
}
|
||||
if (enabled_mask == 0 || !any_dirty) {
|
||||
return;
|
||||
}
|
||||
const u32 min_index = static_cast<u32>(std::countr_zero(enabled_mask));
|
||||
const u32 max_index = 32u - static_cast<u32>(std::countl_zero(enabled_mask));
|
||||
HostBindings<Buffer> bindings{};
|
||||
bindings.min_index = min_index;
|
||||
bindings.max_index = max_index;
|
||||
for (u32 index = min_index; index < max_index; ++index) {
|
||||
flags[Dirty::VertexBuffer0 + index] = false;
|
||||
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
|
||||
if ((enabled_mask & (1u << index)) == 0) {
|
||||
bindings.buffers.push_back(&slot_buffers[NULL_BUFFER_ID]);
|
||||
bindings.offsets.push_back(0);
|
||||
bindings.sizes.push_back(0);
|
||||
bindings.strides.push_back(stride);
|
||||
if (!flags[Dirty::VertexBuffer0 + index]) {
|
||||
flush_bindings();
|
||||
continue;
|
||||
}
|
||||
const Binding& binding = VertexBufferSlot(index);
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
flags[Dirty::VertexBuffer0 + index] = false;
|
||||
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
|
||||
const u32 offset = buffer.Offset(binding.device_addr);
|
||||
buffer.MarkUsage(offset, binding.size);
|
||||
if (!bindings.buffers.empty() && index != last_index + 1) {
|
||||
flush_bindings();
|
||||
}
|
||||
if (bindings.buffers.empty()) {
|
||||
bindings.min_index = index;
|
||||
}
|
||||
bindings.buffers.push_back(&buffer);
|
||||
bindings.offsets.push_back(offset);
|
||||
bindings.sizes.push_back(binding.size);
|
||||
bindings.strides.push_back(stride);
|
||||
last_index = index;
|
||||
}
|
||||
runtime.BindVertexBuffers(bindings);
|
||||
flush_bindings();
|
||||
} else {
|
||||
HostBindings<typename P::Buffer> host_bindings;
|
||||
bool any_valid{false};
|
||||
|
||||
@@ -17,13 +17,11 @@ set(SHADER_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d_buffer.comp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_msaa.frag
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_stencil_msaa.frag
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_bcn.comp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_buffer.comp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d24s8.frag
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d32f.frag
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_d32f_to_abgr8.frag
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#version 430
|
||||
|
||||
#extension GL_EXT_shader_16bit_storage : require
|
||||
#extension GL_EXT_shader_8bit_storage : require
|
||||
|
||||
#define BINDING_INPUT_BUFFER 0
|
||||
#define BINDING_OUTPUT_BUFFER 1
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
uvec3 dim;
|
||||
uint bytes_per_block_log2;
|
||||
|
||||
uvec3 origin;
|
||||
uint layer_stride;
|
||||
|
||||
uint block_size;
|
||||
uint x_shift;
|
||||
uint block_height;
|
||||
uint block_height_mask;
|
||||
} pc;
|
||||
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
|
||||
|
||||
layout(binding = BINDING_OUTPUT_BUFFER, std430) writeonly buffer OutputBuffer {
|
||||
uint out_u32[];
|
||||
};
|
||||
|
||||
layout(local_size_x = 16, local_size_y = 8, local_size_z = 1) in;
|
||||
|
||||
const uint GOB_SIZE_X = 64;
|
||||
const uint GOB_SIZE_Y = 8;
|
||||
|
||||
const uint GOB_SIZE_X_SHIFT = 6;
|
||||
const uint GOB_SIZE_Y_SHIFT = 3;
|
||||
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
|
||||
|
||||
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
|
||||
|
||||
uint SwizzleTable(uint pos) {
|
||||
const uint t[8] = uint[](
|
||||
0x12100200, 0x13110301, 0x16140604, 0x17150705,
|
||||
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
|
||||
);
|
||||
const uint i = pos >> 4;
|
||||
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
|
||||
return (h << 4) | (pos & 0xf);
|
||||
}
|
||||
|
||||
uint SwizzleOffset(uvec2 pos) {
|
||||
pos = pos & SWIZZLE_MASK;
|
||||
return SwizzleTable(pos.y * 64u + pos.x);
|
||||
}
|
||||
|
||||
uvec4 ReadTexel(uint offset) {
|
||||
switch (pc.bytes_per_block_log2) {
|
||||
case 2u:
|
||||
return uvec4(u32data[offset / 4u], 0u, 0u, 0u);
|
||||
case 3u:
|
||||
return uvec4(u64data[offset / 8u], 0u, 0u);
|
||||
case 4u:
|
||||
return u128data[offset / 16u];
|
||||
}
|
||||
return uvec4(0u);
|
||||
}
|
||||
|
||||
void main() {
|
||||
uvec3 coord = gl_GlobalInvocationID;
|
||||
if (coord.x >= pc.dim.x || coord.y >= pc.dim.y || coord.z >= pc.dim.z) {
|
||||
return;
|
||||
}
|
||||
|
||||
uvec3 pos = coord + pc.origin;
|
||||
pos.x <<= pc.bytes_per_block_log2;
|
||||
|
||||
uint swizzle = SwizzleOffset(pos.xy);
|
||||
uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
|
||||
|
||||
uint offset = 0u;
|
||||
offset += pos.z * pc.layer_stride;
|
||||
offset += (block_y >> pc.block_height) * pc.block_size;
|
||||
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
|
||||
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
|
||||
offset += swizzle;
|
||||
|
||||
uvec4 texel = ReadTexel(offset);
|
||||
|
||||
uint words = 1u << (pc.bytes_per_block_log2 - 2u);
|
||||
uint linear_index = coord.x + coord.y * pc.dim.x + coord.z * pc.dim.x * pc.dim.y;
|
||||
uint out_idx = linear_index * words;
|
||||
|
||||
out_u32[out_idx] = texel.x;
|
||||
if (words > 1u) {
|
||||
out_u32[out_idx + 1u] = texel.y;
|
||||
}
|
||||
if (words > 2u) {
|
||||
out_u32[out_idx + 2u] = texel.z;
|
||||
out_u32[out_idx + 3u] = texel.w;
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#version 430
|
||||
|
||||
#define BINDING_INPUT_BUFFER 0
|
||||
#define BINDING_OUTPUT_BUFFER 1
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
uvec3 dim;
|
||||
uint bytes_per_block_log2;
|
||||
|
||||
uvec3 origin;
|
||||
uint slice_size;
|
||||
|
||||
uint block_size;
|
||||
uint x_shift;
|
||||
uint block_height;
|
||||
uint block_height_mask;
|
||||
|
||||
uint block_depth;
|
||||
uint block_depth_mask;
|
||||
} pc;
|
||||
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
|
||||
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
|
||||
|
||||
layout(binding = BINDING_OUTPUT_BUFFER, std430) writeonly buffer OutputBuffer {
|
||||
uint out_u32[];
|
||||
};
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8, local_size_z = 4) in;
|
||||
|
||||
const uint GOB_SIZE_X = 64;
|
||||
const uint GOB_SIZE_Y = 8;
|
||||
|
||||
const uint GOB_SIZE_X_SHIFT = 6;
|
||||
const uint GOB_SIZE_Y_SHIFT = 3;
|
||||
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
|
||||
|
||||
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
|
||||
|
||||
uint SwizzleTable(uint pos) {
|
||||
const uint t[8] = uint[](
|
||||
0x12100200, 0x13110301, 0x16140604, 0x17150705,
|
||||
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
|
||||
);
|
||||
const uint i = pos >> 4;
|
||||
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
|
||||
return (h << 4) | (pos & 0xf);
|
||||
}
|
||||
|
||||
uint SwizzleOffset(uvec2 pos) {
|
||||
pos = pos & SWIZZLE_MASK;
|
||||
return SwizzleTable(pos.y * 64u + pos.x);
|
||||
}
|
||||
|
||||
uvec4 ReadTexel(uint offset) {
|
||||
switch (pc.bytes_per_block_log2) {
|
||||
case 2u:
|
||||
return uvec4(u32data[offset / 4u], 0u, 0u, 0u);
|
||||
case 3u:
|
||||
return uvec4(u64data[offset / 8u], 0u, 0u);
|
||||
case 4u:
|
||||
return u128data[offset / 16u];
|
||||
}
|
||||
return uvec4(0u);
|
||||
}
|
||||
|
||||
void main() {
|
||||
uvec3 coord = gl_GlobalInvocationID;
|
||||
if (coord.x >= pc.dim.x || coord.y >= pc.dim.y || coord.z >= pc.dim.z) {
|
||||
return;
|
||||
}
|
||||
|
||||
uvec3 pos = coord + pc.origin;
|
||||
pos.x <<= pc.bytes_per_block_log2;
|
||||
|
||||
uint swizzle = SwizzleOffset(pos.xy);
|
||||
uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
|
||||
|
||||
uint offset = 0u;
|
||||
offset += (pos.z >> pc.block_depth) * pc.slice_size;
|
||||
offset += (pos.z & pc.block_depth_mask) << (GOB_SIZE_SHIFT + pc.block_height);
|
||||
offset += (block_y >> pc.block_height) * pc.block_size;
|
||||
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
|
||||
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
|
||||
offset += swizzle;
|
||||
|
||||
uvec4 texel = ReadTexel(offset);
|
||||
|
||||
uint words = 1u << (pc.bytes_per_block_log2 - 2u);
|
||||
uint linear_index = coord.x + coord.y * pc.dim.x + coord.z * pc.dim.x * pc.dim.y;
|
||||
uint out_idx = linear_index * words;
|
||||
|
||||
out_u32[out_idx] = texel.x;
|
||||
if (words > 1u) {
|
||||
out_u32[out_idx + 1u] = texel.y;
|
||||
}
|
||||
if (words > 2u) {
|
||||
out_u32[out_idx + 2u] = texel.z;
|
||||
out_u32[out_idx + 3u] = texel.w;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ layout(push_constant) uniform constants {
|
||||
vec2 scale;
|
||||
vec2 size;
|
||||
vec2 resize_factor;
|
||||
vec2 crop_offset;
|
||||
float edge_sharpness;
|
||||
};
|
||||
layout(location = 0) out highp vec2 texcoord;
|
||||
@@ -16,5 +15,5 @@ void main() {
|
||||
float x = float((gl_VertexIndex & 1) << 2);
|
||||
float y = float((gl_VertexIndex & 2) << 1);
|
||||
gl_Position = vec4(x - 1.0f, y - 1.0f, 0.0, 1.0f) * vec4(sign(resize_factor), 1.f, 1.f);
|
||||
texcoord = crop_offset + vec2(x, y) * abs(resize_factor) * 0.5;
|
||||
texcoord = vec2(x, y) * abs(resize_factor) * 0.5;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ layout(push_constant) uniform constants {
|
||||
vec2 scale;
|
||||
vec2 size;
|
||||
vec2 resize_factor;
|
||||
vec2 crop_offset;
|
||||
float edge_sharpness;
|
||||
};
|
||||
layout(set = 0, binding = 0) uniform sampler2D sampler0;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
layout( push_constant ) uniform constants {
|
||||
vec4 ViewportInfo[1];
|
||||
vec2 ResizeFactor;
|
||||
vec2 CropOffset;
|
||||
float EdgeSharpness;
|
||||
};
|
||||
layout(set = 0, binding = 0) uniform sampler2D ps0;
|
||||
|
||||
@@ -1191,7 +1191,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceColorPipeline(const BlitImagePipelineKe
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}, device.StaticPipelineCache()));
|
||||
}));
|
||||
return *blit_color_pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1223,7 +1223,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePip
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}, device.StaticPipelineCache()));
|
||||
}));
|
||||
return *blit_depth_stencil_pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1276,7 +1276,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}, device.StaticPipelineCache()));
|
||||
}));
|
||||
return *clear_color_pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1332,7 +1332,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}, device.StaticPipelineCache()));
|
||||
}));
|
||||
return *clear_stencil_pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1375,7 +1375,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPip
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}, device.StaticPipelineCache()));
|
||||
}));
|
||||
return *blit_msaa_color_pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1413,7 +1413,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceResolveDepthStencilPipeline(VkRenderPas
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}, device.StaticPipelineCache()));
|
||||
}));
|
||||
return *pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1458,7 +1458,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipeline
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}, device.StaticPipelineCache()));
|
||||
}));
|
||||
return *msaa_copy_pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1499,7 +1499,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}, device.StaticPipelineCache());
|
||||
});
|
||||
}
|
||||
|
||||
void BlitImageHelper::ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
|
||||
@@ -1542,7 +1542,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}, device.StaticPipelineCache());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/div_ceil.h"
|
||||
#include "common/settings.h"
|
||||
@@ -19,7 +17,7 @@
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
using PushConstants = std::array<u32, 4 + 2 + 2 + 1>;
|
||||
using PushConstants = std::array<u32, 4 + 2 + 1>;
|
||||
|
||||
SGSR::SGSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, VkExtent2D extent, bool edge_dir)
|
||||
: m_memory_allocator{memory_allocator}
|
||||
@@ -102,28 +100,26 @@ VkImageView SGSR::Draw(const Device& device, Scheduler& scheduler, size_t image_
|
||||
|
||||
const f32 input_image_width = f32(input_image_extent.width);
|
||||
const f32 input_image_height = f32(input_image_extent.height);
|
||||
const f32 crop_width = (crop_rect.right - crop_rect.left) * input_image_width;
|
||||
const f32 crop_height = (crop_rect.bottom - crop_rect.top) * input_image_height;
|
||||
static constexpr f32 EDGE_SHARPNESS_MAX = 2.0f;
|
||||
const f32 edge_sharpness =
|
||||
EDGE_SHARPNESS_MAX - f32(Settings::values.fsr_sharpening_slider.GetValue()) / 200.0f;
|
||||
const f32 viewport_width = (crop_rect.right - crop_rect.left) * input_image_width;
|
||||
const f32 viewport_height = (crop_rect.bottom - crop_rect.top) * input_image_height;
|
||||
// expected [0, 2]
|
||||
const f32 sharpening = f32(Settings::values.fsr_sharpening_slider.GetValue()) / 100.0f;
|
||||
|
||||
// p = (tex * viewport) / input = [0,n] (normalized texcoords)
|
||||
// p * input = [0,1024], [0,768]
|
||||
// layout( push_constant ) uniform constants {
|
||||
// highp vec4 ViewportInfo[1];
|
||||
// highp vec2 ResizeFactor;
|
||||
// highp vec2 CropOffset;
|
||||
// highp float EdgeSharpness;
|
||||
// };
|
||||
PushConstants viewport_con{};
|
||||
viewport_con[0] = std::bit_cast<u32>(1.f / input_image_width);
|
||||
viewport_con[1] = std::bit_cast<u32>(1.f / input_image_height);
|
||||
viewport_con[2] = std::bit_cast<u32>(input_image_width);
|
||||
viewport_con[3] = std::bit_cast<u32>(input_image_height);
|
||||
viewport_con[4] = std::bit_cast<u32>(crop_width / input_image_width);
|
||||
viewport_con[5] = std::bit_cast<u32>(crop_height / input_image_height);
|
||||
viewport_con[6] = std::bit_cast<u32>((std::min)(crop_rect.left, crop_rect.right));
|
||||
viewport_con[7] = std::bit_cast<u32>((std::min)(crop_rect.top, crop_rect.bottom));
|
||||
viewport_con[8] = std::bit_cast<u32>(edge_sharpness);
|
||||
viewport_con[0] = std::bit_cast<u32>(std::abs(1.f / viewport_width));
|
||||
viewport_con[1] = std::bit_cast<u32>(std::abs(1.f / viewport_height));
|
||||
viewport_con[2] = std::bit_cast<u32>(std::abs(viewport_width));
|
||||
viewport_con[3] = std::bit_cast<u32>(std::abs(viewport_height));
|
||||
viewport_con[4] = std::bit_cast<u32>(viewport_width / input_image_width);
|
||||
viewport_con[5] = std::bit_cast<u32>(viewport_height / input_image_height);
|
||||
viewport_con[6] = std::bit_cast<u32>(sharpening);
|
||||
|
||||
UploadImages(device, scheduler);
|
||||
UpdateDescriptorSets(device, source_image_view, image_index);
|
||||
|
||||
@@ -491,7 +491,7 @@ static vk::Pipeline CreateWrappedPipelineImpl(
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = 0,
|
||||
.basePipelineIndex = 0,
|
||||
}, device.StaticPipelineCache());
|
||||
});
|
||||
}
|
||||
|
||||
vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
|
||||
|
||||
@@ -22,9 +22,7 @@
|
||||
#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
|
||||
#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
|
||||
#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
|
||||
#include "video_core/host_shaders/block_linear_unswizzle_2d_buffer_comp_spv.h"
|
||||
#include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h"
|
||||
#include "video_core/host_shaders/block_linear_unswizzle_3d_buffer_comp_spv.h"
|
||||
#include "video_core/renderer_vulkan/vk_compute_pass.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
@@ -270,7 +268,7 @@ ComputePass::ComputePass(const Device& device_, Scheduler& scheduler, Descriptor
|
||||
.layout = *layout,
|
||||
.basePipelineHandle = {},
|
||||
.basePipelineIndex = 0,
|
||||
}, device.StaticPipelineCache());
|
||||
});
|
||||
}
|
||||
|
||||
ComputePass::~ComputePass() = default;
|
||||
@@ -874,479 +872,4 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
|
||||
});
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr u32 BL2D_BINDING_INPUT_BUFFER = 0;
|
||||
constexpr u32 BL2D_BINDING_OUTPUT_BUFFER = 1;
|
||||
|
||||
struct alignas(16) BlockLinearUnswizzle2DPushConstants {
|
||||
std::array<u32, 3> dim;
|
||||
u32 bytes_per_block_log2;
|
||||
std::array<u32, 3> origin;
|
||||
u32 layer_stride;
|
||||
u32 block_size;
|
||||
u32 x_shift;
|
||||
u32 block_height;
|
||||
u32 block_height_mask;
|
||||
};
|
||||
static_assert(sizeof(BlockLinearUnswizzle2DPushConstants) <= 128);
|
||||
|
||||
constexpr std::array<VkDescriptorSetLayoutBinding, 2> BL2D_BINDINGS{{
|
||||
{
|
||||
.binding = BL2D_BINDING_INPUT_BUFFER,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.pImmutableSamplers = nullptr,
|
||||
},
|
||||
{
|
||||
.binding = BL2D_BINDING_OUTPUT_BUFFER,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.pImmutableSamplers = nullptr,
|
||||
},
|
||||
}};
|
||||
|
||||
constexpr std::array<VkDescriptorUpdateTemplateEntry, 2> BL2D_TEMPLATE{{
|
||||
{
|
||||
.dstBinding = BL2D_BINDING_INPUT_BUFFER,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.offset = BL2D_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
|
||||
.stride = sizeof(DescriptorUpdateEntry),
|
||||
},
|
||||
{
|
||||
.dstBinding = BL2D_BINDING_OUTPUT_BUFFER,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.offset = BL2D_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry),
|
||||
.stride = sizeof(DescriptorUpdateEntry),
|
||||
},
|
||||
}};
|
||||
|
||||
constexpr DescriptorBankInfo BL2D_BANK_INFO{
|
||||
.uniform_buffers = 0,
|
||||
.storage_buffers = 2,
|
||||
.texture_buffers = 0,
|
||||
.image_buffers = 0,
|
||||
.textures = 0,
|
||||
.images = 0,
|
||||
.score = 2,
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
BlockLinearUnswizzle2DPass::BlockLinearUnswizzle2DPass(
|
||||
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
|
||||
StagingBufferPool& staging_buffer_pool_,
|
||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
|
||||
: ComputePass(device_, scheduler_, descriptor_pool_, BL2D_BINDINGS, BL2D_TEMPLATE,
|
||||
BL2D_BANK_INFO,
|
||||
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle2DPushConstants)>,
|
||||
BLOCK_LINEAR_UNSWIZZLE_2D_BUFFER_COMP_SPV),
|
||||
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
|
||||
compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
|
||||
|
||||
BlockLinearUnswizzle2DPass::~BlockLinearUnswizzle2DPass() = default;
|
||||
|
||||
bool BlockLinearUnswizzle2DPass::IsSupported(const VideoCommon::ImageInfo& info) {
|
||||
if (info.type != VideoCommon::ImageType::e2D) {
|
||||
return false;
|
||||
}
|
||||
if (info.resources.levels != 1 || info.resources.layers != 1) {
|
||||
return false;
|
||||
}
|
||||
if (info.num_samples > 1) {
|
||||
return false;
|
||||
}
|
||||
if (VideoCore::Surface::IsPixelFormatASTC(info.format) ||
|
||||
VideoCore::Surface::IsPixelFormatBCn(info.format)) {
|
||||
return false;
|
||||
}
|
||||
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
|
||||
if (bytes_per_block != 4 && bytes_per_block != 8 && bytes_per_block != 16) {
|
||||
return false;
|
||||
}
|
||||
return VideoCore::Surface::DefaultBlockWidth(info.format) == 1 &&
|
||||
VideoCore::Surface::DefaultBlockHeight(info.format) == 1;
|
||||
}
|
||||
|
||||
void BlockLinearUnswizzle2DPass::Unswizzle(
|
||||
Image& image, const StagingBufferRef& swizzled,
|
||||
std::span<const VideoCommon::SwizzleParameters> swizzles) {
|
||||
if (swizzles.empty()) {
|
||||
return;
|
||||
}
|
||||
const VideoCommon::SwizzleParameters& sw = swizzles.front();
|
||||
const auto params = VideoCommon::Accelerated::MakeBlockLinearSwizzle2DParams(sw, image.info);
|
||||
|
||||
const u32 width = sw.num_tiles.width;
|
||||
const u32 height = sw.num_tiles.height;
|
||||
const u32 depth = image.info.resources.layers;
|
||||
const u32 bytes_per_block = 1u << params.bytes_per_block_log2;
|
||||
const VkDeviceSize output_size =
|
||||
static_cast<VkDeviceSize>(width) * height * depth * bytes_per_block;
|
||||
|
||||
const StagingBufferRef output =
|
||||
staging_buffer_pool.Request(static_cast<size_t>(output_size), MemoryUsage::DeviceLocal);
|
||||
|
||||
BlockLinearUnswizzle2DPushConstants pc{};
|
||||
pc.dim = {width, height, depth};
|
||||
pc.bytes_per_block_log2 = params.bytes_per_block_log2;
|
||||
pc.origin = params.origin;
|
||||
pc.layer_stride = params.layer_stride;
|
||||
pc.block_size = params.block_size;
|
||||
pc.x_shift = params.x_shift;
|
||||
pc.block_height = params.block_height;
|
||||
pc.block_height_mask = params.block_height_mask;
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
|
||||
compute_pass_descriptor_queue.Acquire(scheduler, 2);
|
||||
compute_pass_descriptor_queue.AddBuffer(swizzled.buffer, sw.buffer_offset + swizzled.offset,
|
||||
image.guest_size_bytes - sw.buffer_offset);
|
||||
compute_pass_descriptor_queue.AddBuffer(output.buffer, output.offset, output_size);
|
||||
|
||||
const void* descriptor_data = compute_pass_descriptor_queue.UpdateData();
|
||||
const VkDescriptorSet set = descriptor_allocator.Commit();
|
||||
|
||||
const u32 gx = Common::DivCeil(width, 16u);
|
||||
const u32 gy = Common::DivCeil(height, 8u);
|
||||
const bool is_initialized = image.ExchangeInitialization();
|
||||
|
||||
const VkBuffer out_buffer = output.buffer;
|
||||
const VkDeviceSize out_offset = output.offset;
|
||||
const VkImage dst_image = image.Handle();
|
||||
const VkImageAspectFlags aspect = image.AspectMask();
|
||||
|
||||
scheduler.Record([this, set, descriptor_data, pc, gx, gy, depth, output_size, out_buffer,
|
||||
out_offset, dst_image, aspect, width, height,
|
||||
is_initialized](vk::CommandBuffer cmdbuf) {
|
||||
if (dst_image == VK_NULL_HANDLE || out_buffer == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
|
||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
||||
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc);
|
||||
cmdbuf.Dispatch(gx, gy, depth);
|
||||
|
||||
const VkBufferMemoryBarrier buffer_barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = out_buffer,
|
||||
.offset = out_offset,
|
||||
.size = output_size,
|
||||
};
|
||||
const VkImageMemoryBarrier pre_copy{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = static_cast<VkAccessFlags>(is_initialized ? VK_ACCESS_SHADER_READ_BIT
|
||||
: VK_ACCESS_NONE),
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dst_image,
|
||||
.subresourceRange{
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||
},
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
||||
(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE
|
||||
: VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT),
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, buffer_barrier, pre_copy);
|
||||
|
||||
const VkBufferImageCopy copy{
|
||||
.bufferOffset = out_offset,
|
||||
.bufferRowLength = 0,
|
||||
.bufferImageHeight = 0,
|
||||
.imageSubresource{
|
||||
.aspectMask = aspect,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = depth,
|
||||
},
|
||||
.imageOffset = {0, 0, 0},
|
||||
.imageExtent = {width, height, 1},
|
||||
};
|
||||
cmdbuf.CopyBufferToImage(out_buffer, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy);
|
||||
|
||||
const VkImageMemoryBarrier post_copy{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dst_image,
|
||||
.subresourceRange{
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||
},
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE,
|
||||
0, {}, {}, post_copy);
|
||||
});
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr u32 BL3DB_BINDING_INPUT_BUFFER = 0;
|
||||
constexpr u32 BL3DB_BINDING_OUTPUT_BUFFER = 1;
|
||||
|
||||
struct alignas(16) BlockLinearUnswizzle3DBufferPushConstants {
|
||||
std::array<u32, 3> dim;
|
||||
u32 bytes_per_block_log2;
|
||||
std::array<u32, 3> origin;
|
||||
u32 slice_size;
|
||||
u32 block_size;
|
||||
u32 x_shift;
|
||||
u32 block_height;
|
||||
u32 block_height_mask;
|
||||
u32 block_depth;
|
||||
u32 block_depth_mask;
|
||||
};
|
||||
static_assert(sizeof(BlockLinearUnswizzle3DBufferPushConstants) <= 128);
|
||||
|
||||
constexpr std::array<VkDescriptorSetLayoutBinding, 2> BL3DB_BINDINGS{{
|
||||
{
|
||||
.binding = BL3DB_BINDING_INPUT_BUFFER,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.pImmutableSamplers = nullptr,
|
||||
},
|
||||
{
|
||||
.binding = BL3DB_BINDING_OUTPUT_BUFFER,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.pImmutableSamplers = nullptr,
|
||||
},
|
||||
}};
|
||||
|
||||
constexpr std::array<VkDescriptorUpdateTemplateEntry, 2> BL3DB_TEMPLATE{{
|
||||
{
|
||||
.dstBinding = BL3DB_BINDING_INPUT_BUFFER,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.offset = BL3DB_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
|
||||
.stride = sizeof(DescriptorUpdateEntry),
|
||||
},
|
||||
{
|
||||
.dstBinding = BL3DB_BINDING_OUTPUT_BUFFER,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.offset = BL3DB_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry),
|
||||
.stride = sizeof(DescriptorUpdateEntry),
|
||||
},
|
||||
}};
|
||||
|
||||
constexpr DescriptorBankInfo BL3DB_BANK_INFO{
|
||||
.uniform_buffers = 0,
|
||||
.storage_buffers = 2,
|
||||
.texture_buffers = 0,
|
||||
.image_buffers = 0,
|
||||
.textures = 0,
|
||||
.images = 0,
|
||||
.score = 2,
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
BlockLinearUnswizzle3DBufferPass::BlockLinearUnswizzle3DBufferPass(
|
||||
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
|
||||
StagingBufferPool& staging_buffer_pool_,
|
||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
|
||||
: ComputePass(device_, scheduler_, descriptor_pool_, BL3DB_BINDINGS, BL3DB_TEMPLATE,
|
||||
BL3DB_BANK_INFO,
|
||||
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle3DBufferPushConstants)>,
|
||||
BLOCK_LINEAR_UNSWIZZLE_3D_BUFFER_COMP_SPV),
|
||||
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
|
||||
compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
|
||||
|
||||
BlockLinearUnswizzle3DBufferPass::~BlockLinearUnswizzle3DBufferPass() = default;
|
||||
|
||||
bool BlockLinearUnswizzle3DBufferPass::IsSupported(const Device& device,
|
||||
const VideoCommon::ImageInfo& info) {
|
||||
if (info.type != VideoCommon::ImageType::e3D) {
|
||||
return false;
|
||||
}
|
||||
if (info.resources.levels != 1 || info.resources.layers != 1) {
|
||||
return false;
|
||||
}
|
||||
if (info.num_samples > 1) {
|
||||
return false;
|
||||
}
|
||||
if (info.size.depth <= 1) {
|
||||
return false;
|
||||
}
|
||||
if (VideoCore::Surface::IsPixelFormatASTC(info.format)) {
|
||||
return false;
|
||||
}
|
||||
if (VideoCore::Surface::IsPixelFormatBCn(info.format) && !device.IsOptimalBcnSupported()) {
|
||||
return false;
|
||||
}
|
||||
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
|
||||
return bytes_per_block == 4 || bytes_per_block == 8 || bytes_per_block == 16;
|
||||
}
|
||||
|
||||
void BlockLinearUnswizzle3DBufferPass::Unswizzle(
|
||||
Image& image, const StagingBufferRef& swizzled,
|
||||
std::span<const VideoCommon::SwizzleParameters> swizzles) {
|
||||
if (swizzles.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const VideoCommon::SwizzleParameters& sw = swizzles.front();
|
||||
const auto params = VideoCommon::Accelerated::MakeBlockLinearSwizzle3DParams(sw, image.info);
|
||||
|
||||
const u32 blocks_x = sw.num_tiles.width;
|
||||
const u32 blocks_y = sw.num_tiles.height;
|
||||
const u32 blocks_z = sw.num_tiles.depth;
|
||||
const u32 bytes_per_block = 1u << params.bytes_per_block_log2;
|
||||
const VkDeviceSize output_size =
|
||||
static_cast<VkDeviceSize>(blocks_x) * blocks_y * blocks_z * bytes_per_block;
|
||||
|
||||
const StagingBufferRef output =
|
||||
staging_buffer_pool.Request(static_cast<size_t>(output_size), MemoryUsage::DeviceLocal);
|
||||
|
||||
BlockLinearUnswizzle3DBufferPushConstants pc{};
|
||||
pc.dim = {blocks_x, blocks_y, blocks_z};
|
||||
pc.bytes_per_block_log2 = params.bytes_per_block_log2;
|
||||
pc.origin = params.origin;
|
||||
pc.slice_size = params.slice_size;
|
||||
pc.block_size = params.block_size;
|
||||
pc.x_shift = params.x_shift;
|
||||
pc.block_height = params.block_height;
|
||||
pc.block_height_mask = params.block_height_mask;
|
||||
pc.block_depth = params.block_depth;
|
||||
pc.block_depth_mask = params.block_depth_mask;
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
|
||||
compute_pass_descriptor_queue.Acquire(scheduler, 2);
|
||||
compute_pass_descriptor_queue.AddBuffer(swizzled.buffer, sw.buffer_offset + swizzled.offset,
|
||||
image.guest_size_bytes - sw.buffer_offset);
|
||||
compute_pass_descriptor_queue.AddBuffer(output.buffer, output.offset, output_size);
|
||||
|
||||
const void* descriptor_data = compute_pass_descriptor_queue.UpdateData();
|
||||
const VkDescriptorSet set = descriptor_allocator.Commit();
|
||||
|
||||
const u32 gx = Common::DivCeil(blocks_x, 8u);
|
||||
const u32 gy = Common::DivCeil(blocks_y, 8u);
|
||||
const u32 gz = Common::DivCeil(blocks_z, 4u);
|
||||
const bool is_initialized = image.ExchangeInitialization();
|
||||
|
||||
const VkBuffer out_buffer = output.buffer;
|
||||
const VkDeviceSize out_offset = output.offset;
|
||||
const VkImage dst_image = image.Handle();
|
||||
const VkImageAspectFlags aspect = image.AspectMask();
|
||||
const VkExtent3D extent{
|
||||
.width = image.info.size.width,
|
||||
.height = image.info.size.height,
|
||||
.depth = image.info.size.depth,
|
||||
};
|
||||
|
||||
scheduler.Record([this, set, descriptor_data, pc, gx, gy, gz, output_size, out_buffer,
|
||||
out_offset, dst_image, aspect, extent,
|
||||
is_initialized](vk::CommandBuffer cmdbuf) {
|
||||
if (dst_image == VK_NULL_HANDLE || out_buffer == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
|
||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
||||
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc);
|
||||
cmdbuf.Dispatch(gx, gy, gz);
|
||||
|
||||
const VkBufferMemoryBarrier buffer_barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = out_buffer,
|
||||
.offset = out_offset,
|
||||
.size = output_size,
|
||||
};
|
||||
const VkImageMemoryBarrier pre_copy{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = static_cast<VkAccessFlags>(is_initialized ? VK_ACCESS_SHADER_READ_BIT
|
||||
: VK_ACCESS_NONE),
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dst_image,
|
||||
.subresourceRange{
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||
},
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
||||
(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE
|
||||
: VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT),
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, buffer_barrier, pre_copy);
|
||||
|
||||
const VkBufferImageCopy copy{
|
||||
.bufferOffset = out_offset,
|
||||
.bufferRowLength = 0,
|
||||
.bufferImageHeight = 0,
|
||||
.imageSubresource{
|
||||
.aspectMask = aspect,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
.imageOffset = {0, 0, 0},
|
||||
.imageExtent = extent,
|
||||
};
|
||||
cmdbuf.CopyBufferToImage(out_buffer, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy);
|
||||
|
||||
const VkImageMemoryBarrier post_copy{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dst_image,
|
||||
.subresourceRange{
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||
},
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE,
|
||||
0, {}, {}, post_copy);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -164,42 +164,4 @@ private:
|
||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
|
||||
};
|
||||
|
||||
class BlockLinearUnswizzle2DPass final : public ComputePass {
|
||||
public:
|
||||
explicit BlockLinearUnswizzle2DPass(const Device& device_, Scheduler& scheduler_,
|
||||
DescriptorPool& descriptor_pool_,
|
||||
StagingBufferPool& staging_buffer_pool_,
|
||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
|
||||
~BlockLinearUnswizzle2DPass();
|
||||
|
||||
[[nodiscard]] static bool IsSupported(const VideoCommon::ImageInfo& info);
|
||||
|
||||
void Unswizzle(Image& image, const StagingBufferRef& swizzled,
|
||||
std::span<const VideoCommon::SwizzleParameters> swizzles);
|
||||
|
||||
private:
|
||||
Scheduler& scheduler;
|
||||
StagingBufferPool& staging_buffer_pool;
|
||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
|
||||
};
|
||||
|
||||
class BlockLinearUnswizzle3DBufferPass final : public ComputePass {
|
||||
public:
|
||||
explicit BlockLinearUnswizzle3DBufferPass(
|
||||
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
|
||||
StagingBufferPool& staging_buffer_pool_,
|
||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
|
||||
~BlockLinearUnswizzle3DBufferPass();
|
||||
|
||||
[[nodiscard]] static bool IsSupported(const Device& device, const VideoCommon::ImageInfo& info);
|
||||
|
||||
void Unswizzle(Image& image, const StagingBufferRef& swizzled,
|
||||
std::span<const VideoCommon::SwizzleParameters> swizzles);
|
||||
|
||||
private:
|
||||
Scheduler& scheduler;
|
||||
StagingBufferPool& staging_buffer_pool;
|
||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -63,8 +63,6 @@ using VideoCommon::GenericEnvironment;
|
||||
using VideoCommon::GraphicsEnvironment;
|
||||
|
||||
constexpr u32 CACHE_VERSION = 18;
|
||||
constexpr size_t VULKAN_CACHE_FLUSH_PIPELINES = 128;
|
||||
constexpr size_t VULKAN_CACHE_FLUSH_MIN_SECONDS = 30;
|
||||
constexpr std::array<char, 8> VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'};
|
||||
|
||||
template <typename Container>
|
||||
@@ -701,10 +699,6 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
|
||||
if (use_vulkan_pipeline_cache) {
|
||||
SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache,
|
||||
CACHE_VERSION);
|
||||
size_t size = 0;
|
||||
vulkan_pipeline_cache.Read(&size, nullptr);
|
||||
last_cache_size.store(size, std::memory_order_relaxed);
|
||||
last_flush = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
if (state.statistics) {
|
||||
@@ -712,35 +706,6 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineCache::QueueVulkanPipelineCacheFlush() {
|
||||
if (!use_vulkan_pipeline_cache || vulkan_pipeline_cache_filename.empty()) {
|
||||
return;
|
||||
}
|
||||
if (++pipelines_since_flush < VULKAN_CACHE_FLUSH_PIPELINES) {
|
||||
return;
|
||||
}
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto megabytes = last_cache_size.load(std::memory_order_relaxed) / (1024 * 1024);
|
||||
const std::chrono::seconds interval{
|
||||
std::max<size_t>(VULKAN_CACHE_FLUSH_MIN_SECONDS, megabytes)};
|
||||
if (last_flush.time_since_epoch().count() != 0 && now - last_flush < interval) {
|
||||
return;
|
||||
}
|
||||
if (flush_in_flight.exchange(true, std::memory_order_acq_rel)) {
|
||||
return;
|
||||
}
|
||||
pipelines_since_flush = 0;
|
||||
last_flush = now;
|
||||
serialization_thread.QueueWork([this] {
|
||||
SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache,
|
||||
CACHE_VERSION);
|
||||
size_t size = 0;
|
||||
vulkan_pipeline_cache.Read(&size, nullptr);
|
||||
last_cache_size.store(size, std::memory_order_relaxed);
|
||||
flush_in_flight.store(false, std::memory_order_release);
|
||||
});
|
||||
}
|
||||
|
||||
GraphicsPipeline* PipelineCache::CurrentGraphicsPipelineSlowPath() {
|
||||
const auto [pair, is_new]{graphics_cache.try_emplace(graphics_key)};
|
||||
auto& pipeline{pair->second};
|
||||
@@ -779,7 +744,7 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
|
||||
std::span<Shader::Environment* const> envs, PipelineStatistics* statistics,
|
||||
bool build_in_parallel) try {
|
||||
auto hash = key.Hash();
|
||||
LOG_DEBUG(Render_Vulkan, "{:#016x}", hash);
|
||||
LOG_INFO(Render_Vulkan, "{:#016x}", hash);
|
||||
size_t env_index{0};
|
||||
std::array<Shader::IR::Program, Maxwell::MaxShaderProgram> programs;
|
||||
const bool uses_vertex_a{key.unique_hashes[0] != 0};
|
||||
@@ -915,7 +880,6 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() {
|
||||
}
|
||||
SerializePipeline(key, env_ptrs, pipeline_cache_filename, CACHE_VERSION);
|
||||
});
|
||||
QueueVulkanPipelineCacheFlush();
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@@ -935,7 +899,6 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
|
||||
SerializePipeline(key, std::array<const GenericEnvironment*, 1>{&env_},
|
||||
pipeline_cache_filename, CACHE_VERSION);
|
||||
});
|
||||
QueueVulkanPipelineCacheFlush();
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@@ -948,7 +911,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Render_Vulkan, "{:#016x}", hash);
|
||||
LOG_INFO(Render_Vulkan, "{:#016x}", hash);
|
||||
|
||||
Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()};
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
@@ -146,8 +144,6 @@ private:
|
||||
vk::PipelineCache LoadVulkanPipelineCache(const std::filesystem::path& filename,
|
||||
u32 expected_cache_version);
|
||||
|
||||
void QueueVulkanPipelineCacheFlush();
|
||||
|
||||
const Device& device;
|
||||
Scheduler& scheduler;
|
||||
DescriptorPool& descriptor_pool;
|
||||
@@ -175,10 +171,6 @@ private:
|
||||
|
||||
std::filesystem::path vulkan_pipeline_cache_filename;
|
||||
vk::PipelineCache vulkan_pipeline_cache;
|
||||
size_t pipelines_since_flush{};
|
||||
std::chrono::steady_clock::time_point last_flush{};
|
||||
std::atomic<size_t> last_cache_size{};
|
||||
std::atomic_bool flush_in_flight{};
|
||||
|
||||
Common::ThreadWorker workers;
|
||||
Common::ThreadWorker serialization_thread;
|
||||
|
||||
@@ -933,10 +933,6 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
|
||||
bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
|
||||
staging_buffer_pool, compute_pass_descriptor_queue);
|
||||
}
|
||||
bl2d_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
|
||||
compute_pass_descriptor_queue);
|
||||
bl3db_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
|
||||
compute_pass_descriptor_queue);
|
||||
}
|
||||
|
||||
void TextureCacheRuntime::Finish() {
|
||||
@@ -1800,13 +1796,6 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
|
||||
}
|
||||
flags |= VideoCommon::ImageFlagBits::Converted;
|
||||
flags |= VideoCommon::ImageFlagBits::CostlyLoad;
|
||||
} else if (runtime->bl2d_unswizzle_pass && BlockLinearUnswizzle2DPass::IsSupported(info)) {
|
||||
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
|
||||
flags |= VideoCommon::ImageFlagBits::CostlyLoad;
|
||||
} else if (runtime->bl3db_unswizzle_pass &&
|
||||
BlockLinearUnswizzle3DBufferPass::IsSupported(runtime->device, info)) {
|
||||
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
|
||||
flags |= VideoCommon::ImageFlagBits::CostlyLoad;
|
||||
}
|
||||
if (IsPixelFormatBCn(info.format) && !runtime->device.IsOptimalBcnSupported()) {
|
||||
flags |= VideoCommon::ImageFlagBits::Converted;
|
||||
@@ -2854,15 +2843,6 @@ void TextureCacheRuntime::AccelerateImageUpload(
|
||||
return astc_decoder_pass->Assemble(image, map, swizzles);
|
||||
}
|
||||
|
||||
if (bl2d_unswizzle_pass && BlockLinearUnswizzle2DPass::IsSupported(image.info)) {
|
||||
return bl2d_unswizzle_pass->Unswizzle(image, map, swizzles);
|
||||
}
|
||||
|
||||
if (bl3db_unswizzle_pass &&
|
||||
BlockLinearUnswizzle3DBufferPass::IsSupported(device, image.info)) {
|
||||
return bl3db_unswizzle_pass->Unswizzle(image, map, swizzles);
|
||||
}
|
||||
|
||||
if (!Settings::values.gpu_unswizzle_enabled.GetValue() || !bl3d_unswizzle_pass) {
|
||||
if (IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D) {
|
||||
ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture");
|
||||
|
||||
@@ -149,8 +149,6 @@ public:
|
||||
std::optional<ASTCDecoderPass> astc_decoder_pass;
|
||||
|
||||
std::optional<BlockLinearUnswizzle3DPass> bl3d_unswizzle_pass;
|
||||
std::optional<BlockLinearUnswizzle2DPass> bl2d_unswizzle_pass;
|
||||
std::optional<BlockLinearUnswizzle3DBufferPass> bl3db_unswizzle_pass;
|
||||
const Settings::ResolutionScalingInfo& resolution;
|
||||
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
@@ -31,63 +30,12 @@ constexpr u32 pdep(u32 value) {
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr u32 SWIZZLE_RUN_BYTES = 16;
|
||||
constexpr u32 SWIZZLE_RUN_SHIFT = 4;
|
||||
constexpr u32 SWIZZLE_RUN_MASK = SWIZZLE_RUN_BYTES - 1;
|
||||
constexpr u32 SWIZZLE_RUN_INDEX_MASK = GOB_SIZE_X / SWIZZLE_RUN_BYTES - 1;
|
||||
|
||||
static_assert((SWIZZLE_X_BITS & SWIZZLE_RUN_MASK) == SWIZZLE_RUN_MASK,
|
||||
"A swizzled run is only contiguous while the low bits of X map to themselves");
|
||||
|
||||
constexpr std::array<u32, GOB_SIZE_X / SWIZZLE_RUN_BYTES> SWIZZLE_X_RUN_TABLE = [] {
|
||||
std::array<u32, GOB_SIZE_X / SWIZZLE_RUN_BYTES> table{};
|
||||
for (u32 index = 0; index < static_cast<u32>(table.size()); ++index) {
|
||||
table[index] = pdep<SWIZZLE_X_BITS>(index << SWIZZLE_RUN_SHIFT);
|
||||
}
|
||||
return table;
|
||||
}();
|
||||
|
||||
template <u32 mask, u32 incr_amount>
|
||||
void incrpdep(u32& value) {
|
||||
static constexpr u32 swizzled_incr = pdep<mask>(incr_amount);
|
||||
value = ((value | ~mask) + swizzled_incr) & mask;
|
||||
}
|
||||
|
||||
template <bool TO_LINEAR>
|
||||
void SwizzleRow(std::span<u8> output, std::span<const u8> input, u32 offset_zy, u32 swizzled_y,
|
||||
u32 x_shift, u32 x, u32 num_bytes, u32 linear) {
|
||||
const auto copy = [&](u32 swizzled_x, u32 count) {
|
||||
const u32 swizzled =
|
||||
offset_zy + ((x >> GOB_SIZE_X_SHIFT) << x_shift) + (swizzled_x | swizzled_y);
|
||||
u8* const dst = &output[TO_LINEAR ? swizzled : linear];
|
||||
const u8* const src = &input[TO_LINEAR ? linear : swizzled];
|
||||
std::memcpy(dst, src, count);
|
||||
x += count;
|
||||
linear += count;
|
||||
};
|
||||
|
||||
u32 swizzled_run = SWIZZLE_X_RUN_TABLE[(x >> SWIZZLE_RUN_SHIFT) & SWIZZLE_RUN_INDEX_MASK];
|
||||
u32 remaining = num_bytes;
|
||||
|
||||
const u32 head =
|
||||
(std::min)(SWIZZLE_RUN_BYTES - (x & SWIZZLE_RUN_MASK), remaining) & SWIZZLE_RUN_MASK;
|
||||
if (head != 0) {
|
||||
copy(swizzled_run | (x & SWIZZLE_RUN_MASK), head);
|
||||
remaining -= head;
|
||||
incrpdep<SWIZZLE_X_BITS, SWIZZLE_RUN_BYTES>(swizzled_run);
|
||||
}
|
||||
|
||||
while (remaining >= SWIZZLE_RUN_BYTES) {
|
||||
copy(swizzled_run, SWIZZLE_RUN_BYTES);
|
||||
remaining -= SWIZZLE_RUN_BYTES;
|
||||
incrpdep<SWIZZLE_X_BITS, SWIZZLE_RUN_BYTES>(swizzled_run);
|
||||
}
|
||||
|
||||
if (remaining != 0) {
|
||||
copy(swizzled_run, remaining);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool TO_LINEAR, u32 BYTES_PER_PIXEL>
|
||||
void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32 height, u32 depth,
|
||||
u32 block_height, u32 block_depth, u32 stride) {
|
||||
@@ -122,9 +70,23 @@ void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32
|
||||
const u32 offset_y = (block_y >> block_height) * block_size +
|
||||
((block_y & block_height_mask) << GOB_SIZE_SHIFT);
|
||||
|
||||
SwizzleRow<TO_LINEAR>(output, input, offset_z + offset_y, swizzled_y, x_shift,
|
||||
origin_x * BYTES_PER_PIXEL, width * BYTES_PER_PIXEL,
|
||||
slice * pitch * height + line * pitch);
|
||||
u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL);
|
||||
for (u32 column = 0; column < width;
|
||||
++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
|
||||
const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
|
||||
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
|
||||
|
||||
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
|
||||
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
|
||||
|
||||
const u32 unswizzled_offset =
|
||||
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
|
||||
|
||||
u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset];
|
||||
const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset];
|
||||
|
||||
std::memcpy(dst, src, BYTES_PER_PIXEL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,9 +129,23 @@ void SwizzleSubrectImpl(std::span<u8> output, std::span<const u8> input, u32 wid
|
||||
const u32 offset_y = (block_y >> block_height) * block_size +
|
||||
((block_y & block_height_mask) << GOB_SIZE_SHIFT);
|
||||
|
||||
SwizzleRow<TO_LINEAR>(output, input, offset_z + offset_y, swizzled_y, x_shift,
|
||||
origin_x * BYTES_PER_PIXEL, extent_x * BYTES_PER_PIXEL,
|
||||
slice * pitch * height + line * pitch);
|
||||
u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL);
|
||||
for (u32 column = 0; column < extent_x;
|
||||
++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
|
||||
const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
|
||||
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
|
||||
|
||||
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
|
||||
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
|
||||
|
||||
const u32 unswizzled_offset =
|
||||
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
|
||||
|
||||
u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset];
|
||||
const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset];
|
||||
|
||||
std::memcpy(dst, src, BYTES_PER_PIXEL);
|
||||
}
|
||||
}
|
||||
unprocessed_lines -= lines_in_y;
|
||||
if (unprocessed_lines == 0) {
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
@@ -18,8 +16,6 @@
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/literals.h"
|
||||
#include <ranges>
|
||||
#include "common/settings.h"
|
||||
@@ -397,17 +393,6 @@ std::vector<const char*> ExtensionListForVulkan(
|
||||
return output;
|
||||
}
|
||||
|
||||
constexpr std::array<char, 8> STATIC_CACHE_MAGIC_NUMBER{'e', 'd', 'e', 'n', 's', 't', 'p', 'c'};
|
||||
constexpr u32 STATIC_CACHE_VERSION = 1;
|
||||
|
||||
std::filesystem::path StaticPipelineCacheFilename() {
|
||||
const auto shader_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
|
||||
if (!Common::FS::CreateDir(shader_dir)) {
|
||||
return {};
|
||||
}
|
||||
return shader_dir / "vulkan_static_pipelines.bin";
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
void Device::RemoveExtension(bool& extension, const std::string& extension_name) {
|
||||
@@ -795,100 +780,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
|
||||
vk::Check(vmaCreateAllocator(&allocator_info, &allocator));
|
||||
|
||||
owns_static_pipeline_cache = surface != VkSurfaceKHR{};
|
||||
LoadStaticPipelineCache();
|
||||
|
||||
// Initialize GPU logging if enabled
|
||||
InitializeGPULogging();
|
||||
}
|
||||
|
||||
Device::~Device() {
|
||||
SaveStaticPipelineCache();
|
||||
ShutdownGPULogging();
|
||||
vmaDestroyAllocator(allocator);
|
||||
}
|
||||
|
||||
void Device::LoadStaticPipelineCache() {
|
||||
const auto create = [this](size_t size, const void* data) {
|
||||
static_pipeline_cache = logical.CreatePipelineCache({
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.initialDataSize = size,
|
||||
.pInitialData = data,
|
||||
});
|
||||
};
|
||||
if (!owns_static_pipeline_cache) {
|
||||
create(0, nullptr);
|
||||
return;
|
||||
}
|
||||
const auto filename = StaticPipelineCacheFilename();
|
||||
if (filename.empty()) {
|
||||
create(0, nullptr);
|
||||
return;
|
||||
}
|
||||
std::vector<char> data;
|
||||
try {
|
||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
create(0, nullptr);
|
||||
return;
|
||||
}
|
||||
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
||||
const size_t total = static_cast<size_t>(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
std::array<char, 8> magic{};
|
||||
u32 version{};
|
||||
if (total < magic.size() + sizeof(version)) {
|
||||
create(0, nullptr);
|
||||
return;
|
||||
}
|
||||
file.read(magic.data(), magic.size())
|
||||
.read(reinterpret_cast<char*>(&version), sizeof(version));
|
||||
if (magic != STATIC_CACHE_MAGIC_NUMBER || version != STATIC_CACHE_VERSION) {
|
||||
create(0, nullptr);
|
||||
return;
|
||||
}
|
||||
data.resize(total - magic.size() - sizeof(version));
|
||||
file.read(data.data(), static_cast<std::streamsize>(data.size()));
|
||||
} catch (const std::ios_base::failure& e) {
|
||||
create(0, nullptr);
|
||||
return;
|
||||
}
|
||||
create(data.size(), data.empty() ? nullptr : data.data());
|
||||
}
|
||||
|
||||
void Device::SaveStaticPipelineCache() const {
|
||||
if (!owns_static_pipeline_cache || !static_pipeline_cache) {
|
||||
return;
|
||||
}
|
||||
const auto filename = StaticPipelineCacheFilename();
|
||||
if (filename.empty()) {
|
||||
return;
|
||||
}
|
||||
size_t size = 0;
|
||||
std::vector<char> data;
|
||||
static_pipeline_cache.Read(&size, nullptr);
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
data.resize(size);
|
||||
static_pipeline_cache.Read(&size, data.data());
|
||||
try {
|
||||
std::ofstream file(filename, std::ios::binary | std::ios::trunc);
|
||||
file.exceptions(std::ofstream::failbit);
|
||||
if (!file.is_open()) {
|
||||
return;
|
||||
}
|
||||
file.write(STATIC_CACHE_MAGIC_NUMBER.data(), STATIC_CACHE_MAGIC_NUMBER.size())
|
||||
.write(reinterpret_cast<const char*>(&STATIC_CACHE_VERSION),
|
||||
sizeof(STATIC_CACHE_VERSION))
|
||||
.write(data.data(), static_cast<std::streamsize>(size));
|
||||
} catch (const std::ios_base::failure& e) {
|
||||
Common::FS::RemoveFile(filename);
|
||||
}
|
||||
}
|
||||
|
||||
VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
|
||||
FormatType format_type) const {
|
||||
if (IsFormatSupported(wanted_format, wanted_usage, format_type)) {
|
||||
|
||||
@@ -268,10 +268,6 @@ public:
|
||||
return physical;
|
||||
}
|
||||
|
||||
VkPipelineCache StaticPipelineCache() const noexcept {
|
||||
return *static_pipeline_cache;
|
||||
}
|
||||
|
||||
/// Returns the main graphics queue.
|
||||
vk::Queue GetGraphicsQueue() const {
|
||||
return graphics_queue;
|
||||
@@ -1092,9 +1088,6 @@ private:
|
||||
/// Returns true if the device natively supports blitting depth stencil images.
|
||||
bool TestDepthStencilBlits(VkFormat format) const;
|
||||
|
||||
void LoadStaticPipelineCache();
|
||||
void SaveStaticPipelineCache() const;
|
||||
|
||||
private:
|
||||
VkInstance instance; ///< Vulkan instance.
|
||||
VmaAllocator allocator; ///< VMA allocator.
|
||||
@@ -1103,8 +1096,6 @@ private:
|
||||
vk::Device logical; ///< Logical device.
|
||||
vk::Queue graphics_queue; ///< Main graphics queue.
|
||||
vk::Queue present_queue; ///< Main present queue.
|
||||
vk::PipelineCache static_pipeline_cache;
|
||||
bool owns_static_pipeline_cache{};
|
||||
u32 instance_version{}; ///< Vulkan instance version.
|
||||
u32 graphics_family{}; ///< Main graphics queue family index.
|
||||
u32 present_family{}; ///< Main present queue family index.
|
||||
|
||||
@@ -75,3 +75,15 @@ if (NOT MSVC)
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-unused-parameter>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-missing-field-initializers>)
|
||||
endif()
|
||||
|
||||
if (PLATFORM_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()
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "common/scm_rev.h"
|
||||
@@ -26,9 +30,17 @@ EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Co
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize SDL3: {}, Exiting...", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
titlebar_timer = SDL_AddTimer(2000, [](void *userdata, SDL_TimerID, Uint32) -> Uint32 {
|
||||
auto* this_ = (EmuWindow_SDL3*)userdata;
|
||||
auto const results = this_->system.GetAndResetPerfStats();
|
||||
auto const title = fmt::format("{} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc, results.average_game_fps, results.emulation_speed * 100.0f);
|
||||
SDL_SetWindowTitle(this_->render_window, title.c_str());
|
||||
return 2000;
|
||||
}, this);
|
||||
}
|
||||
|
||||
EmuWindow_SDL3::~EmuWindow_SDL3() {
|
||||
SDL_RemoveTimer(titlebar_timer);
|
||||
system.HIDCore().UnloadInputDevices();
|
||||
input_subsystem->Shutdown();
|
||||
SDL_Quit();
|
||||
@@ -132,8 +144,7 @@ void EmuWindow_SDL3::Fullscreen() {
|
||||
switch (Settings::values.fullscreen_mode.GetValue()) {
|
||||
case Settings::FullscreenMode::Exclusive:
|
||||
// Set window size to render size before entering fullscreen in exclusive mode.
|
||||
if (const SDL_DisplayMode* display_mode_ptr =
|
||||
SDL_GetDesktopDisplayMode(SDL_GetDisplayForWindow(render_window))) {
|
||||
if (const SDL_DisplayMode* display_mode_ptr = SDL_GetDesktopDisplayMode(SDL_GetDisplayForWindow(render_window))) {
|
||||
display_mode = *display_mode_ptr;
|
||||
SDL_SetWindowSize(render_window, display_mode.w, display_mode.h);
|
||||
SDL_SetWindowFullscreenMode(render_window, &display_mode);
|
||||
@@ -165,92 +176,60 @@ void EmuWindow_SDL3::Fullscreen() {
|
||||
}
|
||||
}
|
||||
|
||||
void EmuWindow_SDL3::WaitEvent() {
|
||||
// Called on main thread
|
||||
SDL_Event event;
|
||||
|
||||
if (!SDL_WaitEvent(&event)) {
|
||||
const char* error = SDL_GetError();
|
||||
if (!error || strcmp(error, "") == 0) {
|
||||
// https://github.com/libsdl-org/SDL/issues/5780
|
||||
// Sometimes SDL will return without actually having hit an error condition;
|
||||
// just ignore it in this case.
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Frontend, "SDL_WaitEvent failed: {}", error);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL3::OnEvent(SDL_Event& event) {
|
||||
// Notice how we skip the "update title" aspect on most events
|
||||
// this is because some WMs do NOT like changing titles while resizing
|
||||
// so let's just... not do that, thanks :)
|
||||
// Afterall we don't really expect the user to pay attention to the titlebar
|
||||
// while they're moving a lot of shit around...
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_WINDOW_RESIZED:
|
||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||
case SDL_EVENT_WINDOW_MAXIMIZED:
|
||||
case SDL_EVENT_WINDOW_RESTORED:
|
||||
OnResize();
|
||||
break;
|
||||
return OnResize();
|
||||
case SDL_EVENT_WINDOW_MINIMIZED:
|
||||
is_shown = false;
|
||||
OnResize();
|
||||
break;
|
||||
return OnResize();
|
||||
case SDL_EVENT_WINDOW_EXPOSED:
|
||||
is_shown = true;
|
||||
OnResize();
|
||||
break;
|
||||
return OnResize();
|
||||
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||
is_open = false;
|
||||
break;
|
||||
return;
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
OnKeyEvent(static_cast<int>(event.key.scancode), event.key.down ? 1 : 0);
|
||||
break;
|
||||
return OnKeyEvent(int(event.key.scancode), event.key.down ? 1 : 0);
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
// ignore if it came from touch
|
||||
if (event.button.which != SDL_TOUCH_MOUSEID)
|
||||
OnMouseMotion(event.motion.x, event.motion.y);
|
||||
break;
|
||||
return;
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
// ignore if it came from touch
|
||||
if (event.button.which != SDL_TOUCH_MOUSEID) {
|
||||
OnMouseButton(event.button.button, event.button.down ? 1 : 0,
|
||||
static_cast<s32>(event.button.x), static_cast<s32>(event.button.y));
|
||||
}
|
||||
break;
|
||||
if (event.button.which != SDL_TOUCH_MOUSEID)
|
||||
OnMouseButton(event.button.button, event.button.down ? 1 : 0, s32(event.button.x), s32(event.button.y));
|
||||
return;
|
||||
case SDL_EVENT_FINGER_DOWN:
|
||||
OnFingerDown(event.tfinger.x, event.tfinger.y,
|
||||
static_cast<std::size_t>(event.tfinger.touchID));
|
||||
break;
|
||||
return OnFingerDown(event.tfinger.x, event.tfinger.y, std::size_t(event.tfinger.touchID));
|
||||
case SDL_EVENT_FINGER_MOTION:
|
||||
OnFingerMotion(event.tfinger.x, event.tfinger.y,
|
||||
static_cast<std::size_t>(event.tfinger.touchID));
|
||||
break;
|
||||
return OnFingerMotion(event.tfinger.x, event.tfinger.y, std::size_t(event.tfinger.touchID));
|
||||
case SDL_EVENT_FINGER_UP:
|
||||
OnFingerUp();
|
||||
break;
|
||||
return OnFingerUp();
|
||||
case SDL_EVENT_QUIT:
|
||||
is_open = false;
|
||||
break;
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const u32 current_time = SDL_GetTicks();
|
||||
if (current_time > last_time + 2000) {
|
||||
const auto results = system.GetAndResetPerfStats();
|
||||
const auto title = fmt::format("{} | {}-{} | FPS: {:.0f} ({:.0f}%)",
|
||||
Common::g_build_fullname,
|
||||
Common::g_scm_branch,
|
||||
Common::g_scm_desc,
|
||||
results.average_game_fps,
|
||||
results.emulation_speed * 100.0);
|
||||
SDL_SetWindowTitle(render_window, title.c_str());
|
||||
last_time = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
// Credits to Samantas5855 and others for this function.
|
||||
void EmuWindow_SDL3::SetWindowIcon() {
|
||||
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
|
||||
// Icons do not work yet
|
||||
#else
|
||||
SDL_IOStream* const yuzu_icon_stream = SDL_IOFromConstMem((void*)yuzu_icon, yuzu_icon_size);
|
||||
if (yuzu_icon_stream == nullptr) {
|
||||
LOG_WARNING(Frontend, "Failed to create Eden icon stream.");
|
||||
@@ -264,6 +243,7 @@ void EmuWindow_SDL3::SetWindowIcon() {
|
||||
// The icon is attached to the window pointer
|
||||
SDL_SetWindowIcon(render_window, window_icon);
|
||||
SDL_DestroySurface(window_icon);
|
||||
#endif
|
||||
}
|
||||
|
||||
void EmuWindow_SDL3::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
|
||||
|
||||
@@ -9,11 +9,12 @@
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/graphics_context.h"
|
||||
|
||||
struct SDL_Window;
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
@@ -35,7 +36,7 @@ public:
|
||||
bool IsShown() const override;
|
||||
|
||||
/// Wait for the next event on the main thread.
|
||||
void WaitEvent();
|
||||
void OnEvent(SDL_Event& event);
|
||||
|
||||
// Sets the window icon from yuzu.bmp
|
||||
void SetWindowIcon();
|
||||
@@ -80,6 +81,9 @@ protected:
|
||||
/// Called when a configuration change affects the minimal size of the window
|
||||
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
|
||||
|
||||
/// Periodic changer of titlebar (independent of event loop)
|
||||
SDL_TimerID titlebar_timer;
|
||||
|
||||
/// Is the window still open?
|
||||
bool is_open = true;
|
||||
|
||||
@@ -89,9 +93,6 @@ protected:
|
||||
/// Internal SDL3 render window
|
||||
SDL_Window* render_window{};
|
||||
|
||||
/// Keeps track of how often to update the title bar during gameplay
|
||||
u32 last_time = 0;
|
||||
|
||||
/// Input subsystem to use with this window.
|
||||
InputCommon::InputSubsystem* input_subsystem;
|
||||
|
||||
|
||||
+81
-71
@@ -8,6 +8,12 @@
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include "common/settings_enums.h"
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
#define SDL_MAIN_USE_CALLBACKS 1
|
||||
#include <SDL3/SDL_main.h>
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
@@ -39,9 +45,7 @@
|
||||
#ifdef _WIN32
|
||||
// windows.h needs to be included before shellapi.h
|
||||
#include <windows.h>
|
||||
|
||||
#include <shellapi.h>
|
||||
|
||||
#include "common/windows/timer_resolution.h"
|
||||
#endif
|
||||
|
||||
@@ -174,8 +178,14 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) {
|
||||
std::cout << std::endl << "* " << message << std::endl << std::endl;
|
||||
}
|
||||
|
||||
/// Application entry point
|
||||
int main(int argc, char** argv) {
|
||||
struct SdlState {
|
||||
Core::System system{};
|
||||
std::unique_ptr<EmuWindow_SDL3> emu_window;
|
||||
};
|
||||
|
||||
extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
||||
SdlState* state = new SdlState();
|
||||
|
||||
#ifdef _WIN32
|
||||
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||
freopen("CONOUT$", "wb", stdout);
|
||||
@@ -193,7 +203,7 @@ int main(int argc, char** argv) {
|
||||
auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
|
||||
if (argv_w == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to get command line arguments");
|
||||
return -1;
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
#endif
|
||||
std::string filepath;
|
||||
@@ -247,7 +257,7 @@ int main(int argc, char** argv) {
|
||||
break;
|
||||
case 'h':
|
||||
PrintHelp(argv[0]);
|
||||
return 0;
|
||||
return SDL_APP_FAILURE;
|
||||
case 'g':
|
||||
filepath = std::string(optarg);
|
||||
break;
|
||||
@@ -264,7 +274,7 @@ int main(int argc, char** argv) {
|
||||
if (!std::regex_match(str_arg, re)) {
|
||||
std::cout << "Wrong format for option --multiplayer\n";
|
||||
PrintHelp(argv[0]);
|
||||
return -1;
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
std::smatch match;
|
||||
@@ -279,11 +289,11 @@ int main(int argc, char** argv) {
|
||||
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
|
||||
if (!std::regex_match(nickname, nickname_re)) {
|
||||
LOG_ERROR(Frontend, "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
|
||||
return -1;
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
if (address.empty()) {
|
||||
LOG_ERROR(Frontend, "Address to room must not be empty");
|
||||
return -1;
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -296,7 +306,7 @@ int main(int argc, char** argv) {
|
||||
break;
|
||||
case 'v':
|
||||
PrintVersion();
|
||||
return -1;
|
||||
return SDL_APP_FAILURE;
|
||||
case 'n':
|
||||
force_null_render = true;
|
||||
break;
|
||||
@@ -358,79 +368,73 @@ int main(int argc, char** argv) {
|
||||
|
||||
if (filepath.empty()) {
|
||||
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
|
||||
return -1;
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
Core::System system{};
|
||||
system.Initialize();
|
||||
state->system.Initialize();
|
||||
|
||||
InputCommon::InputSubsystem input_subsystem{};
|
||||
|
||||
// Apply the command line arguments
|
||||
system.ApplySettings();
|
||||
state->system.ApplySettings();
|
||||
|
||||
std::unique_ptr<EmuWindow_SDL3> emu_window;
|
||||
switch (Settings::values.renderer_backend.GetValue()) {
|
||||
#ifdef HAS_OPENGL
|
||||
case Settings::RendererBackend::OpenGL_GLSL:
|
||||
case Settings::RendererBackend::OpenGL_GLASM:
|
||||
case Settings::RendererBackend::OpenGL_SPIRV:
|
||||
emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, system, fullscreen);
|
||||
state->emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, state->system, fullscreen);
|
||||
break;
|
||||
#endif
|
||||
case Settings::RendererBackend::Vulkan:
|
||||
emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, system, fullscreen);
|
||||
state->emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, state->system, fullscreen);
|
||||
break;
|
||||
case Settings::RendererBackend::Null:
|
||||
emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, system, fullscreen);
|
||||
state->emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, state->system, fullscreen);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Frontend, "Invalid renderer backend");
|
||||
return -1;
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
Common::Windows::SetCurrentTimerResolutionToMaximum();
|
||||
system.CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution());
|
||||
state->system.CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution());
|
||||
#endif
|
||||
|
||||
system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
|
||||
system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
|
||||
system.GetUserChannel().clear();
|
||||
state->system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
state->system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
|
||||
state->system.GetFileSystemController().CreateFactories(*state->system.GetFilesystem());
|
||||
state->system.GetUserChannel().clear();
|
||||
|
||||
Service::AM::FrontendAppletParameters load_parameters{
|
||||
.applet_id = Service::AM::AppletId::Application,
|
||||
};
|
||||
const Core::SystemResultStatus load_result{system.Load(*emu_window, filepath, load_parameters)};
|
||||
|
||||
const Core::SystemResultStatus load_result = state->system.Load(*state->emu_window, filepath, load_parameters);
|
||||
switch (load_result) {
|
||||
case Core::SystemResultStatus::ErrorGetLoader:
|
||||
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath);
|
||||
return -1;
|
||||
case Core::SystemResultStatus::ErrorLoader:
|
||||
LOG_CRITICAL(Frontend, "Failed to load ROM!");
|
||||
return -1;
|
||||
case Core::SystemResultStatus::ErrorNotInitialized:
|
||||
LOG_CRITICAL(Frontend, "CPUCore not initialized");
|
||||
return -1;
|
||||
case Core::SystemResultStatus::ErrorVideoCore:
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!");
|
||||
return -1;
|
||||
case Core::SystemResultStatus::Success:
|
||||
break; // Expected case
|
||||
case Core::SystemResultStatus::ErrorGetLoader:
|
||||
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath);
|
||||
return SDL_APP_FAILURE;
|
||||
case Core::SystemResultStatus::ErrorLoader:
|
||||
LOG_CRITICAL(Frontend, "Failed to load ROM!");
|
||||
return SDL_APP_FAILURE;
|
||||
case Core::SystemResultStatus::ErrorNotInitialized:
|
||||
LOG_CRITICAL(Frontend, "CPUCore not initialized");
|
||||
return SDL_APP_FAILURE;
|
||||
case Core::SystemResultStatus::ErrorVideoCore:
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!");
|
||||
return SDL_APP_FAILURE;
|
||||
default:
|
||||
if (static_cast<u32>(load_result) >
|
||||
static_cast<u32>(Core::SystemResultStatus::ErrorLoader)) {
|
||||
const u16 loader_id = static_cast<u16>(Core::SystemResultStatus::ErrorLoader);
|
||||
const u16 error_id = static_cast<u16>(load_result) - loader_id;
|
||||
LOG_CRITICAL(Frontend,
|
||||
"While attempting to load the ROM requested, an error occurred. Please "
|
||||
"refer to the Eden wiki for more information or the Eden discord for "
|
||||
"additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
|
||||
loader_id, error_id, static_cast<Loader::ResultStatus>(error_id));
|
||||
}
|
||||
break;
|
||||
const u16 loader_id = u16(Core::SystemResultStatus::ErrorLoader);
|
||||
const u16 error_id = u16(load_result) - loader_id;
|
||||
LOG_CRITICAL(Frontend,
|
||||
"While attempting to load the ROM requested, an error occurred. Please "
|
||||
"refer to the Eden wiki for more information or the Eden discord for "
|
||||
"additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
|
||||
loader_id, error_id, Loader::ResultStatus(error_id));
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
if (use_multiplayer) {
|
||||
@@ -439,40 +443,46 @@ int main(int argc, char** argv) {
|
||||
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
|
||||
member->BindOnStateChanged(OnStateChanged);
|
||||
member->BindOnError(OnNetworkError);
|
||||
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port,
|
||||
nickname);
|
||||
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, nickname);
|
||||
member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password);
|
||||
} else {
|
||||
LOG_ERROR(Network, "Could not access RoomMember");
|
||||
return 0;
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Core is loaded, start the GPU (makes the GPU contexts current to this thread)
|
||||
system.GPU().Start();
|
||||
system.GetCpuManager().OnGpuReady();
|
||||
state->system.GPU().Start();
|
||||
state->system.GetCpuManager().OnGpuReady();
|
||||
|
||||
if (Settings::values.use_disk_shader_cache.GetValue()) {
|
||||
system.Renderer().ReadRasterizer()->LoadDiskResources(
|
||||
system.GetApplicationProcessProgramID(), std::stop_token{},
|
||||
state->system.Renderer().ReadRasterizer()->LoadDiskResources(
|
||||
state->system.GetApplicationProcessProgramID(), std::stop_token{},
|
||||
[](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
|
||||
}
|
||||
|
||||
system.RegisterExitCallback([&] {
|
||||
// Just exit right away.
|
||||
exit(0);
|
||||
});
|
||||
void(system.Run());
|
||||
if (system.DebuggerEnabled()) {
|
||||
system.InitializeDebugger();
|
||||
}
|
||||
while (emu_window->IsOpen()) {
|
||||
emu_window->WaitEvent();
|
||||
}
|
||||
system.DetachDebugger();
|
||||
void(system.Pause());
|
||||
system.ShutdownMainProcess();
|
||||
return 0;
|
||||
// don't do anything, SDL3 already exists for us :D
|
||||
state->system.RegisterExitCallback([] {});
|
||||
void(state->system.Run());
|
||||
if (state->system.DebuggerEnabled())
|
||||
state->system.InitializeDebugger();
|
||||
return SDL_APP_SUCCESS;
|
||||
}
|
||||
extern "C" SDL_AppResult SDL_AppIterate(void *appstate) {
|
||||
SdlState *state = (SdlState *)appstate;
|
||||
return state->emu_window->IsOpen() ? SDL_APP_CONTINUE : SDL_APP_SUCCESS;
|
||||
}
|
||||
extern "C" SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
|
||||
SdlState *state = (SdlState *)appstate;
|
||||
state->emu_window->OnEvent(*event);
|
||||
return SDL_APP_SUCCESS;
|
||||
}
|
||||
extern "C" void SDL_AppQuit(void *appstate, SDL_AppResult result) {
|
||||
SdlState *state = (SdlState *)appstate;
|
||||
state->system.DetachDebugger();
|
||||
void(state->system.Pause());
|
||||
state->system.ShutdownMainProcess();
|
||||
delete state;
|
||||
}
|
||||
|
||||
#define VMA_IMPLEMENTATION
|
||||
|
||||
@@ -30,6 +30,7 @@ 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).
|
||||
- `cpp-lint.sh`: Homemade dumb C++ linter.
|
||||
- `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
|
||||
It's recommended to run these scritps after almost any Android change, as they are relatively fast and important both for APK bloat and CI.
|
||||
|
||||
Executable
+127
@@ -0,0 +1,127 @@
|
||||
#!/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