diff --git a/.ci/wasm/build.sh b/.ci/wasm/build.sh new file mode 100644 index 0000000000..bdaab223e0 --- /dev/null +++ b/.ci/wasm/build.sh @@ -0,0 +1,75 @@ +#!/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 <&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}" + +emcmake cmake -B "$OUTDIR" -G "Unix Makefiles" \ + -DCMAKE_BUILD_TYPE=${TYPE} \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -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 \ + -DUSE_FASTER_LINKER=ON \ + -DYUZU_USE_BUNDLED_OPENSSL=OFF \ + -DYUZU_USE_EXTERNAL_FFMPEG=ON \ + -Dzstd_FORCE_BUNDLED=ON \ + -DOpenSSL_FORCE_BUNDLED=ON +cmake --build "$OUTDIR" -- -j$NUM_JOBS diff --git a/.patch/openssl-cmake/0005-wasm-support.patch b/.patch/openssl-cmake/0005-wasm-support.patch new file mode 100644 index 0000000000..8492e3f616 --- /dev/null +++ b/.patch/openssl-cmake/0005-wasm-support.patch @@ -0,0 +1,55 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 1ca57e1..d38c0c6 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -113,9 +113,10 @@ elseif(MINGW) + find_program(MAKE mingw32-make REQUIRED) + set(OPENSSL_BUILD_TOOL ${MAKE}) + else() +- find_program(MAKE make REQUIRED) ++ find_program(MAKE gmake REQUIRED) + set(OPENSSL_BUILD_TOOL ${MAKE}) + endif() ++set(OPENSSL_BUILD_TOOL gmake) + + # Modify configure/build options + if(OPENSSL_TEST AND NOT CMAKE_CROSSCOMPILING) +@@ -140,7 +141,7 @@ if(NUMBER_OF_THREADS GREATER 1) + list(APPEND OPENSSL_CONFIGURE_OPTIONS /FS) + list(APPEND OPENSSL_BUILD_OPTIONS /J ${NUMBER_OF_THREADS}) + else() +- list(APPEND OPENSSL_BUILD_OPTIONS -j ${NUMBER_OF_THREADS}) ++# list(APPEND OPENSSL_BUILD_OPTIONS -j ${NUMBER_OF_THREADS}) + endif() + endif() + +diff --git a/cmake/ConfigureOpenSSL.cmake b/cmake/ConfigureOpenSSL.cmake +index 3012e05..9862aa0 100644 +--- a/cmake/ConfigureOpenSSL.cmake ++++ b/cmake/ConfigureOpenSSL.cmake +@@ -134,10 +134,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_LINKER_FLAGS}" ++ "CC=${CMAKE_C_COMPILER}" ++ "CXX=${CMAKE_CXX_COMPILER}" ++ "LD=${EMSCRIPTEN_LINKER}" + ${CONFIGURE_COMMAND} + WORKING_DIRECTORY ${CONFIGURE_BUILD_DIR} + ${VERBOSE_OPTION} diff --git a/.patch/openssl/0002-wasm-support.patch b/.patch/openssl/0002-wasm-support.patch new file mode 100644 index 0000000000..ddf2498c08 --- /dev/null +++ b/.patch/openssl/0002-wasm-support.patch @@ -0,0 +1,108 @@ +diff --git a/Configurations/10-main.conf b/Configurations/10-main.conf +index e62721e..6fcea0d 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 => add("--target=wasm32-unknown-emscripten"), ++ cxxflags => add("--target=wasm32-unknown-emscripten"), ++ lib_cppflags => add("-DL_ENDIAN"), ++ bn_ops => "THIRTY_TWO_BIT", ++ }, ++ "wasm64" => { ++ inherit_from => [ "BASE_unix" ], ++ CC => "emcc", ++ CXX => "emc++", ++ cflags => add("--target=wasm64-unknown-emscripten"), ++ cxxflags => add("--target=wasm64-unknown-emscripten"), ++ 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..0bd63b0 100644 +--- a/crypto/rand/rand_lib.c ++++ b/crypto/rand/rand_lib.c +@@ -379,17 +379,25 @@ 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) + { ++#ifdef __EMSCRIPTEN__ ++ arc4random_buf(buf, num); ++ 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) + { ++#ifdef __EMSCRIPTEN__ ++ return 1; ++#else + EVP_RAND_CTX *rand; + #ifndef OPENSSL_NO_DEPRECATED_3_0 + const RAND_METHOD *meth = RAND_get_rand_method(); +@@ -401,6 +409,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 +429,10 @@ 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) + { ++#ifdef __EMSCRIPTEN__ ++ arc4random_buf(buf, num); ++ return 1; ++#else + RAND_GLOBAL *dgbl; + EVP_RAND_CTX *rand; + #if !defined(OPENSSL_NO_DEPRECATED_3_0) && !defined(FIPS_MODULE) +@@ -451,6 +464,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) diff --git a/CMakeLists.txt b/CMakeLists.txt index fa1fa37d2e..4afd8b557d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -403,7 +403,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 +429,12 @@ if (Boost_ADDED) target_compile_options(boost_heap INTERFACE $<$:-Wno-shadow>) target_compile_options(boost_icl INTERFACE $<$:-Wno-shadow>) - target_compile_options(boost_asio INTERFACE - $<$:-Wno-conversion> - $<$:-Wno-implicit-fallthrough> - ) + # May not exist (i.e emscripten) + if (TARGET boost_asio) + target_compile_options(boost_asio INTERFACE + $<$:-Wno-conversion> + $<$:-Wno-implicit-fallthrough>) + endif() endif() endif() diff --git a/cpmfile.json b/cpmfile.json index cac6c357b0..eed17a2b5d 100644 --- a/cpmfile.json +++ b/cpmfile.json @@ -213,7 +213,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" diff --git a/externals/cmake-modules/DetectArchitecture.cmake b/externals/cmake-modules/DetectArchitecture.cmake index 105963c8c2..083a255a27 100644 --- a/externals/cmake-modules/DetectArchitecture.cmake +++ b/externals/cmake-modules/DetectArchitecture.cmake @@ -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 diff --git a/externals/cmake-modules/DetectPlatform.cmake b/externals/cmake-modules/DetectPlatform.cmake index 0f7cdfcf46..10b4378850 100644 --- a/externals/cmake-modules/DetectPlatform.cmake +++ b/externals/cmake-modules/DetectPlatform.cmake @@ -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 diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index 6e3f675f2b..c99d642f81 100644 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -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} ) diff --git a/externals/tz/tz/tz.cpp b/externals/tz/tz/tz.cpp index 3269d9cc1e..e0b19d8fd5 100644 --- a/externals/tz/tz/tz.cpp +++ b/externals/tz/tz/tz.cpp @@ -14,7 +14,9 @@ namespace Tz { namespace { +#ifndef EINVAL #define EINVAL 22 +#endif static Rule gmtmem{}; static Rule* const gmtptr = &gmtmem; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a2abc7d1b3..c0b14e21a7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -177,7 +177,6 @@ else() $<$:-Wno-invalid-offsetof> $<$:-Wno-unused-parameter> $<$:-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() $<$:-Wno-nullability-completeness>) endif() - if (ARCHITECTURE_x86_64) + if (ARCHITECTURE_wasm) + # we are evil but fmt is even more evil + add_compile_options( + $<$:-Wno-shorten-64-to-32>) + elseif (ARCHITECTURE_x86_64) add_compile_options($<$:-mcx16>) if (LINUX OR FREEBSD) add_compile_options($<$:-mtls-dialect=gnu2>) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 10e8cd0cc7..9da4e0c428 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -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) diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp index 69eca732eb..a2912aa524 100644 --- a/src/common/fiber.cpp +++ b/src/common/fiber.cpp @@ -11,7 +11,12 @@ #include "common/fiber.h" #include "common/virtual_buffer.h" +#ifdef __EMSCRIPTEN__ +#include +#include +#else #include +#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 stack{}; + std::array astack{}; + u32 canary_2 = CANARY_VALUE; + + emscripten_fiber_t* context{nullptr}; + + std::mutex guard; + std::function entry_point; + std::shared_ptr previous_fiber; + bool is_thread_fiber = false; + bool released = false; +}; + +Fiber::Fiber(std::function&& entry_point_func) : impl{std::make_unique()} { + impl->entry_point = std::move(entry_point_func); + emscripten_fiber_init(impl->context, [](void *user_data) -> void { + auto* fiber = static_cast(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()} {} + +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 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::ThreadToFiber() { + std::shared_ptr fiber = std::shared_ptr{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 stack{}; - std::array rewind_stack{}; u32 canary_2 = CANARY_VALUE; boost::context::detail::fcontext_t context{}; - boost::context::detail::fcontext_t rewind_context{}; std::mutex guard; std::function entry_point; - std::function rewind_point; std::shared_ptr previous_fiber; u8* stack_limit = nullptr; - u8* rewind_stack_limit = nullptr; bool is_thread_fiber = false; bool released = false; }; -void Fiber::SetRewindPoint(std::function&& rewind_func) { - impl->rewind_point = std::move(rewind_func); -} - Fiber::Fiber(std::function&& entry_point_func) : impl{std::make_unique()} { 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(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::ThreadToFiber() { fiber->impl->is_thread_fiber = true; return fiber; } +#endif } // namespace Common diff --git a/src/common/fiber.h b/src/common/fiber.h index eb128f4bb2..fcaac1d5b8 100644 --- a/src/common/fiber.h +++ b/src/common/fiber.h @@ -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 weak_from, Fiber& to); [[nodiscard]] static std::shared_ptr ThreadToFiber(); - void SetRewindPoint(std::function&& rewind_func); /// Only call from main thread's fiber void Exit(); private: diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 087fd796c6..2ae83137c9 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 PRIVATE Boost::asio Boost::process) + endif() else() target_link_libraries(core PUBLIC Boost::headers) endif() diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index 8e6b1d9766..c8dbf93871 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -6,19 +6,20 @@ #pragma once +#ifndef __EMSCRIPTEN__ #include - +#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 diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp index d1c767721b..962b60098d 100644 --- a/src/core/debugger/debugger.cpp +++ b/src/core/debugger/debugger.cpp @@ -6,30 +6,52 @@ #include #include + +#ifdef __EMSCRIPTEN__ +// TODO: gdb stub compat with emscripten? +#else #include #include - #if BOOST_VERSION > 108400 && (!defined(_WINDOWS) && !defined(__ANDROID__)) || defined(YUZU_BOOST_v1) #define USE_BOOST_v1 #endif - #ifdef USE_BOOST_v1 #include #else #include #endif +#endif #include "common/logging.h" #include "common/polyfill_thread.h" #include "common/thread.h" #include "core/core.h" #include "core/debugger/debugger.h" +#ifndef __EMSCRIPTEN__ #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" +#ifdef __EMSCRIPTEN__ +namespace Core { +// Dummy +struct 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 static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) { static_assert(std::is_trivial_v); @@ -400,3 +422,4 @@ void Debugger::NotifyShutdown() { } } // namespace Core +#endif diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 7308b0ff87..c1b4a7b794 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -7,8 +7,14 @@ #include #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(*m_exclusive_monitor), i); } } +#endif } bool KProcess::InsertWatchpoint(KernelCore& kernel, KProcessAddress addr, u64 size, DebugWatchpointType type) { diff --git a/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h index 44be82cd43..8df5c30150 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h +++ b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h @@ -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"); diff --git a/src/hid_core/resources/shared_memory_holder.h b/src/hid_core/resources/shared_memory_holder.h index 5fcaddecc0..26d5867463 100644 --- a/src/hid_core/resources/shared_memory_holder.h +++ b/src/hid_core/resources/shared_memory_holder.h @@ -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"); diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 840602f4ce..01cf6a52f2 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -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 diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 6c8443ff62..c643400e70 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -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; std::shared_ptr touch_screen; std::shared_ptr tas_input; +#ifndef __EMSCRIPTEN__ std::shared_ptr udp_client; +#endif std::shared_ptr camera; std::shared_ptr virtual_amiibo; std::shared_ptr 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) { diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt index 1e6a4d173a..7591aeffd8 100644 --- a/src/shader_recompiler/CMakeLists.txt +++ b/src/shader_recompiler/CMakeLists.txt @@ -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. $<$:-fbracket-depth=1024> - $<$:-fbracket-depth=1024> - ) + $<$:-fbracket-depth=1024>) endif() create_target_directory_groups(shader_recompiler) diff --git a/src/shader_recompiler/environment.h b/src/shader_recompiler/environment.h index 20381cf467..5369430e03 100644 --- a/src/shader_recompiler/environment.h +++ b/src/shader_recompiler/environment.h @@ -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)); } }; diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch_swizzled.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch_swizzled.cpp index 50e618c2f1..d74eb6d46b 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch_swizzled.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch_swizzled.cpp @@ -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]; } } diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_load_swizzled.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_load_swizzled.cpp index 0362f076af..1b5313711e 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_load_swizzled.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_load_swizzled.cpp @@ -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]; } }