diff --git a/.ci/linux/build.sh b/.ci/linux/build.sh deleted file mode 100755 index 2a0a7e58b1..0000000000 --- a/.ci/linux/build.sh +++ /dev/null @@ -1,116 +0,0 @@ -#!/bin/bash -e - -# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - -case "$1" in -amd64 | "") - echo "Making amd64-v3 optimized build of Eden" - ARCH="amd64_v3" - ARCH_FLAGS="-march=x86-64-v3" - export EXTRA_CMAKE_FLAGS=(-DYUZU_BUILD_PRESET=v3) - ;; -steamdeck | zen2) - echo "Making Steam Deck (Zen 2) optimized build of Eden" - ARCH="steamdeck" - ARCH_FLAGS="-march=znver2 -mtune=znver2" - export EXTRA_CMAKE_FLAGS=(-DYUZU_BUILD_PRESET=zen2 -DYUZU_SYSTEM_PROFILE=steamdeck) - ;; -rog-ally | allyx | zen4) - echo "Making ROG Ally X (Zen 4) optimized build of Eden" - ARCH="rog-ally-x" - ARCH_FLAGS="-march=znver4 -mtune=znver4" - export EXTRA_CMAKE_FLAGS=(-DYUZU_BUILD_PRESET=zen2 -DYUZU_SYSTEM_PROFILE=steamdeck) - ;; -legacy) - echo "Making amd64 generic build of Eden" - ARCH=amd64 - ARCH_FLAGS="-march=x86-64 -mtune=generic" - export EXTRA_CMAKE_FLAGS=(-DYUZU_BUILD_PRESET=generic) - ;; -aarch64) - echo "Making armv8-a build of Eden" - ARCH=aarch64 - ARCH_FLAGS="-march=armv8-a -mtune=generic -w" - export EXTRA_CMAKE_FLAGS=(-DYUZU_BUILD_PRESET=generic) - ;; -armv9) - echo "Making armv9-a build of Eden" - ARCH=armv9 - ARCH_FLAGS="-march=armv9-a -mtune=generic -w" - export EXTRA_CMAKE_FLAGS=(-DYUZU_BUILD_PRESET=armv9) - ;; -native) - echo "Making native build of Eden" - ARCH="$(uname -m)" - ARCH_FLAGS="-march=native -mtune=native" - export EXTRA_CMAKE_FLAGS=(-DYUZU_BUILD_PRESET=native) - ;; -*) - echo "Invalid target $1 specified, must be one of native, amd64, steamdeck, zen2, allyx, rog-ally, zen4, legacy, aarch64, armv9" - exit 1 - ;; -esac - -export ARCH_FLAGS="$ARCH_FLAGS -O3" - -if [ -z "$NPROC" ]; then - NPROC="$(nproc)" -fi - -if [ "$1" != "" ]; then shift; fi - -if [ "$TARGET" = "appimage" ]; then - export EXTRA_CMAKE_FLAGS=("${EXTRA_CMAKE_FLAGS[@]}" -DCMAKE_INSTALL_PREFIX=/usr -DYUZU_ROOM=ON -DYUZU_ROOM_STANDALONE=OFF -DYUZU_CMD=OFF) -else - # For the linux-fresh verification target, verify compilation without PCH as well. - export EXTRA_CMAKE_FLAGS=("${EXTRA_CMAKE_FLAGS[@]}" -DYUZU_USE_PRECOMPILED_HEADERS=OFF) -fi - - -if [ "$DEVEL" != "true" ]; then - export EXTRA_CMAKE_FLAGS=("${EXTRA_CMAKE_FLAGS[@]}" -DENABLE_UPDATE_CHECKER=ON) -fi - -if [ "$USE_WEBENGINE" = "true" ]; then - WEBENGINE=ON -else - WEBENGINE=OFF -fi - -if [ "$USE_MULTIMEDIA" = "false" ]; then - MULTIMEDIA=OFF -else - MULTIMEDIA=ON -fi - -if [ -z "$BUILD_TYPE" ]; then - export BUILD_TYPE="Release" -fi - -export EXTRA_CMAKE_FLAGS=("${EXTRA_CMAKE_FLAGS[@]}" $@) - -mkdir -p build && cd build -cmake .. -G Ninja \ - -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ - -DENABLE_QT_TRANSLATION=ON \ - -DUSE_DISCORD_PRESENCE=ON \ - -DCMAKE_CXX_FLAGS="$ARCH_FLAGS" \ - -DCMAKE_C_FLAGS="$ARCH_FLAGS" \ - -DYUZU_USE_BUNDLED_QT=OFF \ - -DYUZU_USE_BUNDLED_SDL2=OFF \ - -DYUZU_USE_EXTERNAL_SDL2=ON \ - -DYUZU_TESTS=OFF \ - -DYUZU_USE_QT_MULTIMEDIA=$MULTIMEDIA \ - -DYUZU_USE_QT_WEB_ENGINE=$WEBENGINE \ - -DYUZU_USE_FASTER_LD=ON \ - -DENABLE_LTO=ON \ - "${EXTRA_CMAKE_FLAGS[@]}" - -ninja -j${NPROC} - -if [ -d "bin/Release" ]; then - strip -s bin/Release/* -else - strip -s bin/* -fi diff --git a/.ci/linux/eden.dwfsprof b/.ci/linux/eden.dwfsprof deleted file mode 100644 index 9a3bee6f14..0000000000 --- a/.ci/linux/eden.dwfsprof +++ /dev/null @@ -1,250 +0,0 @@ -AppRun -eden.desktop -dev.eden_emu.eden.desktop -shared/bin/eden -shared/lib/lib.path -shared/lib/ld-linux-x86-64.so.2 -shared/lib/libQt6Widgets.so.6.4.2 -shared/lib/libQt6DBus.so.6.4.2 -shared/lib/libudev.so.1.7.5 -shared/lib/libbrotlienc.so.1.0.9 -shared/lib/libbrotlidec.so.1.0.9 -shared/lib/libssl.so.3 -shared/lib/libcrypto.so.3 -shared/lib/libavcodec.so.59.37.100 -shared/lib/libavutil.so.57.28.100 -shared/lib/libQt6Gui.so.6.4.2 -shared/lib/libQt6Core.so.6.4.2 -shared/lib/libstdc++.so.6.0.30 -shared/lib/libm.so.6 -shared/lib/libgcc_s.so.1 -shared/lib/libc.so.6 -shared/lib/libdbus-1.so.3.32.4 -shared/lib/libbrotlicommon.so.1.0.9 -shared/lib/libswresample.so.4.7.100 -shared/lib/libvpx.so.7.1.0 -shared/lib/libwebpmux.so.3.0.10 -shared/lib/libwebp.so.7.1.5 -shared/lib/liblzma.so.5.4.1 -shared/lib/libdav1d.so.6.6.0 -shared/lib/librsvg-2.so.2.48.0 -shared/lib/libgobject-2.0.so.0.7400.6 -shared/lib/libglib-2.0.so.0.7400.6 -shared/lib/libcairo.so.2.11600.0 -shared/lib/libzvbi.so.0.13.2 -shared/lib/libz.so.1.2.13 -shared/lib/libsnappy.so.1.1.9 -shared/lib/libaom.so.3.6.0 -shared/lib/libcodec2.so.1.0 -shared/lib/libgsm.so.1.0.19 -shared/lib/libjxl.so.0.7.0 -shared/lib/libjxl_threads.so.0.7.0 -shared/lib/libmp3lame.so.0.0.0 -shared/lib/libopenjp2.so.2.5.0 -shared/lib/libopus.so.0.8.0 -shared/lib/librav1e.so.0.5.1 -shared/lib/libshine.so.3.0.1 -shared/lib/libspeex.so.1.5.2 -shared/lib/libSvtAv1Enc.so.1.4.1 -shared/lib/libtheoraenc.so.1.1.2 -shared/lib/libtheoradec.so.1.1.4 -shared/lib/libtwolame.so.0.0.0 -shared/lib/libvorbis.so.0.4.9 -shared/lib/libvorbisenc.so.2.0.12 -shared/lib/libx264.so.164 -shared/lib/libx265.so.199 -shared/lib/libxvidcore.so.4.3 -shared/lib/libva.so.2.1700.0 -shared/lib/libmfx.so.1.35 -shared/lib/libva-drm.so.2.1700.0 -shared/lib/libva-x11.so.2.1700.0 -shared/lib/libvdpau.so.1.0.0 -shared/lib/libX11.so.6.4.0 -shared/lib/libdrm.so.2.4.0 -shared/lib/libOpenCL.so.1.0.0 -shared/lib/libEGL.so.1.1.0 -shared/lib/libfontconfig.so.1.12.0 -shared/lib/libxkbcommon.so.0.0.0 -shared/lib/libGLX.so.0.0.0 -shared/lib/libOpenGL.so.0.0.0 -shared/lib/libpng16.so.16.39.0 -shared/lib/libharfbuzz.so.0.60000.0 -shared/lib/libmd4c.so.0.4.8 -shared/lib/libfreetype.so.6.18.3 -shared/lib/libicui18n.so.72.1 -shared/lib/libicuuc.so.72.1 -shared/lib/libdouble-conversion.so.3.1 -shared/lib/libb2.so.1.0.4 -shared/lib/libpcre2-16.so.0.11.2 -shared/lib/libzstd.so.1.5.4 -shared/lib/libsystemd.so.0.35.0 -shared/lib/libsoxr.so.0.1.2 -shared/lib/libcairo-gobject.so.2.11600.0 -shared/lib/libgdk_pixbuf-2.0.so.0.4200.10 -shared/lib/libgio-2.0.so.0.7400.6 -shared/lib/libxml2.so.2.9.14 -shared/lib/libpangocairo-1.0.so.0.5000.12 -shared/lib/libpango-1.0.so.0.5000.12 -shared/lib/libffi.so.8.1.2 -shared/lib/libpcre2-8.so.0.11.2 -shared/lib/libpixman-1.so.0.42.2 -shared/lib/libxcb-shm.so.0.0.0 -shared/lib/libxcb.so.1.1.0 -shared/lib/libxcb-render.so.0.0.0 -shared/lib/libXrender.so.1.3.0 -shared/lib/libXext.so.6.4.0 -shared/lib/libhwy.so.1.0.3 -shared/lib/liblcms2.so.2.0.14 -shared/lib/libogg.so.0.8.5 -shared/lib/libnuma.so.1.0.0 -shared/lib/libpthread.so.0 -shared/lib/libXfixes.so.3.1.0 -shared/lib/libX11-xcb.so.1.0.0 -shared/lib/libxcb-dri3.so.0.1.0 -shared/lib/libGLdispatch.so.0.0.0 -shared/lib/libexpat.so.1.8.10 -shared/lib/libgraphite2.so.3.2.1 -shared/lib/libicudata.so.72.1 -shared/lib/libgomp.so.1.0.0 -shared/lib/libcap.so.2.66 -shared/lib/libgcrypt.so.20.4.1 -shared/lib/liblz4.so.1.9.4 -shared/lib/libgmodule-2.0.so.0.7400.6 -shared/lib/libjpeg.so.62.3.0 -shared/lib/libmount.so.1.1.0 -shared/lib/libselinux.so.1 -shared/lib/libpangoft2-1.0.so.0.5000.12 -shared/lib/libfribidi.so.0.4.0 -shared/lib/libthai.so.0.3.1 -shared/lib/libXau.so.6.0.0 -shared/lib/libXdmcp.so.6.0.0 -shared/lib/libgpg-error.so.0.33.1 -shared/lib/libblkid.so.1.1.0 -shared/lib/libdatrie.so.1.4.0 -shared/lib/libbsd.so.0.11.7 -shared/lib/libmd.so.0.0.5 -shared/lib/libvulkan.so.1.3.239 -share/vulkan/icd.d/intel_hasvk_icd.x86_64.json -shared/lib/libvulkan_intel_hasvk.so -shared/lib/libwayland-client.so.0.21.0 -shared/lib/libxcb-present.so.0.0.0 -shared/lib/libxcb-xfixes.so.0.0.0 -shared/lib/libxcb-sync.so.1.0.0 -shared/lib/libxcb-randr.so.0.1.0 -shared/lib/libxshmfence.so.1.0.0 -share/vulkan/icd.d/intel_icd.x86_64.json -shared/lib/libvulkan_intel.so -share/vulkan/icd.d/lvp_icd.x86_64.json -shared/lib/libvulkan_lvp.so -shared/lib/libLLVM-15.so.1 -shared/lib/libedit.so.2.0.70 -shared/lib/libz3.so.4 -shared/lib/libtinfo.so.6.4 -share/vulkan/icd.d/radeon_icd.x86_64.json -shared/lib/libvulkan_radeon.so -shared/lib/libdrm_amdgpu.so.1.0.0 -shared/lib/libelf-0.188.so -shared/lib/libVkLayer_MESA_device_select.so -bin/qt.conf -shared/lib/qt6/plugins/platforms/libqeglfs.so -shared/lib/qt6/plugins/platforms/libqlinuxfb.so -shared/lib/qt6/plugins/platforms/libqminimal.so -shared/lib/qt6/plugins/platforms/libqminimalegl.so -shared/lib/qt6/plugins/platforms/libqoffscreen.so -shared/lib/qt6/plugins/platforms/libqvkkhrdisplay.so -shared/lib/qt6/plugins/platforms/libqvnc.so -shared/lib/qt6/plugins/platforms/libqwayland-egl.so -shared/lib/qt6/plugins/platforms/libqwayland-generic.so -shared/lib/qt6/plugins/platforms/libqxcb.so -shared/lib/libQt6WaylandClient.so.6.4.2 -shared/lib/libwayland-cursor.so.0.21.0 -shared/lib/qt6/plugins/platformthemes/libqgtk3.so -shared/lib/libgtk-3.so.0.2406.32 -shared/lib/libgdk-3.so.0.2406.32 -shared/lib/libatk-1.0.so.0.24609.1 -shared/lib/libepoxy.so.0.0.0 -shared/lib/libXi.so.6.1.0 -shared/lib/libatk-bridge-2.0.so.0.0.0 -shared/lib/libwayland-egl.so.1.21.0 -shared/lib/libXcursor.so.1.0.2 -shared/lib/libXdamage.so.1.1.0 -shared/lib/libXcomposite.so.1.0.0 -shared/lib/libXrandr.so.2.2.0 -shared/lib/libXinerama.so.1.0.0 -shared/lib/libdl.so.2 -shared/lib/libatspi.so.0.0.1 -share/glib-2.0/schemas/gschemas.compiled -shared/lib/gio/modules/giomodule.cache -shared/lib/gio/modules/libdconfsettings.so -shared/lib/gio/modules/libgvfsdbus.so -shared/lib/gvfs/libgvfscommon.so -share/X11/xkb/rules/evdev -share/X11/xkb/keycodes/evdev -share/X11/xkb/keycodes/aliases -share/X11/xkb/types/complete -share/X11/xkb/types/basic -share/X11/xkb/types/mousekeys -share/X11/xkb/types/pc -share/X11/xkb/types/iso9995 -share/X11/xkb/types/level5 -share/X11/xkb/types/extra -share/X11/xkb/types/numpad -share/X11/xkb/compat/complete -share/X11/xkb/compat/basic -share/X11/xkb/compat/ledcaps -share/X11/xkb/compat/lednum -share/X11/xkb/compat/iso9995 -share/X11/xkb/compat/mousekeys -share/X11/xkb/compat/accessx -share/X11/xkb/compat/misc -share/X11/xkb/compat/ledscroll -share/X11/xkb/compat/xfree86 -share/X11/xkb/compat/level5 -share/X11/xkb/compat/caps -share/X11/xkb/symbols/pc -share/X11/xkb/symbols/srvr_ctrl -share/X11/xkb/symbols/keypad -share/X11/xkb/symbols/altwin -share/X11/xkb/symbols/us -share/X11/xkb/symbols/inet -shared/lib/qt6/plugins/platforminputcontexts/libcomposeplatforminputcontextplugin.so -shared/lib/qt6/plugins/platforminputcontexts/libibusplatforminputcontextplugin.so -shared/lib/qt6/plugins/iconengines/libqsvgicon.so -shared/lib/qt6/plugins/imageformats/libqgif.so -shared/lib/qt6/plugins/imageformats/libqico.so -shared/lib/qt6/plugins/imageformats/libqjpeg.so -shared/lib/qt6/plugins/imageformats/libqsvg.so -shared/lib/libQt6Svg.so.6.4.2 -etc/fonts/fonts.conf -shared/lib/qt6/plugins/wayland-shell-integration/libfullscreen-shell-v1.so -shared/lib/qt6/plugins/wayland-shell-integration/libivi-shell.so -shared/lib/qt6/plugins/wayland-shell-integration/libqt-shell.so -shared/lib/qt6/plugins/wayland-shell-integration/libwl-shell-plugin.so -shared/lib/qt6/plugins/wayland-shell-integration/libxdg-shell.so -shared/lib/qt6/plugins/wayland-graphics-integration-client/libdmabuf-server.so -shared/lib/qt6/plugins/wayland-graphics-integration-client/libdrm-egl-server.so -shared/lib/qt6/plugins/wayland-graphics-integration-client/libqt-plugin-wayland-egl.so -shared/lib/qt6/plugins/wayland-graphics-integration-client/libshm-emulation-server.so -shared/lib/qt6/plugins/wayland-graphics-integration-client/libvulkan-server.so -shared/lib/libQt6WaylandEglClientHwIntegration.so.6.4.2 -shared/lib/libQt6OpenGL.so.6.4.2 -share/glvnd/egl_vendor.d/50_mesa.json -shared/lib/libEGL_mesa.so.0.0.0 -shared/lib/libgbm.so.1.0.0 -shared/lib/libglapi.so.0.0.0 -shared/lib/libxcb-dri2.so.0.0.0 -shared/lib/libwayland-server.so.0.21.0 -shared/lib/dri/swrast_dri.so -shared/lib/libsensors.so.5.0.0 -shared/lib/libdrm_radeon.so.1.0.1 -shared/lib/libdrm_nouveau.so.2.0.0 -shared/lib/libdrm_intel.so.1.0.0 -shared/lib/libpciaccess.so.0.11.1 -shared/lib/qt6/plugins/wayland-decoration-client/libbradient.so -shared/lib/gtk-3.0/modules/libcanberra-gtk3-module.so -shared/lib/libcanberra-gtk3.so.0.1.9 -shared/lib/libcanberra.so.0.2.5 -shared/lib/libvorbisfile.so.3.3.8 -shared/lib/libtdb.so.1.4.8 -shared/lib/libltdl.so.7.3.2 -shared/lib/libXss.so.1.0.0 diff --git a/.ci/linux/package.sh b/.ci/linux/package.sh deleted file mode 100755 index 838476097a..0000000000 --- a/.ci/linux/package.sh +++ /dev/null @@ -1,153 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - -# This script assumes you're in the source directory - -export APPIMAGE_EXTRACT_AND_RUN=1 -export BASE_ARCH="$(uname -m)" - -SHARUN="https://github.com/VHSgunzo/sharun/releases/latest/download/sharun-${BASE_ARCH}-aio" -URUNTIME="https://github.com/VHSgunzo/uruntime/releases/latest/download/uruntime-appimage-dwarfs-${BASE_ARCH}" - -case "$1" in - amd64|"") - echo "Packaging amd64-v3 optimized build of Eden" - ARCH="amd64_v3" - ;; - steamdeck|zen2) - echo "Packaging Steam Deck (Zen 2) optimized build of Eden" - ARCH="steamdeck" - ;; - rog-ally|allyx|zen4) - echo "Packaging ROG Ally X (Zen 4) optimized build of Eden" - ARCH="rog-ally-x" - ;; - legacy) - echo "Packaging amd64 generic build of Eden" - ARCH=amd64 - ;; - aarch64) - echo "Packaging armv8-a build of Eden" - ARCH=aarch64 - ;; - armv9) - echo "Packaging armv9-a build of Eden" - ARCH=armv9 - ;; - native) - echo "Packaging native build of Eden" - ARCH="$BASE_ARCH" - ;; - -esac - -export BUILDDIR="$2" - -if [ "$BUILDDIR" = '' ] -then - BUILDDIR=build -fi - -EDEN_TAG=$(git describe --tags --abbrev=0) -echo "Making \"$EDEN_TAG\" build" -# git checkout "$EDEN_TAG" -VERSION="$(echo "$EDEN_TAG")" - -# NOW MAKE APPIMAGE -mkdir -p ./AppDir -cd ./AppDir - -cp ../dist/dev.eden_emu.eden.desktop . -cp ../dist/dev.eden_emu.eden.svg . - -ln -sf ./dev.eden_emu.eden.svg ./.DirIcon - -UPINFO='gh-releases-zsync|eden-emulator|Releases|latest|*.AppImage.zsync' - -if [ "$DEVEL" = 'true' ]; then - sed -i 's|Name=Eden|Name=Eden Nightly|' ./dev.eden_emu.eden.desktop - UPINFO="$(echo "$UPINFO" | sed 's|Releases|nightly|')" -fi - -LIBDIR="/usr/lib" - -# Workaround for Gentoo -if [ ! -d "$LIBDIR/qt6" ] -then - LIBDIR="/usr/lib64" -fi - -# Workaround for Debian -if [ ! -d "$LIBDIR/qt6" ] -then - LIBDIR="/usr/lib/${BASE_ARCH}-linux-gnu" -fi - -# Bundle all libs - -wget --retry-connrefused --tries=30 "$SHARUN" -O ./sharun-aio -chmod +x ./sharun-aio -xvfb-run -a ./sharun-aio l -p -v -e -s -k \ - ../$BUILDDIR/bin/eden* \ - $LIBDIR/lib*GL*.so* \ - $LIBDIR/dri/* \ - $LIBDIR/vdpau/* \ - $LIBDIR/libvulkan* \ - $LIBDIR/libXss.so* \ - $LIBDIR/libdecor-0.so* \ - $LIBDIR/libgamemode.so* \ - $LIBDIR/qt6/plugins/audio/* \ - $LIBDIR/qt6/plugins/bearer/* \ - $LIBDIR/qt6/plugins/imageformats/* \ - $LIBDIR/qt6/plugins/iconengines/* \ - $LIBDIR/qt6/plugins/platforms/* \ - $LIBDIR/qt6/plugins/platformthemes/* \ - $LIBDIR/qt6/plugins/platforminputcontexts/* \ - $LIBDIR/qt6/plugins/styles/* \ - $LIBDIR/qt6/plugins/xcbglintegrations/* \ - $LIBDIR/qt6/plugins/wayland-*/* \ - $LIBDIR/pulseaudio/* \ - $LIBDIR/pipewire-0.3/* \ - $LIBDIR/spa-0.2/*/* \ - $LIBDIR/alsa-lib/* - -rm -f ./sharun-aio - -# Prepare sharun -if [ "$ARCH" = 'aarch64' ]; then - # allow the host vulkan to be used for aarch64 given the sad situation - echo 'SHARUN_ALLOW_SYS_VKICD=1' > ./.env -fi - -# Workaround for Gentoo -if [ -d "shared/libproxy" ]; then - cp shared/libproxy/* lib/ -fi - -ln -f ./sharun ./AppRun -./sharun -g - -# turn appdir into appimage -cd .. -wget -q "$URUNTIME" -O ./uruntime -chmod +x ./uruntime - -#Add udpate info to runtime -echo "Adding update information \"$UPINFO\" to runtime..." -./uruntime --appimage-addupdinfo "$UPINFO" - -echo "Generating AppImage..." -./uruntime --appimage-mkdwarfs -f \ - --set-owner 0 --set-group 0 \ - --no-history --no-create-timestamp \ - --categorize=hotness --hotness-list=.ci/linux/eden.dwfsprof \ - --compression zstd:level=22 -S26 -B32 \ - --header uruntime \ - -N 4 \ - -i ./AppDir -o Eden-"$VERSION"-"$ARCH".AppImage - -echo "Generating zsync file..." -zsyncmake *.AppImage -u *.AppImage -echo "All Done!" \ No newline at end of file diff --git a/.ci/windows/build.sh b/.ci/windows/build.sh deleted file mode 100755 index 48cca8eb4d..0000000000 --- a/.ci/windows/build.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash -ex - -# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - -if [ "$COMPILER" == "clang" ] -then - EXTRA_CMAKE_FLAGS+=( - -DCMAKE_CXX_COMPILER=clang-cl - -DCMAKE_C_COMPILER=clang-cl - -DCMAKE_CXX_FLAGS="-O3" - -DCMAKE_C_FLAGS="-O3" - ) - - BUILD_TYPE="RelWithDebInfo" -fi - -[ -z "$WINDEPLOYQT" ] && { echo "WINDEPLOYQT environment variable required."; exit 1; } - -echo $EXTRA_CMAKE_FLAGS - -mkdir -p build && cd build -cmake .. -G Ninja \ - -DCMAKE_BUILD_TYPE="${BUILD_TYPE:-Release}" \ - -DENABLE_QT_TRANSLATION=ON \ - -DUSE_DISCORD_PRESENCE=ON \ - -DYUZU_USE_BUNDLED_SDL2=ON \ - -DBUILD_TESTING=OFF \ - -DYUZU_TESTS=OFF \ - -DDYNARMIC_TESTS=OFF \ - -DYUZU_CMD=OFF \ - -DYUZU_ROOM_STANDALONE=OFF \ - -DYUZU_USE_QT_MULTIMEDIA=${USE_MULTIMEDIA:-false} \ - -DYUZU_USE_QT_WEB_ENGINE=${USE_WEBENGINE:-false} \ - -DENABLE_LTO=ON \ - -DCMAKE_EXE_LINKER_FLAGS=" /LTCG" \ - -DYUZU_USE_BUNDLED_QT=${BUNDLE_QT:-false} \ - -DUSE_CCACHE=${CCACHE:-false} \ - -DENABLE_UPDATE_CHECKER=${DEVEL:-true} \ - "${EXTRA_CMAKE_FLAGS[@]}" \ - "$@" - -ninja - -set +e -rm -f bin/*.pdb -set -e - -$WINDEPLOYQT --release --no-compiler-runtime --no-opengl-sw --no-system-dxc-compiler --no-system-d3d-compiler --dir pkg bin/eden.exe - -cp bin/* pkg diff --git a/.ci/windows/package.sh b/.ci/windows/package.sh deleted file mode 100644 index 2d126dc5be..0000000000 --- a/.ci/windows/package.sh +++ /dev/null @@ -1,18 +0,0 @@ -GITDATE=$(git show -s --date=short --format='%ad' | tr -d "-") -GITREV=$(git show -s --format='%h') - -ZIP_NAME="Eden-Windows-${ARCH}-${GITDATE}-${GITREV}.zip" - -ARTIFACTS_DIR="artifacts" -PKG_DIR="build/pkg" - -mkdir -p "$ARTIFACTS_DIR" - -TMP_DIR=$(mktemp -d) - -cp -r "$PKG_DIR"/* "$TMP_DIR"/ -cp LICENSE* README* "$TMP_DIR"/ - -7z a -tzip "$ARTIFACTS_DIR/$ZIP_NAME" "$TMP_DIR"/* - -rm -rf "$TMP_DIR" \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1142585b6d..f828f20bd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,13 +119,13 @@ if (YUZU_STATIC_BUILD) set(QuaZip-Qt6_FORCE_BUNDLED ON) set(YUZU_USE_BUNDLED_FFMPEG ON) - set(YUZU_USE_BUNDLED_SDL2 ON) + set(YUZU_USE_BUNDLED_SDL3 ON) set(YUZU_USE_BUNDLED_OPENSSL ON) set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF) elseif(APPLE) set(YUZU_USE_BUNDLED_FFMPEG ON) - set(YUZU_USE_BUNDLED_SDL2 ON) + set(YUZU_USE_BUNDLED_SDL3 ON) set(YUZU_USE_BUNDLED_OPENSSL ON) # these libs do not properly provide static libs/let you do it with cmake @@ -169,9 +169,32 @@ if (MSVC AND NOT CXX_CLANG) set(CMAKE_CXX_FLAGS_INIT "${CMAKE_CXX_FLAGS_INIT} /W3 /WX-") endif() +# Set runtime library to MD/MDd for all configurations +if(MSVC) + if (YUZU_USE_BUNDLED_QT AND ARCHITECTURE_arm64) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set(libflag MT) + else() + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") + set(libflag MD) + endif() + + # Force all projects (including external dependencies) to use the same runtime + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /${libflag}") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /${libflag}d") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /${libflag}") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /${libflag}d") + + # Add this to ensure Cubeb uses the same runtime + add_compile_options( + $<$:$<$:/${libflag}d>> + $<$:$<$:/${libflag}>> + $<$:$<$:/${libflag}>> + $<$:$<$:/${libflag}>>) +endif() + # TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system -cmake_dependent_option(YUZU_USE_EXTERNAL_SDL2 "Build SDL2 from external source" OFF "NOT MSVC;NOT ANDROID" OFF) -cmake_dependent_option(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${MSVC}" "NOT ANDROID" OFF) +cmake_dependent_option(YUZU_USE_BUNDLED_SDL3 "Download bundled SDL3 build" "${MSVC}" "NOT ANDROID" OFF) option(ENABLE_CUBEB "Enables the cubeb audio backend" ON) @@ -205,6 +228,10 @@ cmake_dependent_option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}") +# Install udev rules on Linux (mainly for gyros) +# Only acts on joysticks and nothing else. +cmake_dependent_option(YUZU_INSTALL_UDEV_RULES "Install udev rules for gyro access" OFF "PLATFORM_LINUX" OFF) + option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON) option(YUZU_LEGACY "Apply patches that improve compatibility with older GPUs (e.g. Snapdragon 865) at the cost of performance" OFF) @@ -333,10 +360,6 @@ if (CXX_GCC OR CXX_CLANG) endif() endif() -# Other presets, e.g. steamdeck -# TODO(crueter): Just have every Linux/Windows use old sdl2 -set(YUZU_SYSTEM_PROFILE "generic" CACHE STRING "CMake and Externals profile to use. One of: generic, steamdeck") - # Configure C++ standard # =========================== @@ -534,7 +557,7 @@ if (NOT YUZU_STATIC_ROOM) endif() if (NOT ANDROID) - find_package(SDL2) + find_package(SDL3) endif() if (USE_DISCORD_PRESENCE) @@ -688,25 +711,6 @@ if (MSVC AND CXX_CLANG) link_libraries(llvm-mingw-runtime) endif() -# Set runtime library to MD/MDd for all configurations -if(MSVC) - set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") - - # Force all projects (including external dependencies) to use the same runtime - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MD") - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MDd") - - # Add this to ensure Cubeb uses the same runtime - add_compile_options( - $<$:/MDd> - $<$:/MD> - $<$:/MD> - $<$:/MD> - ) -endif() - add_subdirectory(src) # Set yuzu project or yuzu-cmd project as default StartUp Project in Visual Studio depending on whether QT is enabled or not @@ -724,6 +728,8 @@ endif() # https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html # https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html # https://www.freedesktop.org/software/appstream/docs/ + +# TODO: Icon/install handling n such should be put into dist/CMakeLists.txt if(ENABLE_QT AND UNIX AND NOT APPLE) install(FILES "dist/dev.eden_emu.eden.desktop" DESTINATION "share/applications") @@ -736,3 +742,10 @@ if(ENABLE_QT AND UNIX AND NOT APPLE) install(FILES "dist/dev.eden_emu.eden.metainfo.xml" DESTINATION "share/metainfo") endif() + +if (YUZU_INSTALL_UDEV_RULES) + include(GNUInstallDirs) + + install(FILES "dist/72-eden-input.rules" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/udev/rules.d") +endif() diff --git a/dist/72-eden-input.rules b/dist/72-eden-input.rules new file mode 100644 index 0000000000..b84ed200a3 --- /dev/null +++ b/dist/72-eden-input.rules @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2023 yuzu Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +# Allow systemd-logind to manage user access to hidraw with this file +# On most systems, this file should be installed to /etc/udev/rules.d/72-eden-input.rules +# Consult your distro if this is not the case + +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ENV{ID_INPUT_JOYSTICK}=="1", MODE="0660", TAG+="uaccess" diff --git a/dist/72-yuzu-input.rules b/dist/72-yuzu-input.rules deleted file mode 100644 index d64f8b28d7..0000000000 --- a/dist/72-yuzu-input.rules +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-FileCopyrightText: 2023 yuzu Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -# Allow systemd-logind to manage user access to hidraw with this file -# On most systems, this file should be installed to /etc/udev/rules.d/72-yuzu-input.rules -# Consult your distro if this is not the case - -# Switch Pro Controller (USB/Bluetooth) -KERNEL=="hidraw*", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2009", MODE="0660", TAG+="uaccess" -KERNEL=="hidraw*", KERNELS=="*057e:2009*", MODE="0660", TAG+="uaccess" - -# Joy-Con L (Bluetooth) -KERNEL=="hidraw*", KERNELS=="*057e:2006*", MODE="0660", TAG+="uaccess" - -# Joy-Con R (Bluetooth) -KERNEL=="hidraw*", KERNELS=="*057e:2007*", MODE="0660", TAG+="uaccess" - -# Joy-Con Charging Grip (USB) -KERNEL=="hidraw*", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="200e", MODE="0660", TAG+="uaccess" diff --git a/dist/dev.eden_emu.eden.svg b/dist/dev.eden_emu.eden.svg index f88b52f625..df206b43af 100644 --- a/dist/dev.eden_emu.eden.svg +++ b/dist/dev.eden_emu.eden.svg @@ -6,8 +6,8 @@ viewBox="0 0 512 512" version="1.1" id="svg7" - sodipodi:docname="base.svg.2026_01_12_14_43_47.0.svg" - inkscape:version="1.4.2 (ebf0e94, 2025-05-08)" + sodipodi:docname="1stanni.svg" + inkscape:version="1.4.3 (0d15f75042, 2025-12-25)" inkscape:export-filename="base.svg.2026_01_12_14_43_47.0.svg" inkscape:export-xdpi="96" inkscape:export-ydpi="96" @@ -19,36 +19,34 @@ + id="stop34" /> - + style="stop-color:#ffd700;stop-opacity:0.48031053;" + offset="1" + id="stop35" /> + + id="stop21" /> - + style="stop-color:#830091;stop-opacity:1;" + offset="1" + id="stop22" /> - - - - - - @@ -165,16 +136,6 @@ inkscape:label="Circle" r="191.89999" /> - @@ -187,14 +148,106 @@ + y2="512" + gradientUnits="userSpaceOnUse" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dist/icon_variations/1an_bgcolor b/dist/icon_variations/1an_bgcolor new file mode 100644 index 0000000000..a47436df7f --- /dev/null +++ b/dist/icon_variations/1an_bgcolor @@ -0,0 +1 @@ +#ffd700 diff --git a/dist/languages/ar.ts b/dist/languages/ar.ts index 4da378a1da..8eb16efe96 100644 --- a/dist/languages/ar.ts +++ b/dist/languages/ar.ts @@ -6038,47 +6038,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL غير متوفر! - + OpenGL shared contexts are not supported. OpenGL لا يتم دعم السياقات المشتركة - + Eden has not been compiled with OpenGL support. OpenGL لم يتم تجميع إيدن بدعم - - + + + Error while initializing OpenGL! OpenGL حدث خطأ أثناء تهيئة - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. أو قد لا يكون لديك أحدث برنامج تشغيل للرسومات OpenGL قد لا تدعم بطاقة الرسومات الخاصة بك - + Error while initializing OpenGL 4.6! OpenGL 4.6 حدث خطأ أثناء تهيئة - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 أو قد لا يكون لديك أحدث برنامج تشغيل للرسومات OpenGL 4.6 قد لا تدعم بطاقة الرسومات الخاصة بك.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 قد لا تدعم وحدة معالجة الرسومات لديك ملحقًا واحدًا أو أكثر من ملحقات OpenGL المطلوبة. يُرجى التأكد من تثبيت أحدث برنامج تشغيل للرسومات.<br><br>GL Renderer:<br>1%<br><br>إضافات غير مدعومة: <br>2% + + + This build doesn't have OpenGL support. + هذا الإصدار لا يدعم OpenGL. + GameList @@ -9304,10 +9310,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel إلغاء @@ -9328,7 +9334,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9337,204 +9343,204 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... جارٍ التحقق من السلامة... - - + + Integrity verification succeeded! تم التحقق من السلامة بنجاح! - - + + The operation completed successfully. اكتملت العملية بنجاح. - - + + Integrity verification failed! فشل التحقق من السلامة! - + File contents may be corrupt or missing. قد تكون محتويات الملف تالفة أو مفقودة. - + Integrity verification couldn't be performed تعذر إجراء التحقق من السلامة - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. تم إلغاء تثبيت الفيرموير، قد يكون الفيرموير في حالة سيئة أو تآلف. تعذر التحقق من صحة محتويات الملف. - + Select Dumped Keys Location حدد موقع المفاتيح المفرغة - + Decryption Keys install succeeded تم تثبيت مفاتيح فك التشفير بنجاح - + Decryption Keys install failed فشل تثبيت مفاتيح فك التشفير - + Orphaned Profiles Detected! تم الكشف عن ملفات تعريف مهملة! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> قد تحدث أمور سيئة غير متوقعة إذا لم تقرأ هذا<br>!لقد اكتشف إيدن مجلدات الحفظ التالية بدون ملف تعريف مرفق:<br>%1<br><br>ملفات التعريف التالية صالحة:<br>%2<br><br>انقر على ”موافق“ لفتح مجلد الحفظ وإصلاح ملفات التعريف الخاصة بك.<br>تلميح: انسخ محتويات المجلد الأكبر أو آخر مجلد تم تعديله إلى مكان آخر، واحذف جميع ملفات التعريف اليتيمة، وانقل المحتويات المنسوخة إلى ملف التعريف الصحيح.<br><br>هل ما زلت تشعر بالارتباك؟ انظر إلى <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>صفحة المساعدة</a>.<br> - + Really clear data? مسح البيانات بالفعل؟ - + Important data may be lost! قد يتم فقدان بيانات مهمة! - + Are you REALLY sure? هل أنت متأكد حقًا؟ - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. بمجرد حذفها، لن تتمكن من استعادة بياناتك! قم بذلك فقط إذا كنت متأكدًا بنسبة 100٪ أنك تريد حذف هذه البيانات. - + Clearing... إزالة... - + Select Export Location حدد موقع التصدير - + %1.zip %1.zip - - + + Zipped Archives (*.zip) Zipped Archives (*.zip) - + Exporting data. This may take a while... تصدير البيانات. قد يستغرق هذا بعض الوقت... - + Exporting التصدير - + Exported Successfully تم التصدير بنجاح - + Data was exported successfully. تم تصدير البيانات بنجاح. - + Export Cancelled تم إلغاء التصدير - + Export was cancelled by the user. تم إلغاء التصدير من قبل المستخدم. - + Export Failed فشل التصدير - + Ensure you have write permissions on the targeted directory and try again. تأكد من أن لديك أذونات الكتابة على المجلد المحدد وحاول مرة أخرى. - + Select Import Location حدد موقع الاستيراد - + Import Warning تحذير الاستيراد - + All previous data in this directory will be deleted. Are you sure you wish to proceed? سيتم حذف جميع البيانات السابقة في هذا المجلد. هل أنت متأكد من رغبتك في المتابعة؟ - + Importing data. This may take a while... استيراد البيانات. قد يستغرق هذا بعض الوقت... - + Importing استيراد - + Imported Successfully تم الاستيراد بنجاح - + Data was imported successfully. تم استيراد البيانات بنجاح. - + Import Cancelled تم إلغاء الاستيراد - + Import was cancelled by the user. تم إلغاء الاستيراد من قبل المستخدم. - + Import Failed فشل الاستيراد - + Ensure you have read permissions on the targeted directory and try again. تأكد من أن لديك أذونات قراءة على المجلد المحدد وحاول مرة أخرى. @@ -9896,18 +9902,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed فشل استخراج التعديل - + Failed to create temporary directory %1 %1 فشل إنشاء مجلد المؤقت - + Zip file %1 is empty الملف المضغوط 1% فارغ diff --git a/dist/languages/ca.ts b/dist/languages/ca.ts index 85b0c5c8ec..8e35230811 100644 --- a/dist/languages/ca.ts +++ b/dist/languages/ca.ts @@ -494,7 +494,7 @@ This is mainly a debug option and shouldn't be disabled. Memory Layout - + Distribució de memòria @@ -5972,47 +5972,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL no disponible! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! Error al inicialitzar OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. La seva GPU no suporta OpenGL, o no té instal·lat els últims controladors gràfics. - + Error while initializing OpenGL 4.6! Error inicialitzant OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 La seva GPU no suporta OpenGL 4.6, o no té instal·lats els últims controladors gràfics.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 És possible que la seva GPU no suporti una o més extensions necessàries d'OpenGL. Si us plau, asseguris de tenir els últims controladors de la tarjeta gràfica.<br><br>GL Renderer:<br>%1<br><br>Extensions no suportades:<br>%2 + + + This build doesn't have OpenGL support. + Aquesta compilació no té suport per a OpenGL. + GameList @@ -9196,10 +9202,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Cancel·lar @@ -9220,210 +9226,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting Exportant - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9780,18 +9786,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/cs.ts b/dist/languages/cs.ts index bf8ee5bee4..040a711d05 100644 --- a/dist/languages/cs.ts +++ b/dist/languages/cs.ts @@ -5963,47 +5963,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL není k dispozici! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! Chyba při inicializaci OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Vaše grafická karta pravděpodobně nepodporuje OpenGL nebo nejsou nainstalovány nejnovější ovladače. - + Error while initializing OpenGL 4.6! Chyba při inicializaci OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Vaše grafická karta pravděpodobně nepodporuje OpenGL 4.6 nebo nejsou nainstalovány nejnovější ovladače.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Vaše grafická karta pravděpodobně nepodporuje jedno nebo více rozšíření OpenGL. Ujistěte se prosím, že jsou nainstalovány nejnovější ovladače.<br><br>GL Renderer:<br>%1<br><br>Nepodporované rozšíření:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9184,10 +9190,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9208,210 +9214,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9768,18 +9774,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/da.ts b/dist/languages/da.ts index 2805155565..df9d3804d6 100644 --- a/dist/languages/da.ts +++ b/dist/languages/da.ts @@ -5971,47 +5971,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. - + Error while initializing OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9188,10 +9194,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9212,210 +9218,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9772,18 +9778,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/de.ts b/dist/languages/de.ts index a9797949aa..2c237d6ee4 100644 --- a/dist/languages/de.ts +++ b/dist/languages/de.ts @@ -5986,47 +5986,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL nicht verfügbar! - + OpenGL shared contexts are not supported. Gemeinsame OpenGL-Kontexte werden nicht unterstützt. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! Fehler beim Initialisieren von OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Deine Grafikkarte unterstützt kein OpenGL oder du hast nicht den neusten Treiber installiert. - + Error while initializing OpenGL 4.6! Fehler beim Initialisieren von OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Deine Grafikkarte unterstützt OpenGL 4.6 nicht, oder du benutzt nicht die neuste Treiberversion.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Deine Grafikkarte unterstützt anscheinend nicht eine oder mehrere von yuzu benötigten OpenGL-Erweiterungen. Bitte stelle sicher, dass du den neusten Grafiktreiber installiert hast.<br><br>GL Renderer:<br>%1<br><br>Nicht unterstützte Erweiterungen:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9208,10 +9214,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9232,210 +9238,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9792,18 +9798,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/el.ts b/dist/languages/el.ts index a70b386e96..86481e1373 100644 --- a/dist/languages/el.ts +++ b/dist/languages/el.ts @@ -5962,47 +5962,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! Το OpenGL δεν είναι διαθέσιμο! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! Σφάλμα κατα την αρχικοποίηση του OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. - + Error while initializing OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9180,10 +9186,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9204,210 +9210,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9764,18 +9770,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/es.ts b/dist/languages/es.ts index 6ac892b4fb..087a4f80a3 100644 --- a/dist/languages/es.ts +++ b/dist/languages/es.ts @@ -6053,47 +6053,53 @@ Por favor, vaya a Configuración -> Sistema -> Red y selecciona una interf GRenderWindow - - + + OpenGL not available! ¡OpenGL no está disponible! - + OpenGL shared contexts are not supported. Los contextos compartidos de OpenGL no son compatibles. - + Eden has not been compiled with OpenGL support. Eden no ha sido compilado con soporte para OpenGL. - - + + + Error while initializing OpenGL! ¡Error al inicializar OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Tu GPU no soporta OpenGL, o no tienes instalados los últimos controladores gráficos. - + Error while initializing OpenGL 4.6! ¡Error al iniciar OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Tu GPU no soporta OpenGL 4.6, o no tienes instalado el último controlador de la tarjeta gráfica.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Es posible que la GPU no soporte una o más extensiones necesarias de OpenGL . Por favor, asegúrate de tener los últimos controladores de la tarjeta gráfica.<br><br>GL Renderer:<br>%1<br><br>Extensiones no soportadas:<br>%2 + + + This build doesn't have OpenGL support. + Esta compilación no tiene soporte para OpenGL. + GameList @@ -9310,10 +9316,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Cancelar @@ -9334,7 +9340,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9343,206 +9349,206 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... Verificando integridad... - - + + Integrity verification succeeded! ¡La verificación de integridad ha sido un éxito! - - + + The operation completed successfully. La operación se completó con éxito. - - + + Integrity verification failed! ¡Verificación de integridad se fallo! - + File contents may be corrupt or missing. Los contenidos del archivo pueden estar corruptos. - + Integrity verification couldn't be performed No se pudo ejecutar la verificación de integridad - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Instalacion de firmware cancellado , firmware podria estar en un mal estado o coruptos. contenidos de el archivo no pudieron ser verificados para validez. - + Select Dumped Keys Location Seleccionar ubicación de origen de los llaves volcados - + Decryption Keys install succeeded Instalación de llaves de descifra salo con exito - + Decryption Keys install failed Instalacion de las llaves de descifra se fallo - + Orphaned Profiles Detected! ¡Se detectaron perfiles huérfanos! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> ¡PODRÍAN SUCEDER COSAS MALAS E INESPERADAS SI NO LEE ESTO!<br>Eden ha detectado que los siguientes directorios de guardado no tienen perfil asociado:<br>%1<br><br>Los siguientes perfiles son válidos:<br>%2<br><br>Haga clic en "Aceptar" para abrir la carpeta de guardado y arreglar sus perfiles.<br>Consejo: copie el contenido de la carpeta más grande o la última modificada en otro lugar, elimine todos los perfiles huérfanos y mueva el contenido copiado al perfil correcto.<br><br>¿Aún tiene dudas? Consulte la <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>página de ayuda</a>.<br> - + Really clear data? ¿Realmente deseas borrar los datos? - + Important data may be lost! ¡Podrías perder información importante! - + Are you REALLY sure? ¿Estás REALMENTE seguro? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. ¡Una vez eliminados, tus datos no podrán recuperarse! Haz esto solo si estás 100% seguro de que deseas borrarlos. - + Clearing... Limpiando... - + Select Export Location Selecciona la Ubicación de Exportación. - + %1.zip %1.zip - - + + Zipped Archives (*.zip) Archivos comprimidos (*.zip) - + Exporting data. This may take a while... Exportando datos. Esto puede tardar un poco... - + Exporting Exportando - + Exported Successfully Exportación Exitosa. - + Data was exported successfully. Los datos se exportaron correctamente. - + Export Cancelled Exportación cancelada. - + Export was cancelled by the user. La exportación fue cancelada por el usuario. - + Export Failed Exportación Fallida - + Ensure you have write permissions on the targeted directory and try again. Asegúrate de tener permisos de escritura en el directorio seleccionado e inténtalo nuevamente. - + Select Import Location Seleccionar ubicación de importación. - + Import Warning Advertencia al importar datos - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Todos los datos anteriores en este directorio serán eliminados. ¿Estás seguro de que deseas continuar? - + Importing data. This may take a while... Importando datos. Esto puede tomar unos minutos... - + Importing Importando - + Imported Successfully Importación completada con éxito. - + Data was imported successfully. Los datos se importaron correctamente. - + Import Cancelled La importación fue cancelada. - + Import was cancelled by the user. La importación fue cancelada por el usuario. - + Import Failed Importación Fallida. - + Ensure you have read permissions on the targeted directory and try again. Asegúrate de tener permisos de lectura en el directorio seleccionado e inténtalo nuevamente. @@ -9906,18 +9912,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed Fallo al extraer el mod - + Failed to create temporary directory %1 Fallo al crear directorio temporal %1 - + Zip file %1 is empty El archivo zip %1 está vacío diff --git a/dist/languages/fi.ts b/dist/languages/fi.ts index 7efea10765..34eec963df 100644 --- a/dist/languages/fi.ts +++ b/dist/languages/fi.ts @@ -5940,47 +5940,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! openGL ei ole saatavilla! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! Virhe käynnistäessä OpenGL ydintä! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. - + Error while initializing OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9157,10 +9163,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9181,210 +9187,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9741,18 +9747,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/fr.ts b/dist/languages/fr.ts index dafb0064bd..836a4798b2 100644 --- a/dist/languages/fr.ts +++ b/dist/languages/fr.ts @@ -6029,47 +6029,53 @@ Veuillez aller dans Configurer -> Système -> Réseau puis en choisir une. GRenderWindow - - + + OpenGL not available! OpenGL n'est pas disponible ! - + OpenGL shared contexts are not supported. Les contextes OpenGL partagés ne sont pas pris en charge. - + Eden has not been compiled with OpenGL support. Eden n'a pas été compilé avec le support OpenGL - - + + + Error while initializing OpenGL! Erreur lors de l'initialisation d'OpenGL ! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Votre GPU peut ne pas prendre en charge OpenGL, ou vous n'avez pas les derniers pilotes graphiques. - + Error while initializing OpenGL 4.6! Erreur lors de l'initialisation d'OpenGL 4.6 ! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Votre GPU peut ne pas prendre en charge OpenGL 4.6 ou vous ne disposez pas du dernier pilote graphique: %1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Votre GPU peut ne pas prendre en charge une ou plusieurs extensions OpenGL requises. Veuillez vous assurer que vous disposez du dernier pilote graphique.<br><br>GL Renderer :<br>%1<br><br>Extensions non prises en charge :<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9267,10 +9273,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Annuler @@ -9291,7 +9297,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9300,204 +9306,204 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... Vérification de l'intégrité... - - + + Integrity verification succeeded! La vérification de l'intégrité réussi ! - - + + The operation completed successfully. L'opération s'est déroulée avec succès. - - + + Integrity verification failed! La vérification de l'intégrité a échoué ! - + File contents may be corrupt or missing. Le contenu d'un fichier peut être corrompu or manquant. - + Integrity verification couldn't be performed La vérification de l'intégrité n'a pas pu être effectuée - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Installation du firmware annulée, le firmware est peut-être en mauvais état ou corrompu. Impossible de vérifier la validité du contenu du fichier. - + Select Dumped Keys Location Sélectionner Emplacement Clés Extraites - + Decryption Keys install succeeded Installation des clés de décryptage avec succès - + Decryption Keys install failed Installation des clés de décryptage échoué - + Orphaned Profiles Detected! Profils orphelins détectés ! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> DES CHOSES GRAVES INATTENDUES PEUVENT SURVENIR SI VOUS NE LISEZ PAS CECI !<br>Eden a détecté les répertoires de sauvegarde suivants sans profil associé :<br>%1<br><br>Les profils suivants sont valides :<br>%2<br><br>Cliquez sur « OK » pour ouvrir votre dossier de sauvegarde et corriger vos profils.<br>Astuce : copiez le contenu du dossier le plus volumineux ou le plus récemment modifié ailleurs, supprimez tous les profils orphelins et déplacez le contenu copié vers le profil correct.<br><br>Toujours confus ? Consultez la <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>page d’aide</a>.<br> - + Really clear data? Vraiment effacer les données ? - + Important data may be lost! Des données importantes peuvent être perdues ! - + Are you REALLY sure? Êtes-vous VRAIMENT sûr ? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. Une fois supprimées, vos données NE POURRONT PAS être récupérées ! Ne faites cela que si vous êtes sûr à 100 % de vouloir supprimer ces données. - + Clearing... Suppression en cours… - + Select Export Location Sélectionner l’emplacement d’exportation - + %1.zip %1.zip - - + + Zipped Archives (*.zip) Archives compressées (*.zip) - + Exporting data. This may take a while... Exportation des données en cours. Cela peut prendre un certain temps… - + Exporting Exportation en cours… - + Exported Successfully Exportation réussie - + Data was exported successfully. Les données ont été exportées avec succès. - + Export Cancelled Exportation annulée - + Export was cancelled by the user. L’exportation a été annulée par l’utilisateur. - + Export Failed Échec de l’exportation - + Ensure you have write permissions on the targeted directory and try again. Assurez-vous d’avoir les permissions d’écriture sur le répertoire ciblé et réessayez. - + Select Import Location Sélectionner l’emplacement d’importation - + Import Warning Avertissement d’importation - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Toutes les données précédentes de ce répertoire seront supprimées. Êtes-vous sûr de vouloir continuer ? - + Importing data. This may take a while... Importation des données en cours. Cela peut prendre un certain temps… - + Importing Importation en cours… - + Imported Successfully Importation réussie - + Data was imported successfully. Les données ont été importées avec succès. - + Import Cancelled Importation annulée - + Import was cancelled by the user. L’importation a été annulée par l’utilisateur. - + Import Failed Échec de l’importation - + Ensure you have read permissions on the targeted directory and try again. Assurez-vous d’avoir les permissions de lecture sur le répertoire ciblé et réessayez. @@ -9857,18 +9863,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/hu.ts b/dist/languages/hu.ts index d63f149eef..8175e6e446 100644 --- a/dist/languages/hu.ts +++ b/dist/languages/hu.ts @@ -5955,47 +5955,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL nem elérhető! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! Hiba történt az OpenGL inicializálása során! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Lehetséges, hogy a GPU-d nem támogatja az OpenGL-t, vagy nem a legfrissebb grafikus illesztőprogram van telepítve. - + Error while initializing OpenGL 4.6! Hiba történt az OpenGL 4.6 inicializálása során! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Lehetséges, hogy a GPU-d nem támogatja az OpenGL 4.6-ot, vagy nem a legfrissebb grafikus illesztőprogram van telepítve.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Előfordulhat, hogy a GPU-d nem támogat egy vagy több szükséges OpenGL kiterjesztést. Győződj meg róla, hogy a legújabb videokártya-illesztőprogramot használod.<br><br>GL Renderer:<br>%1<br><br>Nem támogatott kiterjesztések:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9179,10 +9185,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9203,210 +9209,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9763,18 +9769,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/id.ts b/dist/languages/id.ts index 3fd423e6f6..78cb499ac4 100644 --- a/dist/languages/id.ts +++ b/dist/languages/id.ts @@ -5991,47 +5991,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL tidak tersedia! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! Terjadi kesalahan menginisialisasi OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. VGA anda mungkin tidak mendukung OpenGL, atau anda tidak memiliki pemacu piranti (driver) grafis terbaharu. - + Error while initializing OpenGL 4.6! Terjadi kesalahan menginisialisasi OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 VGA anda mungkin tidak mendukung OpenGL 4.6, atau anda tidak memiliki pemacu piranti (driver) grafis terbaharu.<br><br>Pemuat GL:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 VGA anda mungkin tidak mendukung satu atau lebih ekstensi OpenGL. Mohon pastikan bahwa anda memiliki pemacu piranti (driver) grafis terbaharu.<br><br>Pemuat GL:<br>%1<br><br>Ekstensi yang tidak didukung:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9208,10 +9214,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9232,210 +9238,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9792,18 +9798,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/it.ts b/dist/languages/it.ts index 881fce570f..bf25a6feb8 100644 --- a/dist/languages/it.ts +++ b/dist/languages/it.ts @@ -6052,47 +6052,53 @@ Vai su Configura -> Sistema -> Rete e selezionane una. GRenderWindow - - + + OpenGL not available! OpenGL non disponibile! - + OpenGL shared contexts are not supported. Gli shared context di OpenGL non sono supportati. - + Eden has not been compiled with OpenGL support. Eden non è stato compilato con il supporto a OpenGL. - - + + + Error while initializing OpenGL! Errore durante l'inizializzazione di OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. La tua GPU potrebbe non supportare OpenGL, o non hai installato l'ultima versione dei driver video. - + Error while initializing OpenGL 4.6! Errore durante l'inizializzazione di OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 La tua GPU potrebbe non supportare OpenGL 4.6, o non hai installato l'ultima versione dei driver video.<br><br>Renderer GL:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 La tua GPU potrebbe non supportare una o più estensioni OpenGL richieste. Assicurati di aver installato i driver video più recenti.<br><br>Renderer GL:<br>%1<br><br>Estensioni non supportate:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9295,10 +9301,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Annulla @@ -9319,7 +9325,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9328,204 +9334,204 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... Verifica dell'integrità in corso... - - + + Integrity verification succeeded! Verifica dell'integrità riuscita! - - + + The operation completed successfully. L'operazione è stata completata con successo. - - + + Integrity verification failed! Verifica dell'integrità fallita! - + File contents may be corrupt or missing. I contenuti dei file potrebbero essere corrotti o mancanti. - + Integrity verification couldn't be performed Impossibile effettuare la verifica dell'integrità - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Installazione del firmware annullata, il firmware potrebbe essere corrotto o in cattivo stato. Non è stato possibile controllare la validità dei contenuti dei file. - + Select Dumped Keys Location Seleziona il percorso delle chiavi estratte - + Decryption Keys install succeeded Installazione delle chiavi di crittografia riuscita - + Decryption Keys install failed Installazione delle chiavi di crittografia fallita - + Orphaned Profiles Detected! Rilevati profili scollegati! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? Eliminare i dati? - + Important data may be lost! Potresti perdere dei dati importanti! - + Are you REALLY sure? Sei DAVVERO sicuro? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. Una volta eliminati, i tuoi dati NON potranno essere ripristinati! Procedi solo se sei assolutamente sicuro di voler eliminare questi dati. - + Clearing... Eliminazione in corso... - + Select Export Location Scegli dove esportare i dati - + %1.zip %1.zip - - + + Zipped Archives (*.zip) Archivi compressi (*.zip) - + Exporting data. This may take a while... Esportazione dei dati in corso. Potrebbe richiedere un po' di tempo... - + Exporting Esportazione in corso - + Exported Successfully Esportazione completata - + Data was exported successfully. I dati sono stati esportati con successo. - + Export Cancelled Esportazione annullata - + Export was cancelled by the user. L'esportazione è stata annullata dall'utente. - + Export Failed Esportazione fallita - + Ensure you have write permissions on the targeted directory and try again. Assicurati di disporre dei permessi di scrittura nella cartella selezionata e poi riprova. - + Select Import Location Seleziona il file da importare - + Import Warning Attenzione - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Tutti i dati già presenti in questa cartella verranno eliminati. Sei sicuro di voler procedere? - + Importing data. This may take a while... Importazione dei dati in corso. Potrebbe richiedere un po' di tempo... - + Importing Importazione in corso - + Imported Successfully Importazione completata - + Data was imported successfully. I dati sono stati importati con successo. - + Import Cancelled Importazione annullata - + Import was cancelled by the user. L'importazione è stata annullata dall'utente. - + Import Failed Importazione fallita - + Ensure you have read permissions on the targeted directory and try again. Assicurati di disporre dei permessi di lettura nella cartella selezionata e poi riprova. @@ -9889,18 +9895,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed Estrazione mod fallita - + Failed to create temporary directory %1 Impossibile creare la cartella temporanea %1 - + Zip file %1 is empty Il file ZIP %1 è vuoto diff --git a/dist/languages/ja_JP.ts b/dist/languages/ja_JP.ts index 1c743b6f62..d8d4723512 100644 --- a/dist/languages/ja_JP.ts +++ b/dist/languages/ja_JP.ts @@ -5978,47 +5978,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGLは使用できません! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! OpenGL初期化エラー - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. GPUがOpenGLをサポートしていないか、グラフィックスドライバーが最新ではありません。 - + Error while initializing OpenGL 4.6! OpenGL4.6初期化エラー! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 GPUがOpenGL4.6をサポートしていないか、グラフィックスドライバーが最新ではありません。<br><br>GL レンダラ:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 GPUが1つ以上の必要なOpenGL拡張機能をサポートしていない可能性があります。最新のグラフィックドライバを使用していることを確認してください。<br><br>GL レンダラ:<br>%1<br><br>サポートされていない拡張機能:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9202,10 +9208,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9226,210 +9232,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9786,18 +9792,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/ko_KR.ts b/dist/languages/ko_KR.ts index c9fa32ccac..da2f0dd245 100644 --- a/dist/languages/ko_KR.ts +++ b/dist/languages/ko_KR.ts @@ -4,12 +4,12 @@ About Eden - + Eden에 대해 <html><head/><body><p><span style=" font-size:28pt;">Eden</span></p></body></html> - + <html><head/><body><p><span style=" font-size:28pt;">Eden</span></p></body></html> @@ -26,17 +26,24 @@ li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } </style></head><body style=" font-family:'Noto Sans'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">Eden is an experimental open-source emulator for the Nintendo Switch licensed under GPLv3.0+ which is based on the yuzu emulator which ended development back in March 2024. <br /><br />This software should not be used to play games you have not legally obtained.</span></p></body></html> - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> +p, li { white-space: pre-wrap; } +hr { height: 1px; border-width: 0; } +li.unchecked::marker { content: "\2610"; } +li.checked::marker { content: "\2612"; } +</style></head><body style=" font-family:'Noto Sans'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">Eden은 GPLv3.0+ 라이선스를 받은 실험적인 오픈 소스 닌텐도 스위치 에뮬레이터로, 2024년 3월에 개발이 종료된 yuzu 에뮬레이터를 기반으로 합니다. <br /><br />이 소프트웨어는 법적으로 획득하지 않은 게임을 플레이하는 데 사용되어서는 안 됩니다.</span></p></body></html> <html><head/><body><p><a href="https://eden-emulator.github.io/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://git.eden-emu.dev"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/activity/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://discord.gg/HstXbPch7X"><span style=" text-decoration: underline; color:#039be5;">Discord</span></a> | <a href="https://stt.gg/qKgFEAbH"><span style=" text-decoration: underline; color:#039be5;">Stoat</span></a> | <a href="https://nitter.poast.org/edenemuofficial"><span style=" text-decoration: underline; color:#039be5;">Twitter</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> - + <html><head/><body><p><a href="https://eden-emulator.github.io/"><span style=" text-decoration: underline; color:#039be5;">웹사이트</span></a> | <a href="https://git.eden-emu.dev"><span style=" text-decoration: underline; color:#039be5;">소스 코드</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/activity/contributors"><span style=" text-decoration: underline; color:#039be5;">기여자</span></a> | <a href="https://discord.gg/HstXbPch7X"><span style=" text-decoration: underline; color:#039be5;">디스코드</span></a> | <a href="https://stt.gg/qKgFEAbH"><span style=" text-decoration: underline; color:#039be5;">Stoat</span></a> | <a href="https://nitter.poast.org/edenemuofficial"><span style=" text-decoration: underline; color:#039be5;">트위터</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt"><span style=" text-decoration: underline; color:#039be5;">라이선스</span></a></p></body></html> <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; is a trademark of Nintendo. Eden is not affiliated with Nintendo in any way.</span></p></body></html> - + <html><head/><body><p><span style=" font-size:7pt;">&quot;닌텐도 스위치&quot;는 닌텐도의 상표이며, Eden은 닌텐도와 어떤 방식으로도 제휴되어 있지 않습니다.</span></p></body></html> @@ -138,7 +145,7 @@ li.checked::marker { content: "\2612"; } Kick - 추방 + 강퇴 @@ -148,12 +155,12 @@ li.checked::marker { content: "\2612"; } Kick Player - 추방된 플레이어 + 강퇴된 플레이어 Are you sure you would like to <b>kick</b> %1? - 정말로 %1을 <b>추방</b>하겠습니까?? + 정말 %1을 <b>강퇴</b>시키겠습니까? @@ -165,9 +172,9 @@ li.checked::marker { content: "\2612"; } Are you sure you would like to <b>kick and ban</b> %1? This would ban both their forum username and their IP address. - %1을 <b>추방</b>하겠습니까? + 정말 %1을 <b>강퇴 및 차단</b>하겠습니까? -이렇게 하면 포럼 사용자 이름과 IP 주소가 모두 금지됩니다. +이렇게 하면 포럼 사용자 이름과 IP 주소 모두 차단됩니다. @@ -232,7 +239,7 @@ This would ban both their forum username and their IP address. <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - + <html><head/><body><p><span style=" font-size:10pt;">만약 </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden 호환성 목록</span></a><span style=" font-size:10pt;">에 시험 사례를 제출하기로 선택하면, 다음과 같은 정보가 수집되어 사이트에 표시됩니다:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">하드웨어 정보(CPU/GPU/운영 체제)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">사용 중인 eden 버전</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">연결된 eden 계정</li></ul></body></html> @@ -297,12 +304,12 @@ This would ban both their forum username and their IP address. Major The game has major graphical errors - 주요 게임에 주요 그래픽 오류가 있습니다 + 심각 게임에 심각한 그래픽 오류가 있습니다 Minor The game has minor graphical errors - 마이너 게임에 경미한 그래픽 오류가 있습니다 + 경미 게임에 경미한 그래픽 오류가 있습니다 @@ -317,12 +324,12 @@ This would ban both their forum username and their IP address. Major The game has major audio errors - 메이저 게임에 주요 오디오 오류가 있습니다 + 심각 게임에 심각한 오디오 오류가 있습니다 Minor The game has minor audio errors - 마이너 게임에 사소한 오디오 오류가 있습니다 + 경미 게임에 경미한 오디오 오류가 있습니다 @@ -370,17 +377,17 @@ This would ban both their forum username and their IP address. Amiibo editor - + Amiibo 편집기 Controller configuration - + 컨트롤러 구성 Data erase - + 데이터 삭제 @@ -390,12 +397,12 @@ This would ban both their forum username and their IP address. Net connect - + 망 연결 Player select - + 플레이어 선택 @@ -405,52 +412,52 @@ This would ban both their forum username and their IP address. Mii Edit - + Mii 편집 Online web - + 온라인 웹 Shop - + 숍 Photo viewer - + 사진 뷰어 Offline web - + 오프라인 웹 Login share - + 로그인 공유 Wifi web auth - + Wi-Fi 웹 인증 My page - + 마이페이지 Enable Overlay Applet - + 오버레이 애플릿 활성화 Enables Horizon's built-in overlay applet. Press and hold the home button for 1 second to show it. - + Horizon의 내장 오버레이 애플릿을 활성화합니다. HOME 버튼을 1초 동안 길게 누르면 애플릿이 나타납니다. @@ -470,7 +477,7 @@ This would ban both their forum username and their IP address. Mute audio - + 오디오 음소거 @@ -480,7 +487,7 @@ This would ban both their forum username and their IP address. Mute audio when in background - 백그라운드에서 오디오 음소거 + 백그라운드에서 실행될 때 오디오 음소거 @@ -491,61 +498,66 @@ This would ban both their forum username and their IP address. This option increases CPU emulation thread use from 1 to the maximum of 4. This is mainly a debug option and shouldn't be disabled. - + 이 옵션은 CPU 에뮬레이션 스레드 사용량을 1에서 최대 4까지 증가시킵니다. +이 옵션은 주로 디버그용이므로 비활성화해서는 안 됩니다. Memory Layout - + 메모리 레이아웃 Increases the amount of emulated RAM. Doesn't affect performance/stability but may allow HD texture mods to load. - + 에뮬레이트된 RAM의 용량을 증가시킵니다. +성능/안정성에는 영향을 미치지 않지만 HD 텍스처 모드가 로드될 수 있습니다. Limit Speed Percent - 속도 퍼센트 제한 + 속도 제한 비율 Controls the game's maximum rendering speed, but it's up to each game if it runs faster or not. 200% for a 30 FPS game is 60 FPS, and for a 60 FPS game it will be 120 FPS. Disabling it means unlocking the framerate to the maximum your PC can reach. - + 게임의 최대 렌더링 속도를 제어하지만, 실제로 더 빠르게 실행될지는 각 게임에 따라 다릅니다. +30 FPS 게임에서 200%로 설정하면 60 FPS가 되고, 60 FPS 게임에서는 120 FPS가 됩니다. +이 옵션을 비활성화하면 PC가 도달할 수 있는 최대 프레임률까지 프레임 속도 제한이 해제됩니다. Turbo Speed - + 터보 속도 When the Turbo Speed hotkey is pressed, the speed will be limited to this percentage. - + 터보 속도 단축키를 누르면 속도가 이 비율로 제한됩니다. Slow Speed - + 느린 속도 When the Slow Speed hotkey is pressed, the speed will be limited to this percentage. - + 느린 속도 단축키를 누르면 속도가 이 비율로 제한됩니다. Synchronize Core Speed - + 코어 속도 동기화 Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS without affecting game speed (animations, physics, etc.). Can help reduce stuttering at lower framerates. - + CPU 코어 속도를 게임의 최대 렌더링 속도와 동기화하여 게임 속도(애니메이션, 물리 등)에 영향을 주지 않고 FPS를 향상시킵니다. +낮은 프레임률에서 발생하는 끊김 현상을 줄이는 데 도움될 수 있습니다. @@ -555,56 +567,59 @@ Can help reduce stuttering at lower framerates. Change the accuracy of the emulated CPU (for debugging only). - + 에뮬레이트된 CPU의 정확도를 변경합니다(디버깅용으로만 사용). Backend: - + 백엔드: CPU Overclock - + CPU 오버클럭 Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - + 에뮬레이트된 CPU를 오버클럭하여 일부 FPS 제한기를 제거합니다. CPU가 약하면 성능이 저하될 수 있으며 특정 게임이 부정확하게 작동할 수 있습니다. +스위치의 최고 네이티브 클럭으로 실행하려면 부스트(1700MHz)를 사용하고, 2배 클럭으로 실행하려면 고속(2000MHz)을 사용하세요. Custom CPU Ticks - + 사용자 지정 CPU 틱 Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + CPU 틱 값을 사용자 지정으로 설정합니다. 값이 높을수록 성능이 향상될 수 있지만 교착 상태가 발생할 수 있습니다. 77~21000 범위의 값을 권장합니다. Enable Host MMU Emulation (fastmem) - + 호스트 MMU 에뮬레이션 사용 (빠른 메모리) This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. - + 이 최적화는 게스트 프로그램의 메모리 접근 속도를 높입니다. +이 옵션을 활성화하면 게스트 메모리 읽기/쓰기가 메모리에서 직접 수행되고 호스트의 MMU를 사용하게 됩니다. +이 옵션을 비활성화하면 모든 메모리 접근에 소프트웨어 MMU 에뮬레이션을 사용하도록 강제됩니다. Unfuse FMA (improve performance on CPUs without FMA) - FMA 분리 (FMA를 지원하지 않는 CPU에서의 성능을 향상시킵니다) + FMA 분리 (FMA를 지원하지 않는 CPU의 성능 향상) This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. - + 이 옵션은 네이티브 FMA를 지원하지 않는 CPU의 fused-multiply-add 명령어 정확도를 낮춰 속도를 향상시킵니다. @@ -614,7 +629,7 @@ Disabling this forces all memory accesses to use Software MMU Emulation. This option improves the speed of some approximate floating-point functions by using less accurate native approximations. - + 이 옵션은 정확도가 낮은 네이티브 근사값을 사용하여 일부 근사 부동 소수점 함수의 속도를 향상시킵니다. @@ -2077,12 +2092,12 @@ When a program attempts to open the controller applet, it is immediately closed. Boost (1700MHz) - + 부스트(1700MHz) Fast (2000MHz) - + 고속(2000MHz) @@ -5976,47 +5991,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL을 사용할 수 없습니다! - + OpenGL shared contexts are not supported. OpenGL 공유 컨텍스트는 지원되지 않습니다. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! OpenGL을 초기화하는 동안 오류가 발생했습니다! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. 사용하시는 GPU가 OpenGL을 지원하지 않거나, 최신 그래픽 드라이버가 설치되어 있지 않습니다. - + Error while initializing OpenGL 4.6! OpenGL 4.6 초기화 중 오류 발생! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 사용하시는 GPU가 OpenGL 4.6을 지원하지 않거나 최신 그래픽 드라이버가 설치되어 있지 않습니다. <br><br>GL 렌더링 장치:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 사용하시는 GPU가 1개 이상의 OpenGL 확장 기능을 지원하지 않습니다. 최신 그래픽 드라이버가 설치되어 있는지 확인하세요. <br><br>GL 렌더링 장치:<br>%1<br><br>지원하지 않는 확장 기능:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9200,10 +9221,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9224,210 +9245,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9784,18 +9805,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/nb.ts b/dist/languages/nb.ts index bdfc81b0c9..09e4343217 100644 --- a/dist/languages/nb.ts +++ b/dist/languages/nb.ts @@ -5975,47 +5975,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL ikke tilgjengelig! - + OpenGL shared contexts are not supported. Delte OpenGL-kontekster støttes ikke. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! Feil under initialisering av OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Det kan hende at GPU-en din ikke støtter OpenGL, eller at du ikke har den nyeste grafikkdriveren. - + Error while initializing OpenGL 4.6! Feil under initialisering av OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Det kan hende at GPU-en din ikke støtter OpenGL 4.6, eller at du ikke har den nyeste grafikkdriveren.<br><br>GL-renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Det kan hende at GPU-en din ikke støtter én eller flere nødvendige OpenGL-utvidelser. Vennligst sørg for at du har den nyeste grafikkdriveren.<br><br>GL-renderer: <br>%1<br><br>Ikke-støttede utvidelser:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9201,10 +9207,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Avbryt @@ -9225,210 +9231,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? Er du HELT sikker? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip %1.zip - - + + Zipped Archives (*.zip) Zippede arkiver (*.zip) - + Exporting data. This may take a while... - + Exporting Eksporterer - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed Eksportering mislyktes - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing Importerer - + Imported Successfully Importeringen var vellykket - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed Importering mislyktes - + Ensure you have read permissions on the targeted directory and try again. @@ -9785,18 +9791,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty «%1»-zip-filen er tom diff --git a/dist/languages/nl.ts b/dist/languages/nl.ts index f477915014..f4cd04df28 100644 --- a/dist/languages/nl.ts +++ b/dist/languages/nl.ts @@ -5973,47 +5973,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL niet beschikbaar! - + OpenGL shared contexts are not supported. OpenGL gedeelde contexten worden niet ondersteund. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! Fout tijdens het initialiseren van OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Je GPU ondersteunt mogelijk geen OpenGL, of je hebt niet de laatste grafische stuurprogramma. - + Error while initializing OpenGL 4.6! Fout tijdens het initialiseren van OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Je GPU ondersteunt mogelijk OpenGL 4.6 niet, of je hebt niet het laatste grafische stuurprogramma.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Je GPU ondersteunt mogelijk een of meer vereiste OpenGL-extensies niet. Zorg ervoor dat je het laatste grafische stuurprogramma hebt.<br><br>GL Renderer:<br>%1<br><br>Ondersteunde extensies:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9197,10 +9203,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9221,210 +9227,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9781,18 +9787,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/pl.ts b/dist/languages/pl.ts index a107dcffdf..d65dcc0065 100644 --- a/dist/languages/pl.ts +++ b/dist/languages/pl.ts @@ -6041,47 +6041,53 @@ Przejdź do sekcji Konfiguracja -> System -> Sieć i dokonaj wyboru. GRenderWindow - - + + OpenGL not available! OpenGL niedostępny! - + OpenGL shared contexts are not supported. Współdzielone konteksty OpenGL nie są obsługiwane. - + Eden has not been compiled with OpenGL support. Eden nie został skompilowany z obsługą OpenGL. - - + + + Error while initializing OpenGL! Błąd podczas inicjowania OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Twoja karta graficzna może nie obsługiwać OpenGL lub nie masz najnowszych sterowników karty graficznej. - + Error while initializing OpenGL 4.6! Błąd podczas inicjowania OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Twoja karta graficzna może nie obsługiwać OpenGL 4.6 lub nie masz najnowszych sterowników karty graficznej.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Twoja karta graficzna może nie obsługiwać co najmniej jednego wymaganego rozszerzenia OpenGL. Upewnij się, że masz najnowsze sterowniki karty graficznej<br><br>GL Renderer:<br>%1<br><br>Nieobsługiwane rozszerzenia:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9300,10 +9306,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Anuluj @@ -9324,7 +9330,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9332,204 +9338,204 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... Weryfikacja integralności... - - + + Integrity verification succeeded! Weryfikacja integralności zakończona sukcesem! - - + + The operation completed successfully. Operacja zakończona pomyślnie. - - + + Integrity verification failed! Weryfikacja integralności nie powiodła się! - + File contents may be corrupt or missing. Zawartość pliku może być uszkodzona lub brakująca. - + Integrity verification couldn't be performed Nie można było przeprowadzić weryfikacji integralności - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Instalacja firmware’u została anulowana — firmware może być w złym stanie lub uszkodzony. Nie udało się sprawdzić poprawności zawartości plików. - + Select Dumped Keys Location Wybierz lokalizację zrzutu kluczy - + Decryption Keys install succeeded Instalacja kluczy deszyfrujących powiodła się - + Decryption Keys install failed Instalacja kluczy deszyfrujących nie powiodła się - + Orphaned Profiles Detected! Wykryto osierocone profile! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> MOGĄ WYSTĄPIĆ NIEOCZEKIWANE PROBLEMY, JEŚLI TEGO NIE PRZECZYTASZ!<br>Eden wykrył następujące katalogi zapisów bez przypisanego profilu:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Kliknij „OK”, aby otworzyć folder zapisów i naprawić profile.<br>Wskazówka: skopiuj zawartość największego lub ostatnio modyfikowanego folderu w inne miejsce, usuń wszystkie osierocone profile, a następnie przenieś skopiowaną zawartość do właściwego profilu.<br><br>Nadal masz wątpliwości? Zobacz<a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>tronę pomocy</a>.<br> - + Really clear data? Na pewno wyczyścić dane? - + Important data may be lost! Ważne dane mogą zostać utracone! - + Are you REALLY sure? Czy NA PEWNO chcesz to zrobić? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. Po usunięciu Twoje dane NIE WRÓCĄ! Wykonaj to tylko, jeśli w 100% chcesz usunąć te dane. - + Clearing... Czyszczenie… - + Select Export Location Wybierz lokalizację eksportu - + %1.zip %1.zip - - + + Zipped Archives (*.zip) Archiwa ZIP (.zip) - + Exporting data. This may take a while... Eksportowanie danych. To może chwilę potrwać… - + Exporting Eksportowanie - + Exported Successfully Wyeksportowano pomyślnie - + Data was exported successfully. Dane zostały pomyślnie wyeksportowane. - + Export Cancelled Eksport anulowany - + Export was cancelled by the user. Eksport został anulowany przez użytkownika. - + Export Failed Eksport nie powiódł się - + Ensure you have write permissions on the targeted directory and try again. Upewnij się, że masz uprawnienia zapisu do docelowego katalogu i spróbuj ponownie. - + Select Import Location Wybierz lokalizację importu - + Import Warning Ostrzeżenie dotyczące importu - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Wszystkie dotychczasowe dane w tym katalogu zostaną usunięte. Czy na pewno chcesz kontynuować? - + Importing data. This may take a while... Importowanie danych. To może chwilę potrwać… - + Importing Importowanie - + Imported Successfully Zaimportowano pomyślnie - + Data was imported successfully. Dane zostały pomyślnie zaimportowane. - + Import Cancelled Import anulowany - + Import was cancelled by the user. Import został anulowany przez użytkownika. - + Import Failed Import nie powiódł się - + Ensure you have read permissions on the targeted directory and try again. Upewnij się, że masz uprawnienia odczytu do docelowego katalogu i spróbuj ponownie. @@ -9891,18 +9897,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/pt_BR.ts b/dist/languages/pt_BR.ts index 440421e998..26cfd9d0dc 100644 --- a/dist/languages/pt_BR.ts +++ b/dist/languages/pt_BR.ts @@ -119,12 +119,12 @@ li.checked::marker { content: "\2612"; } %1 has been banned - %1 foi banido(a) + %1 foi banido %1 has been unbanned - %1 foi desbanido(a) + %1 foi desbanido @@ -782,13 +782,14 @@ Desabiltar essa opção só serve para propósitos de depuração. Use asynchronous GPU emulation - + Usar emulação assíncrona de GPU Uses an extra CPU thread for rendering. This option should always remain enabled. - + Usa uma thread de CPU extra para renderização. +Esta opção deve estar sempre habilitada. @@ -2801,32 +2802,32 @@ Quando um programa tenta abrir o applet, ele é imediatamente fechado. Bitmask for quick development toggles - + Bitmask para rápida alternativa de desenvolvimento Set debug knobs (bitmask) - + Definir knobs de depuração (bitmask) 16-bit debug knob set for quick development toggles - + Knob de depuração 16-bit definido para rápida alternativa de desenvolvimento (bitmask) - + (bitmask) Debug Knobs: - + Knobs de depuração: Unit Serial: - + Número de série: @@ -2841,7 +2842,7 @@ Quando um programa tenta abrir o applet, ele é imediatamente fechado. Flush log output on each line - + Liberar o log a cada linha @@ -2856,7 +2857,7 @@ Quando um programa tenta abrir o applet, ele é imediatamente fechado. Censor username in logs - + Censurar nome de usuário nos logs @@ -2960,7 +2961,7 @@ Quando um programa tenta abrir o applet, ele é imediatamente fechado. GraphicsExtra - + Gráficos Extra @@ -3040,7 +3041,7 @@ Quando um programa tenta abrir o applet, ele é imediatamente fechado. Save Data - + Salvar dados @@ -3116,7 +3117,7 @@ Quando um programa tenta abrir o applet, ele é imediatamente fechado. Select Save Data Directory... - + Selecionar o diretório de salvamento @@ -3136,22 +3137,22 @@ Quando um programa tenta abrir o applet, ele é imediatamente fechado. Save Data Directory - + Salvar diretório de salvamento Choose an action for the save data directory: - + Escolha uma ação para o diretório de salvamento: Set Custom Path - + Definir caminho personalizado Reset to NAND - + Resetar a NAND @@ -3162,7 +3163,13 @@ New: %2 Would you like to migrate saves from the old location? WARNING: This will overwrite any conflicting saves in the new location! - + Salvamento existe tanto no antigo quanto nas novas locações. + +Antigo: %1 +Novo: %2 + +Você gostaria de migrar seu salvamento do antigo local? +ATENÇÃO: Isso irá sobrescrever qualquer salvamento conflitante no novo local! @@ -3170,51 +3177,57 @@ WARNING: This will overwrite any conflicting saves in the new location! From: %1 To: %2 - + Você gostaria de migrar seus salvamentos para o novo local? + +De: %1 +Para: %2 Migrate Save Data - + Migrar salvamento Migrating save data... - + Migrando salvamento... Cancel - + Cancelar Migration Failed - + Migração Falhou Failed to create destination directory. - + Falha ao criar diretório de destino. Failed to migrate save data: %1 - + Falha ao migrar salvamento: +1% Migration Complete - + Migração Completa Save data has been migrated successfully. Would you like to delete the old save data? - + Salvamento foi migrado com sucesso + +Gostaria de deletar o salvamento antigo? @@ -3363,27 +3376,27 @@ Would you like to delete the old save data? Form - + Formar Extras - + Extras Hacks - + Hacks Changing these options from their default may cause issues. Novitii cavete! - + Alterar essas opções de seus padrões pode causar problemas. È una trappola, Bino! Vulkan Extensions - + Extensões Vulkan @@ -3394,7 +3407,7 @@ Would you like to delete the old save data? Extended Dynamic State is disabled on macOS due to MoltenVK compatibility issues that cause black screens. - + Estado Dinâmico Extendido é desativado no MacOS, devido a problemas de compatibilidade com o MoltenVK, que causa telas pretas. @@ -4709,7 +4722,7 @@ Os valores atuais são %1% e %2% respectivamente. Enable Airplane Mode - + Habilitar Modo Avião @@ -4792,7 +4805,7 @@ Os valores atuais são %1% e %2% respectivamente. Ext. Graphics - + Gráficos Ext. @@ -4807,7 +4820,7 @@ Os valores atuais são %1% e %2% respectivamente. Network - + Rede @@ -4835,12 +4848,12 @@ Os valores atuais são %1% e %2% respectivamente. Import Mod from ZIP - + Importar Mod a partir de ZIP Import Mod from Folder - + Importar Mod a partir da Pasta @@ -4855,87 +4868,92 @@ Os valores atuais são %1% e %2% respectivamente. Mod Install Succeeded - + Instalação de Mod Concluída Successfully installed all mods. - + Todos os Mods foram instalados com sucesso. Mod Install Failed - + Instalação de Mod Falhou Failed to install the following mods: %1 Check the log for details. - + Falha ao instalar os seguintes Mods: + %1 +Cheque o log para mais detalhes. Mod Folder - + Pasta de Mods Zipped Mod Location - + Local de Mod Empacotado Zipped Archives (*.zip) - + Arquivos Empacotados (*.zip) Invalid Selection - + Seleção inválida Only mods, cheats, and patches can be deleted. To delete NAND-installed updates, right-click the game in the game list and click Remove -> Remove Installed Update. - + Apenas Mods, trapaças e correções podem ser deletadas. +Para deletar atualizações instaladas na NAND, pressione o botão direito no jogo na lista e clique em Remover -> Remova Atualização Instalada. You are about to delete the following installed mods: - + Você está a prestes de deletar os seguintes Mods instalados: + Once deleted, these can NOT be recovered. Are you 100% sure you want to delete them? - + +Uma vez deletado, esses NÃO podem ser recuperados. Você tem 100% de certeza que quer deletar eles? Delete add-on(s)? - + Deletar add-on(s)? Successfully deleted - + Deletado com sucesso Successfully deleted all selected mods. - + Todos os Mods selecionados foram deletados com sucesso. &Delete - + &Deletar &Open in File Manager - + &Abrir Gerenciador de Arquivos @@ -5021,27 +5039,27 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t Error saving user image - + Erro ao salvar imagem de usuário Unable to save image to file - + Incapaz de salvar imagem para arquivo &Edit - + &Editar &Delete - + &Deletar Edit User - + Editar Usuário @@ -5226,7 +5244,7 @@ UUID: %2 <html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the user handbook.</p></body></html> - + <html><head/><body><p>Lê as entradas do controle a partir de scripts no mesmo formato que o TAS-nx.<br/>Para uma explicação mais detalhada, por favor consulte o manual do usuário.</p></body></html> @@ -5261,7 +5279,7 @@ UUID: %2 Show recording dialog - + Mostrar gravações de diálogo @@ -5394,7 +5412,7 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Warning: The settings in this page affect the inner workings of Eden's emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing. - + Atenção: As configurações desta página podem mudar o funcionamento interno do touchscreen emulado do Eden. Mudar elas pode causar problemas indesejados, como o touchscreen parando de funcionar parcialmente e até mesmo por completo. Você deverá apenas usar essa página se você sabe o que está fazendo. @@ -5690,7 +5708,7 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Eden Web Service - + Serviço Web do Eden @@ -5705,7 +5723,7 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Generate - + Gerar @@ -5727,19 +5745,19 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta All Good Tooltip - + Tudo certo Must be between 4-20 characters Tooltip - + Deve conter entre 4-20 caracteres Must be 48 characters, and lowercase a-z Tooltip - + Deve ser em 48 caracteres, e letras minúsculas de a-z @@ -5760,12 +5778,12 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Data Manager - + Gestor de Dados Deleting ANY data is IRREVERSABLE! - + Deletar QUAISQUER dados é IRREVERSÍVEL! @@ -5775,12 +5793,12 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta User NAND - + NAND de Usuário System NAND - + NAND do Sistema @@ -5790,7 +5808,7 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Saves - + Salvamentos @@ -5798,17 +5816,17 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Form - + Formar Tooltip - + Dica de Ferramenta Open with your system file manager - + Abrir gerenciador de arquivos do sistema @@ -6028,47 +6046,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL não está disponível! - + OpenGL shared contexts are not supported. Shared contexts do OpenGL não são suportados. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! Erro ao inicializar OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. O seu GPU pode não suportar OpenGL, ou não tem os drivers gráficos mais recentes. - + Error while initializing OpenGL 4.6! Erro ao inicializar o OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 - O teu GPU pode não suportar OpenGL 4.6, ou não tem os drivers gráficos mais recentes. + A sua GPU não tem suporte a OpenGL 4.6, talvez você não esteja usando os drivers gráficos mais atuais.<br><br>Renderizador GL:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Sua GPU pode não suportar uma ou mais extensões necessárias do OpenGL. Verifique se você possui a última versão dos drivers gráficos.<br><br>Renderizador GL:<br>%1<br><br>Extensões não suportadas:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9255,10 +9279,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9279,210 +9303,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9841,18 +9865,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 Falha ao criar um diretório temporário %1 - + Zip file %1 is empty diff --git a/dist/languages/pt_PT.ts b/dist/languages/pt_PT.ts index 1fd356238b..a0b4b1fce9 100644 --- a/dist/languages/pt_PT.ts +++ b/dist/languages/pt_PT.ts @@ -5976,47 +5976,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL não está disponível! - + OpenGL shared contexts are not supported. Shared contexts do OpenGL não são suportados. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! Erro ao inicializar OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. O seu GPU pode não suportar OpenGL, ou não tem os drivers gráficos mais recentes. - + Error while initializing OpenGL 4.6! Erro ao inicializar o OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 O teu GPU pode não suportar OpenGL 4.6, ou não tem os drivers gráficos mais recentes. - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Sua GPU pode não suportar uma ou mais extensões necessárias do OpenGL. Verifique se você possui a última versão dos drivers gráficos.<br><br>Renderizador GL:<br>%1<br><br>Extensões não suportadas:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9200,10 +9206,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9224,210 +9230,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9784,18 +9790,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/ru_RU.ts b/dist/languages/ru_RU.ts index 5fb35d0956..571baa18ec 100644 --- a/dist/languages/ru_RU.ts +++ b/dist/languages/ru_RU.ts @@ -3403,7 +3403,7 @@ Would you like to delete the old save data? Vulkan Extensions - Расшиерния Vulcan + Расширения Vulcan @@ -6056,47 +6056,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL не доступен! - + OpenGL shared contexts are not supported. Общие контексты OpenGL не поддерживаются. - + Eden has not been compiled with OpenGL support. Eden не был скомпилирован с поддержкой OpenGL. - - + + + Error while initializing OpenGL! Ошибка при инициализации OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Ваш ГП может не поддерживать OpenGL, или у вас установлен устаревший графический драйвер. - + Error while initializing OpenGL 4.6! Ошибка при инициализации OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Ваш ГП может не поддерживать OpenGL 4.6, или у вас установлен устаревший графический драйвер.<br><br>Рендерер GL:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Ваш ГП может не поддерживать одно или несколько требуемых расширений OpenGL. Пожалуйста, убедитесь в том, что у вас установлен последний графический драйвер.<br><br>Рендерер GL:<br>%1<br><br>Неподдерживаемые расширения:<br>%2 + + + This build doesn't have OpenGL support. + В этой сборке отсутствует поддержка OpenGL. + GameList @@ -9316,10 +9322,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Отмена @@ -9340,7 +9346,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9349,204 +9355,204 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... Проверка целостности... - - + + Integrity verification succeeded! Проверка целостности прошла успешно! - - + + The operation completed successfully. Операция выполнена успешно. - - + + Integrity verification failed! Проверка целостности не удалась! - + File contents may be corrupt or missing. Содержимое файла может быть повреждено или отсутствовать. - + Integrity verification couldn't be performed Проверка целостности не может быть выполнена - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Установка прошивки отменена, прошивка может быть повреждена. Содержимое файла не удалось проверить на корректность. - + Select Dumped Keys Location Выберите местоположение дампнутых ключей - + Decryption Keys install succeeded Установка ключей дешифровки прошла успешно. - + Decryption Keys install failed Ошибка установки ключей дешифровки - + Orphaned Profiles Detected! Обнаружены «сиротские» профили! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> МОГУТ ПРОИЗОЙТИ НЕПРЕДВИДЕННЫЕ ПРОБЛЕМЫ, ЕСЛИ ВЫ НЕ ПРОЧИТАЕТЕ ЭТО!<br>Eden обнаружил следующие папки сохранений без привязанного профиля:<br>%1<br><br>Найдены следующие корректные профили:<br>%2<br><br>Нажмите «OK», чтобы открыть папку сохранений и исправить профили.<br>Совет: скопируйте содержимое самой большой или последней измененной папки в другое место, удалите все «сиротские» профили и переместите скопированные данные в правильный профиль.<br><br>Если есть вопросы, прочитайте <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>справку</a>.<br> - + Really clear data? Действительно очистить данные? - + Important data may be lost! Важные данные могут быть потеряны! - + Are you REALLY sure? Вы уверены? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. Данные будут безвозвратно потеряны после удаления! Делайте это только если вы абсолютно уверены, что хотите их удалить. - + Clearing... Очистка... - + Select Export Location Выберите папку для экспорта - + %1.zip %1.zip - - + + Zipped Archives (*.zip) Архивы ZIP (*.zip) - + Exporting data. This may take a while... Идет экспорт данных. Это может занять некоторое время... - + Exporting Экспорт - + Exported Successfully Экспорт выполнен успешно - + Data was exported successfully. Данные успешно экспортированы. - + Export Cancelled Экспорт отменен - + Export was cancelled by the user. Экспорт был отменен пользователем. - + Export Failed Не удалось экспортировать - + Ensure you have write permissions on the targeted directory and try again. Проверьте, есть ли права на запись в выбранную папку, и повторите попытку. - + Select Import Location Выберите папку для импорта - + Import Warning Предупреждение при импорте - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Все существующие данные в этой папке будут удалены. Вы уверены, что хотите продолжить? - + Importing data. This may take a while... Идет импорт данных. Это может занять некоторое время... - + Importing Импорт - + Imported Successfully Импорт успешно выполнен - + Data was imported successfully. Данные успешно импортированы. - + Import Cancelled Импорт отменен - + Import was cancelled by the user. Импорт был отменен пользователем. - + Import Failed Не удалось импортировать - + Ensure you have read permissions on the targeted directory and try again. Проверьте, есть ли права на чтение в выбранной папке, и повторите попытку. @@ -9910,18 +9916,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed Ошибка извлечения мода - + Failed to create temporary directory %1 Не удалось создать временную папку %1 - + Zip file %1 is empty ZIP-файл %1 пуст diff --git a/dist/languages/sv.ts b/dist/languages/sv.ts index 6a04aacd39..c7a96cc11b 100644 --- a/dist/languages/sv.ts +++ b/dist/languages/sv.ts @@ -6063,47 +6063,53 @@ Gå till Konfigurera -> System -> Nätverk och gör ett val. GRenderWindow - - + + OpenGL not available! OpenGL är inte tillgängligt! - + OpenGL shared contexts are not supported. Delade OpenGL-kontexter stöds inte. - + Eden has not been compiled with OpenGL support. Eden har inte kompilerats med OpenGL-stöd. - - + + + Error while initializing OpenGL! Fel vid initiering av OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Din GPU kanske inte stöder OpenGL, eller så har du inte den senaste grafikdrivrutinen. - + Error while initializing OpenGL 4.6! Fel vid initiering av OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Din GPU kanske inte stöder OpenGL 4.6, eller så har du inte den senaste grafikdrivrutinen.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Din GPU kanske inte stöder ett eller flera av de nödvändiga OpenGL-tilläggen. Se till att du har den senaste grafikdrivrutinen.<br><br>GL Renderer:<br>%1<br><br>Tillägg som inte stöds:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9317,10 +9323,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Avbryt @@ -9341,7 +9347,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9350,204 +9356,204 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... Verifierar integritet... - - + + Integrity verification succeeded! Integritetsverifieringen lyckades! - - + + The operation completed successfully. Operationen slutfördes utan problem. - - + + Integrity verification failed! Integritetsverifieringen misslyckades! - + File contents may be corrupt or missing. Filens innehåll kan vara skadat eller saknas. - + Integrity verification couldn't be performed Integritetsverifiering kunde inte utföras - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Firmwareinstallationen avbruten, firmware kan vara i dåligt skick eller skadad. Filens innehåll kunde inte kontrolleras för giltighet. - + Select Dumped Keys Location Välj plats för dumpade nycklar - + Decryption Keys install succeeded Installation av avkrypteringsnycklar lyckades - + Decryption Keys install failed Installationen av avkrypteringsnycklar misslyckades - + Orphaned Profiles Detected! Föräldralösa profiler upptäcktes! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> OVÄNTADE PROBLEM KAN UPPSTÅ OM DU INTE LÄSER DETTA! <br>Eden har upptäckt följande sparningskataloger utan bifogade profiler:<br>%1<br><br>Följande profiler är giltiga:<br>%2<br><br>Klicka på ”OK” för att öppna din sparningsmapp och fixa dina profiler.<br>Tips: kopiera innehållet i den största eller senast ändrade mappen till en annan plats, ta bort alla övergivna profiler och flytta det kopierade innehållet till den giltiga profilen.<br><br>Fortfarande förvirrad? Se hjälpsidan<a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>.<br> - + Really clear data? Verkligen tömma data? - + Important data may be lost! Viktig data kan gå förlorad! - + Are you REALLY sure? Är du VERKLIGEN säker? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. När dina data har raderats kan de INTE återställas! Gör detta endast om du är 100% säker på att du vill radera dessa data. - + Clearing... Tömmer... - + Select Export Location Välj exportplats - + %1.zip %1.zip - - + + Zipped Archives (*.zip) Zippade arkiv (*.zip) - + Exporting data. This may take a while... Exporterar data. Detta kan ta en stund... - + Exporting Exporterar - + Exported Successfully Exporten lyckades - + Data was exported successfully. Data har exporterats. - + Export Cancelled Exporten avbröts - + Export was cancelled by the user. Exporten avbröts av användaren. - + Export Failed Exporten misslyckades - + Ensure you have write permissions on the targeted directory and try again. Kontrollera att du har skrivbehörighet till den aktuella katalogen och försök igen. - + Select Import Location Välj importplats - + Import Warning Importvarning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Alla tidigare data i denna katalog kommer att raderas. Är du säker på att du vill fortsätta? - + Importing data. This may take a while... Importerar data. Detta kan ta en stund... - + Importing Importerar - + Imported Successfully Importen lyckades - + Data was imported successfully. Data har importerats. - + Import Cancelled Importen avbröts - + Import was cancelled by the user. Importen avbröts av användaren. - + Import Failed Importen misslyckades - + Ensure you have read permissions on the targeted directory and try again. Kontrollera att du har läsbehörighet till den aktuella katalogen och försök igen. @@ -9911,18 +9917,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed Mod-extrahering misslyckades - + Failed to create temporary directory %1 Det gick inte att skapa den tillfälliga katalogen %1 - + Zip file %1 is empty Zip-filen %1 är tom diff --git a/dist/languages/tr_TR.ts b/dist/languages/tr_TR.ts index eec5e5f2af..08dedb182c 100644 --- a/dist/languages/tr_TR.ts +++ b/dist/languages/tr_TR.ts @@ -6004,47 +6004,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL kullanıma uygun değil! - + OpenGL shared contexts are not supported. OpenGL paylaşılan bağlam desteklenmiyor. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! OpenGl başlatılırken bir hata oluştu! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. GPU'nuz OpenGL desteklemiyor veya güncel bir grafik sürücüsüne sahip değilsiniz. - + Error while initializing OpenGL 4.6! OpenGl 4.6 başlatılırken bir hata oluştu! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 GPU'nuz OpenGL 4.6'yı desteklemiyor veya güncel bir grafik sürücüsüne sahip değilsiniz.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 GPU'nuz gereken bir yada daha fazla OpenGL eklentisini desteklemiyor Lütfen güncel bir grafik sürücüsüne sahip olduğunuzdan emin olun.<br><br>GL Renderer:<br>%1<br><br> Desteklenmeyen Eklentiler:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9228,10 +9234,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9252,210 +9258,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9812,18 +9818,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/uk.ts b/dist/languages/uk.ts index cf69e3dac5..d7089147a7 100644 --- a/dist/languages/uk.ts +++ b/dist/languages/uk.ts @@ -6058,47 +6058,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL недоступний! - + OpenGL shared contexts are not supported. Спільні контексти OpenGL не підтримуються. - + Eden has not been compiled with OpenGL support. Eden не скомпільовано з підтримкою OpenGL. - - + + + Error while initializing OpenGL! Помилка під час ініціалізації OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Ваш ГП може не підтримувати OpenGL або у вас встановлено застарілий графічний драйвер. - + Error while initializing OpenGL 4.6! Помилка під час ініціалізації OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Ваш ГП може не підтримувати OpenGL 4.6 або у вас встановлено застарілий графічний драйвер.<br><br>Візуалізатор GL:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Ваш ГП може не підтримувати одне або кілька розширень, необхідних для OpenGL. Переконайтеся, що у вас встановлено останній графічний драйвер.<br><br>Візуалізатор GL:<br>%1<br><br>Непідтримувані розширення:<br>%2 + + + This build doesn't have OpenGL support. + Ця збірка не підтримує OpenGL. + GameList @@ -9318,10 +9324,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Скасувати @@ -9342,7 +9348,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9351,204 +9357,204 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... Перевірка цілісності... - - + + Integrity verification succeeded! Перевірка цілісності успішна! - - + + The operation completed successfully. Операцію успішно завершено. - - + + Integrity verification failed! Не вдалося перевірити цілісність! - + File contents may be corrupt or missing. Файли вмісту можуть бути пошкоджені або відсутні. - + Integrity verification couldn't be performed Неможливо виконати перевірку цілісності - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Встановлення прошивки скасовано. Можливо, прошивка в поганому стані або пошкоджена. Неможливо перевірити на дійсність файли вмісту. - + Select Dumped Keys Location Виберіть розатшування дампу ключів - + Decryption Keys install succeeded Ключі дешифрування успішно встановлено - + Decryption Keys install failed Не вдалося встановити ключі дешифрування - + Orphaned Profiles Detected! Виявлено покинуті профілі! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> ЯКЩО ВИ ЦЕ НЕ ПРОЧИТАЄТЕ, МОЖУТЬ СТАТИСЯ НЕОЧІКУВАНІ ПОГАНІ РЕЧІ!<br>Eden виявив такі теки збережень без прикріпленого профілю:<br>%1<br><br>Є такі дійсні профілі:<br>%2<br><br>Натисніть «ОК», щоб відкрити теку збережень і полагодити свої профілі.<br>Порада: скопіюйте у будь-яке інше місце вміст найбільшої теки, у якій нещодавно були зміни, видаліть профілі, що лишилися та перемістіть скопійований вміст до провильного профілю.<br><br>Досі не розумієте, що робити? Перегляньте <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>сторінку допомоги</a>.<br> - + Really clear data? Дійсно очистити дані? - + Important data may be lost! Може бути втрачено важливі дані! - + Are you REALLY sure? Ви ДІЙСНО впевнені? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. Після видалення ваші дані буде НЕМОЖЛИВО повернути! Виконуйте цю дію, лише якщо ви на 100% упевнені, що хочете видалити ці дані. - + Clearing... Очищення... - + Select Export Location Виберіть розташування для експортування - + %1.zip %1.zip - - + + Zipped Archives (*.zip) Zip-архіви (*.zip) - + Exporting data. This may take a while... Експортування даних. Це може тривати певний час... - + Exporting Експортування - + Exported Successfully Успішно експортовано - + Data was exported successfully. Дані успішно експортовано. - + Export Cancelled Експортування скасовано - + Export was cancelled by the user. Експортування скасовано користувачем. - + Export Failed Не вдалося експортувати - + Ensure you have write permissions on the targeted directory and try again. Запевніться, що у вас є дозволи на записування до вказаної теки й спробуйте знову. - + Select Import Location Виберіть розташування для імпортування - + Import Warning Попередження щодо імпортування - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Усі попередні в цій теці будуть видалені. Ви впевнені, що хочете продовжити? - + Importing data. This may take a while... Імпортування даних. Це може тривати певний час... - + Importing Імпортування - + Imported Successfully Успішно імпортовано - + Data was imported successfully. Дані успішно імпортовано. - + Import Cancelled Імпортування скасовано - + Import was cancelled by the user. Імпортування скасовано користувачем. - + Import Failed Не вдалося імпортувати - + Ensure you have read permissions on the targeted directory and try again. Запевніться, що у вас є дозволи на читання зі вказаної теки й спробуйте знову. @@ -9912,18 +9918,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed Не вдалося видобути мод - + Failed to create temporary directory %1 Не вдалося створити тимчасову теку %1 - + Zip file %1 is empty Zip-файл %1 порожній diff --git a/dist/languages/vi.ts b/dist/languages/vi.ts index fc59c32ef4..e09ec68d7b 100644 --- a/dist/languages/vi.ts +++ b/dist/languages/vi.ts @@ -5976,47 +5976,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL không khả dụng! - + OpenGL shared contexts are not supported. Các ngữ cảnh OpenGL chung không được hỗ trợ. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! Lỗi khi khởi tạo OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. GPU của bạn có thể không hỗ trợ OpenGL, hoặc bạn không có driver đồ hoạ mới nhất. - + Error while initializing OpenGL 4.6! Lỗi khi khởi tạo OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 GPU của bạn có thể không hỗ trợ OpenGL 4.6, hoặc bạn không có driver đồ hoạ mới nhất.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 GPU của bạn có thể không hỗ trợ một hoặc nhiều tiện ích OpenGL cần thiết. Vui lòng đảm bảo bạn có driver đồ hoạ mới nhất.<br><br>GL Renderer:<br>%1<br><br>Tiện ích không hỗ trợ:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9200,10 +9206,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9224,210 +9230,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9784,18 +9790,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/vi_VN.ts b/dist/languages/vi_VN.ts index 62b8839175..4ee726cf82 100644 --- a/dist/languages/vi_VN.ts +++ b/dist/languages/vi_VN.ts @@ -5976,47 +5976,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! Không có sẵn OpenGL! - + OpenGL shared contexts are not supported. Các ngữ cảnh OpenGL chung không được hỗ trợ. - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! Đã xảy ra lỗi khi khởi tạo OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. GPU của bạn có thể không hỗ trợ OpenGL, hoặc bạn không có driver đồ hoạ mới nhất. - + Error while initializing OpenGL 4.6! Lỗi khi khởi tạo OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 GPU của bạn có thể không hỗ trợ OpenGL 4.6, hoặc bạn không có driver đồ hoạ mới nhất.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 GPU của bạn có thể không hỗ trợ một hoặc nhiều tiện ích OpenGL cần thiết. Vui lòng đảm bảo bạn có driver đồ hoạ mới nhất.<br><br>GL Renderer:<br>%1<br><br>Tiện ích không hỗ trợ:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9200,10 +9206,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9224,210 +9230,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9784,18 +9790,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/languages/zh_CN.ts b/dist/languages/zh_CN.ts index e87c276484..bfc28e8926 100644 --- a/dist/languages/zh_CN.ts +++ b/dist/languages/zh_CN.ts @@ -6044,47 +6044,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL 模式不可用! - + OpenGL shared contexts are not supported. 不支持 OpenGL 共享上下文。 - + Eden has not been compiled with OpenGL support. Eden 尚未编译为支持 OpenGL。 - - + + + Error while initializing OpenGL! 初始化 OpenGL 时出错! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. 您的 GPU 可能不支持 OpenGL ,或者您没有安装最新的显卡驱动。 - + Error while initializing OpenGL 4.6! 初始化 OpenGL 4.6 时出错! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 您的 GPU 可能不支持 OpenGL 4.6 ,或者您没有安装最新的显卡驱动。<br><br>GL 渲染器:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 您的 GPU 可能不支持某些必需的 OpenGL 扩展。请确保您已经安装最新的显卡驱动。<br><br>GL 渲染器:<br>%1<br><br>不支持的扩展:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9293,10 +9299,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel 取消 @@ -9317,7 +9323,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9326,204 +9332,204 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... 正在验证完整性... - - + + Integrity verification succeeded! 完整性验证成功! - - + + The operation completed successfully. 操作成功完成。 - - + + Integrity verification failed! 完整性验证失败! - + File contents may be corrupt or missing. 文件内容可能缺失或已损坏。 - + Integrity verification couldn't be performed 无法执行完整性验证 - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. 固件安装失败。固件可能处于异常状态或已损坏,无法验证文件内容的有效性。 - + Select Dumped Keys Location 选择导出的密钥文件位置 - + Decryption Keys install succeeded 密钥文件安装成功 - + Decryption Keys install failed 密钥文件安装失败 - + Orphaned Profiles Detected! 检测到孤立的配置文件! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> 如果您不阅读此内容,可能会发生意想不到的糟糕情况!<br>Eden 检测到以下存档目录没有附加的配置文件:<br>%1<br><br>下列配置是有效的:<br>%2<br><br>点击“确定”以打开您的存档文件夹并修复配置文件。<br>提示: 将最大或最近修改的文件夹内容复制到其他地方,删除所有孤立的配置文件,然后将复制的内容移到正确的配置文件中。<br><br>还是感到疑惑? 请查看 <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>帮助页</a>。<br> - + Really clear data? 确实要清除数据吗? - + Important data may be lost! 可能会丢失重要的数据! - + Are you REALLY sure? 您真的确定吗? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. 在删除后,您将无法找回数据! 仅在您 100% 确认要删除此数据时才这样做。 - + Clearing... 正在清除... - + Select Export Location 选择导出位置 - + %1.zip %1.zip - - + + Zipped Archives (*.zip) 压缩档案 (*.zip) - + Exporting data. This may take a while... 正在导出数据。这可能需要一些时间... - + Exporting 正在导出 - + Exported Successfully 导出成功 - + Data was exported successfully. 数据已成功导出。 - + Export Cancelled 导出已被取消 - + Export was cancelled by the user. 导出已被用户取消。 - + Export Failed 导出失败 - + Ensure you have write permissions on the targeted directory and try again. 请确认您是否具有目标目录的写入权限然后再次尝试。 - + Select Import Location 选择导入位置 - + Import Warning 导入警告 - + All previous data in this directory will be deleted. Are you sure you wish to proceed? 此目录中的所有先前数据将被删除。您确定要继续吗? - + Importing data. This may take a while... 正在导入数据。这需要一些时间... - + Importing 正在导入 - + Imported Successfully 导入成功 - + Data was imported successfully. 数据已导入成功。 - + Import Cancelled 导入已被取消 - + Import was cancelled by the user. 导入已被用户取消。 - + Import Failed 导入失败 - + Ensure you have read permissions on the targeted directory and try again. 请确认是否您具有目标目录的读取权限然后再次尝试。 @@ -9885,18 +9891,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed 模组提取失败 - + Failed to create temporary directory %1 未能创建临时 %1 目录 - + Zip file %1 is empty 压缩文件 %1 是空的。 diff --git a/dist/languages/zh_TW.ts b/dist/languages/zh_TW.ts index be87e5c11b..ce80998e5d 100644 --- a/dist/languages/zh_TW.ts +++ b/dist/languages/zh_TW.ts @@ -6003,47 +6003,53 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! 無法使用 OpenGL 模式! - + OpenGL shared contexts are not supported. 不支援 OpenGL 共用的上下文。 - + Eden has not been compiled with OpenGL support. - - + + + Error while initializing OpenGL! 初始化 OpenGL 時發生錯誤! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. 您的 GPU 可能不支援 OpenGL,或是未安裝最新的圖形驅動程式 - + Error while initializing OpenGL 4.6! 初始化 OpenGL 4.6 時發生錯誤! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 您的 GPU 可能不支援 OpenGL 4.6,或是未安裝最新的圖形驅動程式<br><br>GL 渲染器:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 您的 GPU 可能不支援某些必需的 OpenGL 功能。請確保您已安裝最新的圖形驅動程式。<br><br>GL 渲染器:<br>%1<br><br>不支援的功能:<br>%2 + + + This build doesn't have OpenGL support. + + GameList @@ -9226,10 +9232,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9250,210 +9256,210 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. @@ -9810,18 +9816,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty diff --git a/dist/qt_themes/default/icons/256x256/eden.png b/dist/qt_themes/default/icons/256x256/eden.png index 3c4bd566a1..d0b02580df 100644 Binary files a/dist/qt_themes/default/icons/256x256/eden.png and b/dist/qt_themes/default/icons/256x256/eden.png differ diff --git a/docs/Caveats.md b/docs/Caveats.md index 8c752cf79d..fb86437737 100644 --- a/docs/Caveats.md +++ b/docs/Caveats.md @@ -65,14 +65,13 @@ export LIBGL_ALWAYS_SOFTWARE=1 ``` - Modify the generated ffmpeg.make (in build dir) if using multiple threads (base system `make` doesn't use `-j4`, so change for `gmake`). -- If using OpenIndiana, due to a bug in SDL2's CMake configuration, audio driver defaults to SunOS ``, which does not exist on OpenIndiana. Using external or bundled SDL2 may solve this. - System OpenSSL generally does not work. Instead, use `-DYUZU_USE_BUNDLED_OPENSSL=ON` to use a bundled static OpenSSL, or build a system dependency from source. ## OmniOS Install `developer/gcc14` on OmniOS using pkgsrc. -Since so many dependencies are missing on `OmniOS`, you may wish to use `-DCPMUTIL_FORCE_BUNDLED=ON -DYUZU_USE_EXTERNAL_SDL2=ON` +Since so many dependencies are missing on `OmniOS`, you may wish to use `-DCPMUTIL_FORCE_BUNDLED=ON` For OmniOS you are required to build glslang yourself: ```sh diff --git a/docs/Debug.md b/docs/Debug.md index f384918fe2..335806d52b 100644 --- a/docs/Debug.md +++ b/docs/Debug.md @@ -9,6 +9,7 @@ When reporting issues or finding bugs, we often need backtraces, debug logs, or If your bug is related to a graphical issue--e.g. mismatched colors, vertex explosions, flickering, etc.--then you are required to include graphical debugging logs in your issue reports. Graphics Debugging is found in General -> Debug on desktop, and Advanced Settings -> Debug on Android. Android users are all set; however, desktop users may need to install the Vulkan Validation Layers: + - Windows: Install the [Vulkan SDK](https://vulkan.lunarg.com/sdk/home) - Linux, BSD, etc: Install `vulkan-validation-layers`, `vulkan-layers`, or similar from your package manager. It should be located in e.g. `/usr/lib64/libVkLayer_khronos_validation.so` @@ -30,15 +31,16 @@ Ignoring SIGSEGV when debugging in host: ### gdb You must have GDB installed for aarch64 to debug the target. Install it through your package manager, e.g.: -* On Arch: - * `sudo pacman -Syu aarch64-linux-gnu-gdb` -* On Gentoo: - * `sudo emerge --ask crossdev` - * `sudo crossdev -t aarch64-unknown-linux-gnu --ex-gdb` -Run `./build/bin/eden-cli -c -d -g ` -Or `Enable GDB Stub` at General > Debug, then hook up an aarch64-gdb: -* `target remote localhost:6543` +- On Arch: + - `sudo pacman -Syu aarch64-linux-gnu-gdb` +- On Gentoo: + - `sudo emerge --ask crossdev` + - `sudo crossdev -t aarch64-unknown-linux-gnu --ex-gdb` + +Run `./build/bin/eden-cli -c -d -g `, or `Enable GDB Stub` at General > Debug, then hook up an aarch64-gdb: + +- `target remote localhost:6543` Type `c` (for continue) and then if it crashes just do a `bt` (backtrace) and `layout asm` @@ -69,26 +71,27 @@ For more information type `info gdb` and read [the man page](https://man7.org/li ## Simple checklist for debugging black screens using Renderdoc -Renderdoc is a free, cross platform, multi-graphics API debugger. It is an invaluable tool for diagnosing issues with graphics applications, and includes support for Vulkan. Get it [here](https://renderdoc.org). +Renderdoc is a free, cross platform, multi-graphics API debugger. It is an invaluable tool for diagnosing issues with graphics applications, and includes support for Vulkan. Get it at [renderdoc.org](https://renderdoc.org). Before using renderdoc to diagnose issues, it is always good to make sure there are no validation errors. Any errors means the behavior of the application is undefined. That said, renderdoc can help debug validation errors if you do have them. When debugging a black screen, there are many ways the application could have setup Vulkan wrong. Here is a short checklist of items to look at to make sure are appropriate: -* Draw call counts are correct (aka not zero, or if rendering many triangles, not 3) -* Vertex buffers are bound -* vertex attributes are correct - Make sure the size & offset of each attribute matches what should it should be -* Any bound push constants and descriptors have the right data - including: - * Matrices have correct values - double check the model, view, & projection matrices are uploaded correctly -* Pipeline state is correct - * viewport range is correct - x,y are 0,0; width & height are screen dimensions, minDepth is 0, maxDepth is 1, NDCDepthRange is 0,1 - * Fill mode matches expected - usually solid - * Culling mode makes sense - commonly back or none - * The winding direction is correct - typically CCW (counter clockwise) - * Scissor region is correct - usually same as viewport's x,y,width, &height -* Blend state is correct -* Depth state is correct - typically enabled with Function set to Less than or Equal -* Swapchain images are bound when rendering to the swapchain -* Image being rendered to is the same as the one being presented when rendering to the swapchain + +- Draw call counts are correct (aka not zero, or if rendering many triangles, not 3) +- Vertex buffers are bound +- vertex attributes are correct - Make sure the size & offset of each attribute matches what should it should be +- Any bound push constants and descriptors have the right data - including: + - Matrices have correct values - double check the model, view, & projection matrices are uploaded correctly +- Pipeline state is correct + - viewport range is correct - x,y are 0,0; width & height are screen dimensions, minDepth is 0, maxDepth is 1, NDCDepthRange is 0,1 + - Fill mode matches expected - usually solid + - Culling mode makes sense - commonly back or none + - The winding direction is correct - typically CCW (counter clockwise) + - Scissor region is correct - usually same as viewport's x,y,width, &height +- Blend state is correct +- Depth state is correct - typically enabled with Function set to Less than or Equal +- Swapchain images are bound when rendering to the swapchain +- Image being rendered to is the same as the one being presented when rendering to the swapchain Alternatively, a [RenderDoc Extension](https://github.com/baldurk/renderdoc-contrib/tree/main/baldurk/whereismydraw) ([Archive](https://web.archive.org/web/20250000000000*/https://github.com/baldurk/renderdoc-contrib/tree/main/baldurk/whereismydraw)) exists which automates doing a lot of these manual steps. diff --git a/docs/Deps.md b/docs/Deps.md index 744713d4f0..2c1c3589df 100644 --- a/docs/Deps.md +++ b/docs/Deps.md @@ -35,6 +35,11 @@ If you are on desktop and plan to use the Qt frontend, you *must* install Qt 6, * For help setting up Qt Creator, run `./install.sh -h qtcreator` +* If you're using clang-cl and want to still use MSVC + * Check the option to add "C++ clang compiler for Windows" on Visual Studio installer and uncheck "x64/x86 build tool for MSVC" while selecting "C++ desktop developement tools" and change Visual Studio to 2026, from 2022. + * At qt creator section generator tab change Visual Studio 17 2022 to 2026. + * Finally, to use clang-cl: `cmake -S . -B build -G "Visual Studio 17 2026" -T ClangCL` + If you are on **Windows** and building with **MSVC** or **clang-cl**, you may go [back home](Build.md) and continue. ## Externals @@ -42,7 +47,7 @@ If you are on **Windows** and building with **MSVC** or **clang-cl**, you may go The following are handled by Eden's externals: * [FFmpeg](https://ffmpeg.org/) (should use `-DYUZU_USE_EXTERNAL_FFMPEG=ON`) -* [SDL2](https://www.libsdl.org/download-2.0.php) 2.0.18+ (should use `-DYUZU_USE_EXTERNAL_SDL2=ON` OR `-DYUZU_USE_BUNDLED_SDL2=ON` to reduce compile time) +* [SDL3](https://www.libsdl.org/download-2.0.php) 3.2.10+ (Use `-DYUZU_USE_BUNDLED_SDL2=ON` to reduce compile time) All other dependencies will be downloaded and built by [CPM](https://github.com/cpm-cmake/CPM.cmake/) if `YUZU_USE_CPM` is on, but will always use system dependencies if available (UNIX-like only): @@ -118,7 +123,7 @@ sudo emerge -a \ dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \ dev-util/vulkan-utility-libraries dev-util/glslang \ media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \ - media-libs/VulkanMemoryAllocator media-libs/libsdl2 media-libs/cubeb \ + media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \ net-libs/enet \ sys-libs/zlib \ dev-cpp/nlohmann_json dev-cpp/simpleini dev-cpp/cpp-httplib dev-cpp/cpp-jwt \ @@ -137,7 +142,8 @@ Required USE flags: * `dev-qt/qtbase network concurrent dbus gui widgets` * `dev-libs/quazip qt6` -* `media-libs/libsdl2 haptic joystick sound video` +* `media-libs/libsdl3 haptic joystick sound video` + * Adding `X vulkan udev opengl` is recommended but not required * `dev-cpp/cpp-httplib ssl` [Caveats](./Caveats.md#gentoo-linux) @@ -148,7 +154,7 @@ Required USE flags: Arch Linux ```sh -sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl opus qt6-base qt6-multimedia qt6-charts sdl2 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers +sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl opus qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers ``` * Building with QT Web Engine requires `qt6-webengine` as well. @@ -161,10 +167,10 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl Ubuntu, Debian, Mint Linux ```sh -sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl2-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev +sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev ``` -* Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required. +* Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required. * To enable QT Web Engine, add `-DYUZU_USE_QT_WEB_ENGINE=ON` when running CMake. @@ -185,13 +191,13 @@ AlmaLinux (use `YUZU_USE_CPM=ON`): sudo dnf install epel-release dnf-utils # (run rpmfusion installation afterwards) # vvv - This will work for most systems -sudo dnf install autoconf cmake libtool libudev cmake gcc gcc-c++ qt6-qtbase-devel zlib-devel openssl-devel boost SDL2 ffmpeg-devel libdrm glslang jq patch +sudo dnf install autoconf cmake libtool libudev cmake gcc gcc-c++ qt6-qtbase-devel zlib-devel openssl-devel boost SDL3 ffmpeg-devel libdrm glslang jq patch # Qt6 private GUI must be taken from CRB repos sudo dnf config-manager --enable crb sudo dnf install qt6-qtbase-private-devel ``` -For systems like OpenEuler or derivates, don't forget to also install: `SDL2-devel pkg-config fmt-dev nlohmann-json-dev`. +For systems like OpenEuler or derivates, don't forget to also install: `SDL3-devel pkg-config fmt-dev nlohmann-json-dev`. * [RPM Fusion](https://rpmfusion.org/Configuration) is required for `ffmpeg-devel` * Fedora 32 or later is required. @@ -208,7 +214,7 @@ First, enable the community repository; [see here](https://wiki.alpinelinux.org/ # Enable the community repository setup-apkrepos -c # Install -apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl2-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch +apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch ``` @@ -216,7 +222,7 @@ apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libqua Void Linux ```sh -xbps-install -Su git make cmake clang pkg-config patch SPIRV-Tools-devel SPIRV-Headers lz4 liblz4-devel boost-devel ffmpeg6-devel catch2 Vulkan-Utility-Libraries Vulkan-Headers glslang openssl-devel SDL2-devel quazip-qt6-devel qt6-base-devel qt6-qt5compat-devel qt6-charts-devel fmt-devel json-c++ libenet-devel libusb-devel +xbps-install -Su git make cmake clang pkg-config patch SPIRV-Tools-devel SPIRV-Headers lz4 liblz4-devel boost-devel ffmpeg6-devel catch2 Vulkan-Utility-Libraries Vulkan-Headers glslang openssl-devel SDL3-devel quazip-qt6-devel qt6-base-devel qt6-qt5compat-devel qt6-charts-devel fmt-devel json-c++ libenet-devel libusb-devel ``` Yes, `nlohmann-json` is just named `json-c++`. Why? @@ -237,7 +243,7 @@ If you're going for a pure build (i.e no downloaded deps), use `-DYUZU_USE_CPM=O Install dependencies from **[Homebrew](https://brew.sh/)** ```sh -brew install autoconf automake boost ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@6 sdl2 speexdsp zlib zstd cmake Catch2 molten-vk vulkan-loader spirv-tools +brew install autoconf automake boost ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@6 sdl3 speexdsp zlib zstd cmake Catch2 molten-vk vulkan-loader spirv-tools ``` If you are compiling on Intel Mac, or are using a Rosetta Homebrew installation, you must replace all references of `/opt/homebrew` with `/usr/local`. @@ -254,7 +260,7 @@ brew install molten-vk
FreeBSD -As root run: `pkg install devel/cmake devel/sdl20 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib devel/unordered-dense vulkan-headers quazip-qt6` +As root run: `pkg install devel/cmake sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib devel/unordered-dense vulkan-headers quazip-qt6` If using FreeBSD 12 or prior, use `devel/pkg-config` instead. @@ -264,7 +270,11 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
NetBSD -For NetBSD +10.1: `pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx`. +For NetBSD +10.1: + +```sh +pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen +``` [Caveats](./Caveats.md#netbsd). @@ -274,7 +284,7 @@ For NetBSD +10.1: `pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv ```sh pkg_add -u -pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gmake qt6 jq fmt nlohmann-json enet boost vulkan-utility-libraries vulkan-headers spirv-headers spirv-tools catch2 sdl2 libusb1-1.0.29 +pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gmake qt6 jq fmt nlohmann-json enet boost vulkan-utility-libraries vulkan-headers spirv-headers spirv-tools catch2 sdl3 libusb1-1.0.29 quazip-qt6 ``` [Caveats](./Caveats.md#openbsd). @@ -284,7 +294,7 @@ pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gm DragonFlyBSD ```sh -pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmake jq nlohmann-json enet spirv-tools sdl2 vulkan-utility-libraries vulkan-headers catch2 libfmt openssl liblz4 boost-libs cpp-httplib qt6-base qt6-charts quazip-qt6 unordered-dense libva-vdpau-driver libva-utils libva-intel-driver +pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmake jq nlohmann-json enet spirv-tools sdl3 vulkan-utility-libraries vulkan-headers catch2 libfmt openssl liblz4 boost-libs cpp-httplib qt6-base qt6-charts quazip-qt6 unordered-dense libva-vdpau-driver libva-utils libva-intel-driver ``` [Caveats](./Caveats.md#dragonflybsd). @@ -294,7 +304,7 @@ pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmak OpenIndiana ```sh -sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl2 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt +sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt ``` [Caveats](./Caveats.md#openindiana). @@ -318,7 +328,7 @@ sudo pkgin install git cmake autoconf build-essential libusb-1 nasm gcc13 ```sh BASE="git make autoconf libtool automake-wrapper jq patch" -MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb unordered_dense openssl SDL2" +MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb unordered_dense openssl SDL3" # Either x86_64 or clang-aarch64 (Windows on ARM) packages="$BASE" for pkg in $MINGW; do @@ -344,7 +354,7 @@ pacman -Syuu --needed --noconfirm $packages HaikuOS ```sh -pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl2_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel +pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel ``` [Caveats](./Caveats.md#haikuos). @@ -355,9 +365,11 @@ pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel b ```sh sudo pkg update -sudo pkg install git cmake ffmpeg6 sdl2 zlib llvm18 +sudo pkg install git cmake ffmpeg6 zlib llvm18 ``` +RedoxOS currently does not support SDL3. You will have to compile it yourself and pray. + [Caveats](./Caveats.md#redoxos).
diff --git a/docs/Development.md b/docs/Development.md index eb9f4159b5..0c7543c156 100644 --- a/docs/Development.md +++ b/docs/Development.md @@ -1,9 +1,11 @@ # Development guidelines ## License Headers + All commits must have proper license header accreditation. You can easily add all necessary license headers by running: + ```sh git fetch origin master:master .ci/license-header.sh -u -c @@ -11,6 +13,7 @@ git push ``` Alternatively, you may omit `-c` and do an amend commit: + ```sh git fetch origin master:master .ci/license-header.sh @@ -22,8 +25,10 @@ If the work is licensed/vendored from other people or projects, you may omit the For more information on the license header script, run `.ci/license-header.sh -h`. ## Pull Requests + Pull requests are only to be merged by core developers when properly tested and discussions conclude on Discord or other communication channels. Labels are recommended but not required. However, all PRs MUST be namespaced and optionally typed: -``` + +```txt [cmake] refactor: CPM over submodules [desktop] feat: implement firmware install from ZIP [hle] stub fw20 functions @@ -34,7 +39,7 @@ Pull requests are only to be merged by core developers when properly tested and - The level of namespacing is generally left to the committer's choice. - However, we never recommend going more than two levels *except* in `hle`, in which case you may go as many as four levels depending on the specificity of your changes. - Ocassionally, up to two additional namespaces may be provided for more clarity. - * Changes that affect the entire project (sans CMake changes) should be namespaced as `meta`. + - Changes that affect the entire project (sans CMake changes) should be namespaced as `meta`. - Maintainers are permitted to change namespaces at will. - Commits within PRs are not required to be namespaced, but it is highly recommended. @@ -50,6 +55,7 @@ When adding new settings, use `tr("Setting:")` if the setting is meant to be a f - Try to not write "slow/fast" options unless it clearly degrades/increases performance for a given case, as most options may modify behaviour that result in different metrics accross different systems. If for example the option is an "accuracy" option, writing "High" is sufficient to imply "Slow". No need to write "High (Slow)". Some examples: + - "[...] negatively affecting image quality", "[...] degrading image quality": Same wording but with less filler. - "[...] this may cause some glitches or crashes in some games", "[...] this may cause soft-crashes": Crashes implies there may be glitches (as crashes are technically a form of a fatal glitch). The entire sentence is structured as "may cause [...] on some games", which is redundant, because "may cause [...] in games" has the same semantic meaning ("may" is a chance that it will occur on "some" given set). - "FIFO Relaxed is similar to FIFO [...]", "FIFO Relaxed [...]": The name already implies similarity. @@ -57,13 +63,16 @@ Some examples: - "[...] it can [...] in some cases", "[...] it can [...]": Implied probability. Before adding a new setting, consider: + - Does the piece of code that the setting pertains to, make a significant difference if it's on/off? - Can it be auto-detected? # IDE setup ## VSCode + Copy this to `.vscode/settings.json`, get CMake tools and it should be ready to build: + ```json { "editor.tabSize": 4, @@ -83,6 +92,7 @@ You may additionally need the `Qt Extension Pack` extension if building Qt. # Build speedup If you have an HDD, use ramdisk (build in RAM), approximatedly you need 4GB for a full build with debug symbols: + ```sh mkdir /tmp/ramdisk chmod 777 /tmp/ramdisk @@ -96,128 +106,11 @@ umount /tmp/ramdisk # Assets and large files A general rule of thumb, before uploading files: + - PNG files: Use [optipng](https://web.archive.org/web/20240325055059/https://optipng.sourceforge.net/). - SVG files: Use [svgo](https://github.com/svg/svgo). May not be used but worth mentioning nonethless: + - OGG files: Use [OptiVorbis](https://github.com/OptiVorbis/OptiVorbis). - Video files: Use ffmpeg, preferably re-encode as AV1. - -# Bisecting older commits - -Since going into the past can be tricky (especially due to the dependencies from the project being lost thru time). This should "restore" the URLs for the respective submodules. - -```sh -#!/bin/sh -e -cat > .gitmodules < externals/dynarmic/src/dynarmic/common/x64_disassemble.cpp < -#include -#include -namespace Dynarmic::Common { -void DumpDisassembledX64(const void* ptr, size_t size) {} -std::vector DisassembleX64(const void* ptr, size_t size) { return {}; } -} -EOF -``` - -If having issues with older artifacts, then run `rm -r externals/dynarmic/build externals/dynarmic/externals externals/nx_tzdb/tzdb_to_nx/externals externals/sirit/externals`. - -Configuring CMake with `-DSIRIT_USE_SYSTEM_SPIRV_HEADERS=1 -DCMAKE_CXX_FLAGS="-Wno-error" -DCMAKE_C_FLAGS="-Wno-error -Wno-array-parameter -Wno-stringop-overflow"` is also recommended. diff --git a/docs/HosKernel.md b/docs/HosKernel.md new file mode 100644 index 0000000000..399d4fb7b1 --- /dev/null +++ b/docs/HosKernel.md @@ -0,0 +1,31 @@ +# HOS Kernel + +In brief, the HOS kernel is a microkernel, some services and programs run in userspace, the primary way to do communication between these is via `HIPC` (not covered here); otherwise most of the primitives reside in the forms of syscalls invoked via `svc #imm`. The kernel supports both 32-bit and 64-bit programs, and has the capacity to use 32, 36 and 39 bits of address space for spawned processes. Most of the networking stack is based off FreeBSD's network stack. + +The emulator implements the majority of the syscalls pertaining to the HOS kernel itself. When we talk about the HOS Kernel (in the context of the emulator) we are strictly speaking about the mechanisms from which syscalls are handled (and it's subsequent side effects, such as the page table book-keeping). The emulator at it's current state is unable to load a custom low-level kernel and do supervisor-level emulation. + +Most programs in NX eventually invoke an `svc`, which, depending on it's immediate value, will go on to be dispatched into one of the specific syscall handlers. + +These can be seen in [svc.cpp](/src/core/hle/kernel/svc.cpp). All of these correspond to syscalls which userspace programs may perform. + +In turn, these syscalls create the mechanisms that allows programs to use CMIF/TIPC as their primary IPC form to contact other services/processes running on the system, the details of which will not be covered here, but you can consult the relevant [SwitchBrew article: 'HIPC'](https://switchbrew.org/wiki/HIPC). + +From the point of view of the programs, no special devices (such as PCIE, Realtek drivers, Bluetooth or USB) has to be handled by the emulator; this is because most of the fun occurs in specialized services such as `usb:u` or `pcie` services. Which aren't emulated (yet). + +Due to the nature of syscalls, many of them interact with memory. The emulated kernel has an internal tree-like structure, borrowed from FreeBSD's intrusive red-black tree; this is used to track and find mappings added or removed. Thus most of the process space is emulated in this way. + +The kernel keeps it's own separate pagetable, in a traditional sense, each process has it's own pagetable, this is true for HOS as well. + +Every process keeps it's own tracking of the following structures: +- Name (13 characters) +- 64-bit ID +- A handle table +- Exclusive monitor +- Threads +- Held locks +- Thread local pages +- A page table for each process + +The emulator willingly restricts itself to only use 4 threads (to emulate 4 cores), this is because most existing applications do not benefit greatly from the added core count, and in fact can be detrimental due to extra contention. This translates equitatively to about 4 `ArmInterface` slots for each process, these are then redirected to whatever is the last `pc` of the last thread running on the core is meant to be; proceed to run it, then when returning (due to halt or interruption), proceed to reschedule the thread. + +The scheduler as-is isn't 100% faithful to the original, and has great timing variance (especially due to the fact the emulator can run in systems with wildly different timings). diff --git a/docs/NvidiaGpu.md b/docs/NvidiaGpu.md index d7fe3d86d3..a595f61a66 100644 --- a/docs/NvidiaGpu.md +++ b/docs/NvidiaGpu.md @@ -853,6 +853,8 @@ Texture Query. Vote Across SIMD Thread Group +`VOTE_vtg` is a kepler leftover. + # VSET `0100 000- ---- ----` diff --git a/docs/Options.md b/docs/Options.md index 3eb6effe92..bd353b16ca 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -29,8 +29,7 @@ These options control dependencies. - `YUZU_TZDB_PATH` (string) Path to a pre-downloaded timezone database (useful for nixOS and Gentoo) - `YUZU_USE_BUNDLED_MOLTENVK` (ON, macOS only) Download bundled MoltenVK lib - `YUZU_USE_BUNDLED_OPENSSL` (ON for MSVC, Android, Solaris, and OpenBSD) Download bundled OpenSSL build -- `YUZU_USE_EXTERNAL_SDL2` (OFF) Compiles SDL2 from source -- `YUZU_USE_BUNDLED_SDL2` (ON for MSVC) Download a prebuilt SDL2 +- `YUZU_USE_BUNDLED_SDL3` (ON for MSVC) Download a prebuilt SDL3 ### Miscellaneous @@ -44,6 +43,9 @@ These options control dependencies. - UNIX may be better off appending `-flto=thin` to compiler args - `USE_FASTER_LINKER` (OFF) Check if a faster linker is available - Not recommended outside of Linux +- `YUZU_INSTALL_UDEV_RULES` (OFF) Install udev rules to enable hidraw access + - Needed for gyroscopes + - Only available on Linux ### Flavors @@ -60,7 +62,7 @@ These options control executables and build flavors. **Desktop only**: -- `YUZU_CMD` (ON) Compile the SDL2 frontend (eden-cli) +- `YUZU_CMD` (ON) Compile the SDL-based frontend (eden-cli) - `YUZU_ROOM` (OFF) Compile dedicated room functionality into the main executable - `YUZU_ROOM_STANDALONE` (OFF) Compile a separate executable for room functionality - `YUZU_STATIC_ROOM` (OFF) Compile the room executable *only* as a static, portable executable @@ -96,5 +98,6 @@ The following options were a part of Eden at one point, but have since been reti - `ENABLE_SDL2` - While technically possible to *not* use SDL2 on desktop, this is **NOT** a supported configuration under any means, and adding this matrix to our build system was not worth the effort. - `YUZU_USE_CPM` - This option once had a purpose, but that purpose has long since passed us by. *All* builds use CPMUtil to manage dependencies now. - If you want to *force* the usage of system dependencies, use `-DCPMUTIL_FORCE_SYSTEM=ON`. +- `YUZU_USE_EXTERNAL_SDL` - This is now handled automatically. It was included even after CPM for purposes that have not applied for a very long time. See `src/dynarmic/CMakeLists.txt` for additional options--usually, these don't need changed diff --git a/docs/README.md b/docs/README.md index 68775f99d8..9e5f635b6f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,21 +2,26 @@ Are you just a casual user? Take a look at our [User Handbook](./user) then! +If you want to register/signup as a contributor, take a gander at the [signup guide](./SIGNUP.md). + This contains documentation created by developers. This contains build instructions, guidelines, instructions/layouts for [cool stuff we made](./CPMUtil), and more. -- **[General Build Instructions](Build.md)** -- **[CMake Options](Options.md)** -- **[Cross Compiling](CrossCompile.md)** -- **[Development Guidelines](Development.md)** -- **[Dependencies](Deps.md)** +- **[General Build Instructions](./Build.md)** +- **[CMake Options](./Options.md)** +- **[Cross Compiling](./CrossCompile.md)** +- **[Development Guidelines](./Development.md)** +- **[Dependencies](./Deps.md)** - **[Debug Guidelines](./Debug.md)** - **[CPM - CMake Package Manager](./CPMUtil)** -- **[Platform-Specific Caveats](Caveats.md)** +- **[Platform-Specific Caveats](./Caveats.md)** - **[The NVIDIA SM86 (Maxwell) GPU](./NvidiaGpu.md)** -- **[Dynarmic](./dynarmic)** - **[Cross compilation](./CrossCompile.md)** - **[Driver Bugs](./DriverBugs.md)** - **[Building Older Commits](./build/OlderCommits.md)** +- Subsystems: + - **[Dynarmic](./dynarmic/README.md)** + - **[HOS Kernel](./HosKernel.md)** + - **[Settings](./Settings.md)** ## Policies @@ -25,3 +30,12 @@ Policies and information on development. - **[AI and LLM Usage](./policies/AI.md)** - **[Release Policy](./policies/Release.md)** - **[Coding guidelines](./policies/Coding.md)** +- **[Coding Style guidelines](./policies/CodingStyle.md)** + +## Externals + +Other useful resources in general, take a quick read if you need. + +- **[SwitchBrew](https://switchbrew.org/wiki/Main_Page)** +- **[IPS file format](https://zerosoft.zophar.net/ips.php)** +- **[IPSwitch file format](https://github.com/3096/ipswitch)** diff --git a/docs/Settings.md b/docs/Settings.md new file mode 100644 index 0000000000..4bae71f44e --- /dev/null +++ b/docs/Settings.md @@ -0,0 +1,327 @@ +# Settings + +> [!WARNING] +> This guide is intended for developers ONLY. If you're looking for configuring the emulator itself, please read **[the user handbook](./user/README.md)**. + +Settings on the emulator are very important, toggles and such can be used to guard and/or add branches to paths where some games may crash while others won't, and viceversa. + +However, this process can be tedious for those unfamiliar; this document serves as a outline/documentation for the settings subsystem. + +## Index + +* [Adding Debug Knobs](#adding-debug-knobs) + * [Advantages](#advantages) + * [Usage](#usage) + * [Accessing Debug Knobs (dev side)](#accessing-debug-knobs-dev-side) + * [Setting Debug Knobs (user side)](#setting-debug-knobs-user-side) + * [Bit Manipulation Examples](#bit-manipulation-examples) + * [Terminology and user communication](#terminology-and-user-communication) + * [Examples](#examples) + * [Example 1: Conditional Debug Logging](#example-1-conditional-debug-logging) + * [Example 2: Performance Tuning](#example-2-performance-tuning) + * [Example 3: Feature Gating](#example-3-feature-gating) + * [Best Practices](#best-practices) +* [Adding Boolean Settings Toggles](#adding-boolean-settings-toggles) + * [Step 1 - Common Setting](#step-1-common-setting) + * [Step 2 - Qt Toggle](#step-2-qt-toggle) + * [Step 3 - Kotlin (Android)](#step-3-kotlin-android) + * [Step 3.1 - BooleanSetting.kt](#step-3-1-booleansetting-kt) + * [Step 3.2 - SettingsItem.kt](#step-3-2-settingsitem-kt) + * [Step 3.3 - SettingsFragmentPresenter.kt](#step-3-3-settingsfragmentpresenter-kt) + * [Step 3.4 - Localization](#step-3-4-localization) + * [Step 4 - Use Your Toggle](#step-4-use-your-toggle) + * [Best Practices](#best-practices) + +## Adding Boolean Settings Toggles + +This guide will walk you through adding a new boolean toggle setting to Eden's configuration across both Qt's (PC) and Kotlin's (Android) UIs. + +--- + +### Step 1 - Common Setting + +Firstly add your desired toggle: + +Example: `src/common/setting.h` +```cpp +SwitchableSetting your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions}; +``` + +Remember to add your toggle to the appropriate category, for example: + +Common Categories: + +* Category::Renderer +* Category::RendererAdvanced +* Category::RendererExtensions +* Category::System +* Category::Core + +> [!WARNING] +> If you wish for your toggle to be `on by default` then change `false` to `true` after `linkage,`. + +--- + +### Step 2 - Qt Toggle + +Add the toggle to the Qt UI, where you wish for it to appear and place it there. + +Example: `src/qt_common/config/shared_translation.cpp` +```cpp +INSERT(Settings, + your_setting_name, + tr("Your Setting Display Name"), + tr("Detailed description of what this setting does.\n" + "You can use multiple lines.\n" + "Explain any caveats or requirements.")); +``` + +#### Make sure to: + +* Keep display naming consistant +* Put detailed info in the description +* Use `\n` for line breaks in descriptions + +--- + +### Step 3 - Kotlin (Android) + +#### Step 3.1 - BooleanSetting.kt + +Add where it should be in the settings. + +Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt` +```kts +RENDERER_YOUR_SETTING_NAME("your_setting_name"), +``` + +#### Make sure to: + +* Ensure the prefix naming matches the intended category. + +--- + +#### Step 3.2 - SettingsItem.kt + +Add the toggle to the Kotlin (Android) UI + +Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt` +```kts +put( + SwitchSetting( + BooleanSetting.RENDERER_YOUR_SETTING_NAME, + titleId = R.string.your_setting_name, + descriptionId = R.string.your_setting_name_description + ) +) +``` + +--- + +#### Step 3.3 - SettingsFragmentPresenter.kt + +Add your setting within the right category. + +Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt` +```kts +add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key) +``` + +> [!WARNING] +> Remember, placing matters! Settings appear in the order of where you add them. + +--- + +#### Step 3.4 - Localization + +Add your setting and description in the appropriate place. + +Example: `src/android/app/src/main/res/values/strings.xml` +```xml +Your Setting Display Name +Detailed description of what this setting does. Explain any caveats, requirements, or warnings here. +``` + +--- + +### Step 4 - Use Your Toggle! + +Now the UI part is done find a place in the code for the toggle, +And use it to your heart's desire! + +Example: +```cpp +const bool your_value = Settings::values.your_setting_name.GetValue(); + +if (your_value) { + // Do something when enabled +} +``` + +If you wish to do something only when the toggle is disabled, +Use `if (!your_value) {` instead of `if (your_value) {`. + +--- + +### Best Practices + +* Naming - Use clear, descriptive names. Something for both the devs and the users. +* Defaults - Choose safe default values (usually false for new features). +* Documentation - Write clear descriptions explaining when and why to use the setting. +* Categories - Put settings in the appropriate category. +* Order - Place related settings near each other. +* Testing - Always test on both PC and Android before committing when possible. + +Thank you for reading, I hope this guide helped you making your toggle! + +## Adding Debug Knobs + +Debug Knobs is a 16-bit integer setting (`debug_knobs`) in the Eden Emulator that serves as a bitmask for gating various testing and debugging features. This allows developers and advanced users to enable or disable specific debug behaviors without requiring deploying of complete but temporary toggles. + +The setting ranges from 0 to 65535 (0x0000 to 0xFFFF), where each bit represents a different debug feature flag. + +--- + +### Advantages + +The main advantage is to avoid deploying new disposable toggles (those made only for testing stage, and are disposed once new feature gets good to merge). This empowers devs to be free of all frontend burocracy and hassle of new toggles. + +Common advantages recap: + +* **Fine-Grained Control**: Enable or disable up to 16 individual debug features independently using bit manipulation on a single build +* **Runtime Configuration**: Change debug behavior at runtime the same way as new toggles would do +* **Safe incremental development**: New debug features can be added while impact can be isolated from previous deployments + +### Usage + +#### Accessing Debug Knobs (dev side) + +Use the `Settings::getDebugKnobAt(u8 i)` function to check if a specific bit is set: + +```cpp +//cpp side +#include "common/settings.h" + +// Check if bit 0 is set +bool feature_enabled = Settings::getDebugKnobAt(0); + +// Check if bit 15 is set +bool another_feature = Settings::getDebugKnobAt(15); +``` + +```kts +//kotlin side +import org.yuzu.yuzu_emu.features.settings.model.Settings + +// Check if bit x is set +bool feature_enabled = Settings.getDebugKnobAt(x); //x as integer from 0 to 15 +``` + +The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise. + +#### Setting Debug Knobs (user side) + +Developers must inform which knobs are tied to each functionality to be tested. + +The debug knobs value can be set through: + +1. **Desktop UI**: In the Debug configuration tab, there's a spinbox for "Debug knobs" (0-65535) +2. **Android UI**: Available as an integer setting in the Debug section +3. **Configuration Files**: Set the `debug_knobs` value in the emulator's configuration + +#### Bit Manipulation Examples + +To enable specific features, calculate the decimal value by setting the appropriate bits: + +* **Enable only bit 0**: Value = 1 (2^0) +* **Enable only bit 1**: Value = 2 (2^1) +* **Enable bits 0 and 1**: Value = 3 (2^0 + 2^1) +* **Enable bit 15**: Value = 32768 (2^15) + +### Terminology and user communication + +There are two main confusions when talking about knobs: + +#### Whether it's zero-based or one-based + +Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1). + +Debug knobs are **zero-based**, which means: +* The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise) +* You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc. + +#### Whether one is talking about the knob itself or about the entire parameter value (which represents all knobs) + +Sometimes when an user reports: knob 3 results, it's unclear whether he's referring to knob setting with value 3 (which means both knob 0 and 1 are enabled), or to knob(3) specifically. +Whenever you're instructing tests or reporting results, be precise about whether one you're talking to avoid confusion: + +#### Setting based terminology + +ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once: +Examples: +- **knobs=0**: no knobs enabled +- **knobs=1**: knob0 enabled, others disabled +- **knobs=2**: knob1 enabled, others disabled +- **knobs=3**: knobs 0 and 1 enabled, others disabled + +... + +#### Knob based terminology + +Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once: +Examples: +- **knob0**: knob 0 enabled, others disabled +- **knob1**: knob 1 enabled, others disabled +- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled + +... + +### Examples + +#### Example 1: Conditional Debug Logging + +```cpp +void SomeFunction() { + if (Settings::getDebugKnobAt(0)) { + LOG_DEBUG(Common, "Debug feature 0 is enabled"); + // Additional debug code here + } + + if (Settings::getDebugKnobAt(1)) { + LOG_DEBUG(Common, "Debug feature 1 is enabled"); + // Different debug behavior + } +} +``` + +#### Example 2: Performance Tuning + +```cpp +bool UseOptimizedPath() { + // Skip optimization if debug bit 2 is set for testing + return !Settings::getDebugKnobAt(2); +} +``` + +#### Example 3: Feature Gating + +```cpp +void ExperimentalFeature() { + static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3; + + if (!Settings::getDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) { + // Fallback to stable implementation + StableImplementation(); + return; + } + + // Experimental implementation + ExperimentalImplementation(); +} +``` + +### Best Practices + +* This setting is intended for development and testing purposes only +* Knobs must be unwired before PR creation +* The setting is per-game configurable, allowing different debug setups for different titles diff --git a/docs/policies/Coding.md b/docs/policies/Coding.md index c50f93b142..062e0beda4 100644 --- a/docs/policies/Coding.md +++ b/docs/policies/Coding.md @@ -1,126 +1,70 @@ # Coding guidelines -These are mostly "suggestions", if you feel like your code is readable, comprehensible to others; and most importantly doesn't result in unreadable spaghetti you're fine to go. +These are **not** stylistic guidelines, they're, for the most part, suggestions on how to architecture new systems or improve upon the existing codebase. -But for new developers you may find that following these guidelines will make everything x10 easier. +# Foreword -## Naming conventions +Don't try to micro-optimize out of the get go, while yes, most of the code is pretty, subpar, most of these are aftertoughts and details that can be glossed over **generally**. -Simply put, types/classes are named as `PascalCase`, same for methods and functions like `AddElement`. Variables are named `like_this_snake_case` and constants are `IN_SCREAMING_CASE`. +Architectural issues are more important, for example an API returning a `std::string` is not as efficient as one that operates on `std::string_view` directly (cost of constructing an `std::string` w/o small-string optimization and all of that). -Except for Qt MOC where `functionName` is preferred. +Regardless of the details, try to keep things simple. As a general rule of thumb. -Template typenames prefer short names like `T`, `I`, `U`, if a longer name is required either `Iterator` or `perform_action` are fine as well. Do not use names like `SS` as systems like solaris define it for registers, in general do not use any of the following for short names: +# C++ guidelines -- `SS`, `DS`, `GS`, `FS`: Segment registers, defined by Solaris `` -- `EAX`, `EBX`, `ECX`, `EDX`, `ESI`, `EDI`, `ESP`, `EBP`, `EIP`: Registers, defined by Solaris. -- `X`: Defined by some utility headers, avoid. -- `_`: Defined by gettext, avoid. -- `N`, `M`, `S`: Preferably don't use this for types, use it for numeric constants. -- `TR`: Used by some weird `` whom define the Task Register as a logical register to provide to the user... (Need to remember which OS in specific). +Everyone has their own way of viewing good/bad C++ practices, my general outline: -Macros must always be in `SCREAMING_CASE`. Do not use short letter macros as systems like Solaris will conflict with them; a good rule of thumb is >5 characters per macro - i.e `THIS_MACRO_IS_GOOD`, `AND_ALSO_THIS_ONE`. +- At your disposal you may use `boost::container::static_vector<>` (beware it has a ctor/initialization cost which goes up the more elements you add). + - Or you may use `boost::container::small_vector<>` (which has an initialization cost as well, and will use extra book-keeping for heap, try to keep a balance). +- Don't use `[[likely]]` or `[[unlikely]]`; PGO builds exist for that. +- Don't use inline assembly to try to outsmart the compiler unless you're 100% sure the assembly you're writing is actually good. + - And if so, try to restructure your C++ code so the compiler vectorizes it/makes it better, right? + - Or if that fails, use intrinsics instead of raw `asm volatile`. +- Use `std::optional<>` instead of `std::unique_ptr<>` if possible. + - `std::unique_ptr<>` carries indirection cost due to it being memory allocated on the heap. + - It isn't often that objects that contain `std::unique_ptr<>`, are allocated on the heap themselves, allocating even more things on the heap seems redundant. +- Avoid `std::recursive_mutex` at all costs. + - It's basically implemented as a linked list most of the time and has HEAVY performance penalties. +- Exploit the fact `std::atomic/std::atomic` is basically free on most arches that matter. + - In x86_64, an atomic `uint32_t` is basically `mov [m32], r32`, which is essentially free/cheap. +- Avoid template parameters unless you really need them. + - For small inlineable functions this is fine, for more complex ones, please consider the generated assembly. +- Dont make your own memcpy/memset/strcpy/strncpy/etc. + - Seriously DON'T DO THIS. You will NOT beat the compiler. + - Nor 30 years of writing optimized `mem*`. + - If your code is slow, don't blame `mem*`, blame your code. +- Try to avoid using `virtual` since vtable indirection has a cost +- Avoid `dynamic_cast` and `typeid` at all costs. + - The reason is because the project has `-fno-rtti` disabled by default, due to the costs of dynamic polymorphism. +- Always copy-on-value for objects with `sizeof(void *) >= sizeof(T) * 2`, i.e objects sized as 2 pointers or less, for bigger objects you can use ref/pointer as usual. +- Try using move semantics instead of references, whenever possible. +- Remember function parameters are extremelly cheap as fuck, don't be afraid to place upto 8 parameters on a given function. +- Don't save a reference in structures of a parent object, i.e: + ```c++ + struct Child { + Parent& parent; + void Mehod() { + parent.Something(); + } + }; + ``` + - Instead you can do the following: + ```c++ + struct Child { + void Mehod(Parent& parent) { + parent.Something(); + } + }; + ``` + - This reduces the amount of pointers you have lying around, and also works better because of the aforementioned cheapness of parameter functions. -Try not using hungarian notation, if you're able. +# Engineering guidelines -## Formatting +Coding isn't also writing stuff but architecturing stuff, consider the following: -Formatting is extremelly lax, the general rule of thumb is: Don't add new lines just to increase line count. The less lines we have to look at, the better. This means also packing densely your code while not making it a clusterfuck. Strike a balance of "this is a short and comprehensible piece of code" and "my eyes are actually happy to see this!". Don't just drop the entire thing in a single line and call it "dense code", that's just spaghetti posing as code. In general, be mindful of what other devs need to look at. - -Do not put if/while/etc braces after lines: - -```c++ -// no dont do this -// this is more lines of code for no good reason (why braces need their separate lines?) -// and those take space in someone's screen, cumulatively -if (thing) -{ //<-- - some(); // ... -} //<-- 2 lines of code for basically "opening" and "closing" an statment - -// do this -if (thing) { //<-- [...] and with your brain you can deduce it's this piece of code - // that's being closed - some(); // ... -} //<-- only one line, and it's clearer since you know its closing something [...] - -// or this, albeit the extra line isn't needed (at your discretion of course) -if (thing) - some(); // ... - -// this is also ok, keeps things in one line and makes it extremely clear -if (thing) some(); - -// NOT ok, don't be "clever" and use the comma operator to stash a bunch of statments -// in a single line, doing this will definitely ruin someone's day - just do the thing below -// vvv -if (thing) some(), thing(), a2(a1(), y1(), j1()), do_complex_shit(wa(), wo(), ploo()); -// ... and in general don't use the comma operator for "multiple statments", EXCEPT if you think -// that it makes the code more readable (the situation may be rare however) - -// Wow so much clearer! Now I can actually see what each statment is meant to do! -if (thing) { - some(); - thing(); - a2(a1(), y1(), j1()); - do_complex_shit(wa(), wo(), ploo()); -} -``` - -Brace rules are lax, if you can get the point across, do it: - -```c++ -// this is fine -do { - if (thing) { - return 0; - } -} while (other); - -// this is also ok --- albeit a bit more dense -do if (thing) return 0; while (other); - -// ok as well -do { - if (thing) return 0; -} while (other); -``` - -There is no 80-column limit but preferably be mindful of other developer's readability (like don't just put everything onto one line). - -```c++ -// someone is going to be mad due to this -SDL_AudioSpec obtained; -device_name.empty() ? device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false) : device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false); - -// maybe consider this -SDL_AudioSpec obtained; -if (device_name.empty()) { - device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false); -} else { - device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false); -} - -// or this is fine as well -SDL_AudioSpec obtained; -device = SDL_OpenAudioDevice(device_name.empty() ? nullptr : device_name.c_str(), capture, &spec, &obtained, false); -``` - -A note about operators: Use them sparingly, yes, the language is lax on them, but some usages can be... tripping to say the least. - -```c++ -a, b, c; //<-- NOT OK multiple statments with comma operator is definitely a recipe for disaster -return c ? a : b; //<-- OK ternaries at end of return statments are clear and fine -return a, b; //<-- NOT OK return will take value of `b` but also evaluate `a`, just use a separate statment -void f(int a[]) //<-- OK? if you intend to use the pointer as an array, otherwise just mark it as * -``` - -And about templates, use them sparingly, don't just do meta-templating for the sake of it, do it when you actually need it. This isn't a competition to see who can make the most complicated and robust meta-templating system. Just use what works, and preferably stick to the standard libary instead of reinventing the wheel. Additionally: - -```c++ -// NOT OK This will create (T * N * C * P) versions of the same function. DO. NOT. DO. THIS. -template inline void what() const noexcept; - -// OK use parameters like a normal person, don't be afraid to use them :) -template inline void what(size_t n, size_t c, size_t p) const noexcept; -``` +- Try to reduce dependency on... dependencies + - While some dependencies are useful `boost::container` and `fmt` to name a few, remember each dependency added incurs a cost. + - It may also be subpar with a hand rolled implementation, biggest exemplar of this is `spirv-tools` providing subpar SPIRV optimizations in comparison to the in-house optimizer. +- Try to rely less on indirection for architecturing systems + - If the underlying HLE kernel emulation requires it, try making a solution that keeps things local + - For example, there isn't a need for file descriptors to each be a pointer, when they could be a fixed table size with elements that may be emplaced at will. diff --git a/docs/policies/CodingStyle.md b/docs/policies/CodingStyle.md new file mode 100644 index 0000000000..659e8f7384 --- /dev/null +++ b/docs/policies/CodingStyle.md @@ -0,0 +1,126 @@ +# Coding Style guidelines + +These are mostly "suggestions", if you feel like your code is readable, comprehensible to others; and most importantly doesn't result in unreadable spaghetti you're fine to go. + +But for new developers you may find that following these guidelines will make everything x10 easier. + +## Naming conventions + +Simply put, types/classes are named as `PascalCase`, same for methods and functions like `AddElement`. Variables are named `like_this_snake_case` and constants are `IN_SCREAMING_CASE`. + +Except for Qt MOC where `functionName` is preferred. + +Template typenames prefer short names like `T`, `I`, `U`, if a longer name is required either `Iterator` or `perform_action` are fine as well. Do not use names like `SS` as systems like solaris define it for registers, in general do not use any of the following for short names: + +- `SS`, `DS`, `GS`, `FS`: Segment registers, defined by Solaris `` +- `EAX`, `EBX`, `ECX`, `EDX`, `ESI`, `EDI`, `ESP`, `EBP`, `EIP`: Registers, defined by Solaris. +- `X`: Defined by some utility headers, avoid. +- `_`: Defined by gettext, avoid. +- `N`, `M`, `S`: Preferably don't use this for types, use it for numeric constants. +- `TR`: Used by some weird `` whom define the Task Register as a logical register to provide to the user... (Need to remember which OS in specific). + +Macros must always be in `SCREAMING_CASE`. Do not use short letter macros as systems like Solaris will conflict with them; a good rule of thumb is >5 characters per macro - i.e `THIS_MACRO_IS_GOOD`, `AND_ALSO_THIS_ONE`. + +Try not using hungarian notation, if you're able. + +## Formatting + +Formatting is extremelly lax, the general rule of thumb is: Don't add new lines just to increase line count. The less lines we have to look at, the better. This means also packing densely your code while not making it a clusterfuck. Strike a balance of "this is a short and comprehensible piece of code" and "my eyes are actually happy to see this!". Don't just drop the entire thing in a single line and call it "dense code", that's just spaghetti posing as code. In general, be mindful of what other devs need to look at. + +Do not put if/while/etc braces after lines: + +```c++ +// no dont do this +// this is more lines of code for no good reason (why braces need their separate lines?) +// and those take space in someone's screen, cumulatively +if (thing) +{ //<-- + some(); // ... +} //<-- 2 lines of code for basically "opening" and "closing" an statment + +// do this +if (thing) { //<-- [...] and with your brain you can deduce it's this piece of code + // that's being closed + some(); // ... +} //<-- only one line, and it's clearer since you know its closing something [...] + +// or this, albeit the extra line isn't needed (at your discretion of course) +if (thing) + some(); // ... + +// this is also ok, keeps things in one line and makes it extremely clear +if (thing) some(); + +// NOT ok, don't be "clever" and use the comma operator to stash a bunch of statments +// in a single line, doing this will definitely ruin someone's day - just do the thing below +// vvv +if (thing) some(), thing(), a2(a1(), y1(), j1()), do_complex_shit(wa(), wo(), ploo()); +// ... and in general don't use the comma operator for "multiple statments", EXCEPT if you think +// that it makes the code more readable (the situation may be rare however) + +// Wow so much clearer! Now I can actually see what each statment is meant to do! +if (thing) { + some(); + thing(); + a2(a1(), y1(), j1()); + do_complex_shit(wa(), wo(), ploo()); +} +``` + +Brace rules are lax, if you can get the point across, do it: + +```c++ +// this is fine +do { + if (thing) { + return 0; + } +} while (other); + +// this is also ok --- albeit a bit more dense +do if (thing) return 0; while (other); + +// ok as well +do { + if (thing) return 0; +} while (other); +``` + +There is no 80-column limit but preferably be mindful of other developer's readability (like don't just put everything onto one line). + +```c++ +// someone is going to be mad due to this +SDL_AudioSpec obtained; +device_name.empty() ? device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false) : device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false); + +// maybe consider this +SDL_AudioSpec obtained; +if (device_name.empty()) { + device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false); +} else { + device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false); +} + +// or this is fine as well +SDL_AudioSpec obtained; +device = SDL_OpenAudioDevice(device_name.empty() ? nullptr : device_name.c_str(), capture, &spec, &obtained, false); +``` + +A note about operators: Use them sparingly, yes, the language is lax on them, but some usages can be... tripping to say the least. + +```c++ +a, b, c; //<-- NOT OK multiple statments with comma operator is definitely a recipe for disaster +return c ? a : b; //<-- OK ternaries at end of return statments are clear and fine +return a, b; //<-- NOT OK return will take value of `b` but also evaluate `a`, just use a separate statment +void f(int a[]) //<-- OK? if you intend to use the pointer as an array, otherwise just mark it as * +``` + +And about templates, use them sparingly, don't just do meta-templating for the sake of it, do it when you actually need it. This isn't a competition to see who can make the most complicated and robust meta-templating system. Just use what works, and preferably stick to the standard libary instead of reinventing the wheel. Additionally: + +```c++ +// NOT OK This will create (T * N * C * P) versions of the same function. DO. NOT. DO. THIS. +template inline void what() const noexcept; + +// OK use parameters like a normal person, don't be afraid to use them :) +template inline void what(size_t n, size_t c, size_t p) const noexcept; +``` diff --git a/docs/user/AddingBooleanToggles.md b/docs/user/AddingBooleanToggles.md deleted file mode 100644 index 1af5fd932d..0000000000 --- a/docs/user/AddingBooleanToggles.md +++ /dev/null @@ -1,159 +0,0 @@ -# User Handbook - Adding Boolean Settings Toggles - -> [!WARNING] -> This guide is intended for developers ONLY. If you are not a developer, this likely irrelevant to yourself. -> -> If you want to add temporary toggles, please refer to **[Adding Debug Knobs](AddingDebugKnobs.md)** - -This guide will walk you through adding a new boolean toggle setting to Eden's configuration across both Qt's (PC) and Kotlin's (Android) UIs. - -## Index - -1. [Step 1 - Common Setting](#step-1-common-setting) -2. [Step 2 - Qt Toggle](#step-2-qt-toggle) -3. [Step 3 - Kotlin (Android)](#step-3-kotlin-android) - - * [Step 3.1 - BooleanSetting.kt](#step-3-1-booleansetting-kt) - * [Step 3.2 - SettingsItem.kt](#step-3-2-settingsitem-kt) - * [Step 3.3 - SettingsFragmentPresenter.kt](#step-3-3-settingsfragmentpresenter-kt) - * [Step 3.4 - Localization](#step-3-4-localization) -4. [Step 4 - Use Your Toggle](#step-4-use-your-toggle) -5. [Best Practices](#best-practices) - ---- - -## Step 1 - Common Setting - -Firstly add your desired toggle: - -Example: `src/common/setting.h` -```cpp -SwitchableSetting your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions}; -``` - -### Remember to add your toggle to the appropriate category, for example: - -Common Categories: - -* Category::Renderer -* Category::RendererAdvanced -* Category::RendererExtensions -* Category::System -* Category::Core - -> [!WARNING] -> If you wish for your toggle to be `on by default` then change `false` to `true` after `linkage,`. - ---- - -## Step 2 - Qt Toggle - -Add the toggle to the Qt UI, where you wish for it to appear and place it there. - -Example: `src/qt_common/config/shared_translation.cpp` -```cpp -INSERT(Settings, - your_setting_name, - tr("Your Setting Display Name"), - tr("Detailed description of what this setting does.\n" - "You can use multiple lines.\n" - "Explain any caveats or requirements.")); -``` - -### Make sure to: - -* Keep display naming consistant -* Put detailed info in the description -* Use `\n` for line breaks in descriptions - ---- - -## Step 3 - Kotlin (Android) - -### Step 3.1 - BooleanSetting.kt - -Add where it should be in the settings. - -Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt` -```kts -RENDERER_YOUR_SETTING_NAME("your_setting_name"), -``` - -### Make sure to: - -* Ensure the prefix naming matches the intended category. - ---- - -### Step 3.2 - SettingsItem.kt - -Add the toggle to the Kotlin (Android) UI - -Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt` -```kts -put( - SwitchSetting( - BooleanSetting.RENDERER_YOUR_SETTING_NAME, - titleId = R.string.your_setting_name, - descriptionId = R.string.your_setting_name_description - ) -) -``` - ---- - -### Step 3.3 - SettingsFragmentPresenter.kt - -Add your setting within the right category. - -Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt` -```kts -add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key) -``` - -> [!WARNING] -> Remember, placing matters! Settings appear in the order of where you add them. - ---- - -### Step 3.4 - Localization - -Add your setting and description in the appropriate place. - -Example: `src/android/app/src/main/res/values/strings.xml` -```xml -Your Setting Display Name -Detailed description of what this setting does. Explain any caveats, requirements, or warnings here. -``` - ---- - -## Step 4 - Use Your Toggle! - -Now the UI part is done find a place in the code for the toggle, -And use it to your heart's desire! - -Example: -```cpp -const bool your_value = Settings::values.your_setting_name.GetValue(); - -if (your_value) { - // Do something when enabled -} -``` - -If you wish to do something only when the toggle is disabled, -Use `if (!your_value) {` instead of `if (your_value) {`. - ---- - -## Best Practices - -* Naming - Use clear, descriptive names. Something for both the devs and the users. -* Defaults - Choose safe default values (usually false for new features). -* Documentation - Write clear descriptions explaining when and why to use the setting. -* Categories - Put settings in the appropriate category. -* Order - Place related settings near each other. -* Testing - Always test on both PC and Android before committing when possible. - -### Thank you for reading, I hope this guide helped you making your toggle! diff --git a/docs/user/AddingDebugKnobs.md b/docs/user/AddingDebugKnobs.md deleted file mode 100644 index 83db52b375..0000000000 --- a/docs/user/AddingDebugKnobs.md +++ /dev/null @@ -1,167 +0,0 @@ -# User Handbook - Adding Debug Knobs - -Debug Knobs is a 16-bit integer setting (`debug_knobs`) in the Eden Emulator that serves as a bitmask for gating various testing and debugging features. This allows developers and advanced users to enable or disable specific debug behaviors without requiring deploying of complete but temporary toggles. - -The setting ranges from 0 to 65535 (0x0000 to 0xFFFF), where each bit represents a different debug feature flag. - -## Index - -1. [Advantages](#advantages) -2. [Usage](#usage) - - * [Accessing Debug Knobs (dev side)](#accessing-debug-knobs-dev-side) - * [Setting Debug Knobs (user side)](#setting-debug-knobs-user-side) - * [Bit Manipulation Examples](#bit-manipulation-examples) -3. [Terminology and user communication](#terminology-and-user-communication) -4. [Examples](#examples) - - * [Example 1: Conditional Debug Logging](#example-1-conditional-debug-logging) - * [Example 2: Performance Tuning](#example-2-performance-tuning) - * [Example 3: Feature Gating](#example-3-feature-gating) -5. [Best Practices](#best-practices) - ---- - -## Advantages - -The main advantage is to avoid deploying new disposable toggles (those made only for testing stage, and are disposed once new feature gets good to merge). This empowers devs to be free of all frontend burocracy and hassle of new toggles. - -Common advantages recap: - -* **Fine-Grained Control**: Enable or disable up to 16 individual debug features independently using bit manipulation on a single build -* **Runtime Configuration**: Change debug behavior at runtime the same way as new toggles would do -* **Safe incremental development**: New debug features can be added while impact can be isolated from previous deployments - -## Usage - -### Accessing Debug Knobs (dev side) - -Use the `Settings::getDebugKnobAt(u8 i)` function to check if a specific bit is set: - -```cpp -//cpp side -#include "common/settings.h" - -// Check if bit 0 is set -bool feature_enabled = Settings::getDebugKnobAt(0); - -// Check if bit 15 is set -bool another_feature = Settings::getDebugKnobAt(15); -``` - -```kts -//kotlin side -import org.yuzu.yuzu_emu.features.settings.model.Settings - -// Check if bit x is set -bool feature_enabled = Settings.getDebugKnobAt(x); //x as integer from 0 to 15 -``` - -The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise. - -### Setting Debug Knobs (user side) - -Developers must inform which knobs are tied to each functionality to be tested. - -The debug knobs value can be set through: - -1. **Desktop UI**: In the Debug configuration tab, there's a spinbox for "Debug knobs" (0-65535) -2. **Android UI**: Available as an integer setting in the Debug section -3. **Configuration Files**: Set the `debug_knobs` value in the emulator's configuration - -### Bit Manipulation Examples - -To enable specific features, calculate the decimal value by setting the appropriate bits: - -* **Enable only bit 0**: Value = 1 (2^0) -* **Enable only bit 1**: Value = 2 (2^1) -* **Enable bits 0 and 1**: Value = 3 (2^0 + 2^1) -* **Enable bit 15**: Value = 32768 (2^15) - -## Terminology and user communication - -There are two main confusions when talking about knobs: - -### Whether it's zero-based or one-based - -Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1). - -Debug knobs are **zero-based**, which means: -* The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise) -* You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc. - -### Whether one is talking about the knob itself or about the entire parameter value (which represents all knobs) - -Sometimes when an user reports: knob 3 results, it's unclear whether he's referring to knob setting with value 3 (which means both knob 0 and 1 are enabled), or to knob(3) specifically. -Whenever you're instructing tests or reporting results, be precise about whether one you're talking to avoid confusion: - -### Setting based terminology - -ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once: -Examples: -- **knobs=0**: no knobs enabled -- **knobs=1**: knob0 enabled, others disabled -- **knobs=2**: knob1 enabled, others disabled -- **knobs=3**: knobs 0 and 1 enabled, others disabled - -... - -### Knob based terminology - -Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once: -Examples: -- **knob0**: knob 0 enabled, others disabled -- **knob1**: knob 1 enabled, others disabled -- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled - -... - -## Examples - -### Example 1: Conditional Debug Logging - -```cpp -void SomeFunction() { - if (Settings::getDebugKnobAt(0)) { - LOG_DEBUG(Common, "Debug feature 0 is enabled"); - // Additional debug code here - } - - if (Settings::getDebugKnobAt(1)) { - LOG_DEBUG(Common, "Debug feature 1 is enabled"); - // Different debug behavior - } -} -``` - -### Example 2: Performance Tuning - -```cpp -bool UseOptimizedPath() { - // Skip optimization if debug bit 2 is set for testing - return !Settings::getDebugKnobAt(2); -} -``` - -### Example 3: Feature Gating - -```cpp -void ExperimentalFeature() { - static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3; - - if (!Settings::getDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) { - // Fallback to stable implementation - StableImplementation(); - return; - } - - // Experimental implementation - ExperimentalImplementation(); -} -``` - -## Best Practices - -* This setting is intended for development and testing purposes only -* Knobs must be unwired before PR creation -* The setting is per-game configurable, allowing different debug setups for different titles diff --git a/docs/user/Architectures.md b/docs/user/Architectures.md index 45f9e85c4f..a03dbc0da8 100644 --- a/docs/user/Architectures.md +++ b/docs/user/Architectures.md @@ -40,7 +40,7 @@ Windows/riscv64 doesn't exist, and may never (until corporate greed no longer co Android/riscv64 is interesting. While support for it may be added if and when RISC-V phones/handhelds ever go mainstream, arm64 devices will always be preferred due to NCE. -Only Fedora/riscv64 has been tested, but in theory, every riscv64 distribution that has *at least* the standard build tools, Qt, FFmpeg, and SDL2 should work. +Only Fedora/riscv64 has been tested, but in theory, every riscv64 distribution that has *at least* the standard build tools, Qt, FFmpeg, and SDL3 should work. ## Other diff --git a/docs/user/CFW.md b/docs/user/CFW.md index ea224d3d36..8f37672eb1 100644 --- a/docs/user/CFW.md +++ b/docs/user/CFW.md @@ -9,3 +9,11 @@ At the moment of writing, we do not support CFW such as Atmosphere, due to: We do however, maintain HLE compatibility with the former mentioned CFW, applications that require Atmosphere to run will run fine in the emulator without any adjustments. If they don't run - then that's a bug! + +## Atmosphere + +Fusee Galee, the bootloader and other low-level mechanisms are not emulated at the moment. + +Having OFW is recommended, but may not be required (untested). + +Extract the contents of Atmosphere into `sdmc`. Then to launch simply use `-hlaunch` instead (orthogonal to `-qlaunch`). diff --git a/docs/user/CommandLine.md b/docs/user/CommandLine.md index cd98d88b19..6e1f0f237c 100644 --- a/docs/user/CommandLine.md +++ b/docs/user/CommandLine.md @@ -1,17 +1,22 @@ # User Handbook - Command Line -There are two main applications, an SDL2 based app (`eden-cli`) and a Qt based app (`eden`); both accept command line arguments. +There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept command line arguments. ## eden + - `./eden `: Running with a single argument and nothing else, will make the emulator look for the given file and load it, this behaviour is similar to `eden-cli`; allows dragging and dropping games into the application. - `-g `: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument. - `-f`: Use fullscreen. - `-u `: Select the index of the user to load as. - `-input-profile `: Specifies input profile name to use (for player #0 only). - `-qlaunch`: Launch QLaunch. +- `-hlaunch`: Launch homebrew launcher `nx-hbloader`. + - Requires a copy of Atmosphere to be extracted onto `sdmc`. + - This is a shorthand for `/sdmc/atmosphere/hbl.nsp`. - `-setup`: Launch setup applet. ## eden-cli + - `--debug/-d`: Enter debug mode, allow gdb stub at port `1234` - `--config/-c`: Specify alternate configuration file. - `--fullscreen/-f`: Set fullscreen. diff --git a/docs/user/Multiplayer.md b/docs/user/Multiplayer.md index 57c4495f46..40e5dedc19 100644 --- a/docs/user/Multiplayer.md +++ b/docs/user/Multiplayer.md @@ -1,4 +1,4 @@ -# Multiplayer +# User Handbook - Multiplayer Use this guide to answer questions regarding and to start using the multiplayer functionality of Eden. ## Multiplayer FAQ diff --git a/docs/user/README.md b/docs/user/README.md index c1c4cd200a..0b1a434a37 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -25,12 +25,12 @@ A copy of this handbook is [available online](https://git.eden-emu.dev/eden-emu/ - **[Importing Saves](./ImportingSaves.md)** - **[Installing Atmosphere Mods](./InstallingAtmosphereMods.md)** - **[Installing Updates & DLCs](./InstallingUpdatesDLC.md)** -- **[Alter Date & Time](./AlterDateTime.md)** +- **[Multiplayer](./Multiplayer.md)** ## 3rd-party Integration - **[Configuring Steam ROM Manager](./SteamROM.md)** -- **[Server hosting](ServerHosting.md)** +- **[Server hosting](./ServerHosting.md)** - **[Syncthing Guide](./SyncthingGuide.md)** - **[Third Party](./ThirdParty.md)** - **[Obtainium](./ThirdParty.md#configuring-obtainium)** @@ -40,12 +40,13 @@ A copy of this handbook is [available online](https://git.eden-emu.dev/eden-emu/ ## Advanced +- **[Command Line](./CommandLine.md)** - **[Custom Firmware](./CFW.md)** +- **[Alter Date & Time](./AlterDateTime.md)** - **[How To Access Logs](./HowToAccessLogs.md)** - **[Gyro Controls](./GyroControls.md)** -- **[Platforms and Architectures](Architectures.md)** -- **[Command Line](CommandLine.md)** -- **[Native Application Development](Native.md)** -- **[Adding Boolean Settings Toggles](AddingBooleanToggles.md)** +- **[Platforms and Architectures](./Architectures.md)** +- **[Native Application Development](./Native.md)** +- **[Adding Boolean Settings Toggles](./AddingBooleanToggles.md)** - **[Adding Debug Knobs](./AddingDebugKnobs.md)** -- **[Testing](Testing.md)** +- **[Testing](./Testing.md)** diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index e230f709fa..13de8f567d 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -136,8 +136,7 @@ if(ENABLE_CUBEB) endif() if (NOT ANDROID) - if (YUZU_USE_EXTERNAL_SDL2) - message(STATUS "Using SDL2 from externals.") + if (NOT YUZU_USE_BUNDLED_SDL3) if (NOT WIN32) # Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers # Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095) @@ -158,21 +157,26 @@ if (NOT ANDROID) set(SDL_FILE ON) endif() - if ("${YUZU_SYSTEM_PROFILE}" STREQUAL "steamdeck") - set(SDL_PIPEWIRE OFF) # build errors out with this on - AddJsonPackage("sdl2_steamdeck") - else() - AddJsonPackage("sdl2_generic") - endif() - elseif (YUZU_USE_BUNDLED_SDL2) - message(STATUS "Using bundled SDL2") + AddJsonPackage(sdl3) + else() + message(STATUS "Using bundled SDL3") if (PLATFORM_FREEBSD) set(BUILD_SHARED_LIBS ON) endif() - AddJsonPackage(sdl2) + AddJsonPackage(sdl3-ci) endif() - find_package(SDL2 2.26.4 REQUIRED) + # Normalize SDL3 link target across package variants. + # Some SDL3 packages export only SDL3::SDL3-shared or SDL3::SDL3-static. + if (NOT TARGET SDL3::SDL3) + if (TARGET SDL3::SDL3-shared) + add_library(SDL3::SDL3 ALIAS SDL3::SDL3-shared) + elseif (TARGET SDL3::SDL3-static) + add_library(SDL3::SDL3 ALIAS SDL3::SDL3-static) + else() + message(FATAL_ERROR "SDL3 package found, but no usable SDL3 target was exported") + endif() + endif() endif() set(BUILD_SHARED_LIBS OFF) diff --git a/externals/cmake-modules/DetectPlatform.cmake b/externals/cmake-modules/DetectPlatform.cmake index 6475884f1f..de1a85b793 100644 --- a/externals/cmake-modules/DetectPlatform.cmake +++ b/externals/cmake-modules/DetectPlatform.cmake @@ -17,6 +17,10 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "SunOS") set(PLATFORM_SUN ON) +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenOrbis") + set(PLATFORM_PS4 ON) +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "managarm") + set(PLATFORM_MANAGARM ON) elseif (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") set(PLATFORM_FREEBSD ON) elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") diff --git a/externals/cpmfile.json b/externals/cpmfile.json index 0f8d34230c..4e9356cb83 100644 --- a/externals/cpmfile.json +++ b/externals/cpmfile.json @@ -30,7 +30,6 @@ "tag": "v%VERSION%", "hash": "5efa8140aadffe105dcf39935b732476e95755f6c7473ada3d0b64df2bc02c557633ae3948a25b45e1cf67e89a3ff6329fb30362e4ac033b9a1d1e453aa2eded", "git_version": "0.37.0", - "version": "0.18.7", "find_args": "MODULE GLOBAL", "patches": [ "0001-mingw.patch", @@ -123,13 +122,13 @@ "BUNDLE_SPEEX ON" ] }, - "sdl2": { + "sdl3-ci": { "ci": true, - "package": "SDL2", - "name": "SDL2", - "repo": "crueter-ci/SDL2", - "version": "2.32.10-3c28e8ecc0", - "min_version": "2.26.4" + "package": "SDL3", + "name": "SDL3", + "repo": "crueter-ci/SDL3", + "version": "3.4.8-d57c3b685c", + "min_version": "3.2.10" }, "catch2": { "package": "Catch2", @@ -157,22 +156,13 @@ "find_args": "MODULE", "git_version": "4.25" }, - "sdl2_generic": { - "package": "SDL2", + "sdl3": { + "package": "SDL3", "repo": "libsdl-org/SDL", "tag": "release-%VERSION%", - "hash": "d5622d6bb7266f7942a7b8ad43e8a22524893bf0c2ea1af91204838d9b78d32768843f6faa248757427b8404b8c6443776d4afa6b672cd8571a4e0c03a829383", - "bundled": true, - "git_version": "2.32.10", - "skip_updates": true - }, - "sdl2_steamdeck": { - "package": "SDL2", - "repo": "libsdl-org/SDL", - "sha": "cc016b0046", - "hash": "b8d9873446cdb922387471df9968e078714683046674ef0d0edddf8e25da65a539a3bae83d635496b970237f90b07b36a69f8d7855d450de59311d6d6e8c3dbc", - "bundled": true, - "skip_updates": "true" + "hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3", + "git_version": "3.4.8", + "version": "3.2.10" }, "moltenvk": { "repo": "V380-Ori/Ryujinx.MoltenVK", diff --git a/externals/demangle/llvm/Demangle/ItaniumDemangle.h b/externals/demangle/llvm/Demangle/ItaniumDemangle.h index 0dc3d73372..a2bac5d735 100644 --- a/externals/demangle/llvm/Demangle/ItaniumDemangle.h +++ b/externals/demangle/llvm/Demangle/ItaniumDemangle.h @@ -1259,7 +1259,7 @@ class ParameterPack final : public Node { // Setup OutputBuffer for a pack expansion, unless we're already expanding // one. void initializePackExpansion(OutputBuffer &OB) const { - if (OB.CurrentPackMax == std::numeric_limits::max()) { + if (OB.CurrentPackMax == (std::numeric_limits::max)()) { OB.CurrentPackMax = static_cast(Data.size()); OB.CurrentPackIndex = 0; } @@ -1353,7 +1353,7 @@ public: const Node *getChild() const { return Child; } void printLeft(OutputBuffer &OB) const override { - constexpr unsigned Max = std::numeric_limits::max(); + constexpr unsigned Max = (std::numeric_limits::max)(); ScopedOverride SavePackIdx(OB.CurrentPackIndex, Max); ScopedOverride SavePackMax(OB.CurrentPackMax, Max); size_t StreamPos = OB.getCurrentPosition(); diff --git a/externals/demangle/llvm/Demangle/Utility.h b/externals/demangle/llvm/Demangle/Utility.h index 30dfbfc8dc..0d2e613ea9 100644 --- a/externals/demangle/llvm/Demangle/Utility.h +++ b/externals/demangle/llvm/Demangle/Utility.h @@ -88,8 +88,8 @@ public: /// If a ParameterPackExpansion (or similar type) is encountered, the offset /// into the pack that we're currently printing. - unsigned CurrentPackIndex = std::numeric_limits::max(); - unsigned CurrentPackMax = std::numeric_limits::max(); + unsigned CurrentPackIndex = (std::numeric_limits::max)(); + unsigned CurrentPackMax = (std::numeric_limits::max)(); /// When zero, we're printing template args and '>' needs to be parenthesized. /// Use a counter so we can simply increment inside parentheses. diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index fcc48c71c4..2d85876ad2 100644 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -11,63 +11,105 @@ set(FFmpeg_HWACCEL_FLAGS) set(FFmpeg_HWACCEL_INCLUDE_DIRS) set(FFmpeg_HWACCEL_LDFLAGS) -if (UNIX AND NOT ANDROID) - find_package(PkgConfig REQUIRED) - if (NOT ANDROID) - pkg_check_modules(LIBVA libva) - pkg_check_modules(CUDA cuda) - pkg_check_modules(FFNVCODEC ffnvcodec) - pkg_check_modules(VDPAU vdpau) +if (NOT YUZU_USE_BUNDLED_FFMPEG) + set(FFmpeg_CROSS_COMPILE_FLAGS "") + if (ANDROID) + # TODO: Maybe use CMAKE_SYSROOT? and probably provide a toolchain file for android + # I mean isn't that the "proper" way anyways? + string(TOLOWER "${CMAKE_HOST_SYSTEM_NAME}" FFmpeg_HOST_SYSTEM_NAME) + set(TOOLCHAIN "${ANDROID_NDK}/toolchains/llvm/prebuilt/${FFmpeg_HOST_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}") + set(SYSROOT "${TOOLCHAIN}/sysroot") + set(FFmpeg_CPU "armv8-a") + list(APPEND FFmpeg_CROSS_COMPILE_FLAGS + --enable-cross-compile + --arch=arm64 + #--cpu=${FFmpeg_CPU} + --cross-prefix="${TOOLCHAIN}/bin/aarch64-linux-android-" + --sysroot="${SYSROOT}" + --target-os=android + --extra-ldflags="--ld-path=${TOOLCHAIN}/bin/ld.lld" + --extra-ldflags="-nostdlib" + ) + set(FFmpeg_IS_CROSS_COMPILING TRUE) + # User attempts to do a FFmpeg cross compilation because... + # Here we just quickly test against host/system processors not matching + # TODO: Test for versions not matching as well? + 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") + 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}" + ) + 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}-") + endif() + set(FFmpeg_IS_CROSS_COMPILING TRUE) endif() +endif() - if (NOT APPLE) - # In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so - if(PLATFORM_SUN) - find_library(LIBDRM_LIB libdrm PATHS /usr/lib/64 /usr/lib/amd64 /usr/lib) - if(LIBDRM_LIB) +if (PLATFORM_PS4 OR PLATFORM_MANAGARM) + # Doesn't support VA-API, don't go thru the embarrassment of trying to enable it + list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi) +elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING) + find_package(PkgConfig REQUIRED) + pkg_check_modules(LIBVA libva) + pkg_check_modules(CUDA cuda) + pkg_check_modules(FFNVCODEC ffnvcodec) + pkg_check_modules(VDPAU vdpau) + + find_package(X11) + if(X11_FOUND) + if (NOT APPLE) + # In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so + if(PLATFORM_SUN) list(APPEND FFmpeg_HWACCEL_LIBRARIES X11 - "${LIBDRM_LIB}") - message(STATUS "Found libdrm at: ${LIBDRM_LIB}") + "${CMAKE_SYSROOT}/usr/lib/xorg/amd64/libdrm.so") else() - message(WARNING "libdrm not found, disabling libdrm support") - list(APPEND FFmpeg_HWACCEL_FLAGS - --disable-libdrm) + pkg_check_modules(LIBDRM libdrm REQUIRED) + list(APPEND FFmpeg_HWACCEL_LIBRARIES + ${LIBDRM_LIBRARIES}) + list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS + ${LIBDRM_INCLUDE_DIRS}) endif() - else() - pkg_check_modules(LIBDRM libdrm REQUIRED) - list(APPEND FFmpeg_HWACCEL_LIBRARIES - ${LIBDRM_LIBRARIES}) - list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS - ${LIBDRM_INCLUDE_DIRS}) list(APPEND FFmpeg_HWACCEL_FLAGS --enable-libdrm) endif() - endif() - - if(LIBVA_FOUND) - find_package(X11 REQUIRED) - pkg_check_modules(LIBVA-DRM libva-drm REQUIRED) - pkg_check_modules(LIBVA-X11 libva-x11 REQUIRED) - list(APPEND FFmpeg_HWACCEL_LIBRARIES - ${X11_LIBRARIES} - ${LIBVA-DRM_LIBRARIES} - ${LIBVA-X11_LIBRARIES} - ${LIBVA_LIBRARIES}) - list(APPEND FFmpeg_HWACCEL_FLAGS - --enable-hwaccel=h264_vaapi - --enable-hwaccel=vp8_vaapi - --enable-hwaccel=vp9_vaapi) - list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS - ${X11_INCLUDE_DIRS} - ${LIBVA-DRM_INCLUDE_DIRS} - ${LIBVA-X11_INCLUDE_DIRS} - ${LIBVA_INCLUDE_DIRS} - ) - message(STATUS "ffmpeg: va-api libraries version ${LIBVA_VERSION} found") + if(LIBVA_FOUND) + pkg_check_modules(LIBVA-DRM libva-drm REQUIRED) + pkg_check_modules(LIBVA-X11 libva-x11 REQUIRED) + list(APPEND FFmpeg_HWACCEL_LIBRARIES + ${X11_LIBRARIES} + ${LIBVA-DRM_LIBRARIES} + ${LIBVA-X11_LIBRARIES} + ${LIBVA_LIBRARIES}) + list(APPEND FFmpeg_HWACCEL_FLAGS + --enable-hwaccel=h264_vaapi + --enable-hwaccel=vp8_vaapi + --enable-hwaccel=vp9_vaapi) + list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS + ${X11_INCLUDE_DIRS} + ${LIBVA-DRM_INCLUDE_DIRS} + ${LIBVA-X11_INCLUDE_DIRS} + ${LIBVA_INCLUDE_DIRS} + ) + message(STATUS "ffmpeg: va-api libraries version ${LIBVA_VERSION} found") + else() + list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi) + message(WARNING "ffmpeg: libva-dev not found, disabling Video Acceleration API (VA-API)...") + endif() else() list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi) - message(WARNING "ffmpeg: libva-dev not found, disabling Video Acceleration API (VA-API)...") + message(WARNING "ffmpeg: X11 libraries not found, disabling VA-API...") endif() if (FFNVCODEC_FOUND) @@ -111,6 +153,28 @@ if (UNIX AND NOT ANDROID) endif() endif() +if (PLATFORM_PS4) + list(APPEND FFmpeg_CROSS_COMPILE_LIBS + -lkernel + -lSceUserService + -lSceSysmodule + -lSceNet + -lSceLibcInternal + ) + list(APPEND FFmpeg_CROSS_COMPILE_FLAGS + --disable-pthreads + --extra-cflags=${CMAKE_SYSROOT}/usr/include + --extra-cxxflags=${CMAKE_SYSROOT}/usr/include + --extra-libs="${FFmpeg_CROSS_COMPILE_LIBS}" + ) +elseif (PLATFORM_MANAGARM) + # Required for proper stuff + list(APPEND FFmpeg_CROSS_COMPILE_FLAGS + --disable-pthreads + --extra-libs="${FFmpeg_CROSS_COMPILE_LIBS}" + ) +endif() + if (YUZU_USE_BUNDLED_FFMPEG) AddJsonPackage(ffmpeg-ci) @@ -181,24 +245,6 @@ else() find_program(BASH_PROGRAM bash REQUIRED) - set(FFmpeg_CROSS_COMPILE_FLAGS "") - if (ANDROID) - string(TOLOWER "${CMAKE_HOST_SYSTEM_NAME}" FFmpeg_HOST_SYSTEM_NAME) - set(TOOLCHAIN "${ANDROID_NDK}/toolchains/llvm/prebuilt/${FFmpeg_HOST_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}") - set(SYSROOT "${TOOLCHAIN}/sysroot") - set(FFmpeg_CPU "armv8-a") - list(APPEND FFmpeg_CROSS_COMPILE_FLAGS - --arch=arm64 - #--cpu=${FFmpeg_CPU} - --enable-cross-compile - --cross-prefix=${TOOLCHAIN}/bin/aarch64-linux-android- - --sysroot=${SYSROOT} - --target-os=android - --extra-ldflags="--ld-path=${TOOLCHAIN}/bin/ld.lld" - --extra-ldflags="-nostdlib" - ) - endif() - # `configure` parameters builds only exactly what yuzu needs from FFmpeg # `--disable-vdpau` is needed to avoid linking issues set(FFmpeg_CC ${CMAKE_C_COMPILER_LAUNCHER} ${CMAKE_C_COMPILER}) @@ -221,8 +267,12 @@ else() --enable-decoder=vp9 --enable-filter=yadif,scale --enable-pic - --cc="${FFmpeg_CC}" - --cxx="${FFmpeg_CXX}" + --cc=${FFmpeg_CC} + --cxx=${FFmpeg_CXX} + --ld=${CMAKE_LINKER} + --extra-cflags=${CMAKE_C_FLAGS} + --extra-cxxflags=${CMAKE_CXX_FLAGS} + --extra-ldflags=${CMAKE_C_LINK_FLAGS} ${FFmpeg_HWACCEL_FLAGS} ${FFmpeg_CROSS_COMPILE_FLAGS} WORKING_DIRECTORY diff --git a/externals/nx_tzdb/NxTzdbCreateHeader.cmake b/externals/nx_tzdb/NxTzdbCreateHeader.cmake index 95606d8629..229c8aa43d 100644 --- a/externals/nx_tzdb/NxTzdbCreateHeader.cmake +++ b/externals/nx_tzdb/NxTzdbCreateHeader.cmake @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2023 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later @@ -16,15 +19,24 @@ if (NOT FILE_LIST) endif() set(DIRECTORY_NAME ${HEADER_NAME}) - set(FILE_DATA "") + +string(APPEND FILE_DATA "[[nodiscard]] static inline std::vector CollectFiles_${DIRECTORY_NAME}() {\n") +string(APPEND FILE_DATA [[ + std::vector vfs_files; + auto const fn = [&](std::string_view name, std::span data) { + vfs_files.push_back(std::make_shared( + std::vector(data.begin(), data.end()), + std::string{name} + )); + }; +]]) foreach(ZONE_FILE ${FILE_LIST}) if (ZONE_FILE STREQUAL "\n") continue() endif() - - string(APPEND FILE_DATA "{\"${ZONE_FILE}\",\n{") - + string(APPEND FILE_DATA " {\n") + string(APPEND FILE_DATA " constexpr uint8_t tzdb_data[] = {\n") file(READ ${ZONE_PATH}/${ZONE_FILE} ZONE_DATA HEX) string(LENGTH "${ZONE_DATA}" ZONE_DATA_LEN) foreach(I RANGE 0 ${ZONE_DATA_LEN} 2) @@ -42,9 +54,12 @@ foreach(ZONE_FILE ${FILE_LIST}) string(APPEND FILE_DATA " ") endif() endforeach() - - string(APPEND FILE_DATA "}},\n") + string(APPEND FILE_DATA " };\n") + string(APPEND FILE_DATA " fn(\"${ZONE_FILE}\", tzdb_data);\n") + string(APPEND FILE_DATA " }\n") endforeach() +string(APPEND FILE_DATA " return vfs_files;\n") +string(APPEND FILE_DATA "}\n") file(READ ${NX_TZDB_SOURCE_DIR}/tzdb_template.h.in NX_TZDB_TEMPLATE_H_IN) file(CONFIGURE OUTPUT ${NX_TZDB_INCLUDE_DIR}/nx_tzdb/${HEADER_NAME}.h CONTENT "${NX_TZDB_TEMPLATE_H_IN}") diff --git a/externals/nx_tzdb/tzdb_template.h.in b/externals/nx_tzdb/tzdb_template.h.in index 289d002ea8..d7907b846d 100644 --- a/externals/nx_tzdb/tzdb_template.h.in +++ b/externals/nx_tzdb/tzdb_template.h.in @@ -9,10 +9,10 @@ namespace NxTzdb { +// @DIRECTORY_NAME@ + // clang-format off -const static std::map> @DIRECTORY_NAME@ = -{ -@FILE_DATA@}; +@FILE_DATA@ // clang-format on } // namespace NxTzdb diff --git a/externals/tz/tz/tz.cpp b/externals/tz/tz/tz.cpp index 04fa6cc8a9..3269d9cc1e 100644 --- a/externals/tz/tz/tz.cpp +++ b/externals/tz/tz/tz.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 1996 Arthur David Olson // SPDX-License-Identifier: BSD-2-Clause @@ -466,8 +469,8 @@ CalendarTimeInternal* timesub(const time_t* timep, s64 offset, const Rule* sp, int signed_y = static_cast(y); tmp->tm_year = signed_y - TM_YEAR_BASE; } - else if ((!std::is_signed_v || std::numeric_limits::min() + TM_YEAR_BASE <= y) && - y - TM_YEAR_BASE <= std::numeric_limits::max()) { + else if ((!std::is_signed_v || (std::numeric_limits::min)() + TM_YEAR_BASE <= y) && + y - TM_YEAR_BASE <= (std::numeric_limits::max)()) { tmp->tm_year = static_cast(y - TM_YEAR_BASE); } else { @@ -558,8 +561,8 @@ CalendarTimeInternal* localsub(Rule const* sp, time_t const* timep, s64 setname, else { newy += years; } - if (!(std::numeric_limits::min() <= newy && - newy <= std::numeric_limits::max())) { + if (!((std::numeric_limits::min)() <= newy && + newy <= (std::numeric_limits::max)())) { return nullptr; } result->tm_year = static_cast(newy); diff --git a/externals/tz/tz/tz.h b/externals/tz/tz/tz.h index dae4459bcb..e70092a987 100644 --- a/externals/tz/tz/tz.h +++ b/externals/tz/tz/tz.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 1996 Arthur David Olson // SPDX-License-Identifier: BSD-2-Clause @@ -26,10 +29,10 @@ constexpr size_t TZ_MAX_CHARS = 50; constexpr size_t MY_TZNAME_MAX = 255; constexpr size_t TZNAME_MAXIMUM = 255; constexpr size_t TZ_MAX_LEAPS = 50; -constexpr s64 TIME_T_MAX = std::numeric_limits::max(); -constexpr s64 TIME_T_MIN = std::numeric_limits::min(); +constexpr s64 TIME_T_MAX = (std::numeric_limits::max)(); +constexpr s64 TIME_T_MIN = (std::numeric_limits::min)(); constexpr size_t CHARS_EXTRA = 3; -constexpr size_t MAX_ZONE_CHARS = std::max(TZ_MAX_CHARS + CHARS_EXTRA, sizeof("UTC")); +constexpr size_t MAX_ZONE_CHARS = (std::max)(TZ_MAX_CHARS + CHARS_EXTRA, sizeof("UTC")); constexpr size_t MAX_TZNAME_CHARS = 2 * (MY_TZNAME_MAX + 1); struct ttinfo { @@ -51,7 +54,7 @@ struct Rule { std::array ats; std::array types; std::array ttis; - std::array chars; + std::array chars; s32 defaulttype; std::array padding1; }; diff --git a/shell.nix b/shell.nix index d8257b88d9..5023cc88a3 100644 --- a/shell.nix +++ b/shell.nix @@ -16,7 +16,7 @@ pkgs.mkShellNoCC { qt6.qtbase qt6.qtmultimedia qt6.qtwayland qt6.qttools qt6.qtwebengine qt6.qt5compat # eden-cli - SDL2 + SDL3 # optional components discord-rpc gamemode ]; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ec064a0e34..58ae00e8d9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -159,7 +159,10 @@ else() endif() if (ARCHITECTURE_x86_64) - add_compile_options(-mcx16) + add_compile_options($<$:-mcx16>) + if (PLATFORM_LINUX OR PLATFORM_FREEBSD) + add_compile_options($<$:-mtls-dialect=gnu2>) + endif() endif() if (APPLE AND CXX_CLANG) @@ -185,7 +188,7 @@ else() add_compile_definitions(_FILE_OFFSET_BITS=64) endif() - if (YUZU_STATIC_BUILD AND NOT APPLE) + if (YUZU_STATIC_BUILD AND NOT APPLE AND NOT MSVC) add_compile_options(-static) # yuzu-cmd requires us to explicitly link libpthread, libgcc, and libstdc++ as static diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 0519ac956d..ccc5b3cabe 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -192,12 +192,6 @@ android { manifestPlaceholders += mapOf("appNameBase" to "Eden") resValue("string", "app_name_suffixed", "Eden") - externalNativeBuild { - cmake { - arguments.add("-DGENSHIN_SPOOF=ON") - } - } - ndk { abiFilters += listOf("arm64-v8a") } @@ -209,6 +203,12 @@ android { resValue("string", "app_name_suffixed", "Eden Optimized") applicationId = "com.miHoYo.Yuanshen" + externalNativeBuild { + cmake { + arguments.add("-DGENSHIN_SPOOF=ON") + } + } + ndk { abiFilters += listOf("arm64-v8a") } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index e482725196..eccf0549e7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -18,6 +18,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"), FIX_BLOOM_EFFECTS("fix_bloom_effects"), EMULATE_BGR565("emulate_bgr565"), + RESCALE_HACK("rescale_hack"), CPUOPT_UNSAFE_HOST_MMU("cpuopt_unsafe_host_mmu"), USE_DOCKED_MODE("use_docked_mode"), USE_AUTO_STUB("use_auto_stub"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index d1702b8140..60e2a89564 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt @@ -67,7 +67,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting { MY_PAGE_APPLET("my_page_applet_mode"), INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide"), OVERLAY_GRID_SIZE("overlay_grid_size"), - GPU_LOG_RING_BUFFER_SIZE("gpu_log_ring_buffer_size") + GPU_LOG_RING_BUFFER_SIZE("gpu_log_ring_buffer_size"), + ANDROID_PIPELINE_WORKERS("pipeline_worker_count") ; override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index db2edba1b9..230390749e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -582,6 +582,16 @@ abstract class SettingsItem( units = "%" ) ) + put( + SliderSetting( + IntSetting.ANDROID_PIPELINE_WORKERS, + titleId = R.string.pipeline_worker_cores, + descriptionId = R.string.pipeline_worker_cores_description, + min = 4, + max = 8, + units = "cores" + ) + ) put( SingleChoiceSetting( IntSetting.RENDERER_ANTI_ALIASING, @@ -747,6 +757,13 @@ abstract class SettingsItem( descriptionId = R.string.fix_bloom_effects_description ) ) + put( + SwitchSetting( + BooleanSetting.RESCALE_HACK, + titleId = R.string.rescale_hack, + descriptionId = R.string.rescale_hack_description + ) + ) put( SwitchSetting( BooleanSetting.EMULATE_BGR565, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index ccbe808cf6..4da218bfcc 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -293,7 +293,9 @@ class SettingsFragmentPresenter( add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key) add(BooleanSetting.FIX_BLOOM_EFFECTS.key) add(BooleanSetting.EMULATE_BGR565.key) + add(BooleanSetting.RESCALE_HACK.key) add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key) + add(IntSetting.ANDROID_PIPELINE_WORKERS.key) add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key) add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key) add(SettingsItem.GPU_UNSWIZZLE_COMBINED) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/AddonUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/AddonUtil.kt index b5613a7a76..60bdaefe9b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/AddonUtil.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/AddonUtil.kt @@ -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: 2023 yuzu Emulator Project @@ -7,5 +7,5 @@ package org.yuzu.yuzu_emu.utils object AddonUtil { - val validAddonDirectories = listOf("cheats", "exefs", "romfs", "romfslite") + val validAddonDirectories = listOf("cheats", "exefs", "romfs", "romfslite", "romfs_ext") } diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index 37da651519..304ff62f07 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h @@ -147,6 +147,13 @@ namespace AndroidSettings { &show_performance_overlay}; + Settings::Setting pipeline_worker_count{linkage, 4, "pipeline_worker_count", + Settings::Category::Android, + Settings::Specialization::Default, + true, + true}; + + Settings::Setting show_input_overlay{linkage, true, "show_input_overlay", Settings::Category::Overlay}; Settings::Setting overlay_snap_to_grid{linkage, false, "overlay_snap_to_grid", diff --git a/src/android/app/src/main/res/drawable/ic_launcher_foreground.png b/src/android/app/src/main/res/drawable/ic_launcher_foreground.png index 53f1cace9b..e7800a9bb9 100644 Binary files a/src/android/app/src/main/res/drawable/ic_launcher_foreground.png and b/src/android/app/src/main/res/drawable/ic_launcher_foreground.png differ diff --git a/src/android/app/src/main/res/drawable/ic_yuzu.png b/src/android/app/src/main/res/drawable/ic_yuzu.png index fce02afa1f..27ac821833 100644 Binary files a/src/android/app/src/main/res/drawable/ic_yuzu.png and b/src/android/app/src/main/res/drawable/ic_yuzu.png differ diff --git a/src/android/app/src/main/res/drawable/ic_yuzu_splash.png b/src/android/app/src/main/res/drawable/ic_yuzu_splash.png index 0e43cb9374..7f20f404ff 100644 Binary files a/src/android/app/src/main/res/drawable/ic_yuzu_splash.png and b/src/android/app/src/main/res/drawable/ic_yuzu_splash.png differ diff --git a/src/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/src/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 23bc2897c3..d7b2e938df 100644 Binary files a/src/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/src/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/src/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index f630e793e3..7f60994e50 100644 Binary files a/src/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/src/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/src/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 1daa3c624f..37e436f599 100644 Binary files a/src/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/src/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/src/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 7fc64e1393..e3e679367d 100644 Binary files a/src/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/src/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/src/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 53ed9b9914..e358e1ebb7 100644 Binary files a/src/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/src/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/android/app/src/main/res/values-ar/strings.xml b/src/android/app/src/main/res/values-ar/strings.xml index 3fc4c0c139..08d56dea49 100644 --- a/src/android/app/src/main/res/values-ar/strings.xml +++ b/src/android/app/src/main/res/values-ar/strings.xml @@ -66,6 +66,8 @@ عرض استهلاك الطاقة الحالي والسعة المتبقية في البطارية عرض بناء التظليل عرض عدد التظليل التي يتم بناؤها + خيوط عامل خط الأنابيب + قم بإدارة عدد النوى المستخدمة لبناء خطوط أنابيب Vulkan، فالقيمة الأعلى ستحسن أداء تجميع خط الأنابيب ولكن درجات الحرارة سترتفع أيضًا. موضع الطبقة حدد مكان عرض الطبقة على الشاشة أعلى اليمين @@ -485,15 +487,15 @@ إجبار السرعة القصوى (لأجهزة Adreno فقط) يجبر وحدة معالجة الرسومات على العمل بأقصى سرعة ممكنة (سيظل يتم تطبيق القيود الحرارية). محاكاة غير متزامنة لوحدة معالجة الرسومات - يُشغّل هذا الخيار محاكاة وحدة معالجة الرسومات بشكل غير متزامن لتقليل توقف وحدة المعالجة المركزية وتحسين الإنتاجية. عطّل هذا الخيار فقط في حال واجهت مشاكل متعلقة بالتوقيت. + يمكن لهذه الحيلة أن تزيد الأداء عن طريق تشغيل محاكاة وحدة معالجة الرسومات بشكل غير متزامن على حساب مشاكل الرسومات وزيادة معدلات الأعطال بسبب العمليات المتعلقة بالتوقيت. عرض غير متزامن - يحسّن الأداء بشكل طفيف عن طريق نقل عملية العرض إلى خيط معالجة منفصل لوحدة المعالجة المركزية. + يمكن لهذه الحيلة أن تزيد من الأداء عن طريق نقل عملية العرض إلى خيط معالجة منفصل على حساب مشاكل الرسوميات. استخدم التنظيف التفاعلي يحسن دقة العرض في بعض الألعاب على حساب الأداء. تمكين سجل التخزين المؤقت يُتيح هذا الخيار الوصول إلى حالات التخزين المؤقت السابقة. وقد يُحسّن جودة العرض وثبات الأداء في بعض الألعاب. مخازن الرؤوس المُحسّنة - يتيح ربط مخزن الرؤوس المُحسّن لتحسين الأداء. يتطلب برامج تشغيل Turnip/QCOM من إصدار Mesa 26.0 أو أحدث. سيؤدي إلى تعطل النظام عند استخدام برامج تشغيل Turnip الأقدم. + يُتيح ربطًا مُحسَّنًا لمخازن الرؤوس لتحسين الأداء. يتطلب برامج تشغيل Mesa 26.0+ Turnip/ برامج تشغيل QCOM. قد يتعطل على برامج تشغيل Turnip القديمة (25.3 وما دون). اختراقات @@ -505,6 +507,8 @@ يقلل من ضبابية التوهج في LA/EOW (Adreno A6XX - A7XX/ Turnip)، ويزيل التوهج في Burnout. تحذير: قد يسبب تشوهات رسومية في ألعاب أخرى. محاكاة BGR565 يُصلح مشاكل انعكاس الألوان في الألعاب أو ظهور تشوهات غريبة أو ظلال غريبة. + تفعيل ميزة إعادة التحجيم القديمة + يُمكّن هذا الخيار من التعامل مع عملية إعادة تحجيم الألعاب بطريقة تقليدية باستخدام مسار إعادة التحجيم السريع استخدم تظليل غير متزامن يقوم بتجميع التظليل بشكل غير متزامن. قد يقلل ذلك من التقطعات ولكنه قد يؤدي أيضًا إلى حدوث أخطاء. إعدادات إلغاء ترتيب بيانات وحدة معالجة الرسومات @@ -523,10 +527,10 @@ إضافات الحالة الديناميكية الموسعة - يتحكم هذا الخيار في عدد الميزات التي يمكن استخدامها في حالة الديناميكية الموسعة. تسمح الأرقام الأعلى بمزيد من الميزات ويمكن أن تزيد من الأداء، ولكنها قد تسبب مشاكل مع بعض برامج التشغيل والأجهزة. + يتحكم هذا الخيار في عدد الميزات التي يمكن استخدامها في ExtendedDynamicState (EDS). كلما زادت القيمة، قلّ عدد عمليات تجميع خط الأنابيب بناءً على الحالة الديناميكية التي يدعمها برنامج التشغيل. معطل حالة ديناميكية لإدخال الرأس - يتيح ميزة الحالة الديناميكية لإدخال الرأس لتحسين الجودة والأداء. + يتيح تمكين هذه الميزة معالجة أكثر مرونة لمدخلات الرؤوس، مما قد يقلل من وقت تجميع خط الأنابيب في vertex/buffer. تظليل العينة يسمح هذا الخيار بتنفيذ مُظلل الأجزاء لكل عينة في جزء متعدد العينات بدلاً من تنفيذه مرة واحدة لكل جزء. يُحسّن هذا من جودة الرسومات على حساب بعض الأداء. diff --git a/src/android/app/src/main/res/values-cs/strings.xml b/src/android/app/src/main/res/values-cs/strings.xml index 4f7d98057c..3354830611 100644 --- a/src/android/app/src/main/res/values-cs/strings.xml +++ b/src/android/app/src/main/res/values-cs/strings.xml @@ -478,10 +478,8 @@ Rozšíření Úroveň EDS - Určuje počet funkcí využívaných v rámci rozšířeného dynamického stavu API Vulkan (Extended Dynamic State). Vyšší hodnoty umožňují využít více funkcí a mohou zvýšit výkon, ale u některých ovladačů a výrobců grafických karet mohou způsobovat problémy s kompatibilitou. Vypnuto Dynamický stav vstupu vrcholů (Vertex Input) - Aktivuje funkci dynamického stavu vstupu vrcholů (Vertex Input Dynamic State) pro lepší kvalitu a výkon. Zobrazení Orientace diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index d2988d2b27..e7246dae2c 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml @@ -476,10 +476,8 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die Erweiterungen Erweiterter dynamischer Status - Steuert die Anzahl der Funktionen, die im \"Vertex Input Dynamic State\" werden können. Höhere Werte ermöglichen mehr Funktionen und können die Leistung steigern, können aber bei einigen Treibern und Anbietern zu Problemen führen. Deaktiviert Vertex Input Dynamic State - Aktiviert die Funktion \"Vertex Input Dynamic State\" für bessere Qualität und Leistung. Sample Shading Ermöglicht es dem Fragment-Shader, in einem Multisample-Fragment pro Sample anstatt einmal pro Fragment ausgeführt zu werden. Verbessert die Grafikqualität auf Kosten der Leistung. diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 35257c2beb..167ffe6ff1 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -66,6 +66,8 @@ Muestra el consumo de energía actual y la capacidad restante de la batería Mostrar construcción de sombreadores Muestra el número actual de sombreadores que se están construyendo + Hilos de trabajo de canalización + Gestiona la cantidad de núcleos usados para la construcción de canalizaciones de Vulkan; un valor más alto mejorará el rendimiento de la contrucción de canalización, pero también aumentarán las temperaturas. Posición de la superposición Elige dónde se muestra la superposición en la pantalla Superior izquierda @@ -479,15 +481,15 @@ Forzar velocidad al máximo (solo Adreno) Fuerza a la GPU a ejecutarse a la velocidad máxima de reloj posible (se seguirán aplicando restricciones térmicas). Emulación de GPU asíncrona - Ejecuta la emulación de la GPU de forma asíncrona para reducir los bloqueos de la CPU y mejorar el rendimiento. Desactiva esta opción solo si experimentas problemas de sincronización. + Este hack puede aumentar el rendimiento ejecutando la emulación de la GPU de forma asíncrona, a costa de problemas gráficos y un aumento en la tasa de fallos debido a operaciones relacionadas con la sincronización. Presentación asíncrona - Mejora ligeramente el rendimiento al mover la presentación a un hilo independiente de la CPU. + Este hack puede aumentar el rendimiento al mover la presentación a un hilo independiente de la CPU a costa de problemas gráficos. Usar limpieza reactiva Mejora la precisión de renderizado en algunos juegos, pero reduce el rendimiento. Activar el historial del búfer Permite el acceso al estado del búfer anterior. Esta opción puede mejorar la calidad de renderizado y la consistencia en el rendimiento de algunos juegos. Búferes de vértices optimizados - Permite la optimización del enlace del búfer de vértices para un mejor rendimiento. Requiere controladores Mesa 26.0+ Turnip/ controladores QCOM. Causará fallos con controladores Turnip más antiguos. + Permite la optimización del enlace del búfer de vértices para un mejor rendimiento. Requiere controladores Mesa 26.0+ Turnip/ controladores QCOM. Fallará con controladores Turnip más antiguos (versión 25.3 o inferior). Hacks @@ -499,6 +501,8 @@ Reduce el efecto de resplandor en LA/EOW (Adreno A6XX - A7XX/ Turnip), elimina el resplandor en Burnout. Advertencia: puede causar artefactos gráficos en otros juegos. Emular BGR565 Soluciona problemas con colores invertidos en juegos, artefactos o sombras extrañas. + Activar la pasada de reescalado heredada + Permite el manejo de versiones anteriores para el paso de configuración de reescalado para juegos mediante el uso de una ruta de reescalado rápida. Usar sombreadores asíncronos Compila los sombreadores de forma asíncrona. Esto puede reducir los tirones, pero también puede introducir errores gráficos. Ajustes de desentrelazado de la GPU @@ -517,10 +521,10 @@ Extensiones Estado dinámico extendido - Controla la cantidad de funciones que se pueden usar en el Estado Dinámico Extendido. Un número mayor permite más funciones y puede aumentar el rendimiento, pero puede causar problemas con algunos controladores y proveedores. + Controla la cantidad de características que se pueden usar en ExtendedDynamicState (EDS). Un valor más alto permitirá reducir la cantidad de compilaciones de canalización basadas en el estado dinámico compatible con el controlador. Desactivado Estado dinámico de entrada de vértices - Activa la función de estado dinámico de entrada de vértices para una mejor calidad y rendimiento. + La activación de esta función permite un manejo más flexible de la entrada de vértices, lo que podría reducir el tiempo de compilación de la canalización en vértices/búfer. Muestreo de sombreado Permite que el sombreador de fragmentos se ejecute por muestra en un fragmento multimuestreado, en lugar de una sola vez por fragmento. Mejora la calidad de los gráficos a coste de algo de rendimiento. diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml index df16737dec..d7f77337cf 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml @@ -413,7 +413,9 @@ Limiter le pourcentage de vitesse Spécifier le pourcentage pour limiter la vitesse d\'émulation. 100% correspond à la vitesse normale. Des valeurs plus élevées ou plus basses augmenteront ou diminueront la limite de vitesse. Vitesse turbo + Lorsque le mode Turbo est activé, l\'émulation s\'exécute à cette vitesse. Vitesse lente + Lorsque le mode Lent est activé, l\'émulation s\'exécute à cette vitesse. Backend du CPU Précision du CPU %1$s%2$s @@ -481,7 +483,6 @@ État dynamique étendu Désactivé État dynamique d\'entrée de sommet - Active la fonctionnalité d\'état dynamique des entrées de sommets pour une meilleure qualité et de meilleures performances. Affichage Orientation diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml index ac09657d6c..40a2035200 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml @@ -476,10 +476,8 @@ Rozszerzenia Rozszerzony stan dynamiczny - Kontroluje liczbę funkcji, które mogą być używane w Extended Dynamic State. Wyższe wartości pozwalają na użycie większej liczby funkcji i mogą zwiększyć wydajność, ale mogą powodować problemy z niektórymi sterownikami i u niektórych producentów. Wyłączone Dynamiczny stan wejścia wierzchołków - Włącza funkcję dynamicznego stanu wejścia wierzchołków, poprawiając jakość i wydajność. Cieniowanie próbkowane Pozwala uruchamiać shader fragmentów dla każdej próbki w wielopróbkowanym fragmencie zamiast raz na fragment. Poprawia jakość grafiki kosztem części wydajności. diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml index d87d6a15e6..e631d3566f 100644 --- a/src/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml @@ -463,7 +463,6 @@ Extended Dynamic State Desativado Vertex Input Dynamic State - Ativa o recurso de vertex input dynamic state para melhor qualidade e desempenho. Tela Orientação diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index caa30851a0..4e5f2149e8 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml @@ -66,6 +66,8 @@ Показать текущее энергопотребление и оставшуюся емкость аккумулятора Показать компиляцию шейдеров Отображает текущее количество шейдеров, которые компилируются + Рабочие потоки конвейера + Позволяет настроить количество ядер, задействованных при построении конвейеров Vulkan. Чем выше значение, тем быстрее будет компиляция конвейеров, однако это приведёт к повышению температуры устройства. Позиция оверлея Расположение оверлея на экране Сверху слева @@ -481,14 +483,16 @@ Принудительная максимальная тактовая частота (только для Adreno) Заставляет ГПУ работать на максимально возможных тактовых частотах (тепловые ограничения все равно будут применяться). Асинхронная эмуляция ГПУ - Выполняет эмуляцию ГПУ асинхронно для снижения задержек ЦП и увеличения производительности. Отключайте только при возникновении проблем с таймингами. + Может повысить производительность за счёт асинхронного запуска эмуляции ГПУ, но ценой появления графических ошибок и увеличения частоты вылетов из-за операций, зависящих от синхронизации. Асинхронная презентация - Немного улучшает производительность, перемещая презентацию в отдельный поток ЦП. + Может повысить производительность за счёт перемещения вывода кадров в отдельный поток ЦП, но ценой возникновения графических проблем. Реактивная очистка Повышение точности рендеринга в некоторых играх за счет снижения производительности. Включить историю буфера Позволяет обращаться к предыдущим состояниям буфера. Эта опция может повысить качество рендеринга и стабильность производительности в некоторых играх. Оптимизированные вершинные буферы + Включает оптимизированную привязку вершинного буфера для повышения производительности. Требует Mesa Turnip 26.0+ / QCOM. Приводит к вылету на старых версиях драйверов Turnip (25.3 и ниже). + Хаки Быстрое время ГПУ @@ -496,6 +500,11 @@ Пропустить внутреннюю инвалидацию ЦП Пропускает некоторые инвалидации кэша на стороне ЦП при обновлениях памяти, уменьшая нагрузку на процессор и повышая производительность. Может вызывать сбои в некоторых играх. Исправить эффекты размытия + Частично убирает размытие в LA/EOW (Adreno A6XX - A7XX/ Turnip), полностью отключает его в Burnout. Внимание: может вызывать графические артефакты в других играх. + Эмулировать BGR565 + Исправляет проблемы с инвертированными цветами в играх, а также со странными артефактами или некорректными тенями. + Включить старый метод изменения разрешения + Включает старый метод обработки этапа перенастройки масштабирования для игр за счёт использования быстрого алгоритма перемасштабирования. Использовать асинхронные шейдеры Компилирует шейдеры асинхронно. Это может уменьшить подтормаживания, но также может вызвать графические артефакты. Настройки распаковки текстур (Unswizzle) @@ -514,10 +523,10 @@ Расширения Расширенное динамическое состояние - Управляет количеством функций, доступных в режиме «Расширенное динамическое состояние». Большее число позволяет задействовать больше функций и может повысить производительность, но способно вызывать проблемы с некоторыми драйверами и графикой. + Управляет количеством функций, доступных для использования в расширенном динамическом состояние. Более высокое значение позволит сократить количество компиляций конвейеров за счёт поддержки динамического состояния драйвером. Отключено Динамическое состояние ввода вершин - Включает функцию динамического состояния ввода вершин для повышения качества и производительности + Включение этой функции обеспечивает более гибкую обработку входных данных вершин, что потенциально сокращает время компиляции конвейера на этапе работы с вершинами и буферами. Сэмпловое затенение Позволяет шейдеру фрагментов выполняться для каждого сэмпла в многосэмпловом фрагменте, а не один раз на фрагмент. Улучшает качество графики ценой некоторого падения производительности. @@ -1150,4 +1159,6 @@ Высококачественное масштабирование от AMD Дополнительный контент Добавить папку - + %1$d %% + + diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index 3338d48d68..cbf4211bac 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml @@ -66,6 +66,8 @@ Показати поточне споживання енергії та залишкову ємність акумулятора Показати побудову шейдерів Показати поточну кількість шейдерів, які наразі компілюються + Потоки обробника конвеєра + Керує кількістю ядер, що використовуються для збірки конвеєрів Vulkan. Вище значення покращить продуктивність компіляції конвеєра, але також підвищить температуру. Позиція оверлею Обрати розташування виводу статистики на екрані Вгорі ліворуч @@ -481,15 +483,15 @@ Максимальна тактова частота (тільки Adreno) Змушує GPU працювати на максимальній тактовій частоті. Асинхронна емуляція ГП - Емуляція ГП виконується асинхронно для зменшення затримок ЦП й покращення пропускної здатності. Вимкніть лише у випадку виникнення проблем із таймінгами. + Це обхідне рішення може покращити продуктивність завдяки асинхронному виконанню емуляції ГП, але спричинить проблеми з графікою та збільшить частоту збоїв чутливих до таймінгів операцій. Асинхронне подання - Трохи покращує продуктивність завдяки переміщенню подання на окремий потік ЦП. + Це обхідне рішення може покращити продуктивність завдяки переміщенню подання на окремий потік ЦП, але спричинить проблеми з графікою. Реактивне очищення Покращує точність рендерингу в деяких іграх. Увімкнути історію буфера Вмикає доступ до попередніх станів буфера. Цей параметр може покращити якість візуалізації та стабільну продуктивність у деяких іграх. Оптимізовані буфери вершин - Застосовує оптимізований буфер вершин, щоб покращити продуктивність. Потребує драйверів Mesa 26.0+ Turnip / QCOM. На старіших драйверах Turnip виникатиме збій. + Застосовує оптимізований буфер вершин, щоб покращити продуктивність. Потребує драйверів Mesa 26.0+ Turnip / QCOM. На старіших драйверах Turnip виникатиме збій (25.3 і нижче). Обхідні рішення @@ -501,6 +503,8 @@ Зменшує розмиття світіння в LA/EOW (Adreno A6XX–A7XX / Turnip), прибирає світіння в Burnout. Увага: може спричинити графічні артефакти в інших іграх. Емулювати BGR565 Виправляє проблеми з інвертованими кольорами в іграх або дивними артефактами чи тінями. + Увімкнути застаріле масштабування + Вмикає застарілу обробку масштабування для ігор, використовуючи швидкий шлях масштабування Асинхронні шейдери Компілює шейдери асинхронно. Це може зменшити затримки, але також може спричинити графічні баги. Налаштування розпакування за допомогою ГП @@ -519,10 +523,10 @@ Розширення Розширений динамічний стан - Керує кількістю функцій, які можна використовувати в «Розширеному динамічному стані». Вище число дозволяє більше функцій і може покращити продуктивність, але може спричинити проблеми з деякими драйверами й виробниками. + Керує кількістю функцій, які можна використовувати в ExtendedDynamicState (EDS). Вище значення дозволить зменшити кількість компіляцій конвеєра на основі динамічного стану, підтримуваного драйвером. Вимкнено Динамічний стан введення вершин - Вмикає можливість динамічного стану введення вершин для кращих якості й продуктивності. + Увімкнення цієї функції дозволить гнучкішу обробку введення вершин. Може зменшити час компіляції конвеєра для вершин/буфера. Простий шейдинг Дозволяє виконувати фрагмент шейдера для кожного зразка в багатозразковому фрагменті замість одного разу для кожного фрагмента. Покращує якість графікі ціною втрати продуктивності. diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index 7777b40767..e9ea040ffb 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml @@ -66,6 +66,8 @@ 显示当前功耗和电池剩余容量 显示着色器编译信息 显示当前正在编译的着色器数量 + 管线工作线程 + 管理用于构建 Vulkan 管线的核心数量,数值越高则管线编译性能越好,但温度也会随之升高。 叠加层位置 选择叠加层在屏幕上显示的位置 左上 @@ -473,17 +475,17 @@ 磁盘着色器缓存 将生成的着色器缓存于磁盘中并进行读取,以减少卡顿。 强制最大时钟 (仅限 Adreno) - 强制 GPU 以最大时钟运行 (仍被温控限制)。 + 强制 GPU 以最大时钟运行 (温控依然生效)。 GPU 异步模拟 - 异步运行 GPU 模拟,以减少 CPU 停顿并提高吞吐量。仅当遇到与时序相关的问题时才应禁用此功能。 + 此技巧可通过异步运行 GPU 模拟来提升性能,但在执行与时序相关的操作时,可能引入图形问题和增加崩溃概率。 异步呈现 - 通过将呈现操作移至单独的 CPU 线程来略微提升性能。 + 此技巧通过将图形呈现移至独立的 CPU 线程来提升性能,但可能会引入图形显示问题。 启用反应性刷新 通过牺牲性能来提高某些游戏的渲染精度。 启用缓冲区历史 启用对先前缓冲区状态的访问。此选项可在某些游戏中提升渲染质量并保持性能的一致性。 优化顶点缓冲区 - 启用经过优化的顶点缓冲区绑定以提升性能。需要 Mesa 26.0 及以上版本的 Turnip 或 QCOM 驱动程序。若使用较旧版本的 Turnip 驱动则会导致崩溃。 + 启用经过优化的顶点缓冲区绑定以提升性能。需要 Mesa 26.0 及以上版本的 Turnip 或 QCOM 驱动程序。若使用较旧版本的 Turnip 驱动 (25.3 及以下版本) 则会导致崩溃。 Hacks @@ -495,6 +497,8 @@ 减少《塞尔达传说:智慧的再现》(Adreno A6XX - A7XX/ Turnip)中的 bloom 模糊,并移除《Burnout》中的 bloom 效果。警告:可能会导致在其他游戏中出现图形异常。 模拟 BGR565 修复了游戏中的颜色反转以及出现的异常画面瑕疵或奇怪阴影问题 + 启用旧版缩放处理 + 启用通过使用快速缩放路径,来为游戏提供缩放配置处理的传统处理方式 使用异步着色器 异步编译着色器。这可能会减少卡顿,但也可能会导致图形错误。 GPU 还原设置 @@ -513,10 +517,10 @@ 扩展 扩展动态状态 - 控制在扩展动态状态中可使用的函数数量。更高的数值允许启用更多功能,并可能提升性能,但同时也可能导致额外的图形问题。 + 控制扩展动态状态 (EDS) 可使用的功能数目。较高的数值将允许根据驱动程序支持的动态状态来减少管线编译的次数。 已禁用 顶点输入动态状态 - 开启顶点输入动态状态功能来获得更好的质量和性能。 + 启用此功能可实现更灵活的顶点输入处理,可能减少顶点/缓冲区的管线编译时间。 采样着色 允许片段着色器在多采样片段中每个样本执行一次,而不是每个片段执行一次。以提高性能为代价改善图形质量。 diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml index d45059a3b0..dd56e9968d 100644 --- a/src/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml @@ -466,7 +466,6 @@ 擴展動態狀態 已停用 頂點輸入動態狀態 - 啟用頂點輸入動態狀態以取得更佳的品質及性能 顯示 方向 diff --git a/src/android/app/src/main/res/values/colors.xml b/src/android/app/src/main/res/values/colors.xml index 472567b323..2198022855 100644 --- a/src/android/app/src/main/res/values/colors.xml +++ b/src/android/app/src/main/res/values/colors.xml @@ -1 +1 @@ -#1F143C +#ffd700 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index d143a12f4e..7372fa5e34 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -71,6 +71,8 @@ Display current power draw and remaining capacity on battery Show Shaders Building Display current number of shaders being built + Pipeline Worker Threads + Manage the amount of cores used for building Vulkan pipelines, the higher value will improve pipeline compilation performance but temperatures will increase as well. Overlay Position Choose where the overlay is displayed on the screen Top Left @@ -491,15 +493,15 @@ Force maximum clocks (Adreno only) Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied). GPU async emulation - Runs GPU emulation asynchronously to reduce CPU stalls and improve throughput. Disable this only if you run into timing-related issues. + This hack can increase performance by running GPU emulation asynchronously at the cost of graphical issues and increased crash rates by timing-related operations. Asynchronous presentation - Slightly improves performance by moving presentation to a separate CPU thread. + This hack can increase performance by moving presentation to a separate CPU thread at the cost of graphical issues. Use reactive flushing Improves rendering accuracy in some games at the cost of performance. Enable buffer history Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. Optimized Vertex Buffers - Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers. + Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers (25.3 and below). Hacks @@ -511,6 +513,8 @@ Reduces bloom blur in LA/EOW (Adreno A6XX - A7XX/ Turnip), removes bloom in Burnout. Warning: may cause graphical artifacts in other games. Emulate BGR565 Fixes problems with inverted colors in games or strange artifacts or strange shadows. + Enable Legacy Rescale Pass + Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path Use asynchronous shaders Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches. GPU Unswizzle Settings @@ -529,10 +533,10 @@ Extensions Extended Dynamic State - Controls the number of features that can be used in Extended Dynamic State. Higher numbers allow for more features and can increase performance, but may cause issues with some drivers and vendors. + Controls the number of features that can be used in ExtendedDynamicState (EDS). The higher value will allow to reduce the amount of pipeline compilations based on the dynamic state supported by driver. Disabled Vertex Input Dynamic State - Enables vertex input dynamic state feature for better quality and performance. + Enabling this feature allows for more flexible vertex input handling, potentially reducing pipeline compilation time in vertex/buffer. Sample Shading Allows the fragment shader to execute per sample in a multi-sampled fragment instead once per fragment. Improves graphics quality at the cost of some performance. diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index e31fd2c5b9..4d0b130afc 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -247,11 +247,11 @@ if(ANDROID) target_compile_definitions(audio_core PUBLIC HAVE_OBOE) else() target_sources(audio_core PRIVATE - sink/sdl2_sink.cpp - sink/sdl2_sink.h) + sink/sdl3_sink.cpp + sink/sdl3_sink.h) - target_link_libraries(audio_core PRIVATE SDL2::SDL2) - target_compile_definitions(audio_core PRIVATE HAVE_SDL2) + target_link_libraries(audio_core PRIVATE SDL3::SDL3) + target_compile_definitions(audio_core PRIVATE HAVE_SDL3) endif() create_target_directory_groups(audio_core) diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl3_sink.cpp similarity index 62% rename from src/audio_core/sink/sdl2_sink.cpp rename to src/audio_core/sink/sdl3_sink.cpp index b016bf2222..990b62deb5 100644 --- a/src/audio_core/sink/sdl2_sink.cpp +++ b/src/audio_core/sink/sdl3_sink.cpp @@ -7,16 +7,40 @@ #include #include -#include +#include #include "audio_core/common/common.h" -#include "audio_core/sink/sdl2_sink.h" +#include "audio_core/sink/sdl3_sink.h" #include "audio_core/sink/sink_stream.h" #include "common/logging.h" #include "common/scope_exit.h" #include "core/core.h" namespace AudioCore::Sink { + +namespace { +SDL_AudioDeviceID FindAudioDeviceByName(const std::string& device_name, bool capture) { + int device_count = 0; + SDL_AudioDeviceID* devices = capture ? SDL_GetAudioRecordingDevices(&device_count) + : SDL_GetAudioPlaybackDevices(&device_count); + if (devices == nullptr) { + return capture ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } + + SDL_AudioDeviceID selected = capture ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING + : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + for (int i = 0; i < device_count; ++i) { + const char* current_name = SDL_GetAudioDeviceName(devices[i]); + if (current_name != nullptr && device_name == current_name) { + selected = devices[i]; + break; + } + } + SDL_free(devices); + return selected; +} +} // Anonymous namespace + /** * SDL sink stream, responsible for sinking samples to hardware. */ @@ -39,13 +63,10 @@ public: system_channels = system_channels_; device_channels = device_channels_; - SDL_AudioSpec spec; + SDL_AudioSpec spec{}; spec.freq = TargetSampleRate; spec.channels = static_cast(device_channels); - spec.format = AUDIO_S16SYS; - spec.samples = TargetSampleCount * 2; - spec.callback = &SDLSinkStream::DataCallback; - spec.userdata = this; + spec.format = SDL_AUDIO_S16; std::string device_name{output_device}; bool capture{false}; @@ -54,22 +75,28 @@ public: capture = true; } - SDL_AudioSpec obtained; - if (device_name.empty()) { - device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false); - } else { - device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false); - } + const SDL_AudioDeviceID audio_device = + device_name.empty() ? (capture ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING + : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) + : FindAudioDeviceByName(device_name, capture); - if (device == 0) { + stream = SDL_OpenAudioDeviceStream(audio_device, &spec, &SDLSinkStream::DataCallback, + this); + + if (stream == nullptr) { LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError()); return; } + SDL_AudioSpec stream_in{}; + SDL_AudioSpec stream_out{}; + static_cast(SDL_GetAudioStreamFormat(stream, &stream_in, &stream_out)); + LOG_INFO(Service_Audio, "Opening SDL stream {} with: rate {} channels {} (system channels {}) " - " samples {}", - device, obtained.freq, obtained.channels, system_channels, obtained.samples); + " format {}", + static_cast(stream), stream_out.freq, stream_out.channels, + system_channels, static_cast(stream_out.format)); } /** @@ -84,13 +111,14 @@ public: * Finalize the sink stream. */ void Finalize() override { - if (device == 0) { + if (stream == nullptr) { return; } Stop(); - SDL_ClearQueuedAudio(device); - SDL_CloseAudioDevice(device); + SDL_ClearAudioStream(stream); + SDL_DestroyAudioStream(stream); + stream = nullptr; } /** @@ -100,23 +128,23 @@ public: * Default false. */ void Start(bool resume = false) override { - if (device == 0 || !paused) { + if (stream == nullptr || !paused) { return; } paused = false; - SDL_PauseAudioDevice(device, 0); + static_cast(SDL_ResumeAudioStreamDevice(stream)); } /** * Stop the sink stream. */ void Stop() override { - if (device == 0 || paused) { + if (stream == nullptr || paused) { return; } SignalPause(); - SDL_PauseAudioDevice(device, 1); + static_cast(SDL_PauseAudioStreamDevice(stream)); } private: @@ -128,7 +156,8 @@ private: * @param stream - Buffer of samples to be filled or read. * @param len - Length of the stream in bytes. */ - static void DataCallback(void* userdata, Uint8* stream, int len) { + static void DataCallback(void* userdata, SDL_AudioStream* stream, int additional_amount, + int total_amount) { auto* impl = static_cast(userdata); if (!impl) { @@ -137,25 +166,46 @@ private: const std::size_t num_channels = impl->GetDeviceChannels(); const std::size_t frame_size = num_channels; - const std::size_t num_frames{len / num_channels / sizeof(s16)}; if (impl->type == StreamType::In) { - std::span input_buffer{reinterpret_cast(stream), - num_frames * frame_size}; + const int bytes_available = SDL_GetAudioStreamAvailable(stream); + if (bytes_available <= 0) { + return; + } + + std::vector input(bytes_available / static_cast(sizeof(s16))); + const int bytes_read = SDL_GetAudioStreamData(stream, input.data(), bytes_available); + if (bytes_read <= 0) { + return; + } + + const std::size_t num_frames = + static_cast(bytes_read) / sizeof(s16) / frame_size; + std::span input_buffer{input.data(), + static_cast(bytes_read) / sizeof(s16)}; impl->ProcessAudioIn(input_buffer, num_frames); } else { - std::span output_buffer{reinterpret_cast(stream), num_frames * frame_size}; + if (additional_amount <= 0 && total_amount <= 0) { + return; + } + + const int bytes_requested = additional_amount > 0 ? additional_amount : total_amount; + std::vector output(bytes_requested / static_cast(sizeof(s16))); + const std::size_t num_frames = + static_cast(bytes_requested) / sizeof(s16) / frame_size; + std::span output_buffer{output.data(), output.size()}; impl->ProcessAudioOutAndRender(output_buffer, num_frames); + static_cast(SDL_PutAudioStreamData(stream, output.data(), bytes_requested)); } } - /// SDL device id of the opened input/output device - SDL_AudioDeviceID device{}; + /// SDL stream attached to an opened input/output device + SDL_AudioStream* stream{}; }; SDLSink::SDLSink(std::string_view target_device_name) { if (!SDL_WasInit(SDL_INIT_AUDIO)) { - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); return; } @@ -218,18 +268,26 @@ std::vector ListSDLSinkDevices(bool capture) { std::vector device_list; if (!SDL_WasInit(SDL_INIT_AUDIO)) { - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); return {}; } } - const int device_count = SDL_GetNumAudioDevices(capture); + int device_count = 0; + SDL_AudioDeviceID* devices = + capture ? SDL_GetAudioRecordingDevices(&device_count) + : SDL_GetAudioPlaybackDevices(&device_count); + if (devices == nullptr) { + return device_list; + } + for (int i = 0; i < device_count; ++i) { - if (const char* name = SDL_GetAudioDeviceName(i, capture)) { + if (const char* name = SDL_GetAudioDeviceName(devices[i])) { device_list.emplace_back(name); } } + SDL_free(devices); return device_list; } @@ -242,7 +300,7 @@ u32 GetSDLLatency() { // REVERTED back to 3833 - Below function IsSDLSuitable() removed, reverting to GetSDLLatency() above. - DIABLO 3 FIX /* bool IsSDLSuitable() { -#if !defined(HAVE_SDL2) +#if !defined(HAVE_SDL3) return false; #else // Check SDL can init diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl3_sink.h similarity index 97% rename from src/audio_core/sink/sdl2_sink.h rename to src/audio_core/sink/sdl3_sink.h index 19ea32595f..d0f4586f53 100644 --- a/src/audio_core/sink/sdl2_sink.h +++ b/src/audio_core/sink/sdl3_sink.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 2018 yuzu Emulator Project diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp index 4420560d22..1b737eb12e 100644 --- a/src/audio_core/sink/sink_details.cpp +++ b/src/audio_core/sink/sink_details.cpp @@ -16,8 +16,8 @@ #ifdef HAVE_CUBEB #include "audio_core/sink/cubeb_sink.h" #endif -#ifdef HAVE_SDL2 -#include "audio_core/sink/sdl2_sink.h" +#ifdef HAVE_SDL3 +#include "audio_core/sink/sdl3_sink.h" #endif #include "audio_core/sink/null_sink.h" #include "common/logging.h" @@ -71,9 +71,9 @@ constexpr SinkDetails sink_details[] = { &GetCubebLatency, }, #endif -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 SinkDetails{ - Settings::AudioEngine::Sdl2, + Settings::AudioEngine::Sdl3, [](std::string_view device_id) -> std::unique_ptr { return std::make_unique(device_id); }, @@ -115,10 +115,10 @@ const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { // BEGIN REINTRODUCED FROM 3833 - REPLACED CODE BLOCK ABOVE - DIABLO 3 FIX // Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which // causes audio issues, in that case go with SDL. -#if defined(HAVE_CUBEB) && defined(HAVE_SDL2) +#if defined(HAVE_CUBEB) && defined(HAVE_SDL3) iter = find_backend(Settings::AudioEngine::Cubeb); if (iter->latency() > TargetSampleCount * 3) { - iter = find_backend(Settings::AudioEngine::Sdl2); + iter = find_backend(Settings::AudioEngine::Sdl3); } #else iter = std::begin(sink_details); diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index fb2bdda146..c5740bfb83 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -184,19 +184,12 @@ if(ARCHITECTURE_x86_64) x64/cpu_detect.h x64/cpu_wait.cpp x64/cpu_wait.h - x64/native_clock.cpp - x64/native_clock.h x64/rdtsc.cpp x64/rdtsc.h - x64/xbyak_abi.h - x64/xbyak_util.h) + x64/xbyak.h) target_link_libraries(common PRIVATE xbyak::xbyak) endif() -if(HAS_NCE) - target_sources(common PRIVATE arm64/native_clock.cpp arm64/native_clock.h) -endif() - if(MSVC) target_compile_definitions( common diff --git a/src/common/arm64/native_clock.cpp b/src/common/arm64/native_clock.cpp deleted file mode 100644 index 76ffb74bab..0000000000 --- a/src/common/arm64/native_clock.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#ifdef ANDROID -#include -#endif -#include "common/arm64/native_clock.h" - -namespace Common::Arm64 { - -namespace { - -NativeClock::FactorType GetFixedPointFactor(u64 num, u64 den) { - return (static_cast(num) << 64) / den; -} - -u64 MultiplyHigh(u64 m, NativeClock::FactorType factor) { - return static_cast((m * factor) >> 64); -} - -} // namespace - -NativeClock::NativeClock() { - const u64 host_cntfrq = GetHostCNTFRQ(); - ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq); - us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq); - ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq); - guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq); - gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq); -} - -std::chrono::nanoseconds NativeClock::GetTimeNS() const { - return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_cntfrq_factor)}; -} - -std::chrono::microseconds NativeClock::GetTimeUS() const { - return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_cntfrq_factor)}; -} - -std::chrono::milliseconds NativeClock::GetTimeMS() const { - return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_cntfrq_factor)}; -} - -s64 NativeClock::GetCNTPCT() const { - return MultiplyHigh(GetUptime(), guest_cntfrq_factor); -} - -s64 NativeClock::GetGPUTick() const { - return MultiplyHigh(GetUptime(), gputick_cntfrq_factor); -} - -s64 NativeClock::GetUptime() const { - s64 cntvct_el0 = 0; - asm volatile("dsb ish\n\t" - "mrs %[cntvct_el0], cntvct_el0\n\t" - "dsb ish\n\t" - : [cntvct_el0] "=r"(cntvct_el0)); - return cntvct_el0; -} - -bool NativeClock::IsNative() const { - return true; -} - -s64 NativeClock::GetHostCNTFRQ() { - u64 cntfrq_el0 = 0; - std::string_view board{""}; -#ifdef ANDROID - char buffer[PROP_VALUE_MAX]; - int len{__system_property_get("ro.product.board", buffer)}; - board = std::string_view(buffer, static_cast(len)); -#endif - if (board == "s5e9925") { // Exynos 2200 - cntfrq_el0 = 25600000; - } else if (board == "exynos2100") { // Exynos 2100 - cntfrq_el0 = 26000000; - } else if (board == "exynos9810") { // Exynos 9810 - cntfrq_el0 = 26000000; - } else if (board == "s5e8825") { // Exynos 1280 - cntfrq_el0 = 26000000; - } else { - asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0)); - } - return cntfrq_el0; -} - -} // namespace Common::Arm64 diff --git a/src/common/arm64/native_clock.h b/src/common/arm64/native_clock.h deleted file mode 100644 index 94bc1882e0..0000000000 --- a/src/common/arm64/native_clock.h +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/wall_clock.h" - -namespace Common::Arm64 { - -class NativeClock final : public WallClock { -public: - explicit NativeClock(); - - std::chrono::nanoseconds GetTimeNS() const override; - - std::chrono::microseconds GetTimeUS() const override; - - std::chrono::milliseconds GetTimeMS() const override; - - s64 GetCNTPCT() const override; - - s64 GetGPUTick() const override; - - s64 GetUptime() const override; - - bool IsNative() const override; - - static s64 GetHostCNTFRQ(); - -public: - using FactorType = unsigned __int128; - - FactorType GetGuestCNTFRQFactor() const { - return guest_cntfrq_factor; - } - -private: - FactorType ns_cntfrq_factor; - FactorType us_cntfrq_factor; - FactorType ms_cntfrq_factor; - FactorType guest_cntfrq_factor; - FactorType gputick_cntfrq_factor; -}; - -} // namespace Common::Arm64 diff --git a/src/common/logging.cpp b/src/common/logging.cpp index 0b82d3e727..68e033b129 100644 --- a/src/common/logging.cpp +++ b/src/common/logging.cpp @@ -349,35 +349,8 @@ struct Impl { // Well, I mean it's the default constructor! explicit Impl() noexcept : filter(Level::Trace) {} - void StartBackendThread() noexcept { - backend_thread = std::jthread([this](std::stop_token stop_token) { - Common::SetCurrentThreadName("Logger"); - Entry entry; - const auto write_logs = [this, &entry]() { - ForEachBackend([&entry](Backend& backend) { - backend.Write(entry); - }); - }; - do { - message_queue.PopWait(entry, stop_token); - write_logs(); - } while (!stop_token.stop_requested()); - // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a - // case where a system is repeatedly spamming logs even on close. - int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100; - while (max_logs_to_write-- && message_queue.TryPop(entry)) - write_logs(); - }); - } - - void StopBackendThread() noexcept { - backend_thread.request_stop(); - if (backend_thread.joinable()) - backend_thread.join(); - ForEachBackend([](Backend& backend) { backend.Flush(); }); - } - - void ForEachBackend(auto lambda) noexcept { + template + void ForEachBackend(F&& lambda) noexcept { lambda(static_cast(color_console_backend)); #ifndef __OPENORBIS__ if (file_backend) @@ -402,9 +375,7 @@ struct Impl { #ifdef ANDROID LogcatBackend lc_backend{}; #endif - MPSCQueue message_queue{}; std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; - std::jthread backend_thread; }; } // namespace @@ -428,13 +399,11 @@ void Initialize() { } void Start() { - if (logging_instance) - logging_instance->StartBackendThread(); } void Stop() { if (logging_instance) - logging_instance->StopBackendThread(); + logging_instance->ForEachBackend([](Backend& backend) { backend.Flush(); }); } void SetGlobalFilter(const Filter& filter) { @@ -449,14 +418,19 @@ void SetColorConsoleBackendEnabled(bool enabled) { void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) { if (logging_instance && logging_instance->filter.CheckMessage(log_class, log_level)) { - logging_instance->message_queue.EmplaceWait(Entry{ - .message = fmt::vformat(format, args), - .timestamp = std::chrono::duration_cast(std::chrono::steady_clock::now() - logging_instance->time_origin), - .log_class = log_class, - .log_level = log_level, - .filename = TrimSourcePath(filename), - .function = function, - .line_num = line_num, + auto const flush = ::Settings::values.log_flush_line.GetValue(); + logging_instance->ForEachBackend([=](Backend& backend) { + backend.Write(Entry{ + .message = fmt::vformat(format, args), + .timestamp = std::chrono::duration_cast(std::chrono::steady_clock::now() - logging_instance->time_origin), + .log_class = log_class, + .log_level = log_level, + .filename = TrimSourcePath(filename), + .function = function, + .line_num = line_num, + }); + if (flush) + backend.Flush(); }); } } diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 54848c4dd1..61f9acb1e7 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -435,4 +435,16 @@ void ToggleSlowMode() { SetSpeedMode(SpeedMode::Standard); } +bool IsOpenGL() { + const auto backend = Settings::values.renderer_backend.GetValue(); + switch (backend) { + case RendererBackend::OpenGL_GLSL: + case RendererBackend::OpenGL_GLASM: + case RendererBackend::OpenGL_SPIRV: + return true; + default: + return false; + } +} + } // namespace Settings diff --git a/src/common/settings.h b/src/common/settings.h index 9749a8df40..e8877fce1e 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -388,8 +388,13 @@ struct Values { true, true}; - SwitchableSetting use_asynchronous_gpu_emulation{ - linkage, true, "use_asynchronous_gpu_emulation", Category::Renderer}; + SwitchableSetting use_asynchronous_gpu_emulation{linkage, +#ifdef __ANDROID__ + false, +#else + true, +#endif + "use_asynchronous_gpu_emulation", Category::Renderer}; // *nix platforms may have issues with the borderless windowed fullscreen mode. // Default to exclusive fullscreen on these platforms for now. SwitchableSetting fullscreen_mode{linkage, @@ -542,7 +547,7 @@ struct Values { true}; SwitchableSetting async_presentation{linkage, #ifdef ANDROID - true, + false, #else false, #endif @@ -554,8 +559,13 @@ struct Values { SwitchableSetting emulate_bgr565{linkage, false, "emulate_bgr565", Category::RendererHacks}; - SwitchableSetting rescale_hack{linkage, false, "rescale_hack", - Category::RendererHacks}; + SwitchableSetting rescale_hack{linkage, +#ifdef __ANDROID__ + true, +#else + false, +#endif + "rescale_hack", Category::RendererHacks}; SwitchableSetting use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders", Category::RendererHacks}; @@ -853,6 +863,8 @@ bool IsFastmemEnabled(); void SetNceEnabled(bool is_64bit); bool IsNceEnabled(); +bool IsOpenGL(); + bool IsDockedMode(); float Volume(); diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 638be4127f..9a406e432b 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -92,11 +92,13 @@ struct EnumMetadata { // AudioEngine must be specified discretely due to having existing but slightly different // canonicalizations // TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id -enum class AudioEngine : u32 { Auto, Cubeb, Sdl2, Null, Oboe, }; +enum class AudioEngine : u32 { Auto, Cubeb, Sdl3, Null, Oboe, }; template<> inline std::vector> EnumMetadata::Canonicalizations() { return { - {"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2}, + {"auto", AudioEngine::Auto}, + {"cubeb", AudioEngine::Cubeb}, + {"sdl3", AudioEngine::Sdl3}, {"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe}, }; } diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp index 4f9c240905..c06f9b6318 100644 --- a/src/common/wall_clock.cpp +++ b/src/common/wall_clock.cpp @@ -1,77 +1,196 @@ -// 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 // SPDX-License-Identifier: GPL-2.0-or-later #include "common/steady_clock.h" +#include "common/uint128.h" #include "common/wall_clock.h" +#ifdef __ANDROID__ +#include +#endif #ifdef ARCHITECTURE_x86_64 #include "common/x64/cpu_detect.h" -#include "common/x64/native_clock.h" #include "common/x64/rdtsc.h" #endif -#ifdef HAS_NCE -#include "common/arm64/native_clock.h" -#endif namespace Common { -class StandardWallClock final : public WallClock { -public: - explicit StandardWallClock() {} - - std::chrono::nanoseconds GetTimeNS() const override { - return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()); - } - - std::chrono::microseconds GetTimeUS() const override { - return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()); - } - - std::chrono::milliseconds GetTimeMS() const override { - return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()); - } - - s64 GetCNTPCT() const override { - return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; - } - - s64 GetGPUTick() const override { - return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den; - } - - s64 GetUptime() const override { - return std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()) - .count(); - } - - bool IsNative() const override { - return false; - } -}; - -std::unique_ptr CreateOptimalClock() { #if defined(ARCHITECTURE_x86_64) - const auto& caps = GetCPUCaps(); +WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept + : invariant{invariant_} + , rdtsc_frequency{rdtsc_frequency_} + , ns_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency_)} + , us_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency_)} + , ms_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency_)} + , cntpct_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency_)} + , gputick_rdtsc_factor{invariant_ ? 0 : GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency_)} +{} - if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) { - return std::make_unique(caps.tsc_frequency); - } else { - // Fallback to StandardWallClock if the hardware TSC - // - Is not invariant - // - Is not more precise than 1 GHz (1ns resolution) - return std::make_unique(); - } +std::chrono::nanoseconds WallClock::GetTimeNS() const { + if (invariant) + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_rdtsc_factor)}; +} + +std::chrono::microseconds WallClock::GetTimeUS() const { + if (invariant) + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_rdtsc_factor)}; +} + +std::chrono::milliseconds WallClock::GetTimeMS() const { + if (invariant) + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_rdtsc_factor)}; +} + +s64 WallClock::GetCNTPCT() const { + if (invariant) + return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; + return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor); +} + +s64 WallClock::GetGPUTick() const { + if (invariant) + return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den; + return MultiplyHigh(GetUptime(), gputick_rdtsc_factor); +} + +s64 WallClock::GetUptime() const { + if (invariant) + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + return s64(Common::X64::FencedRDTSC()); +} + +bool WallClock::IsNative() const { + if (invariant) + return false; + return true; +} #elif defined(HAS_NCE) - return std::make_unique(); +namespace { + +[[nodiscard]] WallClock::FactorType GetFixedPointFactor(u64 num, u64 den) noexcept { + return (WallClock::FactorType(num) << 64) / den; +} + +[[nodiscard]] u64 MultiplyHigh(u64 m, WallClock::FactorType factor) noexcept { + return static_cast((m * factor) >> 64); +} + +[[nodiscard]] s64 GetHostCNTFRQ() noexcept { + u64 cntfrq_el0 = 0; +#ifdef ANDROID + std::string_view board{""}; + char buffer[PROP_VALUE_MAX]; + int len{__system_property_get("ro.product.board", buffer)}; + board = std::string_view(buffer, static_cast(len)); + if (board == "s5e9925") { // Exynos 2200 + cntfrq_el0 = 25600000; + } else if (board == "exynos2100") { // Exynos 2100 + cntfrq_el0 = 26000000; + } else if (board == "exynos9810") { // Exynos 9810 + cntfrq_el0 = 26000000; + } else if (board == "s5e8825") { // Exynos 1280 + cntfrq_el0 = 26000000; + } else { + asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0)); + } + return cntfrq_el0; #else - return std::make_unique(); + asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0)); + return cntfrq_el0; +#endif +} + +} // namespace + +WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept { + const u64 host_cntfrq = std::max(GetHostCNTFRQ(), 1); + ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq); + us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq); + ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq); + guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq); + gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq); +} + +std::chrono::nanoseconds WallClock::GetTimeNS() const { + return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_cntfrq_factor)}; +} + +std::chrono::microseconds WallClock::GetTimeUS() const { + return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_cntfrq_factor)}; +} + +std::chrono::milliseconds WallClock::GetTimeMS() const { + return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_cntfrq_factor)}; +} + +s64 WallClock::GetCNTPCT() const { + return MultiplyHigh(GetUptime(), guest_cntfrq_factor); +} + +s64 WallClock::GetGPUTick() const { + return MultiplyHigh(GetUptime(), gputick_cntfrq_factor); +} + +s64 WallClock::GetUptime() const { + s64 cntvct_el0 = 0; + asm volatile( + "dsb ish\n\t" + "mrs %[cntvct_el0], cntvct_el0\n\t" + "dsb ish\n\t" + : [cntvct_el0] "=r"(cntvct_el0) + ); + return cntvct_el0; +} + +bool WallClock::IsNative() const { + return true; +} +#else +WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept {} + +std::chrono::nanoseconds WallClock::GetTimeNS() const { + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); +} + +std::chrono::microseconds WallClock::GetTimeUS() const { + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); +} + +std::chrono::milliseconds WallClock::GetTimeMS() const { + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); +} + +s64 WallClock::GetCNTPCT() const { + return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; +} + +s64 WallClock::GetGPUTick() const { + return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den; +} + +s64 WallClock::GetUptime() const { + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); +} + +bool WallClock::IsNative() const { + return false; +} +#endif + +WallClock CreateOptimalClock() noexcept { +#if defined(ARCHITECTURE_x86_64) + auto const& caps = GetCPUCaps(); + return WallClock(!(caps.invariant_tsc && caps.tsc_frequency >= std::nano::den), caps.tsc_frequency); +#elif defined(HAS_NCE) + return WallClock(false, 1); +#else + return WallClock(true, 1); #endif } diff --git a/src/common/wall_clock.h b/src/common/wall_clock.h index 7ad6536930..6a6d56a610 100644 --- a/src/common/wall_clock.h +++ b/src/common/wall_clock.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 @@ -20,28 +20,28 @@ public: static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz - virtual ~WallClock() = default; + explicit WallClock(bool invariant, u64 rdtsc_frequency_) noexcept; /// @returns The time in nanoseconds since the construction of this clock. - virtual std::chrono::nanoseconds GetTimeNS() const = 0; + std::chrono::nanoseconds GetTimeNS() const; /// @returns The time in microseconds since the construction of this clock. - virtual std::chrono::microseconds GetTimeUS() const = 0; + std::chrono::microseconds GetTimeUS() const; /// @returns The time in milliseconds since the construction of this clock. - virtual std::chrono::milliseconds GetTimeMS() const = 0; + std::chrono::milliseconds GetTimeMS() const; /// @returns The guest CNTPCT ticks since the construction of this clock. - virtual s64 GetCNTPCT() const = 0; + s64 GetCNTPCT() const; /// @returns The guest GPU ticks since the construction of this clock. - virtual s64 GetGPUTick() const = 0; + s64 GetGPUTick() const; /// @returns The raw host timer ticks since an indeterminate epoch. - virtual s64 GetUptime() const = 0; + s64 GetUptime() const; /// @returns Whether the clock directly uses the host's hardware clock. - virtual bool IsNative() const = 0; + bool IsNative() const; static inline u64 NSToCNTPCT(u64 ns) { return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; @@ -85,8 +85,33 @@ protected: using CPUTickToUsRatio = std::ratio; using CPUTickToCNTPCTRatio = std::ratio; using CPUTickToGPUTickRatio = std::ratio; + +#if defined(ARCHITECTURE_x86_64) + bool invariant; + u64 rdtsc_frequency; + u64 ns_rdtsc_factor; + u64 us_rdtsc_factor; + u64 ms_rdtsc_factor; + u64 cntpct_rdtsc_factor; + u64 gputick_rdtsc_factor; +#elif defined(HAS_NCE) +public: + using FactorType = unsigned __int128; + + FactorType GetGuestCNTFRQFactor() const { + return guest_cntfrq_factor; + } +protected: + FactorType ns_cntfrq_factor; + FactorType us_cntfrq_factor; + FactorType ms_cntfrq_factor; + FactorType guest_cntfrq_factor; + FactorType gputick_cntfrq_factor; +#else + +#endif }; -[[nodiscard]] std::unique_ptr CreateOptimalClock(); +[[nodiscard]] WallClock CreateOptimalClock() noexcept; } // namespace Common diff --git a/src/common/x64/native_clock.cpp b/src/common/x64/native_clock.cpp deleted file mode 100644 index d2d27fafea..0000000000 --- a/src/common/x64/native_clock.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/uint128.h" -#include "common/x64/native_clock.h" -#include "common/x64/rdtsc.h" - -namespace Common::X64 { - -NativeClock::NativeClock(u64 rdtsc_frequency_) - : rdtsc_frequency{rdtsc_frequency_}, ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den, - rdtsc_frequency)}, - us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency)}, - ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency)}, - cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency)}, - gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency)} {} - -std::chrono::nanoseconds NativeClock::GetTimeNS() const { - return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_rdtsc_factor)}; -} - -std::chrono::microseconds NativeClock::GetTimeUS() const { - return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_rdtsc_factor)}; -} - -std::chrono::milliseconds NativeClock::GetTimeMS() const { - return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_rdtsc_factor)}; -} - -s64 NativeClock::GetCNTPCT() const { - return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor); -} - -s64 NativeClock::GetGPUTick() const { - return MultiplyHigh(GetUptime(), gputick_rdtsc_factor); -} - -s64 NativeClock::GetUptime() const { - return static_cast(FencedRDTSC()); -} - -bool NativeClock::IsNative() const { - return true; -} - -} // namespace Common::X64 diff --git a/src/common/x64/native_clock.h b/src/common/x64/native_clock.h deleted file mode 100644 index b2629b0311..0000000000 --- a/src/common/x64/native_clock.h +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/wall_clock.h" - -namespace Common::X64 { - -class NativeClock final : public WallClock { -public: - explicit NativeClock(u64 rdtsc_frequency_); - - std::chrono::nanoseconds GetTimeNS() const override; - - std::chrono::microseconds GetTimeUS() const override; - - std::chrono::milliseconds GetTimeMS() const override; - - s64 GetCNTPCT() const override; - - s64 GetGPUTick() const override; - - s64 GetUptime() const override; - - bool IsNative() const override; - -private: - u64 rdtsc_frequency; - - u64 ns_rdtsc_factor; - u64 us_rdtsc_factor; - u64 ms_rdtsc_factor; - u64 cntpct_rdtsc_factor; - u64 gputick_rdtsc_factor; -}; - -} // namespace Common::X64 diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak.h similarity index 75% rename from src/common/x64/xbyak_abi.h rename to src/common/x64/xbyak.h index 8aea5db583..5ee12576e9 100644 --- a/src/common/x64/xbyak_abi.h +++ b/src/common/x64/xbyak.h @@ -1,13 +1,37 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include #include -#include #include "common/assert.h" +// xbyak hates human beings +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wshadow" +#endif +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wshadow" +#endif + +// You must ensure this matches with src/common/x64/xbyak.h on root dir +#include +#include +#define XBYAK_STD_UNORDERED_SET ankerl::unordered_dense::set +#define XBYAK_STD_UNORDERED_MAP ankerl::unordered_dense::map +#define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap +#include +#include + +#include + namespace Common::X64 { constexpr size_t RegToIndex(const Xbyak::Reg& reg) { @@ -174,12 +198,13 @@ inline ABIFrameInfo ABI_CalculateFrameSize(std::bitset<32> regs, size_t rsp_alig rsp_alignment -= subtraction; subtraction += rsp_alignment & 0xF; - return ABIFrameInfo{static_cast(subtraction), - static_cast(subtraction - xmm_base_subtraction)}; + return ABIFrameInfo{ + s32(subtraction), + s32(subtraction - xmm_base_subtraction) + }; } -inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs, - size_t rsp_alignment, size_t needed_frame_size = 0) { +inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs, size_t rsp_alignment, size_t needed_frame_size = 0) { auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size); for (size_t i = 0; i < regs.size(); ++i) { @@ -202,8 +227,7 @@ inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::b return ABI_SHADOW_SPACE; } -inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs, - size_t rsp_alignment, size_t needed_frame_size = 0) { +inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs, size_t rsp_alignment, size_t needed_frame_size = 0) { auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size); for (size_t i = 0; i < regs.size(); ++i) { @@ -226,4 +250,38 @@ inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bits } } +// Constants for use with cmpps/cmpss +enum { + CMP_EQ = 0, + CMP_LT = 1, + CMP_LE = 2, + CMP_UNORD = 3, + CMP_NEQ = 4, + CMP_NLT = 5, + CMP_NLE = 6, + CMP_ORD = 7, +}; + +constexpr bool IsWithin2G(uintptr_t ref, uintptr_t target) { + const u64 distance = target - (ref + 5); + return !(distance >= 0x8000'0000ULL && distance <= ~0x8000'0000ULL); +} + +inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) { + return IsWithin2G(reinterpret_cast(code.getCurr()), target); +} + +template +inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) { + static_assert(std::is_pointer_v, "Argument must be a (function) pointer."); + size_t addr = reinterpret_cast(f); + if (IsWithin2G(code, addr)) { + code.call(f); + } else { + // ABI_RETURN is a safe temp register to use before a call + code.mov(ABI_RETURN, addr); + code.call(ABI_RETURN); + } +} + } // namespace Common::X64 diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h deleted file mode 100644 index 250e5cddb9..0000000000 --- a/src/common/x64/xbyak_util.h +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2016 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include "common/x64/xbyak_abi.h" - -namespace Common::X64 { - -// Constants for use with cmpps/cmpss -enum { - CMP_EQ = 0, - CMP_LT = 1, - CMP_LE = 2, - CMP_UNORD = 3, - CMP_NEQ = 4, - CMP_NLT = 5, - CMP_NLE = 6, - CMP_ORD = 7, -}; - -constexpr bool IsWithin2G(uintptr_t ref, uintptr_t target) { - const u64 distance = target - (ref + 5); - return !(distance >= 0x8000'0000ULL && distance <= ~0x8000'0000ULL); -} - -inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) { - return IsWithin2G(reinterpret_cast(code.getCurr()), target); -} - -template -inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) { - static_assert(std::is_pointer_v, "Argument must be a (function) pointer."); - size_t addr = reinterpret_cast(f); - if (IsWithin2G(code, addr)) { - code.call(f); - } else { - // ABI_RETURN is a safe temp register to use before a call - code.mov(ABI_RETURN, addr); - code.call(ABI_RETURN); - } -} - -} // namespace Common::X64 diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index c0d6ff9516..e434a31d5d 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -113,8 +113,7 @@ void DynarmicCallbacks32::CallSVC(u32 swi) { } void DynarmicCallbacks32::AddTicks(u64 ticks) { - ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled"); - + ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled"); // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a // rough approximation of the amount of executed ticks in the system, it may be thrown off // if not all cores are doing a similar amount of work. Instead of doing this, we should @@ -123,14 +122,12 @@ void DynarmicCallbacks32::AddTicks(u64 ticks) { u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES; // Always execute at least one tick. amortized_ticks = std::max(amortized_ticks, 1); - m_parent.m_system.CoreTiming().AddTicks(amortized_ticks); } u64 DynarmicCallbacks32::GetTicksRemaining() { - ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled"); - - return std::max(m_parent.m_system.CoreTiming().GetDowncount(), 0); + ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled"); + return std::max(m_parent.m_system.CoreTiming().downcount, 0); } bool DynarmicCallbacks32::CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) { diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h index d93cc1cfc9..623597152f 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.h +++ b/src/core/arm/dynarmic/arm_dynarmic_32.h @@ -36,7 +36,7 @@ public: u64 MemoryRead64(u32 vaddr) override; std::optional MemoryReadCode(u32 vaddr) override; void InstructionSynchronizationBarrierRaised() override { - last_code_addr = 0; //reset back, force refetch + last_code_addr = u64(-1); //reset back, force refetch } void MemoryWrite8(u32 vaddr, u8 value) override; void MemoryWrite16(u32 vaddr, u16 value) override; @@ -54,7 +54,7 @@ public: void ReturnException(u32 pc, Dynarmic::HaltReason hr); // Dynarmic::CodePage cached_code_page; - u64 last_code_addr = 0; + u64 last_code_addr = u64(-1); ArmDynarmic32& m_parent; Core::Memory::Memory& m_memory; Kernel::KProcess* m_process{}; diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index db8f4424eb..e6c7e61265 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -45,7 +45,6 @@ Dynarmic::A64::Vector DynarmicCallbacks64::MemoryRead128(u64 vaddr) { std::optional DynarmicCallbacks64::MemoryReadCode(u64 vaddr) { if (!m_memory.IsValidVirtualAddressRange(vaddr, sizeof(u32))) return std::nullopt; -// return m_memory.Read32(vaddr); auto const aligned_vaddr = vaddr & ~Core::Memory::YUZU_PAGEMASK; if (last_code_addr != aligned_vaddr) { m_memory.ReadBlock(aligned_vaddr, &cached_code_page, sizeof(cached_code_page)); @@ -103,6 +102,7 @@ bool DynarmicCallbacks64::MemoryWriteExclusive128(u64 vaddr, Dynarmic::A64::Vect } void DynarmicCallbacks64::InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op, u64 value) { + last_code_addr = u64(-1); //invalidate cached page switch (op) { case Dynarmic::A64::InstructionCacheOperation::InvalidateByVAToPoU: { static constexpr u64 ICACHE_LINE_SIZE = 64; @@ -128,7 +128,7 @@ void DynarmicCallbacks64::ExceptionRaised(u64 pc, Dynarmic::A64::Exception excep case Dynarmic::A64::Exception::SendEvent: case Dynarmic::A64::Exception::SendEventLocal: case Dynarmic::A64::Exception::Yield: - LOG_TRACE(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", static_cast(exception), pc, m_memory.Read32(pc)); + LOG_TRACE(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, cached = {:08X})", std::size_t(exception), pc, m_memory.Read32(pc), MemoryReadCode(pc).value_or(0)); return; case Dynarmic::A64::Exception::NoExecuteFault: LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#016x}", pc); @@ -150,8 +150,7 @@ void DynarmicCallbacks64::CallSVC(u32 svc) { } void DynarmicCallbacks64::AddTicks(u64 ticks) { - ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled"); - + ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled"); // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a // rough approximation of the amount of executed ticks in the system, it may be thrown off // if not all cores are doing a similar amount of work. Instead of doing this, we should @@ -160,13 +159,12 @@ void DynarmicCallbacks64::AddTicks(u64 ticks) { u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES; // Always execute at least one tick. amortized_ticks = std::max(amortized_ticks, 1); - m_parent.m_system.CoreTiming().AddTicks(amortized_ticks); } u64 DynarmicCallbacks64::GetTicksRemaining() { ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled"); - return std::max(m_parent.m_system.CoreTiming().GetDowncount(), 0); + return std::max(m_parent.m_system.CoreTiming().downcount, 0); } u64 DynarmicCallbacks64::GetCNTPCT() { diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h index 9eff17c5fe..6d0760986f 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.h +++ b/src/core/arm/dynarmic/arm_dynarmic_64.h @@ -43,7 +43,7 @@ public: Dynarmic::A64::Vector MemoryRead128(u64 vaddr) override; std::optional MemoryReadCode(u64 vaddr) override; void InstructionSynchronizationBarrierRaised() override { - last_code_addr = 0; //reset back, force refetch + last_code_addr = u64(-1); //reset back, force refetch } void MemoryWrite8(u64 vaddr, u8 value) override; void MemoryWrite16(u64 vaddr, u16 value) override; @@ -65,7 +65,7 @@ public: void ReturnException(u64 pc, Dynarmic::HaltReason hr); Dynarmic::CodePage cached_code_page; - u64 last_code_addr = 0; + u64 last_code_addr = u64(-1); ArmDynarmic64& m_parent; Core::Memory::Memory& m_memory; u64 m_tpidrro_el0{}; diff --git a/src/core/arm/nce/interpreter_visitor.cpp b/src/core/arm/nce/interpreter_visitor.cpp index 077a696cc8..ec2b155fe4 100644 --- a/src/core/arm/nce/interpreter_visitor.cpp +++ b/src/core/arm/nce/interpreter_visitor.cpp @@ -761,8 +761,7 @@ bool InterpreterVisitor::LDR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3 return this->SIMDOffset(scale, shift, opc_0, Rm, option, Rn, Vt); } -std::optional MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context, - fpsimd_context* fpsimd_context) { +std::optional MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context, fpsimd_context* fpsimd_context) { std::span regs(reinterpret_cast(context->regs), 31); std::span vregs(reinterpret_cast(fpsimd_context->vregs), 32); u64& sp = *reinterpret_cast(&context->sp); @@ -772,9 +771,9 @@ std::optional MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, m u32 instruction = memory.Read32(pc); bool was_executed = false; - auto decoder = Dynarmic::A64::Decode(instruction); + auto decoder = Dynarmic::A64::Decode(visitor, instruction); if (decoder) { - was_executed = decoder->get().call(visitor, instruction); + was_executed = *decoder; } else { was_executed = false; } diff --git a/src/core/arm/nce/patcher.cpp b/src/core/arm/nce/patcher.cpp index ea77166645..bf72b207b9 100644 --- a/src/core/arm/nce/patcher.cpp +++ b/src/core/arm/nce/patcher.cpp @@ -3,7 +3,7 @@ #include #include -#include "common/arm64/native_clock.h" +#include "common/wall_clock.h" #include "common/alignment.h" #include "common/literals.h" #include "core/arm/nce/arm_nce.h" @@ -578,7 +578,11 @@ void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, } void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) { - static Common::Arm64::NativeClock clock{}; +#if defined(HAS_NCE) + static Common::WallClock clock(false, 1); +#else + static Common::WallClock clock(true, 1); +#endif const auto factor = clock.GetGuestCNTFRQFactor(); const auto raw_factor = std::bit_cast>(factor); diff --git a/src/core/arm/symbols.cpp b/src/core/arm/symbols.cpp index 0259c7ea27..4e270c319e 100644 --- a/src/core/arm/symbols.cpp +++ b/src/core/arm/symbols.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -13,117 +16,111 @@ using namespace Common::ELF; namespace Core { namespace Symbols { -template -static Symbols GetSymbols(ByteReader ReadBytes) { - const auto Read8{[&](u64 index) { +struct ModuleHeaderLocation { + u32 version; + u32 header_offset; + u32 version_offset; +}; +static_assert(sizeof(ModuleHeaderLocation) == 0x0C); +struct ModuleHeader { + u32 signature; + u32 dynamic_offset; + u32 bss_start_offset; + u32 bss_end_offset; + u32 exception_info_start_offset; + u32 exception_info_end_offset; + u32 module_offset; + u32 relro_start_offset; + u32 full_relro_end_offset; + u32 nx_debug_link_start_offset; + u32 nx_debug_link_end_offset; + u32 note_gnu_build_id_start_offset; + u32 note_gnu_build_id_end_offset; +}; +static_assert(sizeof(ModuleHeader) == 0x34); + +struct Mod32 { + using Sym = Elf32_Sym; + using Dyn = Elf32_Dyn; +}; +struct Mod64 { + using Sym = Elf64_Sym; + using Dyn = Elf64_Dyn; +}; + +template +static Symbols GetSymbols(F&& ReadBytes) { + const auto Read8 = [&](u64 index) { u8 ret; ReadBytes(&ret, index, sizeof(u8)); return ret; - }}; - - const auto Read32{[&](u64 index) { - u32 ret; - ReadBytes(&ret, index, sizeof(u32)); - return ret; - }}; - - const auto ReadWord{[&](u64 index) { - Word ret; - ReadBytes(&ret, index, sizeof(Word)); - return ret; - }}; - - const u32 mod_offset = Read32(4); - - if (Read32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) { + }; + ModuleHeaderLocation loc{}; + ReadBytes(&loc, 0, sizeof(ModuleHeaderLocation)); + ModuleHeader hdr; + ReadBytes(&hdr, loc.header_offset, sizeof(ModuleHeader)); + if (hdr.signature != Common::MakeMagic('M', 'O', 'D', '0')) { return {}; } - VAddr string_table_offset{}; - VAddr symbol_table_offset{}; - u64 symbol_entry_size{}; - - const auto dynamic_offset = Read32(mod_offset + 0x4) + mod_offset; - - VAddr dynamic_index = dynamic_offset; + VAddr strtab_offs{}; + VAddr symtab_offs{}; + u64 syment_size = sizeof(typename M::Sym); + VAddr dynamic_offset = loc.header_offset + hdr.dynamic_offset; while (true) { - const Word tag = ReadWord(dynamic_index); - const Word value = ReadWord(dynamic_index + sizeof(Word)); - dynamic_index += 2 * sizeof(Word); - - if (tag == ElfDtNull) { + typename M::Dyn dyn; + ReadBytes(&dyn, dynamic_offset, sizeof(typename M::Dyn)); + dynamic_offset += sizeof(typename M::Dyn); + if (dyn.d_tag == ElfDtNull) { break; } - - if (tag == ElfDtStrtab) { - string_table_offset = value; - } else if (tag == ElfDtSymtab) { - symbol_table_offset = value; - } else if (tag == ElfDtSyment) { - symbol_entry_size = value; + if (dyn.d_tag == ElfDtStrtab) { + strtab_offs = dyn.d_un.d_ptr; + } else if (dyn.d_tag == ElfDtSymtab) { + symtab_offs = dyn.d_un.d_ptr; + } else if (dyn.d_tag == ElfDtSyment) { + syment_size = dyn.d_un.d_val; } } - - if (string_table_offset == 0 || symbol_table_offset == 0 || symbol_entry_size == 0) { - return {}; - } - - Symbols out; - - VAddr symbol_index = symbol_table_offset; - while (symbol_index < string_table_offset) { - ELFSymbol symbol{}; - ReadBytes(&symbol, symbol_index, sizeof(ELFSymbol)); - - VAddr string_offset = string_table_offset + symbol.st_name; - std::string name; - for (u8 c = Read8(string_offset); c != 0; c = Read8(++string_offset)) { - name += static_cast(c); + if (strtab_offs > 0 && symtab_offs > 0) { + Symbols out; + VAddr symbol_index = symtab_offs; + while (symbol_index < strtab_offs) { + typename M::Sym symbol{}; + ReadBytes(&symbol, symbol_index, sizeof(typename M::Sym)); + VAddr offs = strtab_offs + symbol.st_name; + std::string name{}; + for (u8 c = Read8(offs); c != 0; c = Read8(++offs)) + name += char(c); + symbol_index += syment_size; + out[name] = std::make_pair(symbol.st_value, symbol.st_size); } - - symbol_index += symbol_entry_size; - out[name] = std::make_pair(symbol.st_value, symbol.st_size); + return out; } - - return out; + return {}; } Symbols GetSymbols(VAddr base, Core::Memory::Memory& memory, bool is_64) { - const auto ReadBytes{ - [&](void* ptr, size_t offset, size_t size) { memory.ReadBlock(base + offset, ptr, size); }}; - - if (is_64) { - return GetSymbols(ReadBytes); - } else { - return GetSymbols(ReadBytes); - } + const auto f = [base, &memory](void* ptr, size_t offset, size_t size) { + memory.ReadBlock(base + offset, ptr, size); + }; + return is_64 ? GetSymbols(f) : GetSymbols(f); } Symbols GetSymbols(std::span data, bool is_64) { - const auto ReadBytes{[&](void* ptr, size_t offset, size_t size) { + const auto f = [data](void* ptr, size_t offset, size_t size) { std::memcpy(ptr, data.data() + offset, size); - }}; - - if (is_64) { - return GetSymbols(ReadBytes); - } else { - return GetSymbols(ReadBytes); - } + }; + return is_64 ? GetSymbols(f) : GetSymbols(f); } std::optional GetSymbolName(const Symbols& symbols, VAddr addr) { - const auto iter = std::find_if(symbols.cbegin(), symbols.cend(), [addr](const auto& pair) { - const auto& [name, sym_info] = pair; - const auto& [start_address, size] = sym_info; - const auto end_address = start_address + size; - return addr >= start_address && addr < end_address; + const auto it = std::find_if(symbols.cbegin(), symbols.cend(), [addr](const auto& e) { + auto const [start, size] = e.second; + auto const end = start + size; + return addr >= start && addr < end; }); - - if (iter == symbols.cend()) { - return std::nullopt; - } - - return iter->first; + return it != symbols.cend() ? std::optional{it->first} : std::nullopt; } } // namespace Symbols diff --git a/src/core/core.cpp b/src/core/core.cpp index 2c8cbe21bb..e730b808e0 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -415,7 +415,6 @@ struct System::Impl { cpu_manager.Shutdown(); debugger.reset(); kernel.Shutdown(); - device_memory.reset(); stop_event = {}; Network::RestartSocketOperations(); diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index b216dc2094..6738a0d73d 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -57,15 +57,51 @@ void CoreTiming::Initialize(std::function&& on_thread_init_) { Reset(); on_thread_init = std::move(on_thread_init_); event_fifo_id = 0; - shutting_down = false; cpu_ticks = 0; if (is_multicore) { - timer_thread.emplace([](CoreTiming& instance) { + timer_thread = std::jthread([this](std::stop_token stop_token) { Common::SetCurrentThreadName("HostTiming"); Common::SetCurrentThreadPriority(Common::ThreadPriority::High); - instance.on_thread_init(); - instance.ThreadLoop(); - }, std::ref(*this)); + on_thread_init(); + has_started = true; + while (!stop_token.stop_requested()) { + while (!paused && !stop_token.stop_requested()) { + paused_set = false; + if (auto const next_time = Advance(); next_time) { + // There are more events left in the queue, wait until the next event. + auto wait_time = *next_time - GetGlobalTimeNs().count(); + if (wait_time > 0) { +#ifdef _WIN32 + while (!paused && !event.IsSet() && wait_time > 0) { + wait_time = *next_time - GetGlobalTimeNs().count(); + if (wait_time >= timer_resolution_ns) { + Common::Windows::SleepForOneTick(); + } else { +#ifdef ARCHITECTURE_x86_64 + Common::X64::MicroSleep(); +#else + std::this_thread::yield(); +#endif + } + } + if (event.IsSet()) + event.Reset(); +#else + event.WaitFor(std::chrono::nanoseconds(wait_time)); +#endif + } + } else { + // Queue is empty, wait until another event is scheduled and signals us to + // continue. + wait_set = true; + event.Wait(); + } + wait_set = false; + } + paused_set = true; + pause_event.Wait(); + } + }); } } @@ -90,7 +126,7 @@ void CoreTiming::SyncPause(bool is_paused) { } Pause(is_paused); - if (timer_thread) { + if (timer_thread.joinable()) { if (!is_paused) { pause_event.Set(); } @@ -190,33 +226,22 @@ void CoreTiming::ResetTicks() { } u64 CoreTiming::GetClockTicks() const { - u64 fres; - if (is_multicore) [[likely]] { - fres = clock->GetCNTPCT(); - } else { - fres = Common::WallClock::CPUTickToCNTPCT(cpu_ticks); + u64 fres = is_multicore ? clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks); + if (auto const overclock = Settings::values.fast_cpu_time.GetValue(); overclock != Settings::CpuClock::Off) { + fres = u64(f64(fres) * (1.7 + 0.3 * u32(overclock))); } - - const auto overclock = Settings::values.fast_cpu_time.GetValue(); - - if (overclock != Settings::CpuClock::Off) { - fres = (u64) ((double) fres * (1.7 + 0.3 * u32(overclock))); - } - - if (Settings::values.sync_core_speed.GetValue()) { - const auto ticks = double(fres); - const auto speed_limit = double(Settings::SpeedLimit())*0.01; - return u64(ticks/speed_limit); - } else { - return fres; - } + if (::Settings::values.sync_core_speed.GetValue()) { + auto const ticks = f64(fres); + auto const speed_limit = f64(Settings::SpeedLimit()) * 0.01; + return u64(ticks / speed_limit); + } + return fres; } u64 CoreTiming::GetGPUTicks() const { - if (is_multicore) [[likely]] { - return clock->GetGPUTick(); - } - return Common::WallClock::CPUTickToGPUTick(cpu_ticks); + return is_multicore + ? clock.GetGPUTick() + : Common::WallClock::CPUTickToGPUTick(cpu_ticks); } std::optional CoreTiming::Advance() { @@ -278,75 +303,29 @@ std::optional CoreTiming::Advance() { } } -void CoreTiming::ThreadLoop() { - has_started = true; - while (!shutting_down) { - while (!paused) { - paused_set = false; - const auto next_time = Advance(); - if (next_time) { - // There are more events left in the queue, wait until the next event. - auto wait_time = *next_time - GetGlobalTimeNs().count(); - if (wait_time > 0) { -#ifdef _WIN32 - while (!paused && !event.IsSet() && wait_time > 0) { - wait_time = *next_time - GetGlobalTimeNs().count(); - if (wait_time >= timer_resolution_ns) { - Common::Windows::SleepForOneTick(); - } else { -#ifdef ARCHITECTURE_x86_64 - Common::X64::MicroSleep(); -#else - std::this_thread::yield(); -#endif - } - } - - if (event.IsSet()) { - event.Reset(); - } -#else - event.WaitFor(std::chrono::nanoseconds(wait_time)); -#endif - } - } else { - // Queue is empty, wait until another event is scheduled and signals us to - // continue. - wait_set = true; - event.Wait(); - } - wait_set = false; - } - - paused_set = true; - pause_event.Wait(); - } -} - void CoreTiming::Reset() { paused = true; - shutting_down = true; pause_event.Set(); event.Set(); - if (timer_thread) { - timer_thread->join(); + if (timer_thread.joinable()) { + timer_thread.request_stop(); + timer_thread.join(); } - timer_thread.reset(); has_started = false; } -std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const { - if (is_multicore) [[likely]] { - return clock->GetTimeNS(); - } - return std::chrono::nanoseconds{Common::WallClock::CPUTickToNS(cpu_ticks)}; +/// @brief Returns current time in nanoseconds. +std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const noexcept { + return is_multicore + ? clock.GetTimeNS() + : std::chrono::nanoseconds{Common::WallClock::CPUTickToNS(cpu_ticks)}; } -std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const { - if (is_multicore) [[likely]] { - return clock->GetTimeUS(); - } - return std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)}; +/// @brief Returns current time in microseconds. +std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const noexcept { + return is_multicore + ? clock.GetTimeUS() + : std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)}; } #ifdef _WIN32 diff --git a/src/core/core_timing.h b/src/core/core_timing.h index ae9f56d519..5967c83b57 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.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 @@ -118,7 +118,7 @@ public: void Idle(); - s64 GetDowncount() const { + s64 GetDowncount() const noexcept { return downcount; } @@ -128,11 +128,8 @@ public: /// Returns the current GPU tick value. u64 GetGPUTicks() const; - /// Returns current time in microseconds. - std::chrono::microseconds GetGlobalTimeUs() const; - - /// Returns current time in nanoseconds. - std::chrono::nanoseconds GetGlobalTimeNs() const; + [[nodiscard]] std::chrono::microseconds GetGlobalTimeUs() const noexcept; + [[nodiscard]] std::chrono::nanoseconds GetGlobalTimeNs() const noexcept; /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. std::optional Advance(); @@ -141,13 +138,11 @@ public: void SetTimerResolutionNs(std::chrono::nanoseconds ns); #endif -private: struct Event; - void ThreadLoop(); void Reset(); - std::unique_ptr clock; + Common::WallClock clock; s64 global_timer = 0; @@ -165,11 +160,10 @@ private: Common::Event pause_event{}; mutable std::mutex basic_lock; std::mutex advance_lock; - std::optional timer_thread; + std::jthread timer_thread; std::atomic paused{}; std::atomic paused_set{}; std::atomic wait_set{}; - std::atomic shutting_down{}; std::atomic has_started{}; std::function on_thread_init{}; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 6af926eac0..80372ec94e 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -569,12 +569,11 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t layers.emplace_back(std::move(extracted)); auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); - if (layered == nullptr) { + auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext)); + if (layered == nullptr && layered_ext == nullptr) { return; } - auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext)); - auto packed = CreateRomFS(std::move(layered), std::move(layered_ext)); if (packed == nullptr) { return; @@ -943,6 +942,8 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs")) || IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfslite"))) AppendCommaIfNotEmpty(types, "LayeredFS"); + if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs_ext"))) + AppendCommaIfNotEmpty(types, "ExtLayeredFS"); if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "cheats"))) AppendCommaIfNotEmpty(types, "Cheats"); @@ -965,13 +966,13 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); if (sdmc_mod_dir != nullptr) { std::string types; - if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) { + if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) AppendCommaIfNotEmpty(types, "LayeredExeFS"); - } if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs")) || - IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfslite"))) { + IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfslite"))) AppendCommaIfNotEmpty(types, "LayeredFS"); - } + if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs_ext"))) + AppendCommaIfNotEmpty(types, "ExtLayeredFS"); if (!types.empty()) { const auto mod_disabled = diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index f3e2e21f4c..a2903dbacf 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -1,7 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include +#include "common/alignment.h" +#include "common/literals.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/sdmc_factory.h" #include "core/file_sys/vfs/vfs.h" @@ -9,8 +14,6 @@ namespace FileSys { -constexpr u64 SDMC_TOTAL_SIZE = 0x10000000000; // 1 TiB - SDMCFactory::SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_) : sd_dir(std::move(sd_dir_)), sd_mod_dir(std::move(sd_mod_dir_)), contents(std::make_unique( @@ -56,7 +59,11 @@ u64 SDMCFactory::GetSDMCFreeSpace() const { } u64 SDMCFactory::GetSDMCTotalSpace() const { - return SDMC_TOTAL_SIZE; + // Resize the SD space automatically, always leaving around 4GiB last from next chunk block + using namespace Common::Literals; + auto const bytes_per_sector = 512; + auto const size_block = (sd_dir->GetSize() + 4_GiB) / 4_GiB; + return Common::AlignUp(size_block * 4_GiB, bytes_per_sector); } } // namespace FileSys diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp index cb68d5b55e..bbdf27039b 100644 --- a/src/core/file_sys/system_archive/system_archive.cpp +++ b/src/core/file_sys/system_archive/system_archive.cpp @@ -15,86 +15,76 @@ namespace FileSys::SystemArchive { -constexpr u64 SYSTEM_ARCHIVE_BASE_TITLE_ID = 0x0100000000000800; -constexpr std::size_t SYSTEM_ARCHIVE_COUNT = 0x28; - -using SystemArchiveSupplier = VirtualDir (*)(); - struct SystemArchiveDescriptor { - u64 title_id; const char* name; - SystemArchiveSupplier supplier; + VirtualDir (*supplier)(); }; -constexpr std::array SYSTEM_ARCHIVES{{ - {0x0100000000000800, "CertStore", nullptr}, - {0x0100000000000801, "ErrorMessage", nullptr}, - {0x0100000000000802, "MiiModel", &MiiModel}, - {0x0100000000000803, "BrowserDll", nullptr}, - {0x0100000000000804, "Help", nullptr}, - {0x0100000000000805, "SharedFont", nullptr}, - {0x0100000000000806, "NgWord", &NgWord1}, - {0x0100000000000807, "SsidList", nullptr}, - {0x0100000000000808, "Dictionary", nullptr}, - {0x0100000000000809, "SystemVersion", &SystemVersion}, - {0x010000000000080A, "AvatarImage", nullptr}, - {0x010000000000080B, "LocalNews", nullptr}, - {0x010000000000080C, "Eula", nullptr}, - {0x010000000000080D, "UrlBlackList", nullptr}, - {0x010000000000080E, "TimeZoneBinary", &TimeZoneBinary}, - {0x010000000000080F, "CertStoreCruiser", nullptr}, - {0x0100000000000810, "FontNintendoExtension", &FontNintendoExtension}, - {0x0100000000000811, "FontStandard", &FontStandard}, - {0x0100000000000812, "FontKorean", &FontKorean}, - {0x0100000000000813, "FontChineseTraditional", &FontChineseTraditional}, - {0x0100000000000814, "FontChineseSimple", &FontChineseSimple}, - {0x0100000000000815, "FontBfcpx", nullptr}, - {0x0100000000000816, "SystemUpdate", nullptr}, - {0x0100000000000817, "0100000000000817", nullptr}, - {0x0100000000000818, "FirmwareDebugSettings", nullptr}, - {0x0100000000000819, "BootImagePackage", nullptr}, - {0x010000000000081A, "BootImagePackageSafe", nullptr}, - {0x010000000000081B, "BootImagePackageExFat", nullptr}, - {0x010000000000081C, "BootImagePackageExFatSafe", nullptr}, - {0x010000000000081D, "FatalMessage", nullptr}, - {0x010000000000081E, "ControllerIcon", nullptr}, - {0x010000000000081F, "PlatformConfigIcosa", nullptr}, - {0x0100000000000820, "PlatformConfigCopper", nullptr}, - {0x0100000000000821, "PlatformConfigHoag", nullptr}, - {0x0100000000000822, "ControllerFirmware", nullptr}, - {0x0100000000000823, "NgWord2", &NgWord2}, - {0x0100000000000824, "PlatformConfigIcosaMariko", nullptr}, - {0x0100000000000825, "ApplicationBlackList", nullptr}, - {0x0100000000000826, "RebootlessSystemUpdateVersion", nullptr}, - {0x0100000000000827, "ContentActionTable", nullptr}, -}}; +constexpr inline SystemArchiveDescriptor GetSystemArchive(u64 title_id) { + switch (title_id) { + case 0x0100000000000800: return {"CertStore", nullptr}; + case 0x0100000000000801: return {"ErrorMessage", nullptr}; + case 0x0100000000000802: return {"MiiModel", &MiiModel}; + case 0x0100000000000803: return {"BrowserDll", nullptr}; + case 0x0100000000000804: return {"Help", nullptr}; + case 0x0100000000000805: return {"SharedFont", nullptr}; + case 0x0100000000000806: return {"NgWord", &NgWord1}; + case 0x0100000000000807: return {"SsidList", nullptr}; + case 0x0100000000000808: return {"Dictionary", nullptr}; + case 0x0100000000000809: return {"SystemVersion", &SystemVersion}; + case 0x010000000000080A: return {"AvatarImage", nullptr}; + case 0x010000000000080B: return {"LocalNews", nullptr}; + case 0x010000000000080C: return {"Eula", nullptr}; + case 0x010000000000080D: return {"UrlBlackList", nullptr}; + case 0x010000000000080E: return {"TimeZoneBinary", &TimeZoneBinary}; + case 0x010000000000080F: return {"CertStoreCruiser", nullptr}; + case 0x0100000000000810: return {"FontNintendoExtension", &FontNintendoExtension}; + case 0x0100000000000811: return {"FontStandard", &FontStandard}; + case 0x0100000000000812: return {"FontKorean", &FontKorean}; + case 0x0100000000000813: return {"FontChineseTraditional", &FontChineseTraditional}; + case 0x0100000000000814: return {"FontChineseSimple", &FontChineseSimple}; + case 0x0100000000000815: return {"FontBfcpx", nullptr}; + case 0x0100000000000816: return {"SystemUpdate", nullptr}; + case 0x0100000000000817: return {"0100000000000817", nullptr}; + case 0x0100000000000818: return {"FirmwareDebugSettings", nullptr}; + case 0x0100000000000819: return {"BootImagePackage", nullptr}; + case 0x010000000000081A: return {"BootImagePackageSafe", nullptr}; + case 0x010000000000081B: return {"BootImagePackageExFat", nullptr}; + case 0x010000000000081C: return {"BootImagePackageExFatSafe", nullptr}; + case 0x010000000000081D: return {"FatalMessage", nullptr}; + case 0x010000000000081E: return {"ControllerIcon", nullptr}; + case 0x010000000000081F: return {"PlatformConfigIcosa", nullptr}; + case 0x0100000000000820: return {"PlatformConfigCopper", nullptr}; + case 0x0100000000000821: return {"PlatformConfigHoag", nullptr}; + case 0x0100000000000822: return {"ControllerFirmware", nullptr}; + case 0x0100000000000823: return {"NgWord2", &NgWord2}; + case 0x0100000000000824: return {"PlatformConfigIcosaMariko", nullptr}; + case 0x0100000000000825: return {"ApplicationBlackList", nullptr}; + case 0x0100000000000826: return {"RebootlessSystemUpdateVersion", nullptr}; + case 0x0100000000000827: return {"ContentActionTable", nullptr}; + case 0x0100000000000828: return {"FunctionBlackList", nullptr}; + case 0x0100000000000829: return {"PlatformConfigCalcio", nullptr}; + case 0x0100000000000830: return {"NgWordT", nullptr}; + case 0x0100000000000831: return {"PlatformConfigAula", nullptr}; + case 0x0100000000000832: return {"CradleFirmware", nullptr}; + case 0x0100000000000835: return {"ErrorMessageUtf8", nullptr}; + case 0x0100000000000859: return {"ClientCertData", nullptr}; + case 0x010000000000085C: return {"GameCardConfigurationData", nullptr}; + default: return {nullptr, nullptr}; + } +} VirtualFile SynthesizeSystemArchive(const u64 title_id) { - if (title_id < SYSTEM_ARCHIVES.front().title_id || title_id > SYSTEM_ARCHIVES.back().title_id) { - return nullptr; + auto const desc = GetSystemArchive(title_id); + LOG_INFO(Service_FS, "Synthesizing system archive '{}' (0x{:016X}).", desc.name, title_id); + if (desc.supplier != nullptr) { + if (auto const dir = desc.supplier(); dir != nullptr) { + if (auto const romfs = CreateRomFS(dir); romfs != nullptr) { + LOG_INFO(Service_FS, " - System archive generation successful!"); + return romfs; + } + } } - - const auto& desc = SYSTEM_ARCHIVES[title_id - SYSTEM_ARCHIVE_BASE_TITLE_ID]; - - LOG_INFO(Service_FS, "Synthesizing system archive '{}' (0x{:016X}).", desc.name, desc.title_id); - - if (desc.supplier == nullptr) { - return nullptr; - } - - const auto dir = desc.supplier(); - - if (dir == nullptr) { - return nullptr; - } - - const auto romfs = CreateRomFS(dir); - - if (romfs == nullptr) { - return nullptr; - } - - LOG_INFO(Service_FS, " - System archive generation successful!"); - return romfs; + return nullptr; } } // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/time_zone_binary.cpp b/src/core/file_sys/system_archive/time_zone_binary.cpp index 316ff0dc6f..9b79b6fcd3 100644 --- a/src/core/file_sys/system_archive/time_zone_binary.cpp +++ b/src/core/file_sys/system_archive/time_zone_binary.cpp @@ -1,86 +1,48 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include "common/swap.h" #include "core/file_sys/system_archive/time_zone_binary.h" +#include "core/file_sys/vfs/vfs_static.h" +#include "core/file_sys/vfs/vfs_types.h" #include "core/file_sys/vfs/vfs_vector.h" #include "nx_tzdb.h" namespace FileSys::SystemArchive { -const static std::map>&> - tzdb_zoneinfo_dirs = {{"Africa", NxTzdb::africa}, - {"America", NxTzdb::america}, - {"Antarctica", NxTzdb::antarctica}, - {"Arctic", NxTzdb::arctic}, - {"Asia", NxTzdb::asia}, - {"Atlantic", NxTzdb::atlantic}, - {"Australia", NxTzdb::australia}, - {"Brazil", NxTzdb::brazil}, - {"Canada", NxTzdb::canada}, - {"Chile", NxTzdb::chile}, - {"Etc", NxTzdb::etc}, - {"Europe", NxTzdb::europe}, - {"Indian", NxTzdb::indian}, - {"Mexico", NxTzdb::mexico}, - {"Pacific", NxTzdb::pacific}, - {"US", NxTzdb::us}}; - -const static std::map>&> - tzdb_america_dirs = {{"Argentina", NxTzdb::america_argentina}, - {"Indiana", NxTzdb::america_indiana}, - {"Kentucky", NxTzdb::america_kentucky}, - {"North_Dakota", NxTzdb::america_north_dakota}}; - -static void GenerateFiles(std::vector& directory, - const std::map>& files) { - for (const auto& [filename, data] : files) { - const auto data_copy{data}; - const std::string filename_copy{filename}; - VirtualFile file{ - std::make_shared(std::move(data_copy), std::move(filename_copy))}; - directory.push_back(file); - } -} - -static std::vector GenerateZoneinfoFiles() { - std::vector zoneinfo_files; - GenerateFiles(zoneinfo_files, NxTzdb::zoneinfo); - return zoneinfo_files; -} - VirtualDir TimeZoneBinary() { std::vector america_sub_dirs; - for (const auto& [dir_name, files] : tzdb_america_dirs) { - std::vector vfs_files; - GenerateFiles(vfs_files, files); - america_sub_dirs.push_back(std::make_shared( - std::move(vfs_files), std::vector{}, dir_name)); - } - + america_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_america_argentina(), std::vector{}, "Argentina")); + america_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_america_indiana(), std::vector{}, "Indiana")); + america_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_america_kentucky(), std::vector{}, "Kentucky")); + america_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_america_north_dakota(), std::vector{}, "North_Dakota")); std::vector zoneinfo_sub_dirs; - for (const auto& [dir_name, files] : tzdb_zoneinfo_dirs) { - std::vector vfs_files; - GenerateFiles(vfs_files, files); - if (dir_name == "America") { - zoneinfo_sub_dirs.push_back(std::make_shared( - std::move(vfs_files), std::move(america_sub_dirs), dir_name)); - } else { - zoneinfo_sub_dirs.push_back(std::make_shared( - std::move(vfs_files), std::vector{}, dir_name)); - } - } - - std::vector zoneinfo_dir{std::make_shared( - GenerateZoneinfoFiles(), std::move(zoneinfo_sub_dirs), "zoneinfo")}; - std::vector root_files; - GenerateFiles(root_files, NxTzdb::base); - - return std::make_shared(std::move(root_files), std::move(zoneinfo_dir), - "data"); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_africa(), std::vector{}, "Africa")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_america(), std::move(america_sub_dirs), "America")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_antarctica(), std::vector{}, "Antarctica")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_arctic(), std::vector{}, "Arctic")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_asia(), std::vector{}, "Asia")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_atlantic(), std::vector{}, "Atlantic")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_australia(), std::vector{}, "Australia")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_brazil(), std::vector{}, "Brazil")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_canada(), std::vector{}, "Canada")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_chile(), std::vector{}, "Chile")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_etc(), std::vector{}, "Etc")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_europe(), std::vector{}, "Europe")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_indian(), std::vector{}, "Indian")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_mexico(), std::vector{}, "Mexico")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_pacific(), std::vector{}, "Pacific")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_us(), std::vector{}, "US")); + std::vector zoneinfo_dir{std::make_shared(NxTzdb::CollectFiles_zoneinfo(), std::move(zoneinfo_sub_dirs), "zoneinfo")}; + // last files (root) + return std::make_shared(NxTzdb::CollectFiles_base(), std::move(zoneinfo_dir), "data"); } } // namespace FileSys::SystemArchive diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index 416da15ecb..9cd2908823 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -13,6 +16,16 @@ namespace IPC { /// Size of the command buffer area, in 32-bit words. constexpr std::size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32); +/// Must match bitfields +constexpr std::size_t MAX_BUFFER_DESCRIPTORS = 16; +constexpr std::size_t MAX_INCOMING_MOVE_HANDLERS = 16; +constexpr std::size_t MAX_INCOMING_COPY_HANDLERS = 16; + +/// Doesn't need to match bitfields but usually not big enough +constexpr std::size_t MAX_OUTGOING_COPY_OBJECTS = 16; +constexpr std::size_t MAX_OUTGOING_MOVE_OBJECTS = 16; +constexpr std::size_t MAX_OUTGOING_DOMAIN_OBJECTS = 16; + enum class ControlCommand : u32 { ConvertSessionToDomain = 0, ConvertDomainToSession = 1, diff --git a/src/core/hle/kernel/k_capabilities.cpp b/src/core/hle/kernel/k_capabilities.cpp index d2288c30d1..90f41dd1fc 100644 --- a/src/core/hle/kernel/k_capabilities.cpp +++ b/src/core/hle/kernel/k_capabilities.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -268,7 +271,8 @@ Result KCapabilities::SetHandleTableCapability(const u32 cap) { Result KCapabilities::SetDebugFlagsCapability(const u32 cap) { // Validate. const DebugFlags pack{cap}; - R_UNLESS(pack.reserved == 0, ResultReservedUsed); + // TODO: Enabling this breaks compatibility with HBloader and such + //R_UNLESS(pack.reserved == 0, ResultReservedUsed); DebugFlags debug_capabilities{m_debug_capabilities}; debug_capabilities.allow_debug.Assign(pack.allow_debug); diff --git a/src/core/hle/kernel/k_page_table_base.cpp b/src/core/hle/kernel/k_page_table_base.cpp index e5b2ed7041..3696f9f251 100644 --- a/src/core/hle/kernel/k_page_table_base.cpp +++ b/src/core/hle/kernel/k_page_table_base.cpp @@ -268,6 +268,7 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool // Set other basic fields. m_enable_aslr = enable_aslr; m_enable_device_address_space_merge = enable_das_merge; + m_allowed_exec_device_mapping = false; m_address_space_start = start; m_address_space_end = end; m_is_kernel = false; diff --git a/src/core/hle/kernel/k_page_table_base.h b/src/core/hle/kernel/k_page_table_base.h index 90f8124a24..726fad989f 100644 --- a/src/core/hle/kernel/k_page_table_base.h +++ b/src/core/hle/kernel/k_page_table_base.h @@ -223,6 +223,7 @@ private: bool m_is_kernel{}; bool m_enable_aslr{}; bool m_enable_device_address_space_merge{}; + bool m_allowed_exec_device_mapping{}; KMemoryBlockSlabManager* m_memory_block_slab_manager{}; KBlockInfoManager* m_block_info_manager{}; KResourceLimit* m_resource_limit{}; @@ -255,6 +256,10 @@ public: return m_enable_aslr; } + void AllowDeviceMappingOfExecPages() { + m_allowed_exec_device_mapping = true; + } + bool Contains(KProcessAddress addr) const { return m_address_space_start <= addr && addr <= m_address_space_end - 1; } diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index bd50f74c27..70e578f22a 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -341,6 +341,11 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPa // Initialize capabilities. R_TRY(m_capabilities.InitializeForKip(caps, std::addressof(m_page_table))); + // Enable mapping device pages as executable on legacy processes. + if (m_capabilities.GetIntendedKernelMajorVersion() < 26) { + m_page_table.AllowDeviceMappingOfExecPages(); + } + // Initialize the process id. m_process_id = m_kernel.CreateNewUserProcessID(); ASSERT(InitialProcessIdMin <= m_process_id); @@ -437,6 +442,11 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, // Initialize capabilities. R_TRY(m_capabilities.InitializeForUser(user_caps, std::addressof(m_page_table))); + // Enable mapping device pages as executable on legacy processes. + if (m_capabilities.GetIntendedKernelMajorVersion() < 26) { + m_page_table.AllowDeviceMappingOfExecPages(); + } + // Initialize the process id. m_process_id = m_kernel.CreateNewUserProcessID(); ASSERT(ProcessIdMin <= m_process_id); @@ -989,6 +999,9 @@ Result KProcess::Run(s32 priority, size_t stack_size) { main_thread->GetContext().r[0] = 0; main_thread->GetContext().r[1] = thread_handle; + // Pass the thread handle to the thread local region. + this->GetMemory().Write32(GetInteger(main_thread->GetTlsAddress()) + 0x110, thread_handle); + // Update our state. this->ChangeState((state == State::Created) ? State::Running : State::RunningAttached); ON_RESULT_FAILURE_2 { diff --git a/src/core/hle/kernel/k_process_page_table.h b/src/core/hle/kernel/k_process_page_table.h index 6462e44a99..97c9238907 100644 --- a/src/core/hle/kernel/k_process_page_table.h +++ b/src/core/hle/kernel/k_process_page_table.h @@ -33,6 +33,10 @@ public: m_page_table.Finalize(); } + void AllowDeviceMappingOfExecPages() { + m_page_table.AllowDeviceMappingOfExecPages(); + } + [[nodiscard]] Core::Memory::Memory& GetMemory() noexcept { return m_page_table.GetMemory(); } [[nodiscard]] Core::Memory::Memory const& GetMemory() const noexcept { return m_page_table.GetMemory(); } [[nodiscard]] Common::PageTable& GetImpl() noexcept { return m_page_table.GetImpl(); } diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index ea9b7eb114..0af315d726 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -76,11 +76,13 @@ namespace Kernel { std::atomic_uint16_t interrupt_flag; std::atomic_uint8_t cache_maintenance_flag; std::atomic_int64_t thread_cpu_time; + std::atomic_uint32_t current_thread_handle; }; static_assert(offsetof(ThreadLocalRegion, disable_count) == 0x100); static_assert(offsetof(ThreadLocalRegion, interrupt_flag) == 0x102); static_assert(offsetof(ThreadLocalRegion, cache_maintenance_flag) == 0x104); static_assert(offsetof(ThreadLocalRegion, thread_cpu_time) == 0x108); + static_assert(offsetof(ThreadLocalRegion, current_thread_handle) == 0x110); class ThreadQueueImplForKThreadSleep final : public KThreadQueueWithoutEndWait { public: @@ -128,24 +130,24 @@ namespace Kernel { // Next, assert things based on the type. switch (type) { - case ThreadType::Main: - ASSERT(arg == 0); - [[fallthrough]]; - case ThreadType::User: - ASSERT(((owner == nullptr) || - (owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask())); - ASSERT(((owner == nullptr) || (prio > Svc::LowestThreadPriority) || - (owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask())); - break; - case ThreadType::HighPriority: - case ThreadType::Dummy: - break; - case ThreadType::Kernel: - UNIMPLEMENTED(); - break; - default: - ASSERT_MSG(false, "KThread::Initialize: Unknown ThreadType {}", static_cast(type)); - break; + case ThreadType::Main: + ASSERT(arg == 0); + [[fallthrough]]; + case ThreadType::User: + ASSERT(((owner == nullptr) || + (owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask())); + ASSERT(((owner == nullptr) || (prio > Svc::LowestThreadPriority) || + (owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask())); + break; + case ThreadType::HighPriority: + case ThreadType::Dummy: + break; + case ThreadType::Kernel: + UNIMPLEMENTED(); + break; + default: + ASSERT_MSG(false, "KThread::Initialize: Unknown ThreadType {}", static_cast(type)); + break; } m_thread_type = type; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 129bda194a..5f88fa498f 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -372,17 +372,17 @@ struct KernelCore::Impl { } // Gets the dummy KThread for the caller, allocating a new one if this is the first time - KThread* GetHostDummyThread(KThread* existing_thread) { - if (tls_data.thread == nullptr) { + KThread* GetHostDummyThread(ThreadLocalData& t, KThread* existing_thread) { + if (t.thread == nullptr) { auto const initialize{[](KThread* thread) { ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess()); return thread; }}; - tls_data.raw_thread.emplace(system.Kernel()); - tls_data.thread = existing_thread ? existing_thread : initialize(&*tls_data.raw_thread); - ASSERT(tls_data.thread != nullptr); + t.raw_thread.emplace(system.Kernel()); + t.thread = existing_thread ? existing_thread : initialize(&*t.raw_thread); + ASSERT(t.thread != nullptr); } - return tls_data.thread; + return t.thread; } /// Registers a CPU core thread by allocating a host thread ID for it @@ -395,7 +395,7 @@ struct KernelCore::Impl { /// Registers a new host thread by allocating a host thread ID for it void RegisterHostThread(KThread* existing_thread) { - (void)GetHostDummyThread(existing_thread); + (void)GetHostDummyThread(tls_data, existing_thread); } [[nodiscard]] u32 GetCurrentHostThreadID() { @@ -419,9 +419,8 @@ struct KernelCore::Impl { } KThread* GetCurrentEmuThread() { - if (!tls_data.current_thread) - tls_data.current_thread = GetHostDummyThread(nullptr); - return tls_data.current_thread; + auto& t = tls_data; + return t.current_thread ? t.current_thread : (t.current_thread = GetHostDummyThread(t, nullptr)); } void SetCurrentEmuThread(KThread* thread) { diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp index ca5ce6fe07..1aa6f87c04 100644 --- a/src/core/hle/kernel/svc/svc_thread.cpp +++ b/src/core/hle/kernel/svc/svc_thread.cpp @@ -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 2023 yuzu Emulator Project @@ -75,7 +75,12 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u KThread::Register(kernel, thread); // Add the thread to the handle table. - R_RETURN(process.GetHandleTable().Add(out_handle, thread)); + R_TRY(process.GetHandleTable().Add(out_handle, thread)); + + // Pass the thread handle to the thread local region. + process.GetMemory().Write32(GetInteger(thread->GetTlsAddress()) + 0x110, *out_handle); + + R_SUCCEED(); } /// Starts the thread for the provided handle diff --git a/src/core/hle/kernel/svc_version.h b/src/core/hle/kernel/svc_version.h index b47874dcde..ec5c5a006a 100644 --- a/src/core/hle/kernel/svc_version.h +++ b/src/core/hle/kernel/svc_version.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 2023 yuzu Emulator Project @@ -53,8 +53,8 @@ constexpr inline u32 RequiredKernelVersion = // This is the highest SVC version supported, to be updated on new kernel releases. // NOTE: Official kernel versions have SVC major = SDK major + 4, SVC minor = SDK minor. -constexpr inline u32 SupportedKernelMajorVersion = ConvertToSvcMajorVersion(20); -constexpr inline u32 SupportedKernelMinorVersion = ConvertToSvcMinorVersion(5); +constexpr inline u32 SupportedKernelMajorVersion = ConvertToSvcMajorVersion(22); +constexpr inline u32 SupportedKernelMinorVersion = ConvertToSvcMinorVersion(2); constexpr inline u32 SupportedKernelVersion = EncodeKernelVersion(SupportedKernelMajorVersion, SupportedKernelMinorVersion); diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index 43c569851d..c33b765000 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -1155,6 +1155,14 @@ void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) { StoreSaveDataThumbnail(ctx, uuid, tid); } +void Module::Interface::GetPinCodeLength(HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(0); +} + void Module::Interface::StoreSaveDataThumbnail(HLERequestContext& ctx, const Common::UUID& uuid, const u64 tid) { IPC::ResponseBuilder rb{ctx, 2}; diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index b01998e9c9..19af7c74c9 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.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 2018 yuzu Emulator Project @@ -48,6 +48,7 @@ public: void StoreSaveDataThumbnailApplication(HLERequestContext& ctx); void GetBaasAccountManagerForSystemService(HLERequestContext& ctx); void StoreSaveDataThumbnailSystem(HLERequestContext& ctx); + void GetPinCodeLength(HLERequestContext& ctx); private: Result InitializeApplicationInfoBase(); diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp index 9eea0ea2ed..195af7ff69 100644 --- a/src/core/hle/service/acc/acc_su.cpp +++ b/src/core/hle/service/acc/acc_su.cpp @@ -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 2018 yuzu Emulator Project @@ -60,7 +60,7 @@ ACC_SU::ACC_SU(std::shared_ptr module_, std::shared_ptr {291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"}, {299, nullptr, "SuspendBackgroundDaemon"}, {400, nullptr, "SetPinCode"}, // 18.0.0+ - {401, nullptr, "GetPinCodeLength"}, // 18.0.0+ + {401, &ACC_SU::GetPinCodeLength, "GetPinCodeLength"}, // 18.0.0+ {402, nullptr, "GetPinCode"}, // 18.0.0+ {410, nullptr, "GetPinCodeErrorCount"}, // 18.0.0+ {411, nullptr, "ResetPinCodeErrorCount"}, // 18.0.0+ diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp index 3832f5216d..c39e916366 100644 --- a/src/core/hle/service/acc/acc_u1.cpp +++ b/src/core/hle/service/acc/acc_u1.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -40,7 +43,7 @@ ACC_U1::ACC_U1(std::shared_ptr module_, std::shared_ptr {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"}, {190, nullptr, "GetUserLastOpenedApplication"}, {191, nullptr, "ActivateOpenContextHolder"}, - {401, nullptr, "GetPinCodeLength"}, // 18.0.0+ + {401, &ACC_U1::GetPinCodeLength, "GetPinCodeLength"}, // 18.0.0+ {402, nullptr, "GetPinCode"}, // 18.0.0+ {997, nullptr, "DebugInvalidateTokenCacheForUser"}, {998, nullptr, "DebugSetUserStateClose"}, diff --git a/src/core/hle/service/am/service/application_creator.cpp b/src/core/hle/service/am/service/application_creator.cpp index d16fd7dd84..81620a5067 100644 --- a/src/core/hle/service/am/service/application_creator.cpp +++ b/src/core/hle/service/am/service/application_creator.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -92,7 +92,7 @@ Result IApplicationCreator::CreateSystemApplication( std::unique_ptr loader; auto process = - CreateProcess(system, application_id, 1, 21); + CreateProcess(system, application_id, 1, 22); R_UNLESS(process != nullptr, ResultUnknown); const auto applet = std::make_shared(system, std::move(process), true); diff --git a/src/core/hle/service/am/service/common_state_getter.cpp b/src/core/hle/service/am/service/common_state_getter.cpp index 0af5b31714..5b5e21b8e0 100644 --- a/src/core/hle/service/am/service/common_state_getter.cpp +++ b/src/core/hle/service/am/service/common_state_getter.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -75,7 +75,8 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr, "Unknown610"}, //21.0.0+ + {611, D<&ICommonStateGetter::Unknown611>, "Unknown611"}, //22.0.0+ {900, D<&ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled>, "SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled"}, //11.0.0+ {910, nullptr, "GetLaunchRequiredTick"}, //17.0.0+ {1000, nullptr, "BeginVrMode3d"}, //19.0.0+ @@ -362,4 +363,14 @@ Result ICommonStateGetter::SetHandlingHomeButtonShortPressedEnabled(bool enabled R_SUCCEED(); } +Result ICommonStateGetter::Unknown610() { + LOG_WARNING(Service_AM, "(STUBBED) called"); + R_SUCCEED(); +} + +Result ICommonStateGetter::Unknown611() { + LOG_WARNING(Service_AM, "(STUBBED) called"); + R_SUCCEED(); +} + } // namespace Service::AM diff --git a/src/core/hle/service/am/service/common_state_getter.h b/src/core/hle/service/am/service/common_state_getter.h index a98024182f..c1bec4f2a2 100644 --- a/src/core/hle/service/am/service/common_state_getter.h +++ b/src/core/hle/service/am/service/common_state_getter.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 2024 yuzu Emulator Project @@ -63,6 +63,8 @@ private: Result SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(); Result PushToGeneralChannel(SharedPointer storage); // cmd 20 Result SetHandlingHomeButtonShortPressedEnabled(bool enabled); + Result Unknown610(); + Result Unknown611(); void SetCpuBoostMode(HLERequestContext& ctx); diff --git a/src/core/hle/service/am/service/library_applet_accessor.cpp b/src/core/hle/service/am/service/library_applet_accessor.cpp index c5b6929ab3..ef91f69c23 100644 --- a/src/core/hle/service/am/service/library_applet_accessor.cpp +++ b/src/core/hle/service/am/service/library_applet_accessor.cpp @@ -78,6 +78,7 @@ ILibraryAppletAccessor::ILibraryAppletAccessor(Core::System& system_, {120, nullptr, "GetLibraryAppletInfo"}, {150, nullptr, "RequestForAppletToGetForeground"}, {160, D<&ILibraryAppletAccessor::GetIndirectLayerConsumerHandle>, "GetIndirectLayerConsumerHandle"}, //2.0.0+ + {170, D<&ILibraryAppletAccessor::Unknown170>, "Unknown170"}, //22.0.0+ }; // clang-format on @@ -217,6 +218,12 @@ Result ILibraryAppletAccessor::GetIndirectLayerConsumerHandle(Out out_handl R_SUCCEED(); } +Result ILibraryAppletAccessor::Unknown170(OutCopyHandle out_event) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + *out_event = m_applet->unknown_event.GetHandle(); + R_SUCCEED(); +} + void ILibraryAppletAccessor::FrontendExecute() { if (m_applet->frontend) { m_applet->frontend->Initialize(); diff --git a/src/core/hle/service/am/service/library_applet_accessor.h b/src/core/hle/service/am/service/library_applet_accessor.h index 841d0d5146..02fcd07fd3 100644 --- a/src/core/hle/service/am/service/library_applet_accessor.h +++ b/src/core/hle/service/am/service/library_applet_accessor.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 2024 yuzu Emulator Project @@ -37,6 +37,7 @@ private: Result GetPopOutDataEvent(OutCopyHandle out_event); Result GetPopInteractiveOutDataEvent(OutCopyHandle out_event); Result GetIndirectLayerConsumerHandle(Out out_handle); + Result Unknown170(OutCopyHandle out_event); void FrontendExecute(); void FrontendExecuteInteractive(); diff --git a/src/core/hle/service/am/service/library_applet_creator.cpp b/src/core/hle/service/am/service/library_applet_creator.cpp index e38729e70a..3de5740237 100644 --- a/src/core/hle/service/am/service/library_applet_creator.cpp +++ b/src/core/hle/service/am/service/library_applet_creator.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -118,9 +118,10 @@ std::shared_ptr CreateGuestApplet(Core::System& system, Firmware1900 = 19, Firmware2000 = 20, Firmware2100 = 21, + Firmware2200 = 22, }; - auto process = CreateProcess(system, program_id, Firmware1400, Firmware2100); + auto process = CreateProcess(system, program_id, Firmware1400, Firmware2200); if (!process) { // Couldn't initialize the guest process return {}; diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp index 8622ab058f..d89f147a75 100644 --- a/src/core/hle/service/caps/caps_su.cpp +++ b/src/core/hle/service/caps/caps_su.cpp @@ -33,8 +33,7 @@ IScreenShotApplicationService::IScreenShotApplicationService( IScreenShotApplicationService::~IScreenShotApplicationService() = default; -Result IScreenShotApplicationService::SetShimLibraryVersion(ShimLibraryVersion library_version, - ClientAppletResourceUserId aruid) { +Result IScreenShotApplicationService::SetShimLibraryVersion(ShimLibraryVersion library_version, ClientAppletResourceUserId aruid) { LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}", library_version, aruid.pid); R_SUCCEED(); diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index b851de8496..79578956b1 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -53,38 +53,53 @@ public: {20102, nullptr, "GetFriendDetailedInfo"}, {20103, nullptr, "SyncFriendList"}, {20104, &IFriendService::RequestSyncFriendList, "RequestSyncFriendList"}, - {20105, &IFriendService::GetFriendListForViewer, "GetFriendListForViewer"}, - {20110, nullptr, "LoadFriendSetting"}, + {20105, &IFriendService::GetFriendListForViewer, "GetFriendListForViewerV1"}, + {20106, nullptr, "UpdateFriendInfoForViewerV1"}, + {20107, nullptr, "GetFriendDetailedInfoV2"}, // 20.0.0+ + {20108, &IFriendService::GetFriendListForViewer, "GetFriendListForViewerV2"}, // 22.0.0+ + {20109, nullptr, "UpdateFriendInfoForViewerV2"}, // 22.0.0+ + {20110, nullptr, "LoadFriendSettingV1"}, + {20111, nullptr, "LoadFriendSettingV2"}, // 22.0.0+ {20200, &IFriendService::GetReceivedFriendRequestCount, "GetReceivedFriendRequestCount"}, - {20201, nullptr, "GetFriendRequestList"}, + {20201, nullptr, "GetFriendRequestListV1"}, + {20202, nullptr, "GetFriendRequestListV2"}, // 20.0.0+ + {20203, nullptr, "GetFriendRequestReceivedNotificationCount"}, // 22.0.0+ {20300, nullptr, "GetFriendCandidateList"}, {20301, nullptr, "GetNintendoNetworkIdInfo"}, - {20302, nullptr, "GetSnsAccountLinkage"}, - {20303, nullptr, "GetSnsAccountProfile"}, - {20304, nullptr, "GetSnsAccountFriendList"}, - {20400, nullptr, "GetBlockedUserList"}, + {20302, nullptr, "GetSnsAccountLinkage"}, // 5.0.0-19.0.1 + {20303, nullptr, "GetSnsAccountProfile"}, // 5.0.0-19.0.1 + {20304, nullptr, "GetSnsAccountFriendList"}, // 5.0.0-19.0.1 + {20400, nullptr, "GetBlockedUserListV1"}, {20401, nullptr, "SyncBlockedUserList"}, - {20500, nullptr, "GetProfileExtraList"}, + {20402, nullptr, "GetBlockedUserListV2"}, // 20.0.0+ + {20500, nullptr, "GetProfileExtraListV1"}, {20501, nullptr, "GetRelationship"}, + {20502, nullptr, "GetProfileExtraListV2"}, // 19.0.0+ {20600, &IFriendService::GetUserPresenceView, "GetUserPresenceViewV1"}, - {20601, &IFriendService::GetUserPresenceView, "GetUserPresenceViewV2"}, - {20700, nullptr, "GetPlayHistoryList"}, + {20601, &IFriendService::GetUserPresenceView, "GetUserPresenceViewV2"}, // 19.0.0+ + {20700, nullptr, "GetPlayHistoryListV1"}, {20701, &IFriendService::GetPlayHistoryStatistics, "GetPlayHistoryStatistics"}, + {20702, nullptr, "GetPlayHistoryListV2"}, // 19.0.0+ {20800, &IFriendService::LoadUserSetting, "LoadUserSettingV1"}, {20801, nullptr, "SyncUserSetting"}, - {20802, &IFriendService::LoadUserSetting, "LoadUserSettingV2"}, + {20802, &IFriendService::LoadUserSetting, "LoadUserSettingV2"}, // 19.0.0+ {20900, &IFriendService::RequestListSummaryOverlayNotification, "RequestListSummaryOverlayNotification"}, {21000, nullptr, "GetExternalApplicationCatalog"}, - {22000, nullptr, "GetReceivedFriendInvitationList"}, - {22001, nullptr, "GetReceivedFriendInvitationDetailedInfo"}, + {22000, nullptr, "GetReceivedFriendInvitationListV1"}, + {22001, nullptr, "GetReceivedFriendInvitationDetailedInfoV1"}, + {22002, nullptr, "GetReceivedFriendInvitationListV2"}, // 19.0.0+ + {22003, nullptr, "GetReceivedFriendInvitationDetailedInfoV2"}, // 19.0.0+ {22010, &IFriendService::GetReceivedFriendInvitationCountCache, "GetReceivedFriendInvitationCountCache"}, {30100, nullptr, "DropFriendNewlyFlags"}, {30101, nullptr, "DeleteFriend"}, {30110, nullptr, "DropFriendNewlyFlag"}, {30120, nullptr, "ChangeFriendFavoriteFlag"}, {30121, nullptr, "ChangeFriendOnlineNotificationFlag"}, + {30130, nullptr, "SetFriendNote"}, // 22.0.0+ + {30131, nullptr, "RequestUploadPendingNote"}, // 22.0.0+ + {30190, nullptr, "RequestSyncLocalUpdates"}, // 22.0.0+ {30200, nullptr, "SendFriendRequest"}, - {30201, nullptr, "SendFriendRequestWithApplicationInfo"}, + {30201, nullptr, "SendFriendRequestWithApplicationInfoV1"}, {30202, nullptr, "CancelFriendRequest"}, {30203, nullptr, "AcceptFriendRequest"}, {30204, nullptr, "RejectFriendRequest"}, @@ -97,21 +112,27 @@ public: {30215, nullptr, "SendFriendRequestWithExternalApplicationCatalogId"}, {30216, nullptr, "ResendFacedFriendRequest"}, {30217, nullptr, "SendFriendRequestWithNintendoNetworkIdInfo"}, - {30300, nullptr, "GetSnsAccountLinkPageUrl"}, - {30301, nullptr, "UnlinkSnsAccount"}, + {30218, nullptr, "SendFriendRequestWithApplicationInfoV2"}, // 20.0.0+ + {30300, nullptr, "GetSnsAccountLinkPageUrl"}, // 5.0.0-19.0.1 + {30301, nullptr, "UnlinkSnsAccount"}, // 5.0.0-19.0.1 {30400, nullptr, "BlockUser"}, - {30401, nullptr, "BlockUserWithApplicationInfo"}, + {30401, nullptr, "BlockUserWithApplicationInfoV1"}, {30402, nullptr, "UnblockUser"}, - {30500, nullptr, "GetProfileExtraFromFriendCode"}, + {30403, nullptr, "BlockUserWithApplicationInfoV2"}, // 20.0.0+ + {30500, nullptr, "GetProfileExtraFromFriendCodeV1"}, + {30501, nullptr, "GetProfileExtraFromFriendCodeV2"}, // 19.0.0+ {30700, nullptr, "DeletePlayHistory"}, + {30701, nullptr, "AddPlayHistoryWithApplication"}, // 19.0.0+ {30810, nullptr, "ChangePresencePermission"}, {30811, nullptr, "ChangeFriendRequestReception"}, {30812, nullptr, "ChangePlayLogPermission"}, {30820, nullptr, "IssueFriendCode"}, {30830, nullptr, "ClearPlayLog"}, - {30900, nullptr, "SendFriendInvitation"}, + {30900, nullptr, "SendFriendInvitationV1"}, + {30901, nullptr, "SendFriendInvitationV2"}, // 19.0.0+ {30910, nullptr, "ReadFriendInvitation"}, {30911, nullptr, "ReadAllFriendInvitations"}, + {31000, nullptr, "OpenUser"}, // 19.0.0+ {40100, nullptr, "DeleteFriendListCache"}, {40400, nullptr, "DeleteBlockedUserListCache"}, {49900, nullptr, "DeleteNetworkServiceAccountCache"}, diff --git a/src/core/hle/service/hle_ipc.cpp b/src/core/hle/service/hle_ipc.cpp index 8569d2bad8..8db8f8538c 100644 --- a/src/core/hle/service/hle_ipc.cpp +++ b/src/core/hle/service/hle_ipc.cpp @@ -128,10 +128,12 @@ Result SessionRequestManager::HandleDomainSyncRequest(Kernel::KServerSession* se return ResultSuccess; } -HLERequestContext::HLERequestContext(Kernel::KernelCore& kernel_, Core::Memory::Memory& memory_, - Kernel::KServerSession* server_session_, - Kernel::KThread* thread_) - : server_session(server_session_), thread(thread_), kernel{kernel_}, memory{memory_} { +HLERequestContext::HLERequestContext(Kernel::KernelCore& kernel_, Core::Memory::Memory& memory_, Kernel::KServerSession* server_session_, Kernel::KThread* thread_) + : server_session(server_session_) + , thread(thread_) + , kernel{kernel_} + , memory{memory_} +{ cmd_buf[0] = 0; } @@ -155,9 +157,6 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { } if (incoming) { // Populate the object lists with the data in the IPC request. - incoming_copy_handles.reserve(handle_descriptor_header->num_handles_to_copy); - incoming_move_handles.reserve(handle_descriptor_header->num_handles_to_move); - for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) { incoming_copy_handles.push_back(rp.Pop()); } @@ -172,11 +171,6 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { } } - buffer_x_descriptors.reserve(command_header->num_buf_x_descriptors); - buffer_a_descriptors.reserve(command_header->num_buf_a_descriptors); - buffer_b_descriptors.reserve(command_header->num_buf_b_descriptors); - buffer_w_descriptors.reserve(command_header->num_buf_w_descriptors); - for (u32 i = 0; i < command_header->num_buf_x_descriptors; ++i) { buffer_x_descriptors.push_back(rp.PopRaw()); } diff --git a/src/core/hle/service/hle_ipc.h b/src/core/hle/service/hle_ipc.h index c2e0e5e8c4..ec8185a09e 100644 --- a/src/core/hle/service/hle_ipc.h +++ b/src/core/hle/service/hle_ipc.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -11,6 +14,7 @@ #include #include #include +#include #include "common/assert.h" #include "common/common_types.h" @@ -181,8 +185,7 @@ private: */ class HLERequestContext { public: - explicit HLERequestContext(Kernel::KernelCore& kernel, Core::Memory::Memory& memory, - Kernel::KServerSession* session, Kernel::KThread* thread); + explicit HLERequestContext(Kernel::KernelCore& kernel, Core::Memory::Memory& memory, Kernel::KServerSession* session, Kernel::KThread* thread); ~HLERequestContext(); /// Returns a pointer to the IPC command buffer for this request. @@ -233,19 +236,19 @@ public: return data_payload_offset; } - [[nodiscard]] const std::vector& BufferDescriptorX() const { + [[nodiscard]] const boost::container::static_vector& BufferDescriptorX() const { return buffer_x_descriptors; } - [[nodiscard]] const std::vector& BufferDescriptorA() const { + [[nodiscard]] const boost::container::static_vector& BufferDescriptorA() const { return buffer_a_descriptors; } - [[nodiscard]] const std::vector& BufferDescriptorB() const { + [[nodiscard]] const boost::container::static_vector& BufferDescriptorB() const { return buffer_b_descriptors; } - [[nodiscard]] const std::vector& BufferDescriptorC() const { + [[nodiscard]] const boost::container::static_vector& BufferDescriptorC() const { return buffer_c_descriptors; } @@ -399,38 +402,35 @@ private: Kernel::KHandleTable* client_handle_table{}; Kernel::KThread* thread{}; - std::vector incoming_move_handles; - std::vector incoming_copy_handles; + boost::container::static_vector buffer_x_descriptors; + boost::container::static_vector buffer_a_descriptors; + boost::container::static_vector buffer_b_descriptors; + boost::container::static_vector buffer_w_descriptors; + boost::container::static_vector buffer_c_descriptors; + boost::container::static_vector incoming_move_handles; + boost::container::static_vector incoming_copy_handles; + boost::container::static_vector outgoing_move_objects; + boost::container::static_vector outgoing_copy_objects; + boost::container::static_vector outgoing_domain_objects; - std::vector outgoing_move_objects; - std::vector outgoing_copy_objects; - std::vector outgoing_domain_objects; + mutable std::array, 3> read_buffer_data_a{}; + mutable std::array, 3> read_buffer_data_x{}; std::optional command_header; std::optional handle_descriptor_header; std::optional data_payload_header; std::optional domain_message_header; - std::vector buffer_x_descriptors; - std::vector buffer_a_descriptors; - std::vector buffer_b_descriptors; - std::vector buffer_w_descriptors; - std::vector buffer_c_descriptors; + std::weak_ptr manager{}; + Kernel::KernelCore& kernel; + Core::Memory::Memory& memory; - u32_le command{}; u64 pid{}; + u32_le command{}; u32 write_size{}; u32 data_payload_offset{}; u32 handles_offset{}; u32 domain_offset{}; - - std::weak_ptr manager{}; - bool is_deferred{false}; - - Kernel::KernelCore& kernel; - Core::Memory::Memory& memory; - - mutable std::array, 3> read_buffer_data_a{}; - mutable std::array, 3> read_buffer_data_x{}; + bool is_deferred = false; }; } // namespace Service diff --git a/src/core/hle/service/jit/jit.cpp b/src/core/hle/service/jit/jit.cpp index 4941a71a01..1179e8b61d 100644 --- a/src/core/hle/service/jit/jit.cpp +++ b/src/core/hle/service/jit/jit.cpp @@ -1,6 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/logging.h" #include "core/arm/debug.h" #include "core/arm/symbols.h" #include "core/core.h" @@ -111,10 +115,7 @@ public: const VAddr input_ptr{context.AddHeap(in_data.data(), in_data.size())}; const VAddr output_ptr{context.AddHeap(out_data.data(), out_data.size())}; - const u64 wrapper_value{context.CallFunction(callbacks.Control, ret_ptr, configuration_ptr, - command, input_ptr, in_data.size(), output_ptr, - out_data.size())}; - + const u64 wrapper_value = context.CallFunction(callbacks.Control, ret_ptr, configuration_ptr, command, input_ptr, in_data.size(), output_ptr, out_data.size()); *out_return_value = context.GetHeap(ret_ptr); context.GetHeap(output_ptr, out_data.data(), out_data.size()); @@ -126,9 +127,7 @@ public: R_THROW(ResultUnknown); } - Result LoadPlugin(u64 tmem_size, InCopyHandle tmem, - InBuffer nrr, - InBuffer nro) { + Result LoadPlugin(u64 tmem_size, InCopyHandle tmem, InBuffer nrr, InBuffer nro) { if (!tmem) { LOG_ERROR(Service_JIT, "Invalid transfer memory handle!"); R_THROW(ResultUnknown); @@ -139,9 +138,10 @@ public: configuration.transfer_memory.size = tmem_size; // Gather up all the callbacks from the loaded plugin - auto symbols{Core::Symbols::GetSymbols(nro, true)}; - const auto GetSymbol{[&](const std::string& name) { return symbols[name].first; }}; - + auto symbols = Core::Symbols::GetSymbols(nro, true); + const auto GetSymbol = [&](const std::string& name) { + return symbols[name].first; + }; callbacks.rtld_fini = GetSymbol("_fini"); callbacks.rtld_init = GetSymbol("_init"); callbacks.Control = GetSymbol("nnjitpluginControl"); @@ -153,8 +153,7 @@ public: callbacks.OnPrepared = GetSymbol("nnjitpluginOnPrepared"); callbacks.Keeper = GetSymbol("nnjitpluginKeeper"); - if (callbacks.GetVersion == 0 || callbacks.Configure == 0 || callbacks.GenerateCode == 0 || - callbacks.OnPrepared == 0) { + if (callbacks.GetVersion == 0 || callbacks.Configure == 0 || callbacks.GenerateCode == 0 || callbacks.OnPrepared == 0 || callbacks.Control == 0) { LOG_ERROR(Service_JIT, "plugin does not implement all necessary functionality"); R_THROW(ResultUnknown); } @@ -164,12 +163,9 @@ public: R_THROW(ResultUnknown); } - context.MapProcessMemory(configuration.sys_ro_memory.offset, - configuration.sys_ro_memory.size); - context.MapProcessMemory(configuration.sys_rx_memory.offset, - configuration.sys_rx_memory.size); - context.MapProcessMemory(configuration.transfer_memory.offset, - configuration.transfer_memory.size); + context.MapProcessMemory(configuration.sys_ro_memory.offset, configuration.sys_ro_memory.size); + context.MapProcessMemory(configuration.sys_rx_memory.offset, configuration.sys_rx_memory.size); + context.MapProcessMemory(configuration.transfer_memory.offset, configuration.transfer_memory.size); // Run ELF constructors, if needed if (callbacks.rtld_init != 0) { @@ -179,7 +175,7 @@ public: // Function prototype: // u64 GetVersion(); const auto version{context.CallFunction(callbacks.GetVersion)}; - if (version != 1) { + if (version > 1) { LOG_ERROR(Service_JIT, "unknown plugin version {}", version); R_THROW(ResultUnknown); } diff --git a/src/core/hle/service/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp index 522d849e6f..ce51fbd3f5 100644 --- a/src/core/hle/service/jit/jit_context.cpp +++ b/src/core/hle/service/jit/jit_context.cpp @@ -24,15 +24,25 @@ using namespace Common::ELF; namespace Service::JIT { +enum HelperFn { + None, + Stop, + Resolve, + Panic, + Memcpy, + Memmove, + Memset, + // sm64 + PanicForPlugin, + AbortImpl, + UnexpectedImpl, + Count +}; + constexpr std::array SVC0_ARM64 = { 0x01, 0x00, 0x00, 0xd4, // svc #0 0xc0, 0x03, 0x5f, 0xd6, // ret }; - -constexpr std::array HELPER_FUNCTIONS{ - "_stop", "_resolve", "_panic", "memcpy", "memmove", "memset", -}; - constexpr size_t STACK_ALIGN = 16; class JITContextImpl; @@ -42,10 +52,12 @@ using IntervalType = boost::icl::interval_set::interval_type; class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { public: - explicit DynarmicCallbacks64(Core::Memory::Memory& memory_, std::vector& local_memory_, - IntervalSet& mapped_ranges_, JITContextImpl& parent_) - : memory{memory_}, local_memory{local_memory_}, - mapped_ranges{mapped_ranges_}, parent{parent_} {} + explicit DynarmicCallbacks64(Core::Memory::Memory& memory_, std::vector& local_memory_, IntervalSet& mapped_ranges_, JITContextImpl& parent_) + : memory{memory_} + , local_memory{local_memory_} + , mapped_ranges{mapped_ranges_} + , parent{parent_} + {} std::optional MemoryReadCode(VAddr vaddr) override { static_assert(Core::Memory::YUZU_PAGESIZE == Dynarmic::CODE_PAGE_SIZE); @@ -57,7 +69,7 @@ public: return cached_code_page.inst[(vaddr & Core::Memory::YUZU_PAGEMASK) / sizeof(u32)]; } void InstructionSynchronizationBarrierRaised() override { - last_code_addr = 0; //reset back, force refetch + last_code_addr = u64(-1); //reset back, force refetch } u8 MemoryRead8(u64 vaddr) override { return ReadMemory(vaddr); @@ -75,13 +87,10 @@ public: return ReadMemory(vaddr); } std::string MemoryReadCString(u64 vaddr) { - std::string result; + std::string result{}; u8 next; - - while ((next = MemoryRead8(vaddr++)) != 0) { - result += next; - } - + while ((next = MemoryRead8(vaddr++)) != 0) + result += char(next); return result; } @@ -157,32 +166,26 @@ private: std::vector& local_memory; IntervalSet& mapped_ranges; JITContextImpl& parent; - Dynarmic::CodePage cached_code_page; - u64 last_code_addr = 0; + u64 last_code_addr = u64(-1); }; class JITContextImpl { public: explicit JITContextImpl(Core::Memory::Memory& memory_) : memory{memory_} { - callbacks = - std::make_unique(memory, local_memory, mapped_ranges, *this); - user_config.callbacks = callbacks.get(); - jit = std::make_unique(user_config); + callbacks.emplace(memory, local_memory, mapped_ranges, *this); + user_config.callbacks = std::addressof(callbacks.value()); + jit.emplace(user_config); } bool LoadNRO(std::span data) { - local_memory.clear(); - relocbase = local_memory.size(); local_memory.insert(local_memory.end(), data.begin(), data.end()); - if (FixupRelocations()) { InsertHelperFunctions(); InsertStack(); return true; } - return false; } @@ -190,11 +193,9 @@ public: // The loaded NRO file has ELF relocations that must be processed before it can run. // Normally this would be processed by RTLD, but in HLE context, we don't have // the linker available, so we have to do it ourselves. - const VAddr mod_offset{callbacks->MemoryRead32(4)}; - if (callbacks->MemoryRead32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) { + if (callbacks->MemoryRead32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) return false; - } // For more info about dynamic entries, see the ELF ABI specification: // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html @@ -205,40 +206,33 @@ public: while (true) { const auto dyn{callbacks->ReadMemory(dynamic_offset)}; dynamic_offset += sizeof(Elf64_Dyn); - if (!dyn.d_tag) { break; - } - if (dyn.d_tag == ElfDtRela) { + } else if (dyn.d_tag == ElfDtRela) { rela_dyn = dyn.d_un.d_ptr; - } - if (dyn.d_tag == ElfDtRelasz) { + } else if (dyn.d_tag == ElfDtRelasz) { num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela); - } - if (dyn.d_tag == ElfDtRelr) { + } else if (dyn.d_tag == ElfDtRelr) { relr_dyn = dyn.d_un.d_ptr; - } - if (dyn.d_tag == ElfDtRelrsz) { + } else if (dyn.d_tag == ElfDtRelrsz) { num_relr = dyn.d_un.d_val / sizeof(Elf64_Relr); } } for (size_t i = 0; i < num_rela; i++) { const auto rela{callbacks->ReadMemory(rela_dyn + i * sizeof(Elf64_Rela))}; - if (Elf64RelType(rela.r_info) != ElfAArch64Relative) { - continue; + if (Elf64RelType(rela.r_info) == ElfAArch64Relative) { + const VAddr contents{callbacks->MemoryRead64(rela.r_offset)}; + callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend); } - const VAddr contents{callbacks->MemoryRead64(rela.r_offset)}; - callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend); } VAddr relr_where = 0; for (size_t i = 0; i < num_relr; i++) { - const auto relr{callbacks->ReadMemory(relr_dyn + i * sizeof(Elf64_Relr))}; - const auto incr{[&](VAddr where) { + const auto relr = callbacks->ReadMemory(relr_dyn + i * sizeof(Elf64_Relr)); + const auto incr = [&](VAddr where) { callbacks->MemoryWrite64(where, callbacks->MemoryRead64(where) + relocbase); - }}; - + }; if ((relr & 1) == 0) { // where pointer relr_where = relocbase + relr; @@ -254,13 +248,12 @@ public: relr_where += 63 * sizeof(Elf64_Addr); } } - return true; } void InsertHelperFunctions() { - for (const auto& name : HELPER_FUNCTIONS) { - helpers[name] = local_memory.size(); + for (size_t i = 0; i < size_t(HelperFn::Count); ++i) { + helpers[i] = local_memory.size(); local_memory.insert(local_memory.end(), SVC0_ARM64.begin(), SVC0_ARM64.end()); } } @@ -268,9 +261,8 @@ public: void InsertStack() { // Allocate enough space to avoid any reasonable risk of // overflowing the stack during plugin execution - const u64 pad_amount{Common::AlignUp(local_memory.size(), STACK_ALIGN) - - local_memory.size()}; - local_memory.insert(local_memory.end(), 0x10000 + pad_amount, 0); + const u64 pad_amount = Common::AlignUp(local_memory.size(), STACK_ALIGN) - local_memory.size(); + local_memory.insert(local_memory.end(), (4096 * 32) + pad_amount, 0); top_of_stack = local_memory.size(); heap_pointer = top_of_stack; } @@ -297,27 +289,21 @@ public: // // For more info, see the AArch64 ABI PCS: // https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst - - for (size_t i = 0; i < 8 && i < argument_stack.size(); i++) { + for (size_t i = 0; i < 8 && i < argument_stack.size(); i++) jit->SetRegister(i, argument_stack[i]); - } - if (argument_stack.size() > 8) { - const VAddr new_sp = Common::AlignDown( - top_of_stack - (argument_stack.size() - 8) * sizeof(u64), STACK_ALIGN); - for (size_t i = 8; i < argument_stack.size(); i++) { + const VAddr new_sp = Common::AlignDown(top_of_stack - (argument_stack.size() - 8) * sizeof(u64), STACK_ALIGN); + for (size_t i = 8; i < argument_stack.size(); i++) callbacks->MemoryWrite64(new_sp + (i - 8) * sizeof(u64), argument_stack[i]); - } jit->SetSP(new_sp); } - // Reset the call state for the next invocation argument_stack.clear(); heap_pointer = top_of_stack; } u64 CallFunction(VAddr func) { - jit->SetRegister(30, helpers["_stop"]); + jit->SetRegister(30, helpers[size_t(HelperFn::Stop)]); jit->SetSP(top_of_stack); SetupArguments(); @@ -326,21 +312,14 @@ public: return jit->GetRegister(0); } - VAddr GetHelper(const std::string& name) { - return helpers[name]; - } - VAddr AddHeap(const void* data, size_t size) { // Require all heap data types to have the same alignment as the // stack pointer, for compatibility - const size_t num_bytes{Common::AlignUp(size, STACK_ALIGN)}; - + const size_t num_bytes = Common::AlignUp(size, STACK_ALIGN); // Make additional memory space if required if (heap_pointer + num_bytes > local_memory.size()) { - local_memory.insert(local_memory.end(), - (heap_pointer + num_bytes) - local_memory.size(), 0); + local_memory.insert(local_memory.end(), (heap_pointer + num_bytes) - local_memory.size(), 0); } - const VAddr location{heap_pointer}; std::memcpy(local_memory.data() + location, data, size); heap_pointer += num_bytes; @@ -351,13 +330,29 @@ public: std::memcpy(data, local_memory.data() + location, size); } - std::unique_ptr callbacks; + VAddr GetHelper(const std::string& name) { + if (name == "_resolve") return helpers[HelperFn::Resolve]; + else if (name == "_panic") return helpers[HelperFn::Panic]; + else if (name == "_stop") return helpers[HelperFn::Stop]; + else if (name == "memset") return helpers[HelperFn::Memset]; + else if (name == "memcpy") return helpers[HelperFn::Memcpy]; + else if (name == "memmove") return helpers[HelperFn::Memmove]; + else if (name == "PanicForPlugin") return helpers[HelperFn::PanicForPlugin]; + else if (name == "_ZN2nn4diag6detail9AbortImplEPKcS3_S3_i") return helpers[HelperFn::AbortImpl]; + else if (name == "_ZN2nn6detail21UnexpectedDefaultImplEPKcS2_i") return helpers[HelperFn::UnexpectedImpl]; + else { + LOG_CRITICAL(Service_JIT, "unresolved {}", name); + return helpers[HelperFn::Panic]; + } + } + + std::optional callbacks; + std::optional jit; std::vector local_memory; - std::vector argument_stack; + std::vector argument_stack; IntervalSet mapped_ranges; Dynarmic::A64::UserConfig user_config; - std::unique_ptr jit; - std::map> helpers; + std::array helpers; Core::Memory::Memory& memory; VAddr top_of_stack; VAddr heap_pointer; @@ -383,44 +378,41 @@ void DynarmicCallbacks64::CallSVC(u32 swi) { } u64 pc{parent.jit->GetPC() - 4}; - auto& helpers{parent.helpers}; - - if (pc == helpers["memcpy"] || pc == helpers["memmove"]) { + if (pc == parent.helpers[size_t(HelperFn::Memcpy)] || pc == parent.helpers[size_t(HelperFn::Memmove)]) { const VAddr dest{parent.jit->GetRegister(0)}; const VAddr src{parent.jit->GetRegister(1)}; const size_t n{parent.jit->GetRegister(2)}; if (dest < src) { - for (size_t i = 0; i < n; i++) { + for (size_t i = 0; i < n; i++) MemoryWrite8(dest + i, MemoryRead8(src + i)); - } } else { - for (size_t i = n; i > 0; i--) { + for (size_t i = n; i > 0; i--) MemoryWrite8(dest + i - 1, MemoryRead8(src + i - 1)); - } } - } else if (pc == helpers["memset"]) { + } else if (pc == parent.helpers[size_t(HelperFn::Memset)]) { const VAddr dest{parent.jit->GetRegister(0)}; const u64 c{parent.jit->GetRegister(1)}; const size_t n{parent.jit->GetRegister(2)}; - - for (size_t i = 0; i < n; i++) { - MemoryWrite8(dest + i, static_cast(c)); - } - } else if (pc == helpers["_resolve"]) { + for (size_t i = 0; i < n; i++) + MemoryWrite8(dest + i, u8(c)); + } else if (pc == parent.helpers[size_t(HelperFn::Resolve)]) { // X0 contains a char* for a symbol to resolve const auto name{MemoryReadCString(parent.jit->GetRegister(0))}; - const auto helper{helpers[name]}; - - if (helper != 0) { - parent.jit->SetRegister(0, helper); - } else { - LOG_WARNING(Service_JIT, "plugin requested unknown function {}", name); - parent.jit->SetRegister(0, helpers["_panic"]); - } - } else if (pc == helpers["_stop"]) { + parent.jit->SetRegister(0, u64(parent.GetHelper(name))); + } else if (pc == parent.helpers[size_t(HelperFn::Stop)]) { parent.jit->HaltExecution(); - } else if (pc == helpers["_panic"]) { + } else if (pc == parent.helpers[size_t(HelperFn::Panic)]) { + LOG_CRITICAL(Service_JIT, "plugin panicked!"); + parent.jit->HaltExecution(); + // SM64 + } else if (pc == parent.helpers[size_t(HelperFn::PanicForPlugin)]) { + LOG_CRITICAL(Service_JIT, "plugin panicked!"); + parent.jit->HaltExecution(); + } else if (pc == parent.helpers[size_t(HelperFn::AbortImpl)]) { + LOG_CRITICAL(Service_JIT, "plugin panicked!"); + parent.jit->HaltExecution(); + } else if (pc == parent.helpers[size_t(HelperFn::UnexpectedImpl)]) { LOG_CRITICAL(Service_JIT, "plugin panicked!"); parent.jit->HaltExecution(); } else { @@ -430,7 +422,8 @@ void DynarmicCallbacks64::CallSVC(u32 swi) { } void DynarmicCallbacks64::ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) { - LOG_CRITICAL(Service_JIT, "Illegal operation PC @ {:08x}", pc); + auto const inst = MemoryRead32(pc); + LOG_CRITICAL(Service_JIT, "{} PC @ {:08x}, data = {:08x}", exception, pc, inst); parent.jit->HaltExecution(); } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index df0339ba85..3bfef0c29c 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -199,25 +199,28 @@ NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) { return NvResult::Success; } -void nvhost_as_gpu::FreeMappingLocked(u64 offset) { - auto const it = mapping_map.find(offset); - auto const mapping = it->second; - if (!mapping.fixed) { - auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; - u32 page_size_bits{mapping.big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS}; - u32 page_size{mapping.big_page ? vm.big_page_size : VM::YUZU_PAGESIZE}; - u64 aligned_size{Common::AlignUp(mapping.size, page_size)}; - allocator.Free(u32(mapping.offset >> page_size_bits), u32(aligned_size >> page_size_bits)); +bool nvhost_as_gpu::FreeMappingLocked(u64 offset) noexcept { + if (auto const it = mapping_map.find(offset); it != mapping_map.end()) { + auto const mapping = it->second; + if (!mapping.fixed) { + auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; + u32 page_size_bits{mapping.big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS}; + u32 page_size{mapping.big_page ? vm.big_page_size : VM::YUZU_PAGESIZE}; + u64 aligned_size{Common::AlignUp(mapping.size, page_size)}; + allocator.Free(u32(mapping.offset >> page_size_bits), u32(aligned_size >> page_size_bits)); + } + nvmap.UnpinHandle(mapping.handle); + // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state + // Only FreeSpace can unmap them fully + if (mapping.sparse_alloc) { + gmmu->MapSparse(offset, mapping.size, mapping.big_page); + } else { + gmmu->Unmap(offset, mapping.size); + } + mapping_map.erase(offset); + return true; } - nvmap.UnpinHandle(mapping.handle); - // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state - // Only FreeSpace can unmap them fully - if (mapping.sparse_alloc) { - gmmu->MapSparse(offset, mapping.size, mapping.big_page); - } else { - gmmu->Unmap(offset, mapping.size); - } - mapping_map.erase(it); + return false; } NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) { @@ -232,7 +235,8 @@ NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) { return NvResult::BadValue; for (const auto mapping_offset : allocation.mappings) - FreeMappingLocked(mapping_offset); + if (!FreeMappingLocked(mapping_offset)) + return NvResult::BadValue; // Unset sparse flag if required if (allocation.sparse) @@ -402,8 +406,8 @@ NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) { } nvmap.UnpinHandle(mapping.handle); - mapping_map.erase(it); - map_buffer_offsets.erase(offset_it); + mapping_map.erase(params.offset); + map_buffer_offsets.erase(params.offset); } return NvResult::Success; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index 862bdd60bf..44892ee368 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -157,7 +157,7 @@ private: NvResult GetVARegions1(IoctlGetVaRegions& params); NvResult GetVARegions3(IoctlGetVaRegions& params, std::span regions); - void FreeMappingLocked(u64 offset); + [[nodiscard]] bool FreeMappingLocked(u64 offset) noexcept; Module& module; diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp index 436b0f4b05..bb0433e1d0 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp @@ -26,8 +26,11 @@ namespace Service::android { BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_, std::shared_ptr buffer_queue_core_, Service::Nvidia::NvCore::NvMap& nvmap_) - : service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots), - clock{Common::CreateOptimalClock()}, nvmap(nvmap_) { + : service_context{service_context_}, core{std::move(buffer_queue_core_)} + , slots(core->slots) + , clock{Common::CreateOptimalClock()} + , nvmap(nvmap_) +{ buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent"); } @@ -485,7 +488,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, slots[slot].buffer_state = BufferState::Queued; slots[slot].frame_number = core->frame_counter; slots[slot].queue_time = timestamp; - slots[slot].presentation_time = clock->GetTimeNS().count(); + slots[slot].presentation_time = clock.GetTimeNS().count(); slots[slot].fence = fence; item.slot = slot; diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.h b/src/core/hle/service/nvnflinger/buffer_queue_producer.h index 06cb49cbad..51c0801a0a 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.h +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.h @@ -89,8 +89,7 @@ private: s32 next_callback_ticket{}; s32 current_callback_ticket{}; std::condition_variable_any callback_condition; - std::unique_ptr clock; - + Common::WallClock clock; Service::Nvidia::NvCore::NvMap& nvmap; }; diff --git a/src/core/hle/service/set/settings_types.h b/src/core/hle/service/set/settings_types.h index 469b8d745c..696938c4c7 100644 --- a/src/core/hle/service/set/settings_types.h +++ b/src/core/hle/service/set/settings_types.h @@ -409,6 +409,13 @@ struct AccountNotificationSettings { static_assert(sizeof(AccountNotificationSettings) == 0x18, "AccountNotificationSettings is an invalid size"); +/// This is nn::settings::system::AccountUserSettings (stubbed) +struct AccountUserSettings { + std::array data; +}; +static_assert(sizeof(AccountUserSettings) == 0x40, + "AccountUserSettings is an invalid size"); + /// This is nn::settings::factory::BatteryLot struct BatteryLot { std::array lot_number; diff --git a/src/core/hle/service/set/system_settings_server.cpp b/src/core/hle/service/set/system_settings_server.cpp index c7a83e69f4..0ed6d28dd4 100644 --- a/src/core/hle/service/set/system_settings_server.cpp +++ b/src/core/hle/service/set/system_settings_server.cpp @@ -340,9 +340,9 @@ ISystemSettingsServer::ISystemSettingsServer(Core::System& system_) {306, nullptr, "GetPinCodeReregistrationGuideAccounts"}, //20.0.0+ {307, nullptr, "SetPinCodeReregistrationGuideAccounts"}, //20.0.0+ {315, C<&ISystemSettingsServer::GetHttpAuthConfigs>, "GetHttpAuthConfigs"}, //21.0.0+ - {319, nullptr, "GetAccountUserSettings"}, //21.0.0+ + {319, C<&ISystemSettingsServer::GetAccountUserSettings>, "GetAccountUserSettings"}, //21.0.0+ {320, nullptr, "SetAccountUserSettings"}, //21.0.0+ - {321, nullptr, "GetDefaultAccountUserSettings"}, //21.0.0+ + {321, C<&ISystemSettingsServer::GetDefaultAccountUserSettings>, "GetDefaultAccountUserSettings"}, //21.0.0+ }; // clang-format on @@ -1428,6 +1428,23 @@ Result ISystemSettingsServer::GetHttpAuthConfigs(Out out_count, OutBuffer out_count, + OutLargeData out_settings) { + LOG_WARNING(Service_SET, "(STUBBED) called"); + + *out_count = 0; + *out_settings = {}; + R_SUCCEED(); +} + +Result ISystemSettingsServer::GetDefaultAccountUserSettings(Out out_settings) { + LOG_WARNING(Service_SET, "(STUBBED) called"); + + *out_settings = {}; + R_SUCCEED(); +} + void ISystemSettingsServer::SetupSettings() { auto system_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "system/save/8000000000000050"; if (!LoadSettingsFile(system_dir, []() { return DefaultSystemSettings(); })) { diff --git a/src/core/hle/service/set/system_settings_server.h b/src/core/hle/service/set/system_settings_server.h index 27a16747f3..2a160dfc6b 100644 --- a/src/core/hle/service/set/system_settings_server.h +++ b/src/core/hle/service/set/system_settings_server.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 2018 yuzu Emulator Project @@ -161,6 +161,10 @@ public: Result GetPanelCrcMode(Out out_panel_crc_mode); Result SetPanelCrcMode(s32 panel_crc_mode); Result GetHttpAuthConfigs(Out out_count, OutBuffer out_configs); + Result GetAccountUserSettings( + Out out_count, + OutLargeData out_settings); + Result GetDefaultAccountUserSettings(Out out_settings); private: bool LoadSettingsFile(std::filesystem::path& path, auto&& default_func); diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 039d53ebf8..65e6876926 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -67,22 +67,24 @@ public: Result GetServicePort(Kernel::KClientPort** out_client_port, const std::string& name); template T> - std::shared_ptr GetService(const std::string& service_name, bool block = false) const { - auto service = registered_services.find(service_name); - if (service == registered_services.end() && !block) { - LOG_DEBUG(Service, "Can't find service: {}", service_name); + std::shared_ptr GetService(const std::string& name, bool block = false) const { + std::unique_lock l{lock}; + auto it = registered_services.find(name); + if (it == registered_services.end() && !block) { + LOG_DEBUG(Service, "Can't find service: {}", name); return nullptr; } else if (block) { using namespace std::literals::chrono_literals; - while (service == registered_services.end()) { + while (it == registered_services.end()) { + l.unlock(); Kernel::Svc::SleepThread( kernel.System(), std::chrono::duration_cast(100ms).count()); - service = registered_services.find(service_name); + l.lock(); + it = registered_services.find(name); } } - - return std::static_pointer_cast(service->second()); + return std::static_pointer_cast(it->second()); } void InvokeControlRequest(HLERequestContext& context); @@ -96,7 +98,7 @@ private: std::unique_ptr controller_interface; /// Map of registered services, retrieved using GetServicePort. - std::mutex lock; + mutable std::mutex lock; ankerl::unordered_dense::map registered_services; ankerl::unordered_dense::map service_ports; diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index fd758d4915..c26f1796cc 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -629,7 +629,7 @@ Errno BSD::BindImpl(s32 fd, std::span addr) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } - ASSERT(addr.size() == sizeof(SockAddrIn)); + ASSERT(addr.size() >= 16); auto addr_in = GetValue(addr); return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in))); @@ -640,7 +640,7 @@ Errno BSD::ConnectImpl(s32 fd, std::span addr) { return Errno::BADF; } - UNIMPLEMENTED_IF(addr.size() != sizeof(SockAddrIn)); + ASSERT(addr.size() >= 16); auto addr_in = GetValue(addr); const Errno result = Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in))); @@ -874,7 +874,7 @@ std::pair BSD::RecvFromImpl(s32 fd, u32 flags, std::vector& mess if (ret < 0) { addr.clear(); } else { - ASSERT(addr.size() == sizeof(SockAddrIn)); + ASSERT(addr.size() >= 16); const SockAddrIn result = Translate(addr_in); PutValue(addr, result); } @@ -899,7 +899,7 @@ std::pair BSD::SendToImpl(s32 fd, u32 flags, std::span mes Network::SockAddrIn addr_in; Network::SockAddrIn* p_addr_in = nullptr; if (!addr.empty()) { - ASSERT(addr.size() == sizeof(SockAddrIn)); + ASSERT(addr.size() >= 16); auto guest_addr_in = GetValue(addr); addr_in = Translate(guest_addr_in); p_addr_in = &addr_in; diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index f9f0ee56eb..9cd60ac240 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -238,7 +238,7 @@ static std::vector SerializeAddrInfo(const std::vector& v Append(data, static_cast(Translate(addrinfo.family))); // ai_family Append(data, static_cast(Translate(addrinfo.socket_type))); // ai_socktype Append(data, static_cast(Translate(addrinfo.protocol))); // ai_protocol - Append(data, sizeof(SockAddrIn)); // ai_addrlen + Append(data, 16); // ai_addrlen // ^ *not* sizeof(SerializedSockAddrIn), not that it matters since they're the same size // ai_addr: diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index b72bf53c16..7992d9822c 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -110,8 +110,9 @@ struct SockAddrIn { u8 family; u16 portno; std::array ip; - std::array zeroes; + std::array zeroes; }; +static_assert(sizeof(SockAddrIn) == 0x100); enum class PollEvents : u16 { // Using Pascal case because IN is a macro on Windows. diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp index ea12977f62..6b93adb5ce 100644 --- a/src/core/hle/service/sockets/sockets_translate.cpp +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -265,13 +265,9 @@ PollEvents Translate(Network::PollEvents flags) { } Network::SockAddrIn Translate(SockAddrIn value) { - if (value.len != 0 && value.len != sizeof(value) && value.len != 6) { - LOG_WARNING(Service, "Unexpected SockAddrIn len={}, expected 0, {}, or 6", - value.len, sizeof(value)); - } - + // All lengths are valid, from [0 upto 256] return { - .family = Translate(static_cast(value.family)), + .family = Translate(Domain(value.family)), .ip = value.ip, .portno = static_cast(value.portno >> 8 | value.portno << 8), }; @@ -279,7 +275,7 @@ Network::SockAddrIn Translate(SockAddrIn value) { SockAddrIn Translate(Network::SockAddrIn value) { return { - .len = sizeof(SockAddrIn), + .len = 16, .family = static_cast(Translate(value.family)), .portno = static_cast(value.portno >> 8 | value.portno << 8), .ip = value.ip, diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 3a9ea308a8..38e77e863f 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -58,9 +58,9 @@ struct Memory::Impl { #ifdef __ANDROID__ heap_tracker.emplace(system.DeviceMemory().buffer); - buffer = std::addressof(*heap_tracker); + host_buffer = std::addressof(*heap_tracker); #else - buffer = std::addressof(system.DeviceMemory().buffer); + host_buffer = std::addressof(system.DeviceMemory().buffer); #endif } @@ -75,8 +75,7 @@ struct Memory::Impl { Common::PageType::Memory); if (current_page_table->fastmem_arena) { - buffer->Map(GetInteger(base), GetInteger(target) - DramMemoryMap::Base, size, perms, - separate_heap); + host_buffer->Map(GetInteger(base), GetInteger(target) - DramMemoryMap::Base, size, perms, separate_heap); } } @@ -88,7 +87,7 @@ struct Memory::Impl { Common::PageType::Unmapped); if (current_page_table->fastmem_arena) { - buffer->Unmap(GetInteger(base), size, separate_heap); + host_buffer->Unmap(GetInteger(base), size, separate_heap); } } @@ -107,7 +106,7 @@ struct Memory::Impl { switch (page_type) { case Common::PageType::RasterizerCachedMemory: if (protect_bytes > 0) { - buffer->Protect(protect_begin, protect_bytes, perms); + host_buffer->Protect(protect_begin, protect_bytes, perms); protect_bytes = 0; } break; @@ -119,7 +118,7 @@ struct Memory::Impl { } if (protect_bytes > 0) { - buffer->Protect(protect_begin, protect_bytes, perms); + host_buffer->Protect(protect_begin, protect_bytes, perms); } } @@ -233,225 +232,138 @@ struct Memory::Impl { return string; } - bool WalkBlock(const Common::ProcessAddress addr, const std::size_t size, auto on_unmapped, - auto on_memory, auto on_rasterizer, auto increment) { - const auto& page_table = *current_page_table; + template + inline bool WalkBlock(const Common::ProcessAddress addr, const std::size_t size, F1&& on_unmapped, F2&& on_memory, F3&& on_rasterizer) { + std::size_t offset = 0; + if (!AddressSpaceContains(*current_page_table, addr, size)) { + on_unmapped(offset, size, addr); + return false; + } std::size_t remaining_size = size; std::size_t page_index = addr >> YUZU_PAGEBITS; std::size_t page_offset = addr & YUZU_PAGEMASK; bool user_accessible = true; - - if (!AddressSpaceContains(page_table, addr, size)) [[unlikely]] { - on_unmapped(size, addr); - return false; - } - - while (remaining_size) { - const std::size_t copy_amount = - (std::min)(static_cast(YUZU_PAGESIZE) - page_offset, remaining_size); - const auto current_vaddr = - static_cast((page_index << YUZU_PAGEBITS) + page_offset); - - const auto [pointer, type] = page_table.entries[page_index].ptr.PointerType(); + while (remaining_size != 0) { + const std::size_t copy_amount = (std::min)(std::size_t(YUZU_PAGESIZE) - page_offset, remaining_size); + const auto current_vaddr = u64((page_index << YUZU_PAGEBITS) + page_offset); + const auto [pointer, type] = current_page_table->entries[page_index].ptr.PointerType(); switch (type) { case Common::PageType::Unmapped: { user_accessible = false; - on_unmapped(copy_amount, current_vaddr); + on_unmapped(offset, copy_amount, current_vaddr); break; } case Common::PageType::Memory: { - u8* mem_ptr = - reinterpret_cast(pointer + page_offset + (page_index << YUZU_PAGEBITS)); - on_memory(copy_amount, mem_ptr); + u8* mem_ptr = reinterpret_cast(pointer + page_offset + (page_index << YUZU_PAGEBITS)); + on_memory(offset, copy_amount, mem_ptr); break; } case Common::PageType::DebugMemory: { - u8* const mem_ptr{GetPointerFromDebugMemory(current_vaddr)}; - on_memory(copy_amount, mem_ptr); + u8* const mem_ptr = GetPointerFromDebugMemory(current_vaddr); + on_memory(offset, copy_amount, mem_ptr); break; } case Common::PageType::RasterizerCachedMemory: { - u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)}; - on_rasterizer(current_vaddr, copy_amount, host_ptr); + u8* const host_ptr = GetPointerFromRasterizerCachedMemory(current_vaddr); + on_rasterizer(current_vaddr, offset, copy_amount, host_ptr); break; } default: UNREACHABLE(); } - page_index++; page_offset = 0; - increment(copy_amount); + offset += copy_amount; remaining_size -= copy_amount; } - return user_accessible; } - template - bool ReadBlockImpl(const Common::ProcessAddress src_addr, void* dest_buffer, - const std::size_t size) { - return WalkBlock( - src_addr, size, - [src_addr, size, &dest_buffer](const std::size_t copy_amount, - const Common::ProcessAddress current_vaddr) { - LOG_ERROR(HW_Memory, - "Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", - GetInteger(current_vaddr), GetInteger(src_addr), size); - std::memset(dest_buffer, 0, copy_amount); - }, - [&](const std::size_t copy_amount, const u8* const src_ptr) { - std::memcpy(dest_buffer, src_ptr, copy_amount); - }, - [&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount, - const u8* const host_ptr) { - if constexpr (!UNSAFE) { - HandleRasterizerDownload(GetInteger(current_vaddr), copy_amount); - } - std::memcpy(dest_buffer, host_ptr, copy_amount); - }, - [&](const std::size_t copy_amount) { - dest_buffer = static_cast(dest_buffer) + copy_amount; - }); - } - - bool ReadBlock(const Common::ProcessAddress src_addr, void* dest_buffer, - const std::size_t size) { - // TODO: If you want a proper multithreaded implementation (w/o cache coherency fights) - // use TBB or something that splits the job properly - return ReadBlockImpl(src_addr, dest_buffer, size); - } - - bool ReadBlockUnsafe(const Common::ProcessAddress src_addr, void* dest_buffer, - const std::size_t size) { - return ReadBlockImpl(src_addr, dest_buffer, size); - } - - const u8* GetSpan(const VAddr src_addr, const std::size_t size) const { - if (current_page_table->entries[src_addr >> YUZU_PAGEBITS].block == - current_page_table->entries[(src_addr + size) >> YUZU_PAGEBITS].block) { - return GetPointerSilent(src_addr); - } - return nullptr; - } - - u8* GetSpan(const VAddr src_addr, const std::size_t size) { - if (current_page_table->entries[src_addr >> YUZU_PAGEBITS].block == - current_page_table->entries[(src_addr + size) >> YUZU_PAGEBITS].block) { - return GetPointerSilent(src_addr); - } - return nullptr; - } - - template - bool WriteBlockImpl(const Common::ProcessAddress dest_addr, const void* src_buffer, - const std::size_t size) { - return WalkBlock( - dest_addr, size, - [dest_addr, size](const std::size_t copy_amount, - const Common::ProcessAddress current_vaddr) { - LOG_ERROR(HW_Memory, - "Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", - GetInteger(current_vaddr), GetInteger(dest_addr), size); - }, - [&](const std::size_t copy_amount, u8* const dest_ptr) { - std::memcpy(dest_ptr, src_buffer, copy_amount); - }, - [&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount, - u8* const host_ptr) { - if constexpr (!UNSAFE) { - HandleRasterizerWrite(GetInteger(current_vaddr), copy_amount); - } - std::memcpy(host_ptr, src_buffer, copy_amount); - }, - [&](const std::size_t copy_amount) { - src_buffer = static_cast(src_buffer) + copy_amount; - }); - } - - bool WriteBlock(const Common::ProcessAddress dest_addr, const void* src_buffer, - const std::size_t size) { - // TODO: If you want a proper multithreaded implementation (w/o cache coherency fights) - // use TBB or something that splits the job properly - return WriteBlockImpl(dest_addr, src_buffer, size); - } - - bool WriteBlockUnsafe(const Common::ProcessAddress dest_addr, const void* src_buffer, - const std::size_t size) { - return WriteBlockImpl(dest_addr, src_buffer, size); - } - - bool ZeroBlock(const Common::ProcessAddress dest_addr, const std::size_t size) { - return WalkBlock( - dest_addr, size, - [dest_addr, size](const std::size_t copy_amount, - const Common::ProcessAddress current_vaddr) { - LOG_ERROR(HW_Memory, - "Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", - GetInteger(current_vaddr), GetInteger(dest_addr), size); - }, - [](const std::size_t copy_amount, u8* const dest_ptr) { - std::memset(dest_ptr, 0, copy_amount); - }, - [&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount, - u8* const host_ptr) { - HandleRasterizerWrite(GetInteger(current_vaddr), copy_amount); - std::memset(host_ptr, 0, copy_amount); - }, - [](const std::size_t copy_amount) {}); - } - - bool CopyBlock(Common::ProcessAddress dest_addr, Common::ProcessAddress src_addr, - const std::size_t size) { - return WalkBlock( - dest_addr, size, - [&](const std::size_t copy_amount, const Common::ProcessAddress current_vaddr) { - LOG_ERROR(HW_Memory, - "Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", - GetInteger(current_vaddr), GetInteger(src_addr), size); - ZeroBlock(dest_addr, copy_amount); - }, - [&](const std::size_t copy_amount, const u8* const src_ptr) { - WriteBlockImpl(dest_addr, src_ptr, copy_amount); - }, - [&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount, - u8* const host_ptr) { + bool ReadBlockImpl(const Common::ProcessAddress addr, void* buffer, const std::size_t size, bool unsafe) { + return WalkBlock(addr, size, + [addr, size, &buffer](const std::size_t offset, const std::size_t copy_amount, const Common::ProcessAddress current_vaddr) { + LOG_ERROR(HW_Memory, "Unmapped @ {:#016X} (start address = {:#016X}, size = {})", GetInteger(current_vaddr), GetInteger(addr), size); + std::memset(reinterpret_cast(buffer) + offset, 0, copy_amount); + }, + [&](const std::size_t offset, const std::size_t copy_amount, const u8* const ptr) { + std::memcpy(reinterpret_cast(buffer) + offset, ptr, copy_amount); + }, + [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t copy_amount, const u8* const ptr) { + if (!unsafe) { HandleRasterizerDownload(GetInteger(current_vaddr), copy_amount); - WriteBlockImpl(dest_addr, host_ptr, copy_amount); - }, - [&](const std::size_t copy_amount) { - dest_addr += copy_amount; - src_addr += copy_amount; - }); + } + std::memcpy(reinterpret_cast(buffer) + offset, ptr, copy_amount); + }); + } + + [[nodiscard]] inline const u8* GetSpan(const VAddr addr, const std::size_t size) const noexcept { + return (current_page_table->entries[addr >> YUZU_PAGEBITS].block == current_page_table->entries[(addr + size) >> YUZU_PAGEBITS].block) ? GetPointerSilent(addr) : nullptr; + } + [[nodiscard]] inline u8* GetSpan(const VAddr addr, const std::size_t size) noexcept { + return (current_page_table->entries[addr >> YUZU_PAGEBITS].block == current_page_table->entries[(addr + size) >> YUZU_PAGEBITS].block) ? GetPointerSilent(addr) : nullptr; + } + + bool WriteBlockImpl(const Common::ProcessAddress addr, const void* buffer, const std::size_t size, bool unsafe) { + return WalkBlock(addr, size, + [addr, size](const std::size_t offset, const std::size_t copy_amount, const Common::ProcessAddress current_vaddr) { + LOG_ERROR(HW_Memory, "Unmapped @ 0x{:016X} (start address = 0x{:016X}, size = {})", GetInteger(current_vaddr), GetInteger(addr), size); + }, + [&](const std::size_t offset, const std::size_t copy_amount, u8* const ptr) { + std::memcpy(ptr, reinterpret_cast(buffer) + offset, copy_amount); + }, + [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t copy_amount, u8* const ptr) { + if (!unsafe) { + HandleRasterizerWrite(GetInteger(current_vaddr), copy_amount); + } + std::memcpy(ptr, reinterpret_cast(buffer) + offset, copy_amount); + }); + } + + bool ZeroBlock(const Common::ProcessAddress addr, const std::size_t size) { + return WalkBlock(addr, size, + [addr, size](const std::size_t offset, const std::size_t copy_amount, const Common::ProcessAddress current_vaddr) { + LOG_ERROR(HW_Memory, "Unmapped @ {:#016X} (start address = {:#016X}, size = {})", GetInteger(current_vaddr), GetInteger(addr), size); + }, + [=](const std::size_t offset, const std::size_t copy_amount, u8* const ptr) { + std::memset(ptr, 0, copy_amount); + }, + [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t copy_amount, u8* const ptr) { + HandleRasterizerWrite(GetInteger(current_vaddr), copy_amount); + std::memset(ptr, 0, copy_amount); + }); + } + + bool CopyBlock(Common::ProcessAddress dest_addr, Common::ProcessAddress src_addr, const std::size_t size) { + return WalkBlock(dest_addr, size, + [&](const std::size_t offset, const std::size_t copy_amount, const Common::ProcessAddress current_vaddr) { + LOG_ERROR(HW_Memory, "Unmapped @ {:#016X} (start address = {:#016X}, size = {})", GetInteger(current_vaddr), GetInteger(src_addr), size); + ZeroBlock(dest_addr + offset, copy_amount); + }, + [&](const std::size_t offset, const std::size_t copy_amount, const u8* const ptr) { + WriteBlockImpl(dest_addr + offset, ptr, copy_amount, false); + }, + [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t copy_amount, u8* const ptr) { + HandleRasterizerDownload(GetInteger(current_vaddr), copy_amount); + WriteBlockImpl(dest_addr + offset, ptr, copy_amount, false); + }); } template - Result PerformCacheOperation(Common::ProcessAddress dest_addr, std::size_t size, - Callback&& cb) { - class InvalidMemoryException : public std::exception {}; - - try { - WalkBlock( - dest_addr, size, - [&](const std::size_t block_size, const Common::ProcessAddress current_vaddr) { - LOG_ERROR(HW_Memory, "Unmapped cache maintenance @ {:#018X}", - GetInteger(current_vaddr)); - throw InvalidMemoryException(); - }, - [&](const std::size_t block_size, u8* const host_ptr) {}, - [&](const Common::ProcessAddress current_vaddr, const std::size_t block_size, - u8* const host_ptr) { cb(current_vaddr, block_size); }, - [](const std::size_t block_size) {}); - } catch (InvalidMemoryException&) { - return Kernel::ResultInvalidCurrentMemory; - } - - return ResultSuccess; + Result PerformCacheOperation(Common::ProcessAddress dest_addr, std::size_t size, Callback&& cb) { + bool had_unmapped = false; + WalkBlock(dest_addr, size, + [&](const std::size_t offset, const std::size_t copy_amount, const Common::ProcessAddress current_vaddr) { + LOG_ERROR(HW_Memory, "Unmapped @ {:#018X}", GetInteger(current_vaddr)); + had_unmapped = true; + }, + [](const std::size_t offset, const std::size_t copy_amount, u8* const host_ptr) {}, + [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t copy_amount, u8* const host_ptr) { cb(current_vaddr, offset, copy_amount); } + ); + return had_unmapped ? Kernel::ResultInvalidCurrentMemory : ResultSuccess; } Result InvalidateDataCache(Common::ProcessAddress dest_addr, std::size_t size) { - auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, - const std::size_t block_size) { + auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t block_size) { // dc ivac: Invalidate to point of coherency // GPU flush -> CPU invalidate HandleRasterizerDownload(GetInteger(current_vaddr), block_size); @@ -460,8 +372,7 @@ struct Memory::Impl { } Result StoreDataCache(Common::ProcessAddress dest_addr, std::size_t size) { - auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, - const std::size_t block_size) { + auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t block_size) { // dc cvac: Store to point of coherency // CPU flush -> GPU invalidate HandleRasterizerWrite(GetInteger(current_vaddr), block_size); @@ -470,8 +381,7 @@ struct Memory::Impl { } Result FlushDataCache(Common::ProcessAddress dest_addr, std::size_t size) { - auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, - const std::size_t block_size) { + auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t block_size) { // dc civac: Store to point of coherency, and invalidate from cache // CPU flush -> GPU invalidate HandleRasterizerWrite(GetInteger(current_vaddr), block_size); @@ -487,7 +397,7 @@ struct Memory::Impl { if (current_page_table->fastmem_arena) { const auto perm{debug ? Common::MemoryPermission{} : Common::MemoryPermission::ReadWrite}; - buffer->Protect(vaddr, size, perm); + host_buffer->Protect(vaddr, size, perm); } // Iterate over a contiguous CPU address space, marking/unmarking the region. @@ -547,7 +457,7 @@ struct Memory::Impl { if (!cached) { perm |= Common::MemoryPermission::Write; } - buffer->Protect(vaddr, size, perm); + host_buffer->Protect(vaddr, size, perm); } // Iterate over a contiguous CPU address space, which corresponds to the specified GPU @@ -859,9 +769,9 @@ struct Memory::Impl { std::mutex sys_core_guard; #ifdef __ANDROID__ std::optional heap_tracker; - Common::HeapTracker* buffer{}; + Common::HeapTracker* host_buffer{}; #else - Common::HostMemory* buffer{}; + Common::HostMemory* host_buffer{}; #endif }; @@ -989,12 +899,12 @@ std::string Memory::ReadCString(Common::ProcessAddress vaddr, std::size_t max_le bool Memory::ReadBlock(const Common::ProcessAddress src_addr, void* dest_buffer, const std::size_t size) { - return impl->ReadBlock(src_addr, dest_buffer, size); + return impl->ReadBlockImpl(src_addr, dest_buffer, size, false); } bool Memory::ReadBlockUnsafe(const Common::ProcessAddress src_addr, void* dest_buffer, const std::size_t size) { - return impl->ReadBlockUnsafe(src_addr, dest_buffer, size); + return impl->ReadBlockImpl(src_addr, dest_buffer, size, true); } const u8* Memory::GetSpan(const VAddr src_addr, const std::size_t size) const { @@ -1007,12 +917,12 @@ u8* Memory::GetSpan(const VAddr src_addr, const std::size_t size) { bool Memory::WriteBlock(const Common::ProcessAddress dest_addr, const void* src_buffer, const std::size_t size) { - return impl->WriteBlock(dest_addr, src_buffer, size); + return impl->WriteBlockImpl(dest_addr, src_buffer, size, false); } bool Memory::WriteBlockUnsafe(const Common::ProcessAddress dest_addr, const void* src_buffer, const std::size_t size) { - return impl->WriteBlockUnsafe(dest_addr, src_buffer, size); + return impl->WriteBlockImpl(dest_addr, src_buffer, size, true); } bool Memory::CopyBlock(Common::ProcessAddress dest_addr, Common::ProcessAddress src_addr, @@ -1066,7 +976,7 @@ bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) { #ifdef __ANDROID__ if (!rasterizer && mapped) { - impl->buffer->DeferredMapSeparateHeap(GetInteger(vaddr)); + impl->host_buffer->DeferredMapSeparateHeap(GetInteger(vaddr)); } #endif @@ -1075,7 +985,7 @@ bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) { bool Memory::InvalidateSeparateHeap(void* fault_address) { #ifdef __ANDROID__ - return impl->buffer->DeferredMapSeparateHeap(static_cast(fault_address)); + return impl->host_buffer->DeferredMapSeparateHeap(static_cast(fault_address)); #else return false; #endif diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index 1723636811..2a5212fed8 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp @@ -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 2019 yuzu Emulator Project @@ -20,6 +20,7 @@ #include "common/settings.h" #include "core/arm/arm_interface.h" #include "core/core.h" +#include "core/hle/ipc.h" #include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/k_process.h" #include "core/hle/result.h" @@ -124,8 +125,7 @@ json GetFullDataAuto(const std::string& timestamp, u64 title_id, Core::System& s } template -json GetHLEBufferDescriptorData(const std::vector& buffer, - Core::Memory::Memory& memory) { +json GetHLEBufferDescriptorData(const boost::container::static_vector& buffer, Core::Memory::Memory& memory) { auto buffer_out = json::array(); for (const auto& desc : buffer) { auto entry = json{ diff --git a/src/dedicated_room/CMakeLists.txt b/src/dedicated_room/CMakeLists.txt index 36c8af5e34..8ca138473f 100644 --- a/src/dedicated_room/CMakeLists.txt +++ b/src/dedicated_room/CMakeLists.txt @@ -7,8 +7,7 @@ add_library(yuzu-room STATIC EXCLUDE_FROM_ALL yuzu_room.cpp yuzu_room.h - yuzu_room.rc -) + yuzu_room.rc) target_link_libraries(yuzu-room PRIVATE common network) if (ENABLE_WEB_SERVICE) diff --git a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_vector.cpp b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_vector.cpp index 6ac8531bfc..d575cd66ee 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_vector.cpp +++ b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_vector.cpp @@ -866,10 +866,10 @@ void EmitIR(oaknut::CodeGenerator& code, EmitContext& template<> void EmitIR(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { - (void)code; - (void)ctx; - (void)inst; - UNREACHABLE(); + EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) { + code.CMGT(Qresult->D2(), Qa->D2(), Qb->D2()); + code.BSL(Qresult->B16(), Qa->B16(), Qb->B16()); + }); } template<> @@ -889,10 +889,10 @@ void EmitIR(oaknut::CodeGenerator& code, EmitContext& template<> void EmitIR(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { - (void)code; - (void)ctx; - (void)inst; - UNREACHABLE(); + EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) { + code.CMHI(Qresult->D2(), Qa->D2(), Qb->D2()); + code.BSL(Qresult->B16(), Qa->B16(), Qb->B16()); + }); } template<> @@ -912,10 +912,10 @@ void EmitIR(oaknut::CodeGenerator& code, EmitContext& template<> void EmitIR(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { - (void)code; - (void)ctx; - (void)inst; - UNREACHABLE(); + EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) { + code.CMGT(Qresult->D2(), Qb->D2(), Qa->D2()); + code.BSL(Qresult->B16(), Qa->B16(), Qb->B16()); + }); } template<> @@ -935,10 +935,10 @@ void EmitIR(oaknut::CodeGenerator& code, EmitContext& template<> void EmitIR(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst* inst) { - (void)code; - (void)ctx; - (void)inst; - UNREACHABLE(); + EmitThreeOp(code, ctx, inst, [&](auto& Qresult, auto& Qa, auto& Qb) { + code.CMHI(Qresult->D2(), Qb->D2(), Qa->D2()); + code.BSL(Qresult->B16(), Qa->B16(), Qb->B16()); + }); } template<> diff --git a/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp b/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp index 50d552aa0e..224559133a 100644 --- a/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp +++ b/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp @@ -121,15 +121,15 @@ void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) { #if defined(ARCHITECTURE_x86_64) { std::shared_lock guard(sig_handler->code_block_infos_mutex); - if (auto const iter = sig_handler->FindCodeBlockInfo(CTX_RIP); iter != sig_handler->code_block_infos.end()) { - FakeCall fc = iter->second.cb(CTX_RIP); - CTX_RSP -= sizeof(u64); - *std::bit_cast(CTX_RSP) = fc.ret_rip; - CTX_RIP = fc.call_rip; + if (auto const iter = sig_handler->FindCodeBlockInfo(CTX_PC); iter != sig_handler->code_block_infos.end()) { + FakeCall fc = iter->second.cb(CTX_PC); + CTX_SP -= sizeof(u64); + *std::bit_cast(CTX_SP) = fc.ret_rip; + CTX_PC = fc.call_rip; return; } } - fmt::print(stderr, "Unhandled {} at rip {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_RIP); + fmt::print(stderr, "Unhandled {} at rip {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_PC); #elif defined(ARCHITECTURE_arm64) { std::shared_lock guard(sig_handler->code_block_infos_mutex); diff --git a/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.cpp b/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.cpp index cf9dab4034..53381aa1df 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.cpp @@ -224,7 +224,7 @@ void A64EmitX64::GenTerminalHandlers() { terminal_handler_fast_dispatch_hint = code.getCurr(); calculate_location_descriptor(); code.L(rsb_cache_miss); - code.mov(r8, reinterpret_cast(fast_dispatch_table.data())); + code.mov(r8, u64(fast_dispatch_table.data())); //code.mov(r12, qword[code.ABI_JIT_PTR + offsetof(A64JitState, pc)]); code.mov(r12, rbx); if (code.HasHostFeature(HostFeature::SSE42)) { @@ -244,7 +244,7 @@ void A64EmitX64::GenTerminalHandlers() { code.align(); fast_dispatch_table_lookup = code.getCurr(); - code.mov(code.ABI_PARAM2, reinterpret_cast(fast_dispatch_table.data())); + code.mov(code.ABI_PARAM2, u64(fast_dispatch_table.data())); if (code.HasHostFeature(HostFeature::SSE42)) { code.crc32(code.ABI_PARAM1, code.ABI_PARAM2); } diff --git a/src/dynarmic/src/dynarmic/backend/x64/abi.cpp b/src/dynarmic/src/dynarmic/backend/x64/abi.cpp index 5d2ee735f3..59ecc974f7 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/abi.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/abi.cpp @@ -26,7 +26,7 @@ struct FrameInfo { }; static_assert(ABI_SHADOW_SPACE <= 32); -static FrameInfo CalculateFrameInfo(const size_t num_gprs, const size_t num_xmms, size_t frame_size) { +static FrameInfo CalculateFrameInfo(const size_t num_gprs, const size_t num_xmms, size_t frame_size) noexcept { // We are initially 8 byte aligned because the return value is pushed onto an aligned stack after a call. const size_t rsp_alignment = (num_gprs % 2 == 0) ? 8 : 0; const size_t total_xmm_size = num_xmms * XMM_SIZE; @@ -40,7 +40,7 @@ static FrameInfo CalculateFrameInfo(const size_t num_gprs, const size_t num_xmms }; } -void ABI_PushRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, std::bitset<32> const& regs) { +static void ABI_PushRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, std::bitset<32> regs) noexcept { using namespace Xbyak::util; const size_t num_gprs = (ABI_ALL_GPRS & regs).count(); @@ -65,7 +65,7 @@ void ABI_PushRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, } } -void ABI_PopRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, std::bitset<32> const& regs) { +static void ABI_PopRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, std::bitset<32> regs) noexcept { using namespace Xbyak::util; const size_t num_gprs = (ABI_ALL_GPRS & regs).count(); @@ -107,13 +107,13 @@ void ABI_PopCallerSaveRegistersAndAdjustStack(BlockOfCode& code, const std::size // Windows ABI registers are not in the same allocation algorithm as unix's void ABI_PushCallerSaveRegistersAndAdjustStackExcept(BlockOfCode& code, const HostLoc exception) { - std::bitset<32> regs = ABI_ALL_CALLER_SAVE; + auto regs = ABI_ALL_CALLER_SAVE; regs.reset(size_t(exception)); ABI_PushRegistersAndAdjustStack(code, 0, regs); } void ABI_PopCallerSaveRegistersAndAdjustStackExcept(BlockOfCode& code, const HostLoc exception) { - std::bitset<32> regs = ABI_ALL_CALLER_SAVE; + auto regs = ABI_ALL_CALLER_SAVE; regs.reset(size_t(exception)); ABI_PopRegistersAndAdjustStack(code, 0, regs); } diff --git a/src/dynarmic/src/dynarmic/backend/x64/constant_pool.cpp b/src/dynarmic/src/dynarmic/backend/x64/constant_pool.cpp index 127149a29e..e2b8c94148 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/constant_pool.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/constant_pool.cpp @@ -6,23 +6,23 @@ * SPDX-License-Identifier: 0BSD */ -#include "dynarmic/backend/x64/constant_pool.h" - #include #include "common/assert.h" #include "dynarmic/backend/x64/block_of_code.h" +#include "dynarmic/backend/x64/constant_pool.h" namespace Dynarmic::Backend::X64 { ConstantPool::ConstantPool(BlockOfCode& code, size_t size) - : code(code), insertion_point(0) { + : code(code) + , insertion_point(0) +{ code.EnsureMemoryCommitted(align_size + size); code.int3(); code.align(align_size); - pool = std::span( - reinterpret_cast(code.AllocateFromCodeSpace(size)), size / align_size); + pool = std::span(reinterpret_cast(code.AllocateFromCodeSpace(size)), size / align_size); } Xbyak::Address ConstantPool::GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) { diff --git a/src/dynarmic/src/dynarmic/backend/x64/hostloc.h b/src/dynarmic/src/dynarmic/backend/x64/hostloc.h index 7191a0ceba..d62090d612 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/hostloc.h +++ b/src/dynarmic/src/dynarmic/backend/x64/hostloc.h @@ -8,8 +8,6 @@ #pragma once #include -#include - #include "common/assert.h" #include "common/common_types.h" #include "dynarmic/backend/x64/xbyak.h" diff --git a/src/dynarmic/src/dynarmic/backend/x64/xbyak.h b/src/dynarmic/src/dynarmic/backend/x64/xbyak.h index ee74794fb4..38401678e6 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/xbyak.h +++ b/src/dynarmic/src/dynarmic/backend/x64/xbyak.h @@ -3,13 +3,11 @@ #pragma once -#include -#include - -// TODO: Defining this crashes e v e r y t h i n g -// #define XBYAK_STD_UNORDERED_SET ankerl::unordered_dense::set -// #define XBYAK_STD_UNORDERED_MAP ankerl::unordered_dense::map -// #define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap - +// You must ensure this matches with src/common/x64/xbyak.h on root dir +#include +#include +#define XBYAK_STD_UNORDERED_SET ankerl::unordered_dense::set +#define XBYAK_STD_UNORDERED_MAP ankerl::unordered_dense::map +#define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap #include #include diff --git a/src/dynarmic/src/dynarmic/common/context.h b/src/dynarmic/src/dynarmic/common/context.h index e849c9861b..db7af8e0ae 100644 --- a/src/dynarmic/src/dynarmic/common/context.h +++ b/src/dynarmic/src/dynarmic/common/context.h @@ -51,7 +51,25 @@ # endif #endif -#ifdef ARCHITECTURE_arm64 +// Some OSes define generic helper macros for all architectures +#if defined(__NetBSD__) +// NetBSD always has useful macros for everything don't they? +// Basically this special path means that for every arch that NetBSD supports +// it atleast has the macro for _UC_MACHINE defined, thus it will be avoided on the +// other macro branches! +// https://github.com/NetBSD/src/blob/trunk/sys/arch/powerpc/include/mcontext.h +// https://github.com/NetBSD/src/blob/trunk/sys/arch/mips/include/mcontext.h +# define CTX_PC (_UC_MACHINE_PC(ucontext)) +// NetBSD defines a redzone for SP but we don't require nor want that +// ((uc)->uc_mcontext.__gregs[_REG_RSP] - 128) -> this is not desirable due to calcs +#if defined(ARCHITECTURE_x86_64) +# define CTX_SP (mctx.__gregs[_REG_RSP]) +#else +# define CTX_SP (_UC_MACHINE_SP(ucontext)) +#endif +#endif + +#if defined(ARCHITECTURE_arm64) # if defined(__APPLE__) # define CTX_PC (mctx->__ss.__pc) # define CTX_SP (mctx->__ss.__sp) @@ -76,9 +94,7 @@ # define CTX_LR (mctx.mc_gpregs.gp_lr) # define CTX_X(i) (mctx.mc_gpregs.gp_x[i]) # define CTX_Q(i) (mctx.mc_fpregs.fp_q[i]) -# elif defined(__NetBSD__) -# define CTX_PC (mctx.mc_gpregs.gp_elr) -# define CTX_SP (mctx.mc_gpregs.gp_sp) +# elif defined(__NetBSD__) // SP + PC defined above # define CTX_LR (mctx.mc_gpregs.gp_lr) # define CTX_X(i) (mctx.mc_gpregs.gp_x[i]) # define CTX_Q(i) (mctx.mc_fpregs.fp_q[i]) @@ -92,35 +108,29 @@ # else # error "unknown platform" # endif -#elif defined(__NetBSD__) -// NetBSD always has useful macros for everything don't they? -// Basically this special path means that for every arch that NetBSD supports -// it atleast has the macro for _UC_MACHINE defined, thus it will be avoided on the -// other macro branches! -// https://github.com/NetBSD/src/blob/trunk/sys/arch/powerpc/include/mcontext.h -// https://github.com/NetBSD/src/blob/trunk/sys/arch/mips/include/mcontext.h -# define CTX_PC (_UC_MACHINE_PC(ucontext)) -# define CTX_SP (_UC_MACHINE_SP(ucontext)) #elif defined(ARCHITECTURE_x86_64) # if defined(__APPLE__) -# define CTX_RIP (mctx->__ss.__rip) -# define CTX_RSP (mctx->__ss.__rsp) +# define CTX_PC (mctx->__ss.__rip) +# define CTX_SP (mctx->__ss.__rsp) # elif defined(__linux__) -# define CTX_RIP (mctx.gregs[REG_RIP]) -# define CTX_RSP (mctx.gregs[REG_RSP]) +# define CTX_PC (mctx.gregs[REG_RIP]) +# define CTX_SP (mctx.gregs[REG_RSP]) # elif defined(__FreeBSD__) -# define CTX_RIP (mctx.mc_rip) -# define CTX_RSP (mctx.mc_rsp) +# define CTX_PC (mctx.mc_rip) +# define CTX_SP (mctx.mc_rsp) # elif defined(__OpenBSD__) // https://github.com/openbsd/src/blob/master/sys/arch/x86_64/include/signal.h -# define CTX_RIP (ucontext->sc_rip) -# define CTX_RSP (ucontext->sc_rsp) +# define CTX_PC (ucontext->sc_rip) +# define CTX_SP (ucontext->sc_rsp) # elif defined(__sun__) -# define CTX_RIP (mctx.gregs[REG_RIP]) -# define CTX_RSP (mctx.gregs[REG_RSP]) +# define CTX_PC (mctx.gregs[REG_RIP]) +# define CTX_SP (mctx.gregs[REG_RSP]) # elif defined(__DragonFly__) -# define CTX_RIP (mctx.mc_rip) -# define CTX_RSP (mctx.mc_rsp) +# define CTX_PC (mctx.mc_rip) +# define CTX_SP (mctx.mc_rsp) +# elif defined(__managarm__) +# define CTX_PC (mctx.__pollution(gregs)[REG_RIP]) +# define CTX_SP (mctx.__pollution(gregs)[REG_RSP]) # else # error "unknown platform" # endif diff --git a/src/dynarmic/src/dynarmic/common/fp/op/FPToFixed.cpp b/src/dynarmic/src/dynarmic/common/fp/op/FPToFixed.cpp index d920f2d900..7c322050c3 100644 --- a/src/dynarmic/src/dynarmic/common/fp/op/FPToFixed.cpp +++ b/src/dynarmic/src/dynarmic/common/fp/op/FPToFixed.cpp @@ -78,7 +78,7 @@ u64 FPToFixed(size_t ibits, FPT op, size_t fbits, bool unsigned_, FPCR fpcr, Rou } // Detect Overflow - const int min_exponent_for_overflow = static_cast(ibits) - static_cast(mcl::bit::highest_set_bit(value.mantissa + (round_up ? Safe::LogicalShiftRight(1, exponent) : 0))) - (unsigned_ ? 0 : 1); + const int min_exponent_for_overflow = int(ibits) - int(mcl::bit::highest_set_bit(value.mantissa + (round_up ? Safe::LogicalShiftRight(1, exponent) : 0))) - (unsigned_ ? 0 : 1); if (exponent >= min_exponent_for_overflow) { // Positive overflow if (unsigned_ || !sign) { @@ -87,10 +87,10 @@ u64 FPToFixed(size_t ibits, FPT op, size_t fbits, bool unsigned_, FPCR fpcr, Rou } // Negative overflow - const u64 min_value = Safe::Negate(static_cast(1) << (ibits - 1)); + const u64 min_value = Safe::Negate(u64(1) << (ibits - 1)); if (!(exponent == min_exponent_for_overflow && int_result == min_value)) { FPProcessException(FPExc::InvalidOp, fpcr, fpsr); - return static_cast(1) << (ibits - 1); + return u64(1) << (ibits - 1); } } diff --git a/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp b/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp index e3f1721132..1a80ae638a 100644 --- a/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp +++ b/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp @@ -26,6 +26,7 @@ void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 Xbyak::Label start, loop; code.jmp(start, code.T_NEAR); code.L(loop); + if (waitpkg) { // TODO: this is because we lack regalloc - so better to be safe :( code.push(Xbyak::util::rax); @@ -33,7 +34,14 @@ void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 code.push(Xbyak::util::rdx); // TODO: This clobbers EAX and EDX did we tell the regalloc? // ARM ptr for address-monitoring - code.umonitor(ptr); + + // XBYAK BUG: code.umonitor(ptr); see issue #255 + // replace once xbyak has been fixed + code.db(0xF3); + if (ptr.getIdx() >= 8) code.db(0x41); + code.db(0x0F); code.db(0xAE); + code.db(uint8_t((3 << 6) | ((6 & 7) << 3) | (ptr.getIdx() & 7))); + // tmp.bit[0] = 0: C0.1 | Slow Wakup | Better Savings // tmp.bit[0] = 1: C0.2 | Fast Wakup | Lesser Savings // edx:eax is implicitly used as a 64-bit deadline timestamp diff --git a/src/dynarmic/src/dynarmic/frontend/A32/decoder/arm.h b/src/dynarmic/src/dynarmic/frontend/A32/decoder/arm.h index d1cd9a923d..a9309ef5a8 100644 --- a/src/dynarmic/src/dynarmic/frontend/A32/decoder/arm.h +++ b/src/dynarmic/src/dynarmic/frontend/A32/decoder/arm.h @@ -26,48 +26,41 @@ namespace Dynarmic::A32 { template using ArmMatcher = Decoder::Matcher; -template -using ArmDecodeTable = std::array>, 0x1000>; - -namespace detail { -inline size_t ToFastLookupIndexArm(u32 instruction) noexcept { - return ((instruction >> 4) & 0x00F) | ((instruction >> 16) & 0xFF0); -} -} // namespace detail - -template -static ArmDecodeTable GetArmDecodeTable() noexcept { - ArmDecodeTable table{}; - for (size_t i = 0; i < table.size(); ++i) { - // PLEASE HEAP ELLIDE - for (auto const& e : std::vector>{ -#define INST(fn, name, bitstring) DYNARMIC_DECODER_GET_MATCHER(ArmMatcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)), +template +static std::optional DecodeArm(V& visitor, u32 instruction) noexcept { + auto const make_fast_index = [](u32 a) { + return ((a >> 4) & 0x00F) | ((a >> 16) & 0xFF0); + }; + struct Handler { + bool (*fn)(V&, u32); + u32 mask; + u32 expect; + }; + alignas(64) static const std::array, 0x1000> table = [&] { + std::array, 0x1000> t{}; + for (size_t i = 0; i < t.size(); ++i) { +#define INST(fn, name, bitstring) \ + do { \ + auto const [mask, expect] = DYNARMIC_DECODER_GET_MATCHER(ArmMatcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)); \ + if ((i & make_fast_index(mask)) == make_fast_index(expect)) { \ + t[i].emplace_back([](V& visitor, u32 instruction) -> bool { \ + return DYNARMIC_DECODER_GET_MATCHER_FUNCTION(ArmMatcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)); \ + }, mask, expect); \ + } \ + } while (0); #include "./arm.inc" #undef INST - }) { - auto const expect = detail::ToFastLookupIndexArm(e.GetExpected()); - auto const mask = detail::ToFastLookupIndexArm(e.GetMask()); - if ((i & mask) == expect) { - table[i].push_back(e); - } } - } - return table; + return t; + }(); + for (auto const& e : table[make_fast_index(instruction)]) + if ((instruction & e.mask) == e.expect) + return e.fn(visitor, instruction); + return std::nullopt; } template -static std::optional>> DecodeArm(u32 instruction) noexcept { - alignas(64) static const auto table = GetArmDecodeTable(); - const auto matches_instruction = [instruction](const auto& matcher) { - return matcher.Matches(instruction); - }; - const auto& subtable = table[detail::ToFastLookupIndexArm(instruction)]; - auto iter = std::find_if(subtable.begin(), subtable.end(), matches_instruction); - return iter != subtable.end() ? std::optional>>(*iter) : std::nullopt; -} - -template -static std::optional GetNameARM(u32 inst) noexcept { +static std::optional GetNameArm(u32 inst) noexcept { std::vector>> list = { #define INST(fn, name, bitstring) { name, DYNARMIC_DECODER_GET_MATCHER(ArmMatcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)) }, #include "./arm.inc" diff --git a/src/dynarmic/src/dynarmic/frontend/A32/decoder/asimd.h b/src/dynarmic/src/dynarmic/frontend/A32/decoder/asimd.h index 5b0336e59f..4feae6f478 100644 --- a/src/dynarmic/src/dynarmic/frontend/A32/decoder/asimd.h +++ b/src/dynarmic/src/dynarmic/frontend/A32/decoder/asimd.h @@ -26,17 +26,16 @@ namespace Dynarmic::A32 { template using ASIMDMatcher = Decoder::Matcher; -template -static std::optional>> DecodeASIMD(u32 instruction) noexcept { - alignas(64) static const auto table = std::array{ -#define INST(fn, name, bitstring) DYNARMIC_DECODER_GET_MATCHER(ASIMDMatcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)), +template +static std::optional DecodeASIMD(V& visitor, u32 instruction) noexcept { +#define INST(fn, name, bitstring) \ + do { \ + auto const [mask, expect] = DYNARMIC_DECODER_GET_MATCHER(ASIMDMatcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)); \ + if ((instruction & mask) == expect) return DYNARMIC_DECODER_GET_MATCHER_FUNCTION(ASIMDMatcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)); \ + } while (0); #include "./asimd.inc" #undef INST - }; - auto iter = std::find_if(table.begin(), table.end(), [instruction](const auto& matcher) { - return matcher.Matches(instruction); - }); - return iter != table.end() ? std::optional>>(*iter) : std::nullopt; + return std::nullopt; } template diff --git a/src/dynarmic/src/dynarmic/frontend/A32/decoder/thumb16.h b/src/dynarmic/src/dynarmic/frontend/A32/decoder/thumb16.h index 590878a6de..465159a00d 100644 --- a/src/dynarmic/src/dynarmic/frontend/A32/decoder/thumb16.h +++ b/src/dynarmic/src/dynarmic/frontend/A32/decoder/thumb16.h @@ -23,17 +23,16 @@ namespace Dynarmic::A32 { template using Thumb16Matcher = Decoder::Matcher; -template -static std::optional>> DecodeThumb16(u16 instruction) { - alignas(64) static const auto table = std::array{ -#define INST(fn, name, bitstring) DYNARMIC_DECODER_GET_MATCHER(Thumb16Matcher, fn, name, Decoder::detail::StringToArray<16>(bitstring)), +template +static std::optional DecodeThumb16(V& visitor, u16 instruction) { +#define INST(fn, name, bitstring) \ + do { \ + auto const [mask, expect] = DYNARMIC_DECODER_GET_MATCHER(Thumb16Matcher, fn, name, Decoder::detail::StringToArray<16>(bitstring)); \ + if ((instruction & mask) == expect) return DYNARMIC_DECODER_GET_MATCHER_FUNCTION(Thumb16Matcher, fn, name, Decoder::detail::StringToArray<16>(bitstring)); \ + } while (0); #include "./thumb16.inc" #undef INST - }; - auto iter = std::find_if(table.begin(), table.end(), [instruction](const auto& matcher) { - return matcher.Matches(instruction); - }); - return iter != table.end() ? std::optional>>(*iter) : std::nullopt; + return std::nullopt; } template diff --git a/src/dynarmic/src/dynarmic/frontend/A32/decoder/thumb32.h b/src/dynarmic/src/dynarmic/frontend/A32/decoder/thumb32.h index 6a09c66afb..b3588c7a1d 100644 --- a/src/dynarmic/src/dynarmic/frontend/A32/decoder/thumb32.h +++ b/src/dynarmic/src/dynarmic/frontend/A32/decoder/thumb32.h @@ -22,17 +22,16 @@ namespace Dynarmic::A32 { template using Thumb32Matcher = Decoder::Matcher; -template -static std::optional>> DecodeThumb32(u32 instruction) { - alignas(64) static const auto table = std::array{ -#define INST(fn, name, bitstring) DYNARMIC_DECODER_GET_MATCHER(Thumb32Matcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)), +template +static std::optional DecodeThumb32(V& visitor, u32 instruction) { +#define INST(fn, name, bitstring) \ + do { \ + auto const [mask, expect] = DYNARMIC_DECODER_GET_MATCHER(Thumb32Matcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)); \ + if ((instruction & mask) == expect) return DYNARMIC_DECODER_GET_MATCHER_FUNCTION(Thumb32Matcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)); \ + } while (0); #include "./thumb32.inc" #undef INST - }; - auto iter = std::find_if(table.begin(), table.end(), [instruction](const auto& matcher) { - return matcher.Matches(instruction); - }); - return iter != table.end() ? std::optional>>(*iter) : std::nullopt; + return std::nullopt; } template diff --git a/src/dynarmic/src/dynarmic/frontend/A32/decoder/vfp.h b/src/dynarmic/src/dynarmic/frontend/A32/decoder/vfp.h index e02cc03a6b..8703aa24b4 100644 --- a/src/dynarmic/src/dynarmic/frontend/A32/decoder/vfp.h +++ b/src/dynarmic/src/dynarmic/frontend/A32/decoder/vfp.h @@ -23,32 +23,18 @@ namespace Dynarmic::A32 { template using VFPMatcher = Decoder::Matcher; -template -static std::optional>> DecodeVFP(u32 instruction) { - using Table = std::vector>; - alignas(64) static const struct Tables { - Table unconditional; - Table conditional; - } tables = []() { - Table list = { -#define INST(fn, name, bitstring) DYNARMIC_DECODER_GET_MATCHER(VFPMatcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)), +template +static std::optional DecodeVFP(V& visitor, u32 instruction) { + bool const i_uncond = (instruction & 0xF0000000) == 0xF0000000; +#define INST(fn, name, bitstring) \ + do { \ + auto const [mask, expect] = DYNARMIC_DECODER_GET_MATCHER(VFPMatcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)); \ + bool const m_uncond = (mask & 0xF0000000) == 0xF0000000; \ + if ((instruction & mask) == expect && m_uncond == i_uncond) return DYNARMIC_DECODER_GET_MATCHER_FUNCTION(VFPMatcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)); \ + } while (0); #include "./vfp.inc" #undef INST - }; - auto const it = std::stable_partition(list.begin(), list.end(), [&](const auto& matcher) { - return (matcher.GetMask() & 0xF0000000) == 0xF0000000; - }); - return Tables{ - Table{list.begin(), it}, - Table{it, list.end()}, - }; - }(); - const bool is_unconditional = (instruction & 0xF0000000) == 0xF0000000; - const Table& table = is_unconditional ? tables.unconditional : tables.conditional; - auto iter = std::find_if(table.begin(), table.end(), [instruction](const auto& matcher) { - return matcher.Matches(instruction); - }); - return iter != table.end() ? std::optional>>(*iter) : std::nullopt; + return std::nullopt; } template diff --git a/src/dynarmic/src/dynarmic/frontend/A32/translate/translate_arm.cpp b/src/dynarmic/src/dynarmic/frontend/A32/translate/translate_arm.cpp index 3fd475d074..6c89a4af36 100644 --- a/src/dynarmic/src/dynarmic/frontend/A32/translate/translate_arm.cpp +++ b/src/dynarmic/src/dynarmic/frontend/A32/translate/translate_arm.cpp @@ -41,12 +41,12 @@ void TranslateArm(IR::Block& block, LocationDescriptor descriptor, TranslateCall tcb->PreCodeTranslationHook(false, arm_pc, visitor.ir); ticks_for_instruction = tcb->GetTicksForCode(false, arm_pc, *arm_instruction); - if (const auto vfp_decoder = DecodeVFP(*arm_instruction)) { - should_continue = vfp_decoder->get().call(visitor, *arm_instruction); - } else if (const auto asimd_decoder = DecodeASIMD(*arm_instruction)) { - should_continue = asimd_decoder->get().call(visitor, *arm_instruction); - } else if (const auto decoder = DecodeArm(*arm_instruction)) { - should_continue = decoder->get().call(visitor, *arm_instruction); + if (const auto vfp_decoder = DecodeVFP(visitor, *arm_instruction)) { + should_continue = *vfp_decoder; + } else if (const auto asimd_decoder = DecodeASIMD(visitor, *arm_instruction)) { + should_continue = *asimd_decoder; + } else if (const auto decoder = DecodeArm(visitor, *arm_instruction)) { + should_continue = *decoder; } else { should_continue = visitor.arm_UDF(); } @@ -88,12 +88,12 @@ bool TranslateSingleArmInstruction(IR::Block& block, LocationDescriptor descript const u64 ticks_for_instruction = 1; - if (const auto vfp_decoder = DecodeVFP(arm_instruction)) { - should_continue = vfp_decoder->get().call(visitor, arm_instruction); - } else if (const auto asimd_decoder = DecodeASIMD(arm_instruction)) { - should_continue = asimd_decoder->get().call(visitor, arm_instruction); - } else if (const auto decoder = DecodeArm(arm_instruction)) { - should_continue = decoder->get().call(visitor, arm_instruction); + if (const auto vfp_decoder = DecodeVFP(visitor, arm_instruction)) { + should_continue = *vfp_decoder; + } else if (const auto asimd_decoder = DecodeASIMD(visitor, arm_instruction)) { + should_continue = *asimd_decoder; + } else if (const auto decoder = DecodeArm(visitor, arm_instruction)) { + should_continue = *decoder; } else { should_continue = visitor.arm_UDF(); } diff --git a/src/dynarmic/src/dynarmic/frontend/A32/translate/translate_thumb.cpp b/src/dynarmic/src/dynarmic/frontend/A32/translate/translate_thumb.cpp index 332860a95c..2a2fbea63f 100644 --- a/src/dynarmic/src/dynarmic/frontend/A32/translate/translate_thumb.cpp +++ b/src/dynarmic/src/dynarmic/frontend/A32/translate/translate_thumb.cpp @@ -126,24 +126,24 @@ void TranslateThumb(IR::Block& block, LocationDescriptor descriptor, TranslateCa if (IsUnconditionalInstruction(is_thumb_16, thumb_instruction) || visitor.ThumbConditionPassed()) { if (is_thumb_16) { - if (const auto decoder = DecodeThumb16(static_cast(thumb_instruction))) { - should_continue = decoder->get().call(visitor, static_cast(thumb_instruction)); + if (const auto decoder = DecodeThumb16(visitor, u16(thumb_instruction))) { + should_continue = *decoder; } else { should_continue = visitor.thumb16_UDF(); } } else { if (MaybeVFPOrASIMDInstruction(thumb_instruction)) { - if (const auto vfp_decoder = DecodeVFP(thumb_instruction)) { - should_continue = vfp_decoder->get().call(visitor, thumb_instruction); - } else if (const auto asimd_decoder = DecodeASIMD(ConvertASIMDInstruction(thumb_instruction))) { - should_continue = asimd_decoder->get().call(visitor, ConvertASIMDInstruction(thumb_instruction)); - } else if (const auto decoder = DecodeThumb32(thumb_instruction)) { - should_continue = decoder->get().call(visitor, thumb_instruction); + if (const auto vfp_decoder = DecodeVFP(visitor, thumb_instruction)) { + should_continue = *vfp_decoder; + } else if (const auto asimd_decoder = DecodeASIMD(visitor, ConvertASIMDInstruction(thumb_instruction))) { + should_continue = *asimd_decoder; + } else if (const auto decoder = DecodeThumb32(visitor, thumb_instruction)) { + should_continue = *decoder; } else { should_continue = visitor.thumb32_UDF(); } - } else if (const auto decoder = DecodeThumb32(thumb_instruction)) { - should_continue = decoder->get().call(visitor, thumb_instruction); + } else if (const auto decoder = DecodeThumb32(visitor, thumb_instruction)) { + should_continue = *decoder; } else { should_continue = visitor.thumb32_UDF(); } @@ -187,25 +187,25 @@ bool TranslateSingleThumbInstruction(IR::Block& block, LocationDescriptor descri const u64 ticks_for_instruction = 1; if (is_thumb_16) { - if (const auto decoder = DecodeThumb16(static_cast(thumb_instruction))) { - should_continue = decoder->get().call(visitor, static_cast(thumb_instruction)); + if (const auto decoder = DecodeThumb16(visitor, u16(thumb_instruction))) { + should_continue = *decoder; } else { should_continue = visitor.thumb16_UDF(); } } else { thumb_instruction = mcl::bit::swap_halves_32(thumb_instruction); if (MaybeVFPOrASIMDInstruction(thumb_instruction)) { - if (const auto vfp_decoder = DecodeVFP(thumb_instruction)) { - should_continue = vfp_decoder->get().call(visitor, thumb_instruction); - } else if (const auto asimd_decoder = DecodeASIMD(ConvertASIMDInstruction(thumb_instruction))) { - should_continue = asimd_decoder->get().call(visitor, ConvertASIMDInstruction(thumb_instruction)); - } else if (const auto decoder = DecodeThumb32(thumb_instruction)) { - should_continue = decoder->get().call(visitor, thumb_instruction); + if (const auto vfp_decoder = DecodeVFP(visitor, thumb_instruction)) { + should_continue = *vfp_decoder; + } else if (const auto asimd_decoder = DecodeASIMD(visitor, ConvertASIMDInstruction(thumb_instruction))) { + should_continue = *asimd_decoder; + } else if (const auto decoder = DecodeThumb32(visitor, thumb_instruction)) { + should_continue = *decoder; } else { should_continue = visitor.thumb32_UDF(); } - } else if (const auto decoder = DecodeThumb32(thumb_instruction)) { - should_continue = decoder->get().call(visitor, thumb_instruction); + } else if (const auto decoder = DecodeThumb32(visitor, thumb_instruction)) { + should_continue = *decoder; } else { should_continue = visitor.thumb32_UDF(); } diff --git a/src/dynarmic/src/dynarmic/frontend/A64/decoder/a64.h b/src/dynarmic/src/dynarmic/frontend/A64/decoder/a64.h index ccf890cbcd..f0b7baba7b 100644 --- a/src/dynarmic/src/dynarmic/frontend/A64/decoder/a64.h +++ b/src/dynarmic/src/dynarmic/frontend/A64/decoder/a64.h @@ -26,45 +26,37 @@ namespace Dynarmic::A64 { template using Matcher = Decoder::Matcher; -template -using DecodeTable = std::array>, 0x1000>; - -namespace detail { -inline size_t ToFastLookupIndex(u32 instruction) { - return ((instruction >> 10) & 0x00F) | ((instruction >> 18) & 0xFF0); -} -} // namespace detail - -template -inline DecodeTable GetDecodeTable() { - DecodeTable table{}; - for (size_t i = 0; i < table.size(); ++i) { - // PLEASE HEAP ELLIDE - for (auto const& e : std::vector>{ -#define INST(fn, name, bitstring) DYNARMIC_DECODER_GET_MATCHER(Matcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)), +template +static std::optional Decode(V& visitor, u32 instruction) noexcept { + auto const make_fast_index = [](u32 a) { + return ((a >> 10) & 0x00F) | ((a >> 18) & 0xFF0); + }; + struct Handler { + bool (*fn)(V&, u32); + u32 mask; + u32 expect; + }; + alignas(64) static const std::array, 0x1000> table = [&] { + std::array, 0x1000> t{}; + for (size_t i = 0; i < t.size(); ++i) { +#define INST(fn, name, bitstring) \ + do { \ + auto const [mask, expect] = DYNARMIC_DECODER_GET_MATCHER(Matcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)); \ + if ((i & make_fast_index(mask)) == make_fast_index(expect)) { \ + t[i].emplace_back([](V& visitor, u32 instruction) -> bool { \ + return DYNARMIC_DECODER_GET_MATCHER_FUNCTION(Matcher, fn, name, Decoder::detail::StringToArray<32>(bitstring)); \ + }, mask, expect); \ + } \ + } while (0); #include "./a64.inc" #undef INST - }) { - const auto expect = detail::ToFastLookupIndex(e.GetExpected()); - const auto mask = detail::ToFastLookupIndex(e.GetMask()); - if ((i & mask) == expect) - table[i].push_back(e); } - } - return table; -} - -/// In practice it must always suceed, otherwise something else unrelated would have gone awry -template -inline std::optional>> Decode(u32 instruction) { - alignas(64) static const auto table = GetDecodeTable(); - const auto& subtable = table[detail::ToFastLookupIndex(instruction)]; - auto iter = std::find_if(subtable.begin(), subtable.end(), [instruction](const auto& matcher) { - return matcher.Matches(instruction); - }); - return iter != subtable.end() - ? std::optional{ std::reference_wrapper>(*iter) } - : std::nullopt; + return t; + }(); + for (auto const& e : table[make_fast_index(instruction)]) + if ((instruction & e.mask) == e.expect) + return e.fn(visitor, instruction); + return std::nullopt; } template diff --git a/src/dynarmic/src/dynarmic/frontend/A64/translate/a64_translate.cpp b/src/dynarmic/src/dynarmic/frontend/A64/translate/a64_translate.cpp index 4afce6bd29..7840c63d79 100644 --- a/src/dynarmic/src/dynarmic/frontend/A64/translate/a64_translate.cpp +++ b/src/dynarmic/src/dynarmic/frontend/A64/translate/a64_translate.cpp @@ -24,9 +24,8 @@ void Translate(IR::Block& block, LocationDescriptor descriptor, MemoryReadCodeFu do { const u64 pc = visitor.ir.current_location->PC(); if (const auto instruction = memory_read_code(pc)) { - auto decoder = Decode(*instruction); - if (decoder) { - should_continue = decoder->get().call(visitor, *instruction); + if (auto decoder = Decode(visitor, *instruction)) { + should_continue = *decoder; } else { should_continue = visitor.RaiseException(Exception::UnallocatedEncoding); } @@ -47,13 +46,9 @@ void Translate(IR::Block& block, LocationDescriptor descriptor, MemoryReadCodeFu bool TranslateSingleInstruction(IR::Block& block, LocationDescriptor descriptor, u32 instruction) { TranslatorVisitor visitor{block, descriptor, {}}; - bool should_continue = true; - auto const decoder = Decode(instruction); - if (decoder) { - should_continue = decoder->get().call(visitor, instruction); - } else { - should_continue = false; - } + bool should_continue = false; + if (auto const decoder = Decode(visitor, instruction)) + should_continue = *decoder; visitor.ir.current_location = visitor.ir.current_location->AdvancePC(4); block.CycleCount()++; diff --git a/src/dynarmic/src/dynarmic/frontend/decoder/decoder_detail.h b/src/dynarmic/src/dynarmic/frontend/decoder/decoder_detail.h index c22e585c26..e383fb0262 100644 --- a/src/dynarmic/src/dynarmic/frontend/decoder/decoder_detail.h +++ b/src/dynarmic/src/dynarmic/frontend/decoder/decoder_detail.h @@ -8,31 +8,25 @@ #pragma once -#include #include #include - +#include #include "common/assert.h" #include "dynarmic/mcl/bit.hpp" -#include "dynarmic/mcl/function_info.hpp" namespace Dynarmic::Decoder { namespace detail { template -inline consteval std::array StringToArray(const char (&str)[N + 1]) { - std::array result{}; - for (size_t i = 0; i < N; i++) { - result[i] = str[i]; - } - return result; +inline consteval std::array StringToArray(const char (&s)[N + 1]) { + std::array r{}; + for (size_t i = 0; i < N; i++) + r[i] = s[i]; + return r; } -/** - * Helper functions for the decoders. - * - * @tparam MatcherT The type of the Matcher to use. - */ +/// @brief Helper functions for the decoders. +/// @tparam MatcherT The type of the Matcher to use. template struct detail { using opcode_type = typename MatcherT::opcode_type; @@ -40,17 +34,15 @@ struct detail { static constexpr size_t opcode_bitsize = mcl::bitsizeof; - /** - * Generates the mask and the expected value after masking from a given bitstring. - * A '0' in a bitstring indicates that a zero must be present at that bit position. - * A '1' in a bitstring indicates that a one must be present at that bit position. - */ + /// @brief Generates the mask and the expected value after masking from a given bitstring. + /// A '0' in a bitstring indicates that a zero must be present at that bit position. + /// A '1' in a bitstring indicates that a one must be present at that bit position. #ifdef __clang__ static constexpr auto GetMaskAndExpect(std::array bitstring) { #else static consteval auto GetMaskAndExpect(std::array bitstring) { #endif - const auto one = static_cast(1); + const auto one = opcode_type(1); opcode_type mask = 0, expect = 0; for (size_t i = 0; i < opcode_bitsize; i++) { const size_t bit_position = opcode_bitsize - i - 1; @@ -101,7 +93,7 @@ struct detail { } #if !defined(DYNARMIC_IGNORE_ASSERTS) && !defined(__ANDROID__) // Avoids a MSVC ICE, and avoids Android NDK issue. - ASSERT(std::all_of(masks.begin(), masks.end(), [](auto m) { return m != 0; })); + DEBUG_ASSERT(std::all_of(masks.begin(), masks.end(), [](auto m) { return m != 0; })); #endif return std::make_tuple(masks, shifts); } @@ -109,65 +101,32 @@ struct detail { /// @brief This struct's Make member function generates a lambda which decodes an instruction /// based on the provided arg_masks and arg_shifts. The Visitor member function to call is /// provided as a template argument. - template - struct VisitorCaller; - #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable : 4800) // forcing value to bool 'true' or 'false' (performance warning) #endif - template - struct VisitorCaller { - template - static constexpr auto Make(std::integer_sequence, - ReturnType (V::*const fn)(Args...), - const std::array arg_masks, - const std::array arg_shifts) { - static_assert(std::is_same_v, "Member function is not from Matcher's Visitor"); - return [fn, arg_masks, arg_shifts](V& v, opcode_type instruction) { - (void)instruction; - (void)arg_masks; - (void)arg_shifts; - return (v.*fn)(Args((instruction & arg_masks[iota]) >> arg_shifts[iota])...); - }; - } - }; - - template - struct VisitorCaller { - template - static constexpr auto Make(std::integer_sequence, - ReturnType (V::*const fn)(Args...) const, - const std::array arg_masks, - const std::array arg_shifts) { - static_assert(std::is_same_v, "Member function is not from Matcher's Visitor"); - return [fn, arg_masks, arg_shifts](const V& v, opcode_type instruction) { - (void)instruction; - (void)arg_masks; - (void)arg_shifts; - return (v.*fn)(Args((instruction & arg_masks[iota]) >> arg_shifts[iota])...); - }; + template + struct VisitorCaller { + template + static inline constexpr auto Invoke(std::index_sequence, V& visitor, [[maybe_unused]] opcode_type instruction, ReturnType (V::*const fn)(Args...), [[maybe_unused]] const std::array arg_masks, [[maybe_unused]] const std::array arg_shifts) { + return (visitor.*fn)(Args((instruction & arg_masks[iota]) >> arg_shifts[iota])...); } }; #ifdef _MSC_VER # pragma warning(pop) #endif - /// @brief Creates a matcher that can match and parse instructions based on bitstring. - /// See also: GetMaskAndExpect and GetArgInfo for format of bitstring. - template - static constexpr auto GetMatcher(F fn) { - constexpr size_t args_count = mcl::parameter_count_v; - constexpr auto mask = std::get<0>(GetMaskAndExpect(bitstring)); - constexpr auto expect = std::get<1>(GetMaskAndExpect(bitstring)); - constexpr auto arg_masks = std::get<0>(GetArgInfo(bitstring)); - constexpr auto arg_shifts = std::get<1>(GetArgInfo(bitstring)); - const auto proxy_fn = VisitorCaller::Make(std::make_index_sequence(), fn, arg_masks, arg_shifts); - return MatcherT(mask, expect, proxy_fn); + template + static inline constexpr auto GetMatcherFunction(V& visitor, [[maybe_unused]] opcode_type instruction, ReturnType (V::*const fn)(Args...)) { + constexpr auto arg_masks = std::get<0>(GetArgInfo(bitstring)); + constexpr auto arg_shifts = std::get<1>(GetArgInfo(bitstring)); + return VisitorCaller::Invoke(std::index_sequence_for(), visitor, instruction, fn, arg_masks, arg_shifts); } }; -#define DYNARMIC_DECODER_GET_MATCHER(MatcherT, fn, name, bitstring) Decoder::detail::detail>::template GetMatcher(&V::fn) +#define DYNARMIC_DECODER_GET_MATCHER(MatcherT, fn, name, bitstring) Decoder::detail::detail>::GetMaskAndExpect(bitstring) + +#define DYNARMIC_DECODER_GET_MATCHER_FUNCTION(MatcherT, fn, name, bitstring) Decoder::detail::detail>::template GetMatcherFunction(visitor, instruction, &V::fn) } // namespace detail } // namespace Dynarmic::Decoder diff --git a/src/dynarmic/src/dynarmic/frontend/decoder/matcher.h b/src/dynarmic/src/dynarmic/frontend/decoder/matcher.h index 194f646ed2..49095f67e3 100644 --- a/src/dynarmic/src/dynarmic/frontend/decoder/matcher.h +++ b/src/dynarmic/src/dynarmic/frontend/decoder/matcher.h @@ -15,50 +15,41 @@ namespace Dynarmic::Decoder { /// Generic instruction handling construct. -/// @tparam Visitor An arbitrary visitor type that will be passed through -/// to the function being handled. This type must be the -/// type of the first parameter in a handler function. -/// @tparam OpcodeType Type representing an opcode. This must be the -/// type of the second parameter in a handler function. -template +/// @tparam V An arbitrary visitor type that will be passed through +/// to the function being handled. This type must be the +/// type of the first parameter in a handler function. +/// @tparam T Type representing an opcode. This must be the +/// type of the second parameter in a handler function. +template class Matcher { public: - using opcode_type = OpcodeType; - using visitor_type = Visitor; - using handler_return_type = typename Visitor::instruction_return_type; - using handler_function = std::function; - Matcher(opcode_type mask, opcode_type expected, handler_function func) - : mask{mask}, expected{expected}, fn{std::move(func)} {} + using opcode_type = T; + using visitor_type = V; - /// Gets the mask for this instruction. - inline opcode_type GetMask() const noexcept { + constexpr Matcher(T mask, T expected) noexcept + : mask{mask} + , expected{expected} + {} + + /// @brief Gets the mask for this instruction. + constexpr inline T GetMask() const noexcept { return mask; } - /// Gets the expected value after masking for this instruction. - inline opcode_type GetExpected() const noexcept { + /// @brief Gets the expected value after masking for this instruction. + constexpr inline T GetExpected() const noexcept { return expected; } - /// Tests to see if the given instruction is the instruction this matcher represents. + /// @brief Tests to see if the given instruction is the instruction this matcher represents. /// @param instruction The instruction to test /// @returns true if the given instruction matches. - inline bool Matches(opcode_type instruction) const noexcept { + constexpr inline bool Matches(T instruction) const noexcept { return (instruction & mask) == expected; } - /// Calls the corresponding instruction handler on visitor for this type of instruction. - /// @param v The visitor to use - /// @param instruction The instruction to decode. - inline handler_return_type call(Visitor& v, opcode_type instruction) const noexcept { - ASSERT(Matches(instruction)); - return fn(v, instruction); - } - -private: - opcode_type mask; - opcode_type expected; - handler_function fn; + T mask; + T expected; }; } // namespace Dynarmic::Decoder diff --git a/src/dynarmic/src/dynarmic/frontend/imm.h b/src/dynarmic/src/dynarmic/frontend/imm.h index 56fbcbda0e..f305b5bfc8 100644 --- a/src/dynarmic/src/dynarmic/frontend/imm.h +++ b/src/dynarmic/src/dynarmic/frontend/imm.h @@ -27,9 +27,8 @@ class Imm { public: static constexpr size_t bit_size = bit_size_; - explicit Imm(u32 value) - : value(value) { - ASSERT((mcl::bit::get_bits<0, bit_size - 1>(value) == value) && "More bits in value than expected"); + explicit Imm(u32 value) : value(value) { + DEBUG_ASSERT((mcl::bit::get_bits<0, bit_size - 1>(value) == value) && "More bits in value than expected"); } template diff --git a/src/dynarmic/tests/A64/a64.cpp b/src/dynarmic/tests/A64/a64.cpp index e85986ea5a..9bd9c36a86 100644 --- a/src/dynarmic/tests/A64/a64.cpp +++ b/src/dynarmic/tests/A64/a64.cpp @@ -1787,7 +1787,7 @@ TEST_CASE("A64: rand3", "[a64]") { A64::Jit jit{config}; memset(backing_memory, 0, memory_size); - // cat rand2.txt | awk '{print "env.code_mem.emplace_back(0x"$2"); // "$0}' > rand2-out.txt + // cat rand2.txt | awk '{print "env.code_mem.emplace_back(0x"$2"); // "$0}' > rand2-out.tx env.MemoryWrite32(100, 0x58028edd); // 0000000000000084 58028edd ldr x29, #20952 env.MemoryWrite32(104, 0x14000000); // 0000000000000ea4 14000000 b #0 @@ -1821,7 +1821,7 @@ TEST_CASE("A64: rand2", "[a64][.]") { A64::Jit jit{config}; memset(backing_memory, 0, memory_size); - // cat rand2.txt | awk '{print "env.code_mem.emplace_back(0x"$2"); // "$0}' > rand2-out.txt + // cat rand2.txt | awk '{print "env.code_mem.emplace_back(0x"$2"); // "$0}' > rand2-out.tx const std::array code32 = {0xea80f352, 0x6e65e59d, 0x1e20c343, 0x2e3a7192, 0x2e267249, 0xd500405f, 0x6f01f461, 0x6eb684fc, 0x58028edd, 0x0ea5f5b6, 0x0ea069fb, 0x2e769517, 0x5e066063, 0x1e65c3f5, 0x4f00ff52, 0x93401cf6, 0x1e274248, 0x6f67aaf5, 0x5e0c0782, 0x5ef43f3c, 0x2e6595b7, 0x4e20590f, 0xb35aa451, 0x6ee2c5ed, 0x4e32bf46, 0x2ea1ba8f, 0x2f68a85e, 0x9237d90a, 0x5e23dd10, 0x0e762e32, 0x4e31a8cf, 0xce1f3360, 0x781a4ac0, 0x13834066, 0x5fa8101c, 0x6f7c5594, 0x0e71bb68, 0xbc0b3e8f, 0x785dbbda, 0x6f51e794, 0xce50af75, 0x1ad728ec, 0x6ee0da4c, 0xb84efa14, 0x2eb3f613, 0x4e287ade, 0x4eb8c734, 0x2e83f4e8, 0x0e397c80, 0xd08f93f8, 0xce718e48, 0x0f672a0d, 0x2e9edd40, 0x0e14128b, 0x6f5942e6, 0x8b3a0f03, 0x3c5d16b9, 0x7f7e3743, 0x4f4c54e4, 0x0ea0a9e9, 0x9e59dbe6, 0x6e7ddcd3, 0xcec08377, 0x9ba759f8, 0x2ea5046e, 0x0e24c569, 0xb8979780, 0x4e31b98c, 0x4efe4f46, 0x4ea7c762, 0x7e61c9c6, 0x6e30c880, 0x1ada0c25, 0x4e603a2f, 0xda9d7218, 0x0d40c5d9, 0x5e214b05, 0x9ba9efc5, 0x5e61b81e, 0x6e7bc31c, 0x0e61a163, 0x9e5832d2, 0x4e772248, 0x4e3d17c8, 0x92624f60, 0x7a1a02dc, 0x79891f65, 0x6eb45036, 0x0e321ee8, 0x4e2566f0, 0x4ea02b9b, 0x0f9dcb3d, 0x2e21b9f9, 0x0e21a8c3, 0xda1700bd, 0x6ea0fb38, 0x7e607a0b, 0x72845817, 0x7f61068e, 0x0d60e529, 0x4ea0ca5c, 0x1a94b20f, 0x8b87419d, 0x7ea9ed71, 0x2ea1a86e, 0x4d40c4da, 0x5ea0eada, 0x784ba96e, 0x7eb6ee02, 0x3db1c710, 0x0e217836, 0x7ee0bb96, 0x4e786c08, 0x4e976a08, 0x489ffe86, 0x4e79fc9b, 0x0e21cbce, 0x5ef7fc65, 0x4ea1286d, 0xd29c771e, 0x6f5c2839, 0x0ea00a9d, 0x6ee44c06, 0x5ee1d858, 0x5ef2fda6, 0x7eb0c9fe, 0x7f762791, 0x2e212ae6, 0x4e61c9db, 0x13003c57, 0x5ee1b8f8, 0x0f2396d2, 0x6ea0db1e, 0x0e71ba82, 0xab29c807, 0x6ef8f8b3, 0x1f18d4a1, 0x0e261d15, 0x1e290081, 0x1b0c7d12, 0x4e7771c3, 0xf845f1e4, 0x4d40c9e8, 0xce778452, 0x6eb9879d, 0x6e21c93d, 0xcec0829f, 0x52a0969f, 0x1e772b4f, 0x7ee1da88, 0x5f52fe0a, 0x7f3387b1, 0x5e214850, 0x1e65c025, 0x0e2ca294, 0x2e614829, 0x1e640077, 0x9e240048, 0x4ebe9537, 0x9bb7925e, 0x38b669c5, 0x2840d089, 0x6f43e648, 0x2e662d28, 0x4eabaff3, 0x6e734cc7, 0x0e31baee, 0x7ee0d93c, 0x5e282bde, 0x7e21bba4, 0x4e6c75fa, 0x5ac01217, 0x7f4304af, 0x1e7878ed, 0x1ada2196, 0x7ee1aba3, 0x93407f3c, 0x4f6c34eb, 0x6e3447a9, 0x7e7ae545, 0x5e0802bb, 0x6eeae63a, 0x7ee1da62, 0x5e280bb3, 0xf81d4009, 0x1e603b21, 0x5e281a14, 0x6eb0a99b, 0x1e266a25, 0x0d60cafe, 0x0e0b6194, 0x7a4ed2c5, 0x92b762ec, 0x4e6b5749, 0x3c16a6e5, 0x4ea0a92b, 0x0fa58b6a, 0x5f76148c, 0x6e30c95f, 0x1e6540fd, 0x5e28e40f, 0x0d403fd4, 0x7e30da36, 0x7fda9b51, 0x2ea04bde, 0x1e25c3d2, 0x1ee0434c, 0x5e21d8e7, 0x5ee1ba51, 0x5e61aba9, 0x4e2849fb, 0x5ee098ea, 0x4e60f63d, 0x0f280443, 0x5ee0da27, 0x2e78a6ce, 0x78054afc, 0x4e14286b, 0x4e218bd8, 0x2a3d2551, 0x3a04017a, 0x5f4317cd, 0x0e604a37, 0x9a834614, 0x0e2edf4d, 0x7a51a0a0, 0x5f8e9043, 0x6ea06bb2, 0xaa2857dd, 0x7a1903fc, 0x301ba9ba, 0x9ac929cd, 0x4e061ff0, 0x2e38fcfc, 0x0e2f614a, 0x7ee0d8e4, 0x6e73afda, 0x7f4156f7, 0x0e6078bf, 0x4ee1d9ed, 0x93403fbe, 0xce6f8640, 0x4e3855e3, 0x6f76fe23, 0x112466e8, 0x1e358a90, 0x7f45272c, 0x6ea19a9d, 0x8a696350, 0x1e3900f6, 0x5e61c866, 0x0e3fbfd0, 0x5ee09ad0, 0x0e651d27, 0x4dffc35e, 0x2e20c6ce, 0x0fbe118d, 0x1e656a15, 0xd1357365, 0x0e20a847, 0xce4a835c, 0x4e203905, 0x2e60090d, 0x7f4a27bb, 0x1e64c316, 0xce7d86a4, 0x7ebded2d, 0x6e70a97e, 0x4eb9a42b, 0x0e209bef, 0x6f151730, 0x0e7e30f7, 0x4e724509, 0xd503375f, 0xce58b6ae, 0x5e21a9b8, 0xcb2ca538, 0x5ac01131, 0x6ea19a24, 0xeb40c8b3, 0xc8df7d65, 0x78108341, 0x3218ab9b, 0x0f3da7dd, 0x2e003089, 0x4e21cab5, 0x8aa5c924, 0x1a94950c, 0x123e506f, 0x13117e37, 0x1ee6005b, 0x5ac00647, 0x5eec8cd5, 0x7ef0fb3d, 0x9223272a, 0x5ee0cb02, 0x6e66071d, 0x6ea1dbbf, 0x5e61c903, 0x5ac015ea, 0x93db6206, 0x7e62b5e3, 0x6ea0c87b, 0xdac0090e, 0x48df7d90, 0x6e206ba5, 0x9e2503c2, 0x6e25fc89, 0x4d60e2db, 0x1e3e22a0, 0x2eb81c19, 0x7856ea00, 0x5fbfb22d, 0x1e630244, 0x4e202a83, 0x1f50a722, 0x7f7b55d2, 0x0fae89b9, 0x4e781d73, 0xce738c3a, 0x4f15a591, 0x6e21c7e1, 0x586ff77e, 0x8a5d3592, 0x93401c67, 0x5e61cb86, 0xce6bc2c1, 0x6e393f10, 0x9bb70ec3, 0xdac0098c, 0x4da84b95, 0x7f494476, 0x9ace5c11, 0x7e61ca14, 0x4f7a60ef, 0x1ad32b39, 0x0ea3777f, 0x5e61da7f, 0x4f1404e2, 0x4e3244e2, 0x6e1b1ceb, 0x0dee5aac, 0x4e2f9dc4, 0x5ea1b8c3, 0x1e59f863, 0xd500403f, 0x4e3ae7d0, 0x4ef5c6ea, 0x08dffe3b, 0x6e36f4f6, 0x2e764f29, 0x0e726f23, 0x5f42375b, 0x7f71fc40, 0x6e618aad, 0x93403e5b, 0x0e205976, 0x0e7250c4, 0x6eb0abc9, 0x2e2049f0, 0x5f14754d, 0x7f6ce468, 0x6f950bbe, 0x6e31aa47, 0x4eb83396, 0x0dccc952, 0x2ea1ca90, 0xce69c701, 0xb0bed69e, 0x7c5dec39, 0x4e2868a2, 0x0e591b08, 0x5f34e6dd, 0x3a449184, 0x5e3ce6de, 0x4ea149b7, 0x4e7ad29b, 0xba198503, 0x1f683e8f, 0xfa52f2a7, 0x6e30dffc, 0x4e6c3d17, 0x2eae3248, 0xd503349f, 0x1e60002c, 0x0f180680, 0x9e240049, 0x6f75774e, 0xa90d8678, 0x9ad924c4, 0x7eb0f85b, 0x0e205aaf, 0x7ee08899, 0x5f4bffd8, 0x1b0ff5f3, 0x4ee11dcd, 0x2e218948, 0x0dcb2733, 0x4eac107c, 0x4ea04a53, 0x4e287b44, 0x0e60b82a, 0x5ee0ebbc, 0xce454ff1, 0x5e1761e7, 0x5e09202f, 0x0e0c0754, 0x1e72e6b9, 0x7e21da70, 0x0fbdb20c, 0x5efb8c84, 0xd500401f, 0x3a47526e, 0x1e680acf, 0x7f7375fc, 0xf80522da, 0x4ee60c02, 0x4d40c2e7, 0x6f89096b, 0x7ee1bb6e, 0x5e280b4a, 0x1e3120c8, 0x7eb2ef96, 0x4fd012dd, 0x0f3027ef, 0x4e2078a8, 0xd503201f, 0x2e2312d9, 0x6ebf1c6e, 0x5ee1f8df, 0x4e607a46, 0x6e30c877, 0x6c09d2d1, 0x4e61abd8, 0x0e35267e, 0x6ac17728, 0x0e861aa0, 0x6f63fe26, 0x6f157628, 0x6f30a5f9, 0x4d60cc0c, 0x4e21cb59, 0x2e68a3fb, 0x7efae601, 0x6ea0f82c, 0x9b25ec12, 0x1a1a0305, 0x0e043fe1, 0x6e73c0ed, 0x6ea1b8c0, 0x7e20380b, 0x0f0534e8, 0x1f56bc7d, 0xba0c0128, 0x1e672160, 0x6e7b259b, 0x7ee07b5d, 0x9a820443, 0x4e040581, 0x2f1d87e8, 0x1acd2f5b, 0x6e20794f, 0x2e6a3c93, 0xc8dffe13, 0xce5ab1c6, 0x6eea55f6, 0x4ea039b3, 0x0d602fec, 0x2e246e2f, 0x7857be39, 0xb80608fb, 0x1e67c017, 0x9bcf7f63, 0x0f92d857, 0x5e0812f7, 0x1e210172, 0x7e6128e9, 0x7ea94d41, 0x981179e1, 0x1effb018, 0x2e600828, 0x0eb9c6b2, 0x6ee1baae, 0x4ea0db28, 0x2ea1487b, 0x4ea6c7f0, 0x2e2374c7, 0x7e30d8dd, 0xb9991fa7, 0x4e791e3e, 0x889f7c4b, 0x0e6c753c, 0x1e740ad1, 0x1e244324, 0x1ef33010, 0x5ac01102, 0x9bd97fba, 0x6e290143, 0x1e2220d8, 0x4d8d5aee, 0x6f28570b, 0xfa4ab0c1, 0xdac00b14, 0x7ea1a90e, 0x2e3027d8, 0x6f25a733, 0x4e61a96e, 0x4e1a2fcb, 0x0e22fe0a, 0xc8df7cd0, 0x5e280a55, 0x4e012b20, 0x7e70dbf4, 0x520c5a4e, 0x6ea6c57f, 0x0e861af8, 0xd503233f, 0x889ffe3c, 0x5e274ea9, 0x4e21a89a, 0x0e170c02, 0x6efd4c0b, 0xd5033ebf, 0x6e61a92c, 0x2e205b72, 0x789fb828, 0x0e626e94, 0x2ea6724c, 0x9a10028b, 0x2c6c51fc, 0x5a9de6b9, 0x6e6881f3, 0x5ee0ea6b, 0x0faec36e, 0x0e955bca, 0x1acf206d, 0x7f6f571b, 0x4e286930, 0x12b41ceb, 0x1e770b7a, 0x0ea18ac2, 0x5e282aaf, 0xf2b7fa1e, 0x1ac34311, 0x13167d11, 0x4ea63412, 0x6e758038, 0x2f1d85d6, 0x0f275480, 0x0ead6c71, 0x6e204b69, 0x1e6303f4, 0x5e0031ef, 0x13001e40, 0x7a16006f, 0x6e6ae4c0, 0x0f0f242f, 0x6e674f50, 0x4e606b7a, 0x7e6ee684, 0x1e6b5957, 0x7ea1bbab, 0x7ea0b6cb, 0xce4da241, 0x0ea1b953, 0x0eb2af4b, 0x9ac309d0, 0x6e61d8bd, 0x5ea0d890, 0x5f47d1e7, 0xfa5981ca, 0x1e7f7959, 0x6ef24dd8, 0x0e0a41d1, 0x5ee0e898, 0x4e6038e2, 0x13097d65, 0x6f839088, 0x9e290265, 0x0e208824, 0x2e65af79, 0x6f36a561, 0x9ad3204b, 0x0e21482e, 0x1e24431d, 0xd50330bf, 0x0df641aa, 0x6e602a83, 0xce30505f, 0x5e025238, 0xd503201f, 0x4e608880, 0x4de9c38d, 0x5e0f5348, 0x6eb48ca9, 0x50fda31b, 0x2e251eec, 0x7842ba50, 0xd8a1cd86, 0x2ea09862, 0x0ea09983, 0x2ea333b0, 0x0ea6032c, 0x4f94801b, 0x7e3ee57d, 0x38135e4f, 0xd8fdd9dd, 0x5ee0fcde, 0x9e64033d, 0x6e37f547, 0x6e3dd7ef, 0x13003f3d, 0x0e602f9f, 0x4e7ad014, 0x9b3b6857, 0x5ea0cb67, 0x0eb31c9f, 0x4e7c5372, 0x5e61b8c0, 0x0ea19b23, 0x0ee6e1df, 0x6e63a626, 0x2f139405, 0x7eb0f96d, 0x9e588c63, 0x2e714c3a, 0x6e8c941e, 0x0f61b331, 0x6f01f625, 0x4e78d4ea, 0x6f403709, 0x1a0300da, 0xda0102c8, 0x7e61d9fd, 0xb89469bb, 0x0c838780, 0x2e60a590, 0x4dfd29e1, 0x4e150f2e, 0xce2810bc, 0x5f541591, 0x9ee60259, 0x2eb40e56, 0x5e014027, 0x2ef71faf, 0x4e2d452f, 0x5ee0a813, 0x4eb03301, 0x38443acf, 0x6eabd502, 0x0e2ee71e, 0x5a960364, 0xce7ec596, 0x7efbed09, 0x4ef42ea2, 0x0eb30ea5, 0x5ee0d9f8, 0x6f513552, 0xf89eb3fa, 0x7ea2eca6, 0x9b00cc19, 0xf897409e, 0x1e73485f, 0x381afa77, 0x0f169f3b, 0x5ee1aa70, 0x5e1803ee, 0x0dbf5a4c, 0xce78c7a6, 0x9b0b260c, 0x2ef8fa19, 0x6e70aa4b, 0xce45b805, 0x2ea08e86, 0x4ee0bafd, 0x2ea09a1f, 0x4e218900, 0x6e744f13, 0xce518653, 0xf81b7a68, 0xce45ac5e, 0x7e62e416, 0x1a1b02b6, 0x7e21db48, 0x381daaaf, 0x6b2c0987, 0x0e2ec651, 0x4eae8502, 0x9bde7ca0, 0x6f47201f, 0x7e61a8a3, 0x6e60d5db, 0x4e2879de, 0xf81d194e, 0x4f1b8d05, 0x4d0048b2, 0x6e203be9, 0x4e3e7eb1, 0x0e260ef8, 0x2e688518, 0x7e3fec46, 0xdac00843, 0xf85c8917, 0x2e212a0f, 0x0e8196da, 0xd503359f, 0xce4c81f2, 0x6ee19992, 0x6e21ca79, 0x4d40c1d2, 0x4f5816ef, 0x4e34c3ea, 0x4df7c283, 0x7ef7eeb6, 0x18e276ce, 0xab0d21c0, 0xd5032f7f, 0x4ea00dbf, 0x5ac01251, 0xd0121955, 0x7f1495e4, 0x7ef0fa11, 0x5e24dd9c, 0x9add25b5, 0x0eb2bdef, 0x9e1977c7, 0x6f4b26bd, 0x0e200a9c, 0x9b4f7c00, 0x0ea0392e, 0x7e212a2c, 0x0b248b90, 0x1acc27a1, 0x2e701c90, 0x5ee1b870, 0x5e280aba, 0x5ea0780e, 0x1e264246, 0x4e052d04, 0x0e731dc4, 0xce461997, 0x9a9e9413, 0x3d462048, 0x5ea1fac5, 0x2ea0c8c4, 0x9a030280, 0x2ebda4b8, 0x5eef8614, 0x6eadc4e0, 0xbd035a8f, 0x4e606b84, 0x4eb1aba1, 0x4e286928, 0x4e2858cc, 0x9add0ce9, 0x4e070d65, 0x5fd399d5, 0x0f03fde7, 0x6ee90c74, 0x4ef8e31e, 0x381d986a, 0x5ea0ebf4, 0x5ea0d87e, 0x2e76ac9e, 0x6eb36cd4, 0x2e6e1c4c, 0x2e2feebc, 0x1ace4b03, 0x5ee0db12, 0x5ea0e9b1, 0x2e1c32d5, 0x5fa49a09, 0x0e258737, 0x7e21ca8e, 0xce4f9988, 0x5f7f56a6, 0x0e739766, 0x4e28586c, 0x6e619908, 0xd500401f, 0xf88b9252, 0x6e251c8e, 0x9e20015b, 0x7f1486b9, 0x717c339b, 0x1f31ff70, 0x4ea0eb62, 0x9acb0926, 0x489f7d85, 0x4e209b54, 0x2e84cf03, 0x2e65946c, 0x0e7d80cd, 0xc8dffecc, 0xce668bd8, 0x6e2188af, 0xeb4ada34, 0x2b25ec33, 0x0d40e6e7, 0x4eb2c757, 0x4ec82ad0, 0x7e21cb0a, 0x0e21a847, 0x4e0b1ec0, 0x381e6ac0, 0x6e61c8f5, 0x0f10071c, 0x2ee21daa, 0x5e61ab31, 0x6e218892, 0x2e7e7cb5, 0x6f2826aa, 0x7f6b54df, 0x4eaa2620, 0xdac00034, 0x4f6477be, 0x7e6148ea, 0x4eef1f57, 0x78459aeb, 0x2ebc3f10, 0x2e35f4eb, 0x4fbf19ce, 0xd8d0e58e, 0x2e21bbc7, 0x6ee0cab6, 0x9bc57e3f, 0x2f854037, 0x4e92181c, 0x6e6d1f89, 0x0f305545, 0x4ee19a57, 0x0e887bdf, 0x5e1a4185, 0x7ef0c821, 0x2eb6607c, 0x2ea0d9b8, 0x9e0380f4, 0x2ebf1c83, 0x1e62597d, 0x7f6e2548, 0x5ac00205, 0x4e616adb, 0xce638b8c, 0x5e1653cf, 0x2e6069be, 0x0e2ac641, 0x1e33c76f, 0xce44956d, 0x9bb90d31, 0x1e24c20a, 0x7ee038c1, 0x93407e5e, 0x4e280127, 0xc8df7f7d, 0xba42f263, 0x1e6f199c, 0x6e212889, 0x6e92f60e, 0x6ebdc499, 0x8b9acbf8, 0x4d40c581, 0x3a020250, 0x6e6a6716, 0x9248403b, 0x9081ffea, 0x4e603856, 0x9ad1242b, 0x6f270579, 0x1a070349, 0xcec08133, 0xd503305f, 0x5a1a00ca, 0x2e60b8a2, 0x0e5f28fd, 0x0e31a3da, 0x7e61cbc1, 0xd503399f, 0x5f5e54aa, 0x0eb8bdea, 0x4eba8f10, 0x4e2a2e60, 0x2f3da7d6, 0x1e58e297, 0x6e71aa3e, 0x6b86701a, 0xce4fa5e6, 0x4ee7c463, 0x8a79307f, 0x0ebea541, 0x2e218af4, 0x4e774f8a, 0xb9b95dc5, 0x6e61abd5, 0x4dd1e814, 0x4da72098, 0x98307582, 0x3a512101, 0x7ef95497, 0x1ace5535, 0x5a0c0349, 0x4e28581b, 0x6ebf1c02, 0x5ea1da23, 0x1e274314, 0x5e25dd29, 0x6e75f594, 0x6eaf6ed5, 0x4e214abe, 0x4e064172, 0x2e21c8f4, 0xf84c5b08, 0x1e244312, 0x14000000}; for (size_t i = 0; i < code32.size(); ++i) env.MemoryWrite32(100 + i, code32[i]); @@ -2576,3 +2576,30 @@ TEST_CASE("A64: CLZ{W}", "[a64]") { REQUIRE(jit.GetRegister(1) == 4); REQUIRE(jit.GetRegister(2) == 5); } + +TEST_CASE("A64: Manual Vector Min/Max U64 (Optimizer Test)", "[a64]") { + A64TestEnv env; + A64::UserConfig jit_user_config{}; + jit_user_config.callbacks = &env; + A64::Jit jit{jit_user_config}; + + oaknut::VectorCodeGenerator code{env.code_mem, nullptr}; + + // MaxU64 pattern: (a > b) ? a : b + code.CMHI(V2.D2(), V0.D2(), V1.D2()); // V2 = Mask (A > B) + code.BSL(V2.B16(), V0.B16(), V1.B16()); // V2 = Resul + + // MinU64 pattern: (a > b) ? b : a + code.CMHI(V3.D2(), V0.D2(), V1.D2()); // V3 = Mask (A > B) + code.BSL(V3.B16(), V1.B16(), V0.B16()); // V3 = Resul + + jit.SetPC(0); + jit.SetVector(0, {100, 20}); + jit.SetVector(1, {50, 200}); + + env.ticks_left = env.code_mem.size(); + CheckedRun([&]() { jit.Run(); }); + + CHECK(jit.GetVector(2) == Vector{100, 200}); + CHECK(jit.GetVector(3) == Vector{50, 20}); +} diff --git a/src/dynarmic/tests/decoder_tests.cpp b/src/dynarmic/tests/decoder_tests.cpp index 2a6c0f6a4f..17ed2c0ce5 100644 --- a/src/dynarmic/tests/decoder_tests.cpp +++ b/src/dynarmic/tests/decoder_tests.cpp @@ -51,7 +51,7 @@ TEST_CASE("ASIMD Decoder: Ensure table order correctness", "[decode][a32][.]") { const bool iserr = is_decode_error(*iter, instruction); const auto alternative = std::find_if(table.cbegin(), iter, [instruction](const auto& m) { - return m.Matches(instruction); + return (instruction & mask) == expect; }); const bool altiserr = is_decode_error(*alternative, instruction); diff --git a/src/dynarmic/tests/print_info.cpp b/src/dynarmic/tests/print_info.cpp index 6d6109d849..988a8d294e 100644 --- a/src/dynarmic/tests/print_info.cpp +++ b/src/dynarmic/tests/print_info.cpp @@ -43,12 +43,12 @@ using namespace Dynarmic; std::string_view GetNameOfA32Instruction(u32 instruction) { - if (auto const vfp_decoder = A32::DecodeVFP(instruction)) - return *A32::GetNameVFP(instruction); - else if (auto const asimd_decoder = A32::DecodeASIMD(instruction)) - return *A32::GetNameASIMD(instruction); - else if (auto const decoder = A32::DecodeArm(instruction)) - return *A32::GetNameARM(instruction); + if (auto const vfp_decoder = A32::GetNameVFP(instruction)) + return *vfp_decoder; + else if (auto const asimd_decoder = A32::GetNameASIMD(instruction)) + return *asimd_decoder; + else if (auto const decoder = A32::GetNameArm(instruction)) + return *decoder; return ""; } diff --git a/src/dynarmic/tests/x64_cpu_info.cpp b/src/dynarmic/tests/x64_cpu_info.cpp index 8e67f0fc39..8837e64ae0 100644 --- a/src/dynarmic/tests/x64_cpu_info.cpp +++ b/src/dynarmic/tests/x64_cpu_info.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + /* This file is part of the dynarmic project. * Copyright (c) 2020 MerryMage * SPDX-License-Identifier: 0BSD @@ -11,7 +14,7 @@ #include #include -#include +#include "dynarmic/backend/x64/xbyak.h" TEST_CASE("Host CPU supports", "[a64]") { using Cpu = Xbyak::util::Cpu; @@ -36,8 +39,6 @@ TEST_CASE("Host CPU supports", "[a64]") { X(tAMX_TILE), X(tAVX), X(tAVX2), - X(tAVX512_4FMAPS), - X(tAVX512_4VNNIW), X(tAVX512_BF16), X(tAVX512_BITALG), X(tAVX512_FP16), @@ -50,10 +51,8 @@ TEST_CASE("Host CPU supports", "[a64]") { X(tAVX512BW), X(tAVX512CD), X(tAVX512DQ), - X(tAVX512ER), X(tAVX512F), X(tAVX512IFMA), - X(tAVX512PF), X(tAVX512VBMI), X(tAVX512VL), X(tAVX_VNNI), @@ -81,7 +80,6 @@ TEST_CASE("Host CPU supports", "[a64]") { X(tPCLMULQDQ), X(tPOPCNT), X(tPREFETCHW), - X(tPREFETCHWT1), X(tRDRAND), X(tRDSEED), X(tRDTSCP), diff --git a/src/frontend_common/data_manager.cpp b/src/frontend_common/data_manager.cpp index 280cbba4ab..e4e9a32ce1 100644 --- a/src/frontend_common/data_manager.cpp +++ b/src/frontend_common/data_manager.cpp @@ -46,10 +46,10 @@ const std::string GetDataDirString(DataDir dir, const std::string &user_id) u64 ClearDir(DataDir dir, const std::string &user_id) { fs::path data_dir = GetDataDir(dir, user_id); - u64 result = fs::remove_all(data_dir); - + std::error_code ec; + u64 result = fs::remove_all(data_dir, ec); // mkpath at the end just so it actually exists - fs::create_directories(data_dir); + fs::create_directories(data_dir, ec); return result; } diff --git a/src/hid_core/frontend/emulated_controller.cpp b/src/hid_core/frontend/emulated_controller.cpp index 604414c470..ef841e2411 100644 --- a/src/hid_core/frontend/emulated_controller.cpp +++ b/src/hid_core/frontend/emulated_controller.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -108,10 +108,10 @@ void EmulatedController::ReloadFromSettings() { // Other or debug controller should always be a pro controller if (npad_id_type != NpadIdType::Other) { SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type)); - original_npad_type = npad_type; + original_npad_type = npad_type.load(); } else { SetNpadStyleIndex(NpadStyleIndex::Fullkey); - original_npad_type = npad_type; + original_npad_type = npad_type.load(); } // Disable special features before disconnecting @@ -577,10 +577,9 @@ void EmulatedController::UnloadInput() { } void EmulatedController::EnableConfiguration() { - std::unique_lock lock1{connect_mutex}, lock2{npad_mutex}; - is_configuring = true; - tmp_is_connected = is_connected; - tmp_npad_type = npad_type; + is_configuring.store(true); + tmp_is_connected.store(is_connected); + tmp_npad_type.store(npad_type); } void EmulatedController::DisableConfiguration() { @@ -600,7 +599,7 @@ void EmulatedController::DisableConfiguration() { Disconnect(); } SetNpadStyleIndex(tmp_npad_type); - original_npad_type = tmp_npad_type; + original_npad_type.store(tmp_npad_type); } // Apply temporary connected status to the real controller @@ -614,19 +613,16 @@ void EmulatedController::DisableConfiguration() { } void EmulatedController::EnableSystemButtons() { - std::unique_lock lock{mutex}; system_buttons_enabled = true; } void EmulatedController::DisableSystemButtons() { - std::unique_lock lock{mutex}; system_buttons_enabled = false; controller.home_button_state.raw = 0; controller.capture_button_state.raw = 0; } void EmulatedController::ResetSystemButtons() { - std::unique_lock lock{mutex}; controller.home_button_state.home.Assign(false); controller.capture_button_state.capture.Assign(false); } @@ -763,8 +759,7 @@ void EmulatedController::StartMotionCalibration() { } } -void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index, - Common::UUID uuid) { +void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index, Common::UUID uuid) { const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type); const auto& player = Settings::values.players.GetValue()[player_index]; @@ -772,7 +767,6 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback return; } - std::unique_lock lock{mutex}; bool value_changed = false; const auto new_status = TransformToButton(callback); auto& current_status = controller.button_values[index]; @@ -818,7 +812,6 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback controller.debug_pad_button_state.raw = 0; controller.home_button_state.raw = 0; controller.capture_button_state.raw = 0; - lock.unlock(); TriggerOnChange(ControllerTriggerType::Button, false); return; } @@ -922,8 +915,6 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback break; } - lock.unlock(); - if (!is_connected) { if (npad_type == NpadStyleIndex::Handheld) { if (npad_id_type == NpadIdType::Handheld) { @@ -952,7 +943,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback, auto trigger_guard = SCOPE_GUARD { TriggerOnChange(ControllerTriggerType::Stick, !is_configuring); }; - std::unique_lock lock{mutex}; const auto stick_value = TransformToStick(callback); // Only read stick values that have the same uuid or are over the threshold to avoid flapping @@ -1009,7 +999,6 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac auto trigger_guard = SCOPE_GUARD { TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring); }; - std::unique_lock lock{mutex}; const auto trigger_value = TransformToTrigger(callback); // Only read trigger values that have the same uuid or are pressed once @@ -1057,7 +1046,6 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback SCOPE_EXIT { TriggerOnChange(ControllerTriggerType::Motion, !is_configuring); }; - std::unique_lock lock{mutex}; auto& raw_status = controller.motion_values[index].raw_status; auto& emulated = controller.motion_values[index].emulated; @@ -1093,7 +1081,6 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback auto trigger_guard = SCOPE_GUARD { TriggerOnChange(ControllerTriggerType::Color, !is_configuring); }; - std::unique_lock lock{mutex}; controller.color_values[index] = TransformToColor(callback); if (is_configuring) { @@ -1136,15 +1123,13 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback } } -void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback, - std::size_t index) { +void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index) { if (index >= controller.battery_values.size()) { return; } SCOPE_EXIT { TriggerOnChange(ControllerTriggerType::Battery, !is_configuring); }; - std::unique_lock lock{mutex}; controller.battery_values[index] = TransformToBattery(callback); if (is_configuring) { @@ -1209,47 +1194,33 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback SCOPE_EXIT { TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring); }; - std::unique_lock lock{mutex}; controller.camera_values = TransformToCamera(callback); - - if (is_configuring) { - return; + if (!is_configuring) { + controller.camera_state.sample++; + controller.camera_state.format = Core::IrSensor::ImageTransferProcessorFormat(controller.camera_values.format); + controller.camera_state.data = controller.camera_values.data; } - - controller.camera_state.sample++; - controller.camera_state.format = - static_cast(controller.camera_values.format); - controller.camera_state.data = controller.camera_values.data; } void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) { SCOPE_EXIT { TriggerOnChange(ControllerTriggerType::RingController, !is_configuring); }; - std::unique_lock lock{mutex}; const auto force_value = TransformToStick(callback); - controller.ring_analog_value = force_value.x; - - if (is_configuring) { - return; + if (!is_configuring) { + controller.ring_analog_state.force = force_value.x.value; } - - controller.ring_analog_state.force = force_value.x.value; } void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) { SCOPE_EXIT { TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring); }; - std::unique_lock lock{mutex}; controller.nfc_values = TransformToNfc(callback); - - if (is_configuring) { - return; + if (!is_configuring) { + controller.nfc_state = controller.nfc_values; } - - controller.nfc_state = controller.nfc_values; } bool EmulatedController::SetVibration(bool should_vibrate) { @@ -1258,7 +1229,6 @@ bool EmulatedController::SetVibration(bool should_vibrate) { vibration_value.high_amplitude = 1.0f; vibration_value.low_amplitude = 1.0f; } - return SetVibration(DeviceIndex::Left, vibration_value); } @@ -1268,7 +1238,6 @@ bool EmulatedController::SetVibration(u32 slot, Core::HID::VibrationGcErmCommand vibration_value.high_amplitude = 1.0f; vibration_value.low_amplitude = 1.0f; } - return SetVibration(DeviceIndex::Left, vibration_value); } @@ -1632,7 +1601,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles) // Attempt to reconnect with the original type if (npad_type != original_npad_type) { Disconnect(); - const auto current_npad_type = npad_type; + const auto current_npad_type = npad_type.load(); SetNpadStyleIndex(original_npad_type); if (IsControllerSupported()) { Connect(); @@ -1650,7 +1619,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles) // Fallback Fullkey controllers to Pro controllers if (IsControllerFullkey() && supported_style_tag.fullkey) { - LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type); + LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type.load()); SetNpadStyleIndex(NpadStyleIndex::Fullkey); Connect(); return; @@ -1658,7 +1627,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles) // Fallback Dual joycon controllers to Pro controllers if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) { - LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type); + LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type.load()); SetNpadStyleIndex(NpadStyleIndex::Fullkey); Connect(); return; @@ -1666,19 +1635,16 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles) // Fallback Pro controllers to Dual joycon if (npad_type == NpadStyleIndex::Fullkey && supported_style_tag.joycon_dual) { - LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type); + LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type.load()); SetNpadStyleIndex(NpadStyleIndex::JoyconDual); Connect(); return; } - - LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller", - npad_type); + LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller", npad_type.load()); } bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const { - std::unique_lock lock{mutex}; - const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type; + const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load(); switch (type) { case NpadStyleIndex::Fullkey: case NpadStyleIndex::GameCube: @@ -1693,39 +1659,26 @@ bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const { } bool EmulatedController::IsControllerSupported(bool use_temporary_value) const { - std::unique_lock lock{mutex}; - const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type; + const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load(); switch (type) { - case NpadStyleIndex::Fullkey: - return supported_style_tag.fullkey.As(); - case NpadStyleIndex::Handheld: - return supported_style_tag.handheld.As(); - case NpadStyleIndex::JoyconDual: - return supported_style_tag.joycon_dual.As(); - case NpadStyleIndex::JoyconLeft: - return supported_style_tag.joycon_left.As(); - case NpadStyleIndex::JoyconRight: - return supported_style_tag.joycon_right.As(); - case NpadStyleIndex::GameCube: - return supported_style_tag.gamecube.As(); - case NpadStyleIndex::Pokeball: - return supported_style_tag.palma.As(); - case NpadStyleIndex::NES: - return supported_style_tag.lark.As(); - case NpadStyleIndex::SNES: - return supported_style_tag.lucia.As(); - case NpadStyleIndex::N64: - return supported_style_tag.lagoon.As(); - case NpadStyleIndex::SegaGenesis: - return supported_style_tag.lager.As(); - default: - return false; + case NpadStyleIndex::Fullkey: return supported_style_tag.fullkey.As(); + case NpadStyleIndex::Handheld: return supported_style_tag.handheld.As(); + case NpadStyleIndex::JoyconDual: return supported_style_tag.joycon_dual.As(); + case NpadStyleIndex::JoyconLeft: return supported_style_tag.joycon_left.As(); + case NpadStyleIndex::JoyconRight: return supported_style_tag.joycon_right.As(); + case NpadStyleIndex::GameCube: return supported_style_tag.gamecube.As(); + case NpadStyleIndex::Pokeball: return supported_style_tag.palma.As(); + case NpadStyleIndex::NES: return supported_style_tag.lark.As(); + case NpadStyleIndex::SNES: return supported_style_tag.lucia.As(); + case NpadStyleIndex::N64: return supported_style_tag.lagoon.As(); + case NpadStyleIndex::SegaGenesis: return supported_style_tag.lager.As(); + default: return false; } } void EmulatedController::Connect(bool use_temporary_value) { if (!IsControllerSupported(use_temporary_value)) { - const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type; + const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load(); LOG_ERROR(Service_HID, "Controller type {} is not supported", type); return; } @@ -1733,12 +1686,10 @@ void EmulatedController::Connect(bool use_temporary_value) { auto trigger_guard = SCOPE_GUARD { TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); }; - std::unique_lock lock1{connect_mutex}, lock2{mutex}; if (is_configuring) { tmp_is_connected = true; return; } - if (is_connected) { trigger_guard.Cancel(); return; @@ -1750,12 +1701,10 @@ void EmulatedController::Disconnect() { auto trigger_guard = SCOPE_GUARD { TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); }; - std::unique_lock lock1{connect_mutex}, lock2{mutex}; if (is_configuring) { tmp_is_connected = false; return; } - if (!is_connected) { trigger_guard.Cancel(); return; @@ -1764,19 +1713,16 @@ void EmulatedController::Disconnect() { } bool EmulatedController::IsConnected(bool get_temporary_value) const { - std::shared_lock lock{connect_mutex}; if (get_temporary_value && is_configuring) return tmp_is_connected; return is_connected; } NpadIdType EmulatedController::GetNpadIdType() const { - std::shared_lock lock{mutex}; return npad_id_type; } NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const { - std::shared_lock lock{npad_mutex}; if (get_temporary_value && is_configuring) return tmp_npad_type; return npad_type; @@ -1786,8 +1732,6 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) { auto trigger_guard = SCOPE_GUARD { TriggerOnChange(ControllerTriggerType::Type, !is_configuring); }; - std::unique_lock lock1{mutex}, lock2{npad_mutex}; - if (is_configuring) { if (tmp_npad_type == npad_type_) { trigger_guard.Cancel(); @@ -1796,14 +1740,12 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) { tmp_npad_type = npad_type_; return; } - if (npad_type == npad_type_) { trigger_guard.Cancel(); return; } if (is_connected) { - LOG_WARNING(Service_HID, "Controller {} type changed while it's connected", - Service::HID::NpadIdTypeToIndex(npad_id_type)); + LOG_WARNING(Service_HID, "Controller {} type changed while it's connected", Service::HID::NpadIdTypeToIndex(npad_id_type)); } npad_type = npad_type_; } @@ -1832,37 +1774,30 @@ LedPattern EmulatedController::GetLedPattern() const { } ButtonValues EmulatedController::GetButtonsValues() const { - std::unique_lock lock{mutex}; return controller.button_values; } SticksValues EmulatedController::GetSticksValues() const { - std::unique_lock lock{mutex}; return controller.stick_values; } TriggerValues EmulatedController::GetTriggersValues() const { - std::unique_lock lock{mutex}; return controller.trigger_values; } ControllerMotionValues EmulatedController::GetMotionValues() const { - std::unique_lock lock{mutex}; return controller.motion_values; } ColorValues EmulatedController::GetColorsValues() const { - std::unique_lock lock{mutex}; return controller.color_values; } BatteryValues EmulatedController::GetBatteryValues() const { - std::unique_lock lock{mutex}; return controller.battery_values; } CameraValues EmulatedController::GetCameraValues() const { - std::unique_lock lock{mutex}; return controller.camera_values; } @@ -1871,72 +1806,54 @@ RingAnalogValue EmulatedController::GetRingSensorValues() const { } HomeButtonState EmulatedController::GetHomeButtons() const { - std::unique_lock lock{mutex}; - if (is_configuring) { + if (is_configuring) return {}; - } return controller.home_button_state; } CaptureButtonState EmulatedController::GetCaptureButtons() const { - std::unique_lock lock{mutex}; - if (is_configuring) { + if (is_configuring) return {}; - } return controller.capture_button_state; } NpadButtonState EmulatedController::GetNpadButtons() const { - std::unique_lock lock{mutex}; - if (is_configuring) { + if (is_configuring) return {}; - } return {controller.npad_button_state.raw & GetTurboButtonMask()}; } DebugPadButton EmulatedController::GetDebugPadButtons() const { - std::unique_lock lock{mutex}; - if (is_configuring) { + if (is_configuring) return {}; - } return controller.debug_pad_button_state; } AnalogSticks EmulatedController::GetSticks() const { - std::unique_lock lock{mutex}; - - if (is_configuring) { + if (is_configuring) return {}; - } - return controller.analog_stick_state; } NpadGcTriggerState EmulatedController::GetTriggers() const { - std::unique_lock lock{mutex}; - if (is_configuring) { + if (is_configuring) return {}; - } return controller.gc_trigger_state; } MotionState EmulatedController::GetMotions() const { - std::unique_lock lock{mutex}; return controller.motion_state; } ControllerColors EmulatedController::GetColors() const { - std::unique_lock lock{mutex}; return controller.colors_state; } BatteryLevelState EmulatedController::GetBattery() const { - std::unique_lock lock{mutex}; return controller.battery_state; } const CameraState& EmulatedController::GetCamera() const { - std::unique_lock lock{mutex}; return controller.camera_state; } @@ -1945,15 +1862,14 @@ RingSensorForce EmulatedController::GetRingSensorForce() const { } const NfcState& EmulatedController::GetNfc() const { - std::unique_lock lock{mutex}; return controller.nfc_state; } NpadColor EmulatedController::GetNpadColor(u32 color) { return { - .r = static_cast((color >> 16) & 0xFF), - .g = static_cast((color >> 8) & 0xFF), - .b = static_cast(color & 0xFF), + .r = u8((color >> 16) & 0xFF), + .g = u8((color >> 8) & 0xFF), + .b = u8(color & 0xFF), .a = 0xff, }; } diff --git a/src/hid_core/frontend/emulated_controller.h b/src/hid_core/frontend/emulated_controller.h index e4256faa3e..bee8fb8b3e 100644 --- a/src/hid_core/frontend/emulated_controller.h +++ b/src/hid_core/frontend/emulated_controller.h @@ -11,8 +11,10 @@ #include #include #include -#include #include +#include + +#include #include "common/common_types.h" #include "common/input.h" @@ -576,13 +578,7 @@ private: NpadButton GetTurboButtonMask() const; const NpadIdType npad_id_type; - NpadStyleIndex npad_type{NpadStyleIndex::None}; - NpadStyleIndex original_npad_type{NpadStyleIndex::None}; NpadStyleTag supported_style_tag{NpadStyleSet::All}; - bool is_connected{false}; - bool is_configuring{false}; - bool is_initialized{false}; - bool system_buttons_enabled{true}; f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard}; u32 turbo_button_state{0}; std::size_t nfc_handles{0}; @@ -591,9 +587,16 @@ private: std::array last_vibration_timepoint{}; std::array controller_connected{}; + // Atomically synched values + std::atomic npad_type{HID::NpadStyleIndex::None}; + std::atomic original_npad_type{HID::NpadStyleIndex::None}; // Temporary values to avoid doing changes while the controller is in configuring mode - NpadStyleIndex tmp_npad_type{NpadStyleIndex::None}; - bool tmp_is_connected{false}; + std::atomic tmp_npad_type{HID::NpadStyleIndex::None}; + std::atomic tmp_is_connected{false}; + std::atomic is_connected{false}; + std::atomic is_configuring{false}; + std::atomic is_initialized{false}; + std::atomic system_buttons_enabled{true}; ButtonParams button_params; StickParams stick_params; @@ -632,10 +635,7 @@ private: StickDevices virtual_stick_devices; ControllerMotionDevices virtual_motion_devices; - mutable std::shared_mutex mutex; - mutable std::shared_mutex callback_mutex; - mutable std::shared_mutex npad_mutex; - mutable std::shared_mutex connect_mutex; + mutable std::mutex callback_mutex; ankerl::unordered_dense::map callback_list; int last_callback_key = 0; diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 406ee967b0..91dd8558ab 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -79,8 +79,8 @@ else() helpers/joycon_protocol/rumble.cpp helpers/joycon_protocol/rumble.h) - target_link_libraries(input_common PRIVATE SDL2::SDL2) - target_compile_definitions(input_common PRIVATE HAVE_SDL2) + target_link_libraries(input_common PRIVATE SDL3::SDL3) + target_compile_definitions(input_common PRIVATE HAVE_SDL3) endif() if (ENABLE_LIBUSB) diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h index 112e970e15..820c4b2eaa 100644 --- a/src/input_common/drivers/joycon.h +++ b/src/input_common/drivers/joycon.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -6,7 +9,7 @@ #include #include #include -#include +#include #include "input_common/input_engine.h" diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 948f650fb1..ad854110ef 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -15,29 +15,79 @@ namespace InputCommon { namespace { Common::UUID GetGUID(SDL_Joystick* joystick) { - const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); + const SDL_GUID guid = SDL_GetJoystickGUID(joystick); std::array data{}; std::memcpy(data.data(), guid.data, sizeof(data)); // Clear controller name crc std::memset(data.data() + 2, 0, sizeof(u16)); return Common::UUID{data}; } + +using GamepadBindings = std::vector; + +SDL_GamepadBinding EmptyBinding() { + SDL_GamepadBinding binding{}; + binding.input_type = SDL_GAMEPAD_BINDTYPE_NONE; + return binding; +} + +GamepadBindings GetBindings(SDL_Gamepad* controller) { + if (controller == nullptr) { + return {}; + } + + int binding_count = 0; + SDL_GamepadBinding** bindings = SDL_GetGamepadBindings(controller, &binding_count); + if (bindings == nullptr) { + return {}; + } + + GamepadBindings cached_bindings{}; + cached_bindings.reserve(static_cast(binding_count)); + for (int i = 0; i < binding_count; ++i) { + if (const auto* current = bindings[i]) { + cached_bindings.emplace_back(*current); + } + } + SDL_free(bindings); + return cached_bindings; +} + +template +SDL_GamepadBinding FindBinding(const GamepadBindings& bindings, Predicate matches) { + const auto it = std::find_if(bindings.begin(), bindings.end(), matches); + return it != bindings.end() ? *it : EmptyBinding(); +} + +SDL_GamepadBinding GetBindingForButton(const GamepadBindings& bindings, SDL_GamepadButton button) { + return FindBinding(bindings, [button](const SDL_GamepadBinding& current) { + return current.output_type == SDL_GAMEPAD_BINDTYPE_BUTTON && + current.output.button == static_cast(button); + }); +} + +SDL_GamepadBinding GetBindingForAxis(const GamepadBindings& bindings, SDL_GamepadAxis axis) { + return FindBinding(bindings, [axis](const SDL_GamepadBinding& current) { + return current.output_type == SDL_GAMEPAD_BINDTYPE_AXIS && + current.output.axis.axis == static_cast(axis); + }); +} } // Anonymous namespace -static int SDLEventWatcher(void* user_data, SDL_Event* event) { +static bool SDLEventWatcher(void* user_data, SDL_Event* event) { auto* const sdl_state = static_cast(user_data); sdl_state->HandleGameControllerEvent(*event); - return 0; + return true; } class SDLJoystick { public: SDLJoystick(Common::UUID guid_, int port_, SDL_Joystick* joystick, - SDL_GameController* game_controller) - : guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, - sdl_controller{game_controller, &SDL_GameControllerClose} { + SDL_Gamepad* game_controller) + : guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_CloseJoystick}, + sdl_controller{game_controller, &SDL_CloseGamepad} { EnableMotion(); } @@ -45,30 +95,49 @@ public: if (!sdl_controller) { return; } - SDL_GameController* controller = sdl_controller.get(); + SDL_Gamepad* controller = sdl_controller.get(); if (HasMotion()) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_FALSE); - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_FALSE); + SDL_SetGamepadSensorEnabled(controller, SDL_SENSOR_ACCEL, false); + SDL_SetGamepadSensorEnabled(controller, SDL_SENSOR_GYRO, false); } - has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE; - has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE; + has_accel = SDL_GamepadHasSensor(controller, SDL_SENSOR_ACCEL); + has_gyro = SDL_GamepadHasSensor(controller, SDL_SENSOR_GYRO); if (has_accel) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); + if (!SDL_SetGamepadSensorEnabled(controller, SDL_SENSOR_ACCEL, true)) { + LOG_WARNING(Input, "Failed to enable accelerometer sensor: {}", SDL_GetError()); + } } if (has_gyro) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); + if (!SDL_SetGamepadSensorEnabled(controller, SDL_SENSOR_GYRO, true)) { + LOG_WARNING(Input, "Failed to enable gyroscope sensor: {}", SDL_GetError()); + } } + + LOG_INFO(Input, "Controller motion capabilities: accel={} gyro={}", has_accel, has_gyro); } bool HasMotion() const { return has_gyro || has_accel; } - bool UpdateMotion(SDL_ControllerSensorEvent event) { + bool UpdateMotion(SDL_GamepadSensorEvent event) { constexpr float gravity_constant = 9.80665f; std::scoped_lock lock{mutex}; - const u64 time_difference = event.timestamp - last_motion_update; - last_motion_update = event.timestamp; + const u64 sensor_timestamp = event.sensor_timestamp != 0 ? event.sensor_timestamp + : event.timestamp; + + if (last_motion_update == 0) { + last_motion_update = sensor_timestamp; + return false; + } + if (sensor_timestamp < last_motion_update) { + return false; + } + + // SDL3 reports sensor timestamps in nanoseconds, while the input stack expects + // delta timestamps in microseconds. + const u64 time_difference = (sensor_timestamp - last_motion_update) / 1000; + last_motion_update = sensor_timestamp; switch (event.sensor) { case SDL_SENSOR_ACCEL: { motion.accel_x = -event.data[0] / gravity_constant; @@ -102,7 +171,7 @@ public: } motion_error_count = 0; - motion.delta_timestamp = time_difference * 1000; + motion.delta_timestamp = time_difference; return true; } @@ -136,26 +205,41 @@ public: f32 high_amplitude = vibration.high_amplitude * high_frequency_scale; if (sdl_controller) { - return SDL_GameControllerRumble(sdl_controller.get(), static_cast(low_amplitude), - static_cast(high_amplitude), - rumble_max_duration_ms) != -1; + return SDL_RumbleGamepad(sdl_controller.get(), static_cast(low_amplitude), + static_cast(high_amplitude), rumble_max_duration_ms); } else if (sdl_joystick) { - return SDL_JoystickRumble(sdl_joystick.get(), static_cast(low_amplitude), + return SDL_RumbleJoystick(sdl_joystick.get(), static_cast(low_amplitude), static_cast(high_amplitude), - rumble_max_duration_ms) != -1; + rumble_max_duration_ms); } return false; } bool HasHDRumble() const { + constexpr Uint16 valve_vendor_id = 0x28DE; + const auto is_known_hd_type = [](SDL_GamepadType type) { + return type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO || + type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT || + type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT || + type == SDL_GAMEPAD_TYPE_PS5; + }; + + // Valve hardware doesn't have any enums in SDL, so we have to support it manually. + // Since they have HD rumble, we can assume that all their hardware supports it, even if we can't detect the exact type. if (sdl_controller) { - const auto type = SDL_GameControllerGetType(sdl_controller.get()); - return (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) || - (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT) || - (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) || - (type == SDL_CONTROLLER_TYPE_PS5); + if (is_known_hd_type(SDL_GetGamepadType(sdl_controller.get())) || + SDL_GetGamepadVendor(sdl_controller.get()) == valve_vendor_id) { + return true; + } } + + if (sdl_joystick) { + if (SDL_GetJoystickVendor(sdl_joystick.get()) == valve_vendor_id) { + return true; + } + } + return false; } @@ -201,11 +285,11 @@ public: return sdl_joystick.get(); } - SDL_GameController* GetSDLGameController() const { + SDL_Gamepad* GetSDLGameController() const { return sdl_controller.get(); } - void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { + void SetSDLJoystick(SDL_Joystick* joystick, SDL_Gamepad* controller) { sdl_joystick.reset(joystick); sdl_controller.reset(controller); } @@ -232,20 +316,36 @@ public: return false; } - Common::Input::BatteryLevel GetBatteryLevel(SDL_JoystickPowerLevel battery_level) { - switch (battery_level) { - case SDL_JOYSTICK_POWER_EMPTY: - return Common::Input::BatteryLevel::Empty; - case SDL_JOYSTICK_POWER_LOW: - return Common::Input::BatteryLevel::Low; - case SDL_JOYSTICK_POWER_MEDIUM: - return Common::Input::BatteryLevel::Medium; - case SDL_JOYSTICK_POWER_FULL: - case SDL_JOYSTICK_POWER_MAX: - return Common::Input::BatteryLevel::Full; - case SDL_JOYSTICK_POWER_WIRED: + Common::Input::BatteryLevel GetBatteryLevel(SDL_PowerState battery_level, int percent) { + if (battery_level == SDL_POWERSTATE_CHARGING) { return Common::Input::BatteryLevel::Charging; - case SDL_JOYSTICK_POWER_UNKNOWN: + } + + if (percent >= 0 && percent <= 100) { + if (percent <= 5) { + return Common::Input::BatteryLevel::Empty; + } + if (percent <= 20) { + return Common::Input::BatteryLevel::Critical; + } + if (percent <= 40) { + return Common::Input::BatteryLevel::Low; + } + if (percent <= 70) { + return Common::Input::BatteryLevel::Medium; + } + return Common::Input::BatteryLevel::Full; + } + + switch (battery_level) { + case SDL_POWERSTATE_ON_BATTERY: + return Common::Input::BatteryLevel::Medium; + case SDL_POWERSTATE_NO_BATTERY: + return Common::Input::BatteryLevel::None; + case SDL_POWERSTATE_CHARGED: + return Common::Input::BatteryLevel::Full; + case SDL_POWERSTATE_ERROR: + case SDL_POWERSTATE_UNKNOWN: default: return Common::Input::BatteryLevel::None; } @@ -253,28 +353,28 @@ public: std::string GetControllerName() const { if (sdl_controller) { - switch (SDL_GameControllerGetType(sdl_controller.get())) { - case SDL_CONTROLLER_TYPE_XBOX360: + switch (SDL_GetGamepadType(sdl_controller.get())) { + case SDL_GAMEPAD_TYPE_XBOX360: return "Xbox 360 Controller"; - case SDL_CONTROLLER_TYPE_XBOXONE: + case SDL_GAMEPAD_TYPE_XBOXONE: return "Xbox One Controller"; - case SDL_CONTROLLER_TYPE_PS3: + case SDL_GAMEPAD_TYPE_PS3: return "DualShock 3 Controller"; - case SDL_CONTROLLER_TYPE_PS4: + case SDL_GAMEPAD_TYPE_PS4: return "DualShock 4 Controller"; - case SDL_CONTROLLER_TYPE_PS5: + case SDL_GAMEPAD_TYPE_PS5: return "DualSense Controller"; default: break; } - const auto name = SDL_GameControllerName(sdl_controller.get()); + const auto name = SDL_GetGamepadName(sdl_controller.get()); if (name) { return name; } } if (sdl_joystick) { - const auto name = SDL_JoystickName(sdl_joystick.get()); + const auto name = SDL_GetJoystickName(sdl_joystick.get()); if (name) { return name; } @@ -286,8 +386,8 @@ public: private: Common::UUID guid; int port; - std::unique_ptr sdl_joystick; - std::unique_ptr sdl_controller; + std::unique_ptr sdl_joystick; + std::unique_ptr sdl_controller; mutable std::mutex mutex; u64 last_motion_update{}; @@ -323,7 +423,10 @@ std::shared_ptr SDLDriver::GetSDLJoystickByGUID(const std::string& } std::shared_ptr SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { - auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); + auto sdl_joystick = SDL_GetJoystickFromID(sdl_id); + if (sdl_joystick == nullptr) { + return nullptr; + } const auto guid = GetGUID(sdl_joystick); std::scoped_lock lock{joystick_map_mutex}; @@ -345,34 +448,70 @@ std::shared_ptr SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl return *vec_it; } -void SDLDriver::InitJoystick(int joystick_index) { - SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); - SDL_GameController* sdl_gamecontroller = nullptr; +std::shared_ptr SDLDriver::GetSDLJoystickByGamepadID(SDL_JoystickID sdl_id) { + auto* const sdl_gamepad = SDL_GetGamepadFromID(sdl_id); + if (sdl_gamepad == nullptr) { + return nullptr; + } - if (SDL_IsGameController(joystick_index)) { - sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); + auto* const sdl_joystick = SDL_GetGamepadJoystick(sdl_gamepad); + if (sdl_joystick == nullptr) { + return nullptr; + } + + const auto guid = GetGUID(sdl_joystick); + + std::scoped_lock lock{joystick_map_mutex}; + const auto map_it = joystick_map.find(guid); + if (map_it == joystick_map.end()) { + return nullptr; + } + + const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), + [sdl_joystick, sdl_gamepad](const auto& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick || + joystick->GetSDLGameController() == sdl_gamepad; + }); + + if (vec_it == map_it->second.end()) { + return nullptr; + } + + return *vec_it; +} + +void SDLDriver::InitJoystick(SDL_JoystickID joystick_id) { + SDL_Joystick* sdl_joystick = SDL_OpenJoystick(joystick_id); + SDL_Gamepad* sdl_gamecontroller = nullptr; + int battery_percent = -1; + SDL_PowerState battery_state = SDL_POWERSTATE_UNKNOWN; + + if (SDL_IsGamepad(joystick_id)) { + sdl_gamecontroller = SDL_OpenGamepad(joystick_id); } if (!sdl_joystick) { - LOG_ERROR(Input, "Failed to open joystick {}", joystick_index); + LOG_ERROR(Input, "Failed to open joystick {}", joystick_id); return; } + battery_state = SDL_GetJoystickPowerInfo(sdl_joystick, &battery_percent); + const auto guid = GetGUID(sdl_joystick); if (Settings::values.enable_joycon_driver) { if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && (guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) { - LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index); - SDL_JoystickClose(sdl_joystick); + LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_id); + SDL_CloseJoystick(sdl_joystick); return; } } if (Settings::values.enable_procon_driver) { if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && guid.uuid[8] == 0x09) { - LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index); - SDL_JoystickClose(sdl_joystick); + LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_id); + SDL_CloseJoystick(sdl_joystick); return; } } @@ -382,6 +521,8 @@ void SDLDriver::InitJoystick(int joystick_index) { auto joystick = std::make_shared(guid, 0, sdl_joystick, sdl_gamecontroller); PreSetController(joystick->GetPadIdentifier()); joystick->EnableMotion(); + SetBattery(joystick->GetPadIdentifier(), + joystick->GetBatteryLevel(battery_state, battery_percent)); joystick_map[guid].emplace_back(std::move(joystick)); return; } @@ -394,6 +535,8 @@ void SDLDriver::InitJoystick(int joystick_index) { if (joystick_it != joystick_guid_list.end()) { (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); (*joystick_it)->EnableMotion(); + SetBattery((*joystick_it)->GetPadIdentifier(), + (*joystick_it)->GetBatteryLevel(battery_state, battery_percent)); return; } @@ -401,6 +544,8 @@ void SDLDriver::InitJoystick(int joystick_index) { auto joystick = std::make_shared(guid, port, sdl_joystick, sdl_gamecontroller); PreSetController(joystick->GetPadIdentifier()); joystick->EnableMotion(); + SetBattery(joystick->GetPadIdentifier(), + joystick->GetBatteryLevel(battery_state, battery_percent)); joystick_guid_list.emplace_back(std::move(joystick)); } @@ -428,55 +573,60 @@ void SDLDriver::PumpEvents() const { void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) { switch (event.type) { - case SDL_JOYBUTTONUP: { + case SDL_EVENT_JOYSTICK_BUTTON_UP: { if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); SetButton(identifier, event.jbutton.button, false); } break; } - case SDL_JOYBUTTONDOWN: { + case SDL_EVENT_JOYSTICK_BUTTON_DOWN: { if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); SetButton(identifier, event.jbutton.button, true); } break; } - case SDL_JOYHATMOTION: { + case SDL_EVENT_JOYSTICK_HAT_MOTION: { if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); SetHatButton(identifier, event.jhat.hat, event.jhat.value); } break; } - case SDL_JOYAXISMOTION: { + case SDL_EVENT_JOYSTICK_AXIS_MOTION: { if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f); } break; } - case SDL_CONTROLLERSENSORUPDATE: { - if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) { - if (joystick->UpdateMotion(event.csensor)) { + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: { + auto joystick = GetSDLJoystickByGamepadID(event.gsensor.which); + if (!joystick) { + joystick = GetSDLJoystickBySDLID(event.gsensor.which); + } + if (joystick) { + if (joystick->UpdateMotion(event.gsensor)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); SetMotion(identifier, 0, joystick->GetMotion()); } } break; } - case SDL_JOYBATTERYUPDATED: { + case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: { if (auto joystick = GetSDLJoystickBySDLID(event.jbattery.which)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); - SetBattery(identifier, joystick->GetBatteryLevel(event.jbattery.level)); + SetBattery(identifier, + joystick->GetBatteryLevel(event.jbattery.state, event.jbattery.percent)); } break; } - case SDL_JOYDEVICEREMOVED: + case SDL_EVENT_JOYSTICK_REMOVED: LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); - CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); + CloseJoystick(SDL_GetJoystickFromID(event.jdevice.which)); break; - case SDL_JOYDEVICEADDED: + case SDL_EVENT_JOYSTICK_ADDED: LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); InitJoystick(event.jdevice.which); break; @@ -496,12 +646,13 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en // Disable raw input. When enabled this setting causes SDL to die when a web applet opens SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, Settings::values.enable_raw_input ? "1" : "0"); - // Prevent SDL from adding undesired axis - SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); + // SDL3 defaults Steam Controller Bluetooth HIDAPI support to off, which can disable gyro. + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); + SDL_SetHint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION, "1"); + SDL_SetHint(SDL_HINT_AUTO_UPDATE_SENSORS, "1"); // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); // Disable hidapi drivers for joycon controllers when the custom joycon driver is enabled @@ -523,16 +674,13 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en } SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, "1"); - // Share the same button mapping with non-Nintendo controllers - SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); - // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native // driver on Linux. SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0"); // If the frontend is going to manage the event loop, then we don't start one here - start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0; - if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { + start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD) == 0; + if (start_thread && !SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) { LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError()); return; } @@ -552,19 +700,24 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en } // Because the events for joystick connection happens before we have our event watcher added, we // can just open all the joysticks right here - for (int i = 0; i < SDL_NumJoysticks(); ++i) { - InitJoystick(i); + int joystick_count = 0; + SDL_JoystickID* joysticks = SDL_GetJoysticks(&joystick_count); + if (joysticks != nullptr) { + for (int i = 0; i < joystick_count; ++i) { + InitJoystick(joysticks[i]); + } + SDL_free(joysticks); } } SDLDriver::~SDLDriver() { CloseJoysticks(); - SDL_DelEventWatch(&SDLEventWatcher, this); + SDL_RemoveEventWatch(&SDLEventWatcher, this); initialized = false; if (start_thread) { vibration_thread.join(); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD); } } @@ -758,17 +911,17 @@ Common::ParamPackage SDLDriver::BuildMotionParam(int port, const Common::UUID& g } Common::ParamPackage SDLDriver::BuildParamPackageForBinding( - int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const { - switch (binding.bindType) { - case SDL_CONTROLLER_BINDTYPE_NONE: + int port, const Common::UUID& guid, const SDL_GamepadBinding& binding) const { + switch (binding.input_type) { + case SDL_GAMEPAD_BINDTYPE_NONE: break; - case SDL_CONTROLLER_BINDTYPE_AXIS: - return BuildAnalogParamPackageForButton(port, guid, binding.value.axis); - case SDL_CONTROLLER_BINDTYPE_BUTTON: - return BuildButtonParamPackageForButton(port, guid, binding.value.button); - case SDL_CONTROLLER_BINDTYPE_HAT: - return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat, - static_cast(binding.value.hat.hat_mask)); + case SDL_GAMEPAD_BINDTYPE_AXIS: + return BuildAnalogParamPackageForButton(port, guid, binding.input.axis.axis); + case SDL_GAMEPAD_BINDTYPE_BUTTON: + return BuildButtonParamPackageForButton(port, guid, binding.input.button); + case SDL_GAMEPAD_BINDTYPE_HAT: + return BuildHatParamPackageForButton(port, guid, binding.input.hat.hat, + static_cast(binding.input.hat.hat_mask)); } return {}; } @@ -808,8 +961,8 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p // Add the missing bindings for ZL/ZR static constexpr ZButtonBindings switch_to_sdl_axis{{ - {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT}, - {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, + {Settings::NativeButton::ZL, SDL_GAMEPAD_AXIS_LEFT_TRIGGER}, + {Settings::NativeButton::ZR, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER}, }}; // Parameters contain two joysticks return dual @@ -828,41 +981,41 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p ButtonBindings SDLDriver::GetDefaultButtonBinding( const std::shared_ptr& joystick) const { // Default SL/SR mapping for other controllers - auto sll_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; - auto srl_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; - auto slr_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; - auto srr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; + auto sll_button = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER; + auto srl_button = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER; + auto slr_button = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER; + auto srr_button = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER; if (joystick->IsJoyconLeft()) { - sll_button = SDL_CONTROLLER_BUTTON_PADDLE2; - srl_button = SDL_CONTROLLER_BUTTON_PADDLE4; + sll_button = SDL_GAMEPAD_BUTTON_LEFT_PADDLE1; + srl_button = SDL_GAMEPAD_BUTTON_LEFT_PADDLE2; } if (joystick->IsJoyconRight()) { - slr_button = SDL_CONTROLLER_BUTTON_PADDLE3; - srr_button = SDL_CONTROLLER_BUTTON_PADDLE1; + slr_button = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2; + srr_button = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1; } return { - std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, - {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, - {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, - {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, - {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, - {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, - {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, - {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, - {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, - {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, - {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, - {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, + std::pair{Settings::NativeButton::A, SDL_GAMEPAD_BUTTON_EAST}, + {Settings::NativeButton::B, SDL_GAMEPAD_BUTTON_SOUTH}, + {Settings::NativeButton::X, SDL_GAMEPAD_BUTTON_NORTH}, + {Settings::NativeButton::Y, SDL_GAMEPAD_BUTTON_WEST}, + {Settings::NativeButton::LStick, SDL_GAMEPAD_BUTTON_LEFT_STICK}, + {Settings::NativeButton::RStick, SDL_GAMEPAD_BUTTON_RIGHT_STICK}, + {Settings::NativeButton::L, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER}, + {Settings::NativeButton::R, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER}, + {Settings::NativeButton::Plus, SDL_GAMEPAD_BUTTON_START}, + {Settings::NativeButton::Minus, SDL_GAMEPAD_BUTTON_BACK}, + {Settings::NativeButton::DLeft, SDL_GAMEPAD_BUTTON_DPAD_LEFT}, + {Settings::NativeButton::DUp, SDL_GAMEPAD_BUTTON_DPAD_UP}, + {Settings::NativeButton::DRight, SDL_GAMEPAD_BUTTON_DPAD_RIGHT}, + {Settings::NativeButton::DDown, SDL_GAMEPAD_BUTTON_DPAD_DOWN}, {Settings::NativeButton::SLLeft, sll_button}, {Settings::NativeButton::SRLeft, srl_button}, {Settings::NativeButton::SLRight, slr_button}, {Settings::NativeButton::SRRight, srr_button}, - {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, - {Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1}, + {Settings::NativeButton::Home, SDL_GAMEPAD_BUTTON_GUIDE}, + {Settings::NativeButton::Screenshot, SDL_GAMEPAD_BUTTON_MISC1}, }; } @@ -872,15 +1025,16 @@ ButtonMapping SDLDriver::GetSingleControllerMapping( ButtonMapping mapping; mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); auto* controller = joystick->GetSDLGameController(); + const auto bindings = GetBindings(controller); for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { - const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + const auto binding = GetBindingForButton(bindings, sdl_button); mapping.insert_or_assign( switch_button, BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); } for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { - const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + const auto binding = GetBindingForAxis(bindings, sdl_axis); mapping.insert_or_assign( switch_button, BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); @@ -897,29 +1051,31 @@ ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptrGetSDLGameController(); auto* controller2 = joystick2->GetSDLGameController(); + const auto bindings = GetBindings(controller); + const auto bindings2 = GetBindings(controller2); for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { if (IsButtonOnLeftSide(switch_button)) { - const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button); + const auto binding = GetBindingForButton(bindings2, sdl_button); mapping.insert_or_assign( switch_button, BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); continue; } - const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + const auto binding = GetBindingForButton(bindings, sdl_button); mapping.insert_or_assign( switch_button, BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); } for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { if (IsButtonOnLeftSide(switch_button)) { - const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis); + const auto binding = GetBindingForAxis(bindings2, sdl_axis); mapping.insert_or_assign( switch_button, BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); continue; } - const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + const auto binding = GetBindingForAxis(bindings, sdl_axis); mapping.insert_or_assign( switch_button, BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); @@ -957,46 +1113,43 @@ AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& p } AnalogMapping mapping = {}; - const auto& binding_left_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); - const auto& binding_left_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); + const auto bindings = GetBindings(controller); + const auto binding_left_x = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_LEFTX); + const auto binding_left_y = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_LEFTY); if (params.Has("guid2")) { const auto identifier = joystick2->GetPadIdentifier(); PreSetController(identifier); - PreSetAxis(identifier, binding_left_x.value.axis); - PreSetAxis(identifier, binding_left_y.value.axis); - const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); - const auto left_offset_y = GetAxis(identifier, binding_left_y.value.axis); + PreSetAxis(identifier, binding_left_x.input.axis.axis); + PreSetAxis(identifier, binding_left_y.input.axis.axis); + const auto left_offset_x = -GetAxis(identifier, binding_left_x.input.axis.axis); + const auto left_offset_y = GetAxis(identifier, binding_left_y.input.axis.axis); mapping.insert_or_assign(Settings::NativeAnalog::LStick, - BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, - binding_left_y.value.axis, + BuildParamPackageForAnalog(identifier, binding_left_x.input.axis.axis, + binding_left_y.input.axis.axis, left_offset_x, left_offset_y)); } else { const auto identifier = joystick->GetPadIdentifier(); PreSetController(identifier); - PreSetAxis(identifier, binding_left_x.value.axis); - PreSetAxis(identifier, binding_left_y.value.axis); - const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); - const auto left_offset_y = GetAxis(identifier, binding_left_y.value.axis); + PreSetAxis(identifier, binding_left_x.input.axis.axis); + PreSetAxis(identifier, binding_left_y.input.axis.axis); + const auto left_offset_x = -GetAxis(identifier, binding_left_x.input.axis.axis); + const auto left_offset_y = GetAxis(identifier, binding_left_y.input.axis.axis); mapping.insert_or_assign(Settings::NativeAnalog::LStick, - BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, - binding_left_y.value.axis, + BuildParamPackageForAnalog(identifier, binding_left_x.input.axis.axis, + binding_left_y.input.axis.axis, left_offset_x, left_offset_y)); } - const auto& binding_right_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); - const auto& binding_right_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); + const auto binding_right_x = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_RIGHTX); + const auto binding_right_y = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_RIGHTY); const auto identifier = joystick->GetPadIdentifier(); PreSetController(identifier); - PreSetAxis(identifier, binding_right_x.value.axis); - PreSetAxis(identifier, binding_right_y.value.axis); - const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis); - const auto right_offset_y = GetAxis(identifier, binding_right_y.value.axis); + PreSetAxis(identifier, binding_right_x.input.axis.axis); + PreSetAxis(identifier, binding_right_y.input.axis.axis); + const auto right_offset_x = -GetAxis(identifier, binding_right_x.input.axis.axis); + const auto right_offset_y = GetAxis(identifier, binding_right_y.input.axis.axis); mapping.insert_or_assign(Settings::NativeAnalog::RStick, - BuildParamPackageForAnalog(identifier, binding_right_x.value.axis, - binding_right_y.value.axis, right_offset_x, + BuildParamPackageForAnalog(identifier, binding_right_x.input.axis.axis, + binding_right_y.input.axis.axis, right_offset_x, right_offset_y)); return mapping; } @@ -1102,19 +1255,16 @@ bool SDLDriver::IsStickInverted(const Common::ParamPackage& params) { const auto& axis_x = params.Get("axis_x", 0); const auto& axis_y = params.Get("axis_y", 0); - const auto& binding_left_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); - const auto& binding_right_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); - const auto& binding_left_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); - const auto& binding_right_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); + const auto bindings = GetBindings(controller); + const auto binding_left_x = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_LEFTX); + const auto binding_right_x = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_RIGHTX); + const auto binding_left_y = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_LEFTY); + const auto binding_right_y = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_RIGHTY); - if (axis_x != binding_left_y.value.axis && axis_x != binding_right_y.value.axis) { + if (axis_x != binding_left_y.input.axis.axis && axis_x != binding_right_y.input.axis.axis) { return false; } - if (axis_y != binding_left_x.value.axis && axis_y != binding_right_x.value.axis) { + if (axis_y != binding_left_x.input.axis.axis && axis_y != binding_right_x.input.axis.axis) { return false; } return true; diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index 1677ab8289..bf4a45e83d 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -11,25 +11,22 @@ #include #include -#include +#include #include "common/common_types.h" #include "common/threadsafe_queue.h" #include "input_common/input_engine.h" union SDL_Event; -using SDL_GameController = struct _SDL_GameController; -using SDL_Joystick = struct _SDL_Joystick; -using SDL_JoystickID = s32; namespace InputCommon { class SDLJoystick; using ButtonBindings = - std::array, 20>; + std::array, 20>; using ZButtonBindings = - std::array, 2>; + std::array, 2>; class SDLDriver : public InputEngine { public: @@ -46,6 +43,7 @@ public: /// Get the nth joystick with the corresponding GUID std::shared_ptr GetSDLJoystickBySDLID(SDL_JoystickID sdl_id); + std::shared_ptr GetSDLJoystickByGamepadID(SDL_JoystickID sdl_id); /** * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so @@ -72,7 +70,7 @@ public: bool IsVibrationEnabled(const PadIdentifier& identifier) override; private: - void InitJoystick(int joystick_index); + void InitJoystick(SDL_JoystickID joystick_id); void CloseJoystick(SDL_Joystick* sdl_joystick); /// Needs to be called before SDL_QuitSubSystem. @@ -92,7 +90,7 @@ private: Common::ParamPackage BuildMotionParam(int port, const Common::UUID& guid) const; Common::ParamPackage BuildParamPackageForBinding( - int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const; + int port, const Common::UUID& guid, const SDL_GamepadBinding& binding) const; Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, int axis_y, float offset_x, diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h index 792f124e14..dd3bd4398c 100644 --- a/src/input_common/helpers/joycon_protocol/joycon_types.h +++ b/src/input_common/helpers/joycon_protocol/joycon_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -10,7 +13,7 @@ #include #include -#include +#include #include "common/bit_field.h" #include "common/common_funcs.h" diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index b04a70590a..5f0a8af3aa 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -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: 2017 Citra Emulator Project @@ -25,7 +25,7 @@ #ifdef ENABLE_LIBUSB #include "input_common/drivers/gc_adapter.h" #endif -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 #include "input_common/drivers/joycon.h" #include "input_common/drivers/sdl_driver.h" #endif @@ -90,7 +90,7 @@ struct InputSubsystem::Impl { #endif RegisterEngine("virtual_amiibo", virtual_amiibo); RegisterEngine("virtual_gamepad", virtual_gamepad); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 RegisterEngine("sdl", sdl); RegisterEngine("joycon", joycon); #endif @@ -124,7 +124,7 @@ struct InputSubsystem::Impl { #endif UnregisterEngine(virtual_amiibo); UnregisterEngine(virtual_gamepad); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 UnregisterEngine(sdl); UnregisterEngine(joycon); #endif @@ -154,7 +154,7 @@ struct InputSubsystem::Impl { #endif auto udp_devices = udp_client->GetInputDevices(); devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 auto joycon_devices = joycon->GetInputDevices(); devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end()); auto sdl_devices = sdl->GetInputDevices(); @@ -189,7 +189,7 @@ struct InputSubsystem::Impl { if (engine == udp_client->GetEngineName()) { return udp_client; } -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 if (engine == sdl->GetEngineName()) { return sdl; } @@ -280,7 +280,7 @@ struct InputSubsystem::Impl { if (engine == virtual_gamepad->GetEngineName()) { return true; } -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 if (engine == sdl->GetEngineName()) { return true; } @@ -301,7 +301,7 @@ struct InputSubsystem::Impl { gcadapter->BeginConfiguration(); #endif udp_client->BeginConfiguration(); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 sdl->BeginConfiguration(); joycon->BeginConfiguration(); #endif @@ -317,7 +317,7 @@ struct InputSubsystem::Impl { gcadapter->EndConfiguration(); #endif udp_client->EndConfiguration(); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 sdl->EndConfiguration(); joycon->EndConfiguration(); #endif @@ -325,7 +325,7 @@ struct InputSubsystem::Impl { void PumpEvents() const { update_engine->PumpEvents(); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 sdl->PumpEvents(); #endif } @@ -350,7 +350,7 @@ struct InputSubsystem::Impl { std::shared_ptr gcadapter; #endif -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 std::shared_ptr sdl; std::shared_ptr joycon; #endif diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt index 399fbe67a0..a8c4e51240 100644 --- a/src/qt_common/CMakeLists.txt +++ b/src/qt_common/CMakeLists.txt @@ -26,6 +26,7 @@ add_library(qt_common STATIC util/compress.h util/compress.cpp util/fs.h util/fs.cpp util/mod.h util/mod.cpp + util/vk.h util/vk.cpp abstract/frontend.h abstract/frontend.cpp abstract/progress.h abstract/progress.cpp @@ -33,7 +34,9 @@ add_library(qt_common STATIC qt_string_lookup.h qt_compat.h - discord/discord.h) + discord/discord.h + render/context.h + render/emu_thread.h render/emu_thread.cpp) if (UNIX) target_sources(qt_common PRIVATE gui_settings.cpp gui_settings.h) @@ -56,9 +59,7 @@ if (USE_DISCORD_PRESENCE) endif() # TODO(crueter) -if (ENABLE_QT) - target_link_libraries(qt_common PRIVATE Qt6::Widgets) -endif() +target_link_libraries(qt_common PRIVATE Qt6::Widgets) target_compile_definitions(qt_common PUBLIC # Use QStringBuilder for string concatenation to reduce @@ -80,7 +81,7 @@ target_compile_definitions(qt_common PUBLIC ) target_link_libraries(qt_common PRIVATE core Qt6::Core Qt6::Concurrent SimpleIni::SimpleIni QuaZip::QuaZip) -target_link_libraries(qt_common PUBLIC frozen::frozen-headers) +target_link_libraries(qt_common PUBLIC frozen::frozen-headers Vulkan::Headers) target_link_libraries(qt_common PRIVATE gamemode::headers frontend_common) if (ENABLE_OPENGL) diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index 506d74084f..6b8f8e73b6 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -165,8 +165,9 @@ std::unique_ptr InitializeTranslations(QObject* parent) { INSERT(Settings, use_disk_shader_cache, tr("Use persistent pipeline cache"), tr("Allows saving shaders to storage for faster loading on following game " "boots.\nDisabling it is only intended for debugging.")); - INSERT(Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"), - tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled.")); + INSERT( + Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"), + tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled.")); INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"), tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for " "decoding, or perform no decoding at all (black screen on videos).\n" diff --git a/src/qt_common/qt_common.cpp b/src/qt_common/qt_common.cpp index 3091171df8..0a134d5047 100644 --- a/src/qt_common/qt_common.cpp +++ b/src/qt_common/qt_common.cpp @@ -1,25 +1,52 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "common/fs/fs.h" +#include "common/literals.h" + +#include "common/memory_detect.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "hid_core/hid_core.h" +#include "network/network.h" #include "qt_common.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging.h" +#include "common/scm_rev.h" +#include "core/memory.h" + +#ifdef ARCHITECTURE_x86_64 +#include "common/x64/cpu_detect.h" +#endif + #include #include -#include "common/logging.h" #include "core/frontend/emu_window.h" +#include "qt_common/util/meta.h" #include +#include + +#include #include #if !defined(WIN32) && !defined(__APPLE__) #include #elif defined(__APPLE__) #include -#include #endif +#ifdef _WIN32 +#include +#include +#include +#include "common/windows/timer_resolution.h" +#include "core/core_timing.h" +#endif + +using namespace Common::Literals; + namespace QtCommon { QWidget* rootObject = nullptr; @@ -27,6 +54,11 @@ QWidget* rootObject = nullptr; std::unique_ptr system = nullptr; std::shared_ptr vfs = nullptr; std::unique_ptr provider = nullptr; +std::unique_ptr emu_thread = nullptr; + +const QStringList supported_file_extensions = {QStringLiteral("nro"), QStringLiteral("nso"), + QStringLiteral("nca"), QStringLiteral("xci"), + QStringLiteral("nsp"), QStringLiteral("kip")}; Core::Frontend::WindowSystemType GetWindowSystemType() { // Determine WSI type based on Qt platform. @@ -58,37 +90,8 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) // Our Win32 Qt external doesn't have the private API. wsi.render_surface = reinterpret_cast(window->winId()); #elif defined(__APPLE__) - id layer = reinterpret_cast(objc_msgSend)( + wsi.render_surface = reinterpret_cast(objc_msgSend)( reinterpret_cast(window->winId()), sel_registerName("layer")); - - // In Qt 6, the layer of the NSView might be a QContainerLayer. - // MoltenVK needs a CAMetalLayer. We search for it in sublayers. - Class metal_layer_class = objc_getClass("CAMetalLayer"); - id metal_layer = nullptr; - - if (layer) { - if (reinterpret_cast(objc_msgSend)( - layer, sel_registerName("isKindOfClass:"), metal_layer_class)) { - metal_layer = layer; - } else { - id sublayers = reinterpret_cast(objc_msgSend)( - layer, sel_registerName("sublayers")); - if (sublayers) { - unsigned long count = reinterpret_cast(objc_msgSend)( - sublayers, sel_registerName("count")); - for (unsigned long i = 0; i < count; ++i) { - id sublayer = reinterpret_cast(objc_msgSend)( - sublayers, sel_registerName("objectAtIndex:"), i); - if (reinterpret_cast(objc_msgSend)( - sublayer, sel_registerName("isKindOfClass:"), metal_layer_class)) { - metal_layer = sublayer; - break; - } - } - } - } - } - wsi.render_surface = reinterpret_cast(metal_layer ? metal_layer : layer); #else QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); wsi.display_connection = pni->nativeResourceForWindow("display", window); @@ -103,18 +106,148 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) } const QString tr(const char* str) { - return QGuiApplication::tr(str); + return rootObject->tr(str); } const QString tr(const std::string& str) { - return QGuiApplication::tr(str.c_str()); + return rootObject->tr(str.c_str()); +} + +static void LogRuntimes() { +#ifdef _MSC_VER + // It is possible that the name of the dll will change. + // vcruntime140.dll is for 2015 and onwards + static constexpr char runtime_dll_name[] = "vcruntime140.dll"; + UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr); + bool runtime_version_inspection_worked = false; + if (sz > 0) { + std::vector buf(sz); + if (GetFileVersionInfoA(runtime_dll_name, 0, sz, buf.data())) { + VS_FIXEDFILEINFO* pvi; + sz = sizeof(VS_FIXEDFILEINFO); + if (VerQueryValueA(buf.data(), "\\", reinterpret_cast(&pvi), &sz)) { + if (pvi->dwSignature == VS_FFI_SIGNATURE) { + runtime_version_inspection_worked = true; + LOG_INFO(Frontend, "MSVC Compiler: {} Runtime: {}.{}.{}.{}", _MSC_VER, + pvi->dwProductVersionMS >> 16, pvi->dwProductVersionMS & 0xFFFF, + pvi->dwProductVersionLS >> 16, pvi->dwProductVersionLS & 0xFFFF); + } + } + } + } + if (!runtime_version_inspection_worked) { + LOG_INFO(Frontend, "Unable to inspect {}", runtime_dll_name); + } +#endif + LOG_INFO(Frontend, "Qt Compile: {} Runtime: {}", QT_VERSION_STR, qVersion()); +} + +static QString PrettyProductName() { +#ifdef _WIN32 + // After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2 + // With that notation change they changed the registry key used to denote the current version + QSettings windows_registry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), + QSettings::NativeFormat); + const QString release_id = windows_registry.value(QStringLiteral("ReleaseId")).toString(); + if (release_id == QStringLiteral("2009")) { + const u32 current_build = windows_registry.value(QStringLiteral("CurrentBuild")).toUInt(); + const QString display_version = + windows_registry.value(QStringLiteral("DisplayVersion")).toString(); + const u32 ubr = windows_registry.value(QStringLiteral("UBR")).toUInt(); + u32 version = 10; + if (current_build >= 22000) { + version = 11; + } + return QStringLiteral("Windows %1 Version %2 (Build %3.%4)") + .arg(QString::number(version), display_version, QString::number(current_build), + QString::number(ubr)); + } +#endif + return QSysInfo::prettyProductName(); +} + +static void RemoveCachedContents() { + const auto cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir); + const auto offline_fonts = cache_dir / "fonts"; + const auto offline_manual = cache_dir / "offline_web_applet_manual"; + const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information"; + const auto offline_system_data = cache_dir / "offline_web_applet_system_data"; + + Common::FS::RemoveDirRecursively(offline_fonts); + Common::FS::RemoveDirRecursively(offline_manual); + Common::FS::RemoveDirRecursively(offline_legal_information); + Common::FS::RemoveDirRecursively(offline_system_data); } void Init(QWidget* root) { - system = std::make_unique(); rootObject = root; + + system = std::make_unique(); vfs = std::make_unique(); provider = std::make_unique(); + + // initialization stuff + Common::FS::CreateEdenPaths(); + + system->Initialize(); + + Common::Log::Initialize(); + Common::Log::Start(); + + Network::Init(); + + QtCommon::Meta::RegisterMetaTypes(); + + // build version + const auto branch_name = std::string(Common::g_scm_branch); + const auto description = std::string(Common::g_scm_desc); + const auto build_id = std::string(Common::g_build_id); + + const auto yuzu_build = fmt::format("Eden Development Build | {}-{}", branch_name, description); + const auto override_build = + fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id); + const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build; + const auto processor_count = std::thread::hardware_concurrency(); + + // info logging + LOG_INFO(Frontend, "Eden Version: {}", yuzu_build_version); + LogRuntimes(); +#ifdef ARCHITECTURE_x86_64 + const auto& caps = Common::GetCPUCaps(); + std::string cpu_string = caps.cpu_string; + if (caps.avx || caps.avx2 || caps.avx512f) { + cpu_string += " | AVX"; + if (caps.avx512f) { + cpu_string += "512"; + } else if (caps.avx2) { + cpu_string += '2'; + } + if (caps.fma || caps.fma4) { + cpu_string += " | FMA"; + } + } + LOG_INFO(Frontend, "Host CPU: {}", cpu_string); + if (std::optional processor_core = Common::GetProcessorCount()) { + LOG_INFO(Frontend, "Host CPU Cores: {}", *processor_core); + } +#endif + LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count); + LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString()); + LOG_INFO(Frontend, "Host RAM: {:.2f} GiB", + Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB}); + LOG_INFO(Frontend, "Host Swap: {:.2f} GiB", Common::GetMemInfo().TotalSwapMemory / f64{1_GiB}); +#ifdef _WIN32 + LOG_INFO(Frontend, "Host Timer Resolution: {:.4f} ms", + std::chrono::duration_cast>( + Common::Windows::SetCurrentTimerResolutionToMaximum()) + .count()); + QtCommon::system->CoreTiming().SetTimerResolutionNs( + Common::Windows::GetCurrentTimerResolution()); +#endif + + // Remove cached contents generated during the previous session + RemoveCachedContents(); } std::filesystem::path GetEdenCommand() { @@ -137,4 +270,15 @@ std::filesystem::path GetEdenCommand() { return command; } +void SetupContentProviders() { + system->SetContentProvider(std::make_unique()); + system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, + provider.get()); + system->GetFileSystemController().CreateFactories(*vfs); +} + +void SetupHID() { + system->HIDCore().ReloadInputDevices(); +} + } // namespace QtCommon diff --git a/src/qt_common/qt_common.h b/src/qt_common/qt_common.h index 19c09ef5d0..dd860ef5f7 100644 --- a/src/qt_common/qt_common.h +++ b/src/qt_common/qt_common.h @@ -6,19 +6,31 @@ #include #include -#include + #include "core/core.h" #include "core/file_sys/registered_cache.h" +#include "core/frontend/emu_window.h" -#include +#include "qt_common/render/emu_thread.h" + +#include "core/file_sys/vfs/vfs_real.h" + +enum class StartGameType { + Normal, // Can use custom configuration + Global, // Only uses global configuration +}; namespace QtCommon { +// TODO: Remove QWidget dependency extern QWidget* rootObject; extern std::unique_ptr system; extern std::shared_ptr vfs; extern std::unique_ptr provider; +extern std::unique_ptr emu_thread; + +extern const QStringList supported_file_extensions; typedef std::function QtProgressCallback; @@ -28,6 +40,9 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) void Init(QWidget* root); +void SetupContentProviders(); +void SetupHID(); + const QString tr(const char* str); const QString tr(const std::string& str); diff --git a/src/qt_common/render/context.h b/src/qt_common/render/context.h new file mode 100644 index 0000000000..a0ecade9c8 --- /dev/null +++ b/src/qt_common/render/context.h @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include "common/logging.h" +#include "common/settings.h" +#include "core/frontend/graphics_context.h" + +#ifdef HAS_OPENGL +#include +#include +#endif + +#ifdef HAS_OPENGL +class OpenGLSharedContext : public Core::Frontend::GraphicsContext { +public: + /// Create the original context that should be shared from + explicit OpenGLSharedContext(QSurface* surface_) : surface{surface_} { + QSurfaceFormat format; + format.setVersion(4, 6); + format.setProfile(QSurfaceFormat::CompatibilityProfile); + format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); + if (Settings::values.renderer_debug) { + format.setOption(QSurfaceFormat::FormatOption::DebugContext); + } + // TODO: expose a setting for buffer value (ie default/single/double/triple) + format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); + format.setSwapInterval(0); + + context = std::make_unique(); + context->setFormat(format); + if (!context->create()) { + LOG_ERROR(Frontend, "Unable to create main openGL context"); + } + } + + /// Create the shared contexts for rendering and presentation + explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) { + + // disable vsync for any shared contexts + auto format = share_context->format(); + const int swap_interval = + Settings::values.vsync_mode.GetValue() == Settings::VSyncMode::Immediate ? 0 : 1; + + format.setSwapInterval(main_surface ? swap_interval : 0); + + context = std::make_unique(); + context->setShareContext(share_context); + context->setFormat(format); + if (!context->create()) { + LOG_ERROR(Frontend, "Unable to create shared openGL context"); + } + + if (!main_surface) { + offscreen_surface = std::make_unique(nullptr); + offscreen_surface->setFormat(format); + offscreen_surface->create(); + surface = offscreen_surface.get(); + } else { + surface = main_surface; + } + } + + ~OpenGLSharedContext() { + DoneCurrent(); + } + + void SwapBuffers() override { + context->swapBuffers(surface); + } + + void MakeCurrent() override { + // We can't track the current state of the underlying context in this wrapper class because + // Qt may make the underlying context not current for one reason or another. In particular, + // the WebBrowser uses GL, so it seems to conflict if we aren't careful. + // Instead of always just making the context current (which does not have any caching to + // check if the underlying context is already current) we can check for the current context + // in the thread local data by calling `currentContext()` and checking if its ours. + if (QOpenGLContext::currentContext() != context.get()) { + context->makeCurrent(surface); + } + } + + void DoneCurrent() override { + context->doneCurrent(); + } + + QOpenGLContext* GetShareContext() { + return context.get(); + } + + const QOpenGLContext* GetShareContext() const { + return context.get(); + } + +private: + // Avoid using Qt parent system here since we might move the QObjects to new threads + // As a note, this means we should avoid using slots/signals with the objects too + std::unique_ptr context; + std::unique_ptr offscreen_surface{}; + QSurface* surface; +}; +#endif + +class DummyContext : public Core::Frontend::GraphicsContext {}; diff --git a/src/qt_common/render/emu_thread.cpp b/src/qt_common/render/emu_thread.cpp new file mode 100644 index 0000000000..1b65867509 --- /dev/null +++ b/src/qt_common/render/emu_thread.cpp @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "core/core.h" +#include "core/cpu_manager.h" +#include "emu_thread.h" +#include "qt_common/qt_common.h" +#include "video_core/gpu.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_base.h" + +EmuThread::EmuThread() {} + +EmuThread::~EmuThread() = default; + +void EmuThread::run() { + Common::SetCurrentThreadName("EmuControlThread"); + + auto& gpu = QtCommon::system->GPU(); + auto stop_token = m_stop_source.get_token(); + + QtCommon::system->RegisterHostThread(); + + // Main process has been loaded. Make the context current to this thread and begin GPU and CPU + // execution. + gpu.ObtainContext(); + + emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); + if (Settings::values.use_disk_shader_cache.GetValue()) { + QtCommon::system->Renderer().ReadRasterizer()->LoadDiskResources( + QtCommon::system->GetApplicationProcessProgramID(), stop_token, + [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { + emit LoadProgress(stage, value, total); + }); + } + emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); + + gpu.ReleaseContext(); + gpu.Start(); + + QtCommon::system->GetCpuManager().OnGpuReady(); + + if (QtCommon::system->DebuggerEnabled()) { + QtCommon::system->InitializeDebugger(); + } + + while (!stop_token.stop_requested()) { + std::unique_lock lk{m_should_run_mutex}; + if (m_should_run) { + QtCommon::system->Run(); + m_stopped.Reset(); + + m_should_run_cv.wait(lk, stop_token, [&] { return !m_should_run; }); + } else { + QtCommon::system->Pause(); + m_stopped.Set(); + + EmulationPaused(lk); + m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; }); + EmulationResumed(lk); + } + } + + // Shutdown the main emulated process + QtCommon::system->DetachDebugger(); + QtCommon::system->ShutdownMainProcess(); +} + +// Unlock while emitting signals so that the main thread can +// continue pumping events. + +void EmuThread::EmulationPaused(std::unique_lock& lk) { + lk.unlock(); + emit DebugModeEntered(); + lk.lock(); +} + +void EmuThread::EmulationResumed(std::unique_lock& lk) { + lk.unlock(); + emit DebugModeLeft(); + lk.lock(); +} diff --git a/src/qt_common/render/emu_thread.h b/src/qt_common/render/emu_thread.h new file mode 100644 index 0000000000..92bae50979 --- /dev/null +++ b/src/qt_common/render/emu_thread.h @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include "common/logging.h" +#include "common/thread.h" + +namespace Core { +class System; +} // namespace Core + +namespace VideoCore { +enum class LoadCallbackStage; +} // namespace VideoCore + +class EmuThread final : public QThread { + Q_OBJECT + +public: + explicit EmuThread(); + ~EmuThread() override; + + /** + * Start emulation (on new thread) + * @warning Only call when not running! + */ + void run() override; + + /** + * Sets whether the emulation thread should run or not + * @param should_run Boolean value, set the emulation thread to running if true + */ + void SetRunning(bool should_run) { + // TODO: Prevent other threads from modifying the state until we finish. + { + // Notify the running thread to change state. + std::unique_lock run_lk{m_should_run_mutex}; + m_should_run = should_run; + m_should_run_cv.notify_one(); + } + + // Wait until paused, if pausing. + if (!should_run) { + m_stopped.Wait(); + } + } + + /** + * Check if the emulation thread is running or not + * @return True if the emulation thread is running, otherwise false + */ + bool IsRunning() const { + return m_should_run; + } + + /** + * Requests for the emulation thread to immediately stop running + */ + void ForceStop() { + LOG_WARNING(Frontend, "Force stopping EmuThread"); + m_stop_source.request_stop(); + } + +private: + void EmulationPaused(std::unique_lock& lk); + void EmulationResumed(std::unique_lock& lk); + +private: + std::stop_source m_stop_source; + std::mutex m_should_run_mutex; + std::condition_variable_any m_should_run_cv; + Common::Event m_stopped; + bool m_should_run{true}; + +signals: + /** + * Emitted when the CPU has halted execution + * + * @warning When connecting to this signal from other threads, make sure to specify either + * Qt::QueuedConnection (invoke slot within the destination object's message thread) or even + * Qt::BlockingQueuedConnection (additionally block source thread until slot returns) + */ + void DebugModeEntered(); + + /** + * Emitted right before the CPU continues execution + * + * @warning When connecting to this signal from other threads, make sure to specify either + * Qt::QueuedConnection (invoke slot within the destination object's message thread) or even + * Qt::BlockingQueuedConnection (additionally block source thread until slot returns) + */ + void DebugModeLeft(); + + void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); +}; diff --git a/src/qt_common/util/content.cpp b/src/qt_common/util/content.cpp index 4d9b324608..f22242e7f7 100644 --- a/src/qt_common/util/content.cpp +++ b/src/qt_common/util/content.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#include "core/file_sys/card_image.h" #include "qt_common/util/content.h" #include "qt_common/util/game.h" @@ -181,15 +182,13 @@ void InstallFirmware(const QString& location, bool recursive) { QString UnzipFirmwareToTmp(const QString& location) { namespace fs = std::filesystem; - fs::path tmp{fs::temp_directory_path()}; - - if (!fs::create_directories(tmp / "eden" / "firmware")) { + fs::path tmp{fs::temp_directory_path() / "eden" / "firmware"}; + std::error_code ec; + fs::remove_all(tmp, ec); + if (!fs::create_directories(tmp, ec)) { return QString(); } - tmp /= "eden"; - tmp /= "firmware"; - QString qCacheDir = QString::fromStdString(tmp.string()); QFile zip(location); @@ -496,5 +495,94 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, const std::str }); } -// TODO(crueter): Port InstallFirmware et al. from QML Branch +bool CheckKeys() { + if (!ContentManager::AreKeysPresent()) { + QtCommon::Frontend::Information( + tr("Keys not installed"), + tr("Install decryption keys and restart Eden before attempting to install firmware.")); + return false; + } + + return true; +} + +void InstallFirmware() { + if (!CheckKeys()) + return; + + const QString firmware_source_location = + QtCommon::Frontend::GetExistingDirectory(tr("Select Dumped Firmware Source Location"), {}); + + if (!firmware_source_location.isEmpty()) + QtCommon::Content::InstallFirmware(firmware_source_location, false); +} + +void InstallFirmwareZip() { + if (!CheckKeys()) + return; + + const QString firmware_zip_location = QtCommon::Frontend::GetOpenFileName( + tr("Select Dumped Firmware ZIP"), {}, tr("Zipped Archives (*.zip)")); + + if (firmware_zip_location.isEmpty()) + return; + + const QString qCacheDir = QtCommon::Content::UnzipFirmwareToTmp(firmware_zip_location); + + // In this case, it has to be done recursively, since sometimes people + // will pack it into a subdirectory after dumping + if (!qCacheDir.isEmpty()) { + QtCommon::Content::InstallFirmware(qCacheDir, true); + std::error_code ec; + std::filesystem::remove_all(std::filesystem::temp_directory_path() / "eden" / "firmware", + ec); + + if (ec) { + QtCommon::Frontend::Warning( + tr("Firmware cleanup failed"), + tr("Failed to clean up extracted firmware cache.\n" + "Check write permissions in the system temp directory and try " + "again.\nOS reported error: %1") + .arg(QString::fromStdString(ec.message()))); + } + } +} + +void configureFilesystemProvider(const std::string& filepath) { + // Ensure all NCAs are registered before launching the game + const auto file = QtCommon::vfs->OpenFile(filepath, FileSys::OpenMode::Read); + if (!file) { + return; + } + + auto loader = Loader::GetLoader(*QtCommon::system, file); + if (!loader) { + return; + } + + const auto file_type = loader->GetFileType(); + if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { + return; + } + + u64 program_id = 0; + const auto res2 = loader->ReadProgramId(program_id); + if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { + QtCommon::provider->AddEntry(FileSys::TitleType::Application, + FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), + program_id, file); + } else if (res2 == Loader::ResultStatus::Success && + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { + const auto nsp = file_type == Loader::FileType::NSP + ? std::make_shared(file) + : FileSys::XCI{file}.GetSecurePartitionNSP(); + for (const auto& title : nsp->GetNCAs()) { + for (const auto& entry : title.second) { + QtCommon::provider->AddEntry(entry.first.first, entry.first.second, title.first, + entry.second->GetBaseFile()); + } + } + } +} + } // namespace QtCommon::Content diff --git a/src/qt_common/util/content.h b/src/qt_common/util/content.h index 20dacf540a..452764ef54 100644 --- a/src/qt_common/util/content.h +++ b/src/qt_common/util/content.h @@ -37,9 +37,12 @@ inline const QString GetKeyInstallResultString(FirmwareManager::KeyInstallResult } void InstallFirmware(const QString& location, bool recursive); - QString UnzipFirmwareToTmp(const QString& location); +bool CheckKeys(); +void InstallFirmware(); +void InstallFirmwareZip(); + // Keys // void InstallKeys(); @@ -54,6 +57,9 @@ void ExportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& void ImportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id = "", std::function callback = {}); +// Loader // +void configureFilesystemProvider(const std::string& filepath); + // Profiles // void FixProfiles(); } // namespace QtCommon::Content diff --git a/src/qt_common/util/game.cpp b/src/qt_common/util/game.cpp index e037fdae6c..ddbe979765 100644 --- a/src/qt_common/util/game.cpp +++ b/src/qt_common/util/game.cpp @@ -11,7 +11,6 @@ #include "qt_common/abstract/frontend.h" #include "qt_common/config/uisettings.h" #include "qt_common/qt_common.h" -#include "yuzu/util/util.h" #include #include @@ -22,6 +21,7 @@ #include #include "common/scope_exit.h" #include "common/string_util.h" +#include "common/fs/file.h" #else #include #include "fmt/ostream.h" @@ -367,6 +367,7 @@ void ResetMetadata(bool show_message) { // Uhhh // +// TODO(crueter): Make QtCommon::Shortcut // Messages in pre-defined message boxes for less code spaghetti inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title) { int result = 0; @@ -533,4 +534,110 @@ void CreateHomeMenuShortcut(ShortcutTarget target) { CreateShortcut(game_path, QLaunchId, "Switch Home Menu", target, "-qlaunch", false); } +bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) { +#if defined(WIN32) +#pragma pack(push, 2) + struct IconDir { + WORD id_reserved; + WORD id_type; + WORD id_count; + }; + + struct IconDirEntry { + BYTE width; + BYTE height; + BYTE color_count; + BYTE reserved; + WORD planes; + WORD bit_count; + DWORD bytes_in_res; + DWORD image_offset; + }; +#pragma pack(pop) + + const QImage source_image = image.convertToFormat(QImage::Format_RGB32); + constexpr std::array scale_sizes{256, 128, 64, 48, 32, 24, 16}; + constexpr int bytes_per_pixel = 4; + + const IconDir icon_dir{ + .id_reserved = 0, + .id_type = 1, + .id_count = static_cast(scale_sizes.size()), + }; + + Common::FS::IOFile icon_file(icon_path.string(), Common::FS::FileAccessMode::Write, + Common::FS::FileType::BinaryFile); + if (!icon_file.IsOpen()) { + return false; + } + + if (!icon_file.Write(icon_dir)) { + return false; + } + + std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size()); + for (std::size_t i = 0; i < scale_sizes.size(); i++) { + const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel; + const IconDirEntry icon_entry{ + .width = static_cast(scale_sizes[i]), + .height = static_cast(scale_sizes[i]), + .color_count = 0, + .reserved = 0, + .planes = 1, + .bit_count = bytes_per_pixel * 8, + .bytes_in_res = static_cast(sizeof(BITMAPINFOHEADER) + image_size), + .image_offset = static_cast(image_offset), + }; + image_offset += icon_entry.bytes_in_res; + if (!icon_file.Write(icon_entry)) { + return false; + } + } + + for (std::size_t i = 0; i < scale_sizes.size(); i++) { + const QImage scaled_image = source_image.scaled( + scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + const BITMAPINFOHEADER info_header{ + .biSize = sizeof(BITMAPINFOHEADER), + .biWidth = scaled_image.width(), + .biHeight = scaled_image.height() * 2, + .biPlanes = 1, + .biBitCount = bytes_per_pixel * 8, + .biCompression = BI_RGB, + .biSizeImage{}, + .biXPelsPerMeter{}, + .biYPelsPerMeter{}, + .biClrUsed{}, + .biClrImportant{}, + }; + + if (!icon_file.Write(info_header)) { + return false; + } + + for (int y = 0; y < scaled_image.height(); y++) { + const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y); + std::vector line_data(scaled_image.width() * bytes_per_pixel); + std::memcpy(line_data.data(), line, line_data.size()); + if (!icon_file.Write(line_data)) { + return false; + } + } + } + icon_file.Close(); + + return true; +#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) + // Convert and write the icon as a PNG + if (!image.save(QString::fromStdString(icon_path.string()))) { + LOG_ERROR(Frontend, "Could not write icon as PNG to file"); + } else { + LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); + } + return true; +#else + return false; +#endif +} + } // namespace QtCommon::Game diff --git a/src/qt_common/util/game.h b/src/qt_common/util/game.h index 0d7f03fa86..9a61bb9349 100644 --- a/src/qt_common/util/game.h +++ b/src/qt_common/util/game.h @@ -69,6 +69,14 @@ void CreateShortcut(const std::string& game_path, const u64 program_id, std::string GetShortcutPath(ShortcutTarget target); void CreateHomeMenuShortcut(ShortcutTarget target); +/** + * Saves a windows icon to a file + * @param path The icons path + * @param image The image to save + * @return bool If the operation succeeded + */ +[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image); + } // namespace QtCommon::Game #endif // QT_GAME_UTIL_H diff --git a/src/qt_common/util/mod.cpp b/src/qt_common/util/mod.cpp index 4e7fd54e79..3e75cb21fa 100644 --- a/src/qt_common/util/mod.cpp +++ b/src/qt_common/util/mod.cpp @@ -83,8 +83,9 @@ QStringList GetModFolders(const QString& root, const QString& fallbackName) { // now make a temp directory... const auto mod_dir = fs::temp_directory_path() / "eden" / "mod" / name.toStdString(); const auto tmp = mod_dir / to_make; - fs::remove_all(mod_dir); - if (!fs::create_directories(tmp)) { + std::error_code ec; + fs::remove_all(mod_dir, ec); + if (!fs::create_directories(tmp, ec)) { LOG_ERROR(Frontend, "Failed to create temporary directory {}", tmp.string()); return {}; } @@ -116,9 +117,9 @@ QStringList GetModFolders(const QString& root, const QString& fallbackName) { const QString ExtractMod(const QString& path) { namespace fs = std::filesystem; fs::path tmp{fs::temp_directory_path() / "eden" / "unzip_mod"}; - - fs::remove_all(tmp); - if (!fs::create_directories(tmp)) { + std::error_code ec; + fs::remove_all(tmp, ec); + if (!fs::create_directories(tmp, ec)) { QtCommon::Frontend::Critical(tr("Mod Extract Failed"), tr("Failed to create temporary directory %1") .arg(QString::fromStdString(tmp.string()))); diff --git a/src/yuzu/vk_device_info.cpp b/src/qt_common/util/vk.cpp similarity index 98% rename from src/yuzu/vk_device_info.cpp rename to src/qt_common/util/vk.cpp index 1e8e140d77..8ca7f4a665 100644 --- a/src/yuzu/vk_device_info.cpp +++ b/src/qt_common/util/vk.cpp @@ -11,13 +11,13 @@ #include "common/dynamic_library.h" #include "common/logging.h" +#include "qt_common/util/vk.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_instance.h" #include "video_core/vulkan_common/vulkan_library.h" #include "video_core/vulkan_common/vulkan_surface.h" #include "video_core/vulkan_common/vulkan_wrapper.h" #include "vulkan/vulkan_core.h" -#include "yuzu/vk_device_info.h" class QWindow; diff --git a/src/qt_common/util/vk.h b/src/qt_common/util/vk.h new file mode 100644 index 0000000000..3e6730f643 --- /dev/null +++ b/src/qt_common/util/vk.h @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include "common/common_types.h" +#include "common/settings_enums.h" +#include "vulkan/vulkan_core.h" + +static const std::vector default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_FIFO_KHR}; + +// Converts a setting to a present mode (or vice versa) +static inline constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) { + switch (mode) { + case Settings::VSyncMode::Immediate: + return VK_PRESENT_MODE_IMMEDIATE_KHR; + case Settings::VSyncMode::Mailbox: + return VK_PRESENT_MODE_MAILBOX_KHR; + case Settings::VSyncMode::Fifo: + return VK_PRESENT_MODE_FIFO_KHR; + case Settings::VSyncMode::FifoRelaxed: + return VK_PRESENT_MODE_FIFO_RELAXED_KHR; + default: + return VK_PRESENT_MODE_FIFO_KHR; + } +} + +static inline constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) { + switch (mode) { + case VK_PRESENT_MODE_IMMEDIATE_KHR: + return Settings::VSyncMode::Immediate; + case VK_PRESENT_MODE_MAILBOX_KHR: + return Settings::VSyncMode::Mailbox; + case VK_PRESENT_MODE_FIFO_KHR: + return Settings::VSyncMode::Fifo; + case VK_PRESENT_MODE_FIFO_RELAXED_KHR: + return Settings::VSyncMode::FifoRelaxed; + default: + return Settings::VSyncMode::Fifo; + } +} + +class QWindow; + +namespace Settings { +enum class VSyncMode : u32; +} + +namespace VkDeviceInfo { +// Short class to record Vulkan driver information for configuration purposes +class Record { +public: + explicit Record(std::string_view name, const std::vector& vsync_modes, + bool has_broken_compute); + ~Record(); + + const std::string name; + const std::vector vsync_support; + const bool has_broken_compute; +}; + +// TODO(crueter): Port some configure_graphics.cpp stuff +void PopulateRecords(std::vector& records, QWindow* window); +} // namespace VkDeviceInfo diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index c2511942d9..62a82dce9d 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -14,6 +14,38 @@ namespace Shader::Backend::SPIRV { namespace { +class DescriptorIndex { +public: + explicit DescriptorIndex(EmitContext& ctx, const IR::Value& index) + : id{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)}, + is_non_uniform{ctx.profile.support_sampled_image_array_nonuniform_indexing && + !index.IsImmediate()} { + if (!is_non_uniform) { + return; + } + if (ctx.profile.supported_spirv < 0x00010400) { + ctx.AddExtension("SPV_EXT_descriptor_indexing"); + } + ctx.AddCapability(spv::Capability::ShaderNonUniform); + ctx.AddCapability(spv::Capability::SampledImageArrayNonUniformIndexing); + Decorate(ctx, id); + } + + Id Value() const { + return id; + } + + void Decorate(EmitContext& ctx, Id object) const { + if (is_non_uniform) { + ctx.Decorate(object, spv::Decoration::NonUniform); + } + } + +private: + Id id; + bool is_non_uniform; +}; + class ImageOperands { public: [[maybe_unused]] static constexpr bool ImageSampleOffsetAllowed = false; @@ -189,8 +221,12 @@ private: Id Texture(EmitContext& ctx, IR::TextureInstInfo info, [[maybe_unused]] const IR::Value& index) { const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; if (def.count > 1) { - const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, ctx.Def(index))}; - return ctx.OpLoad(def.sampled_type, pointer); + const DescriptorIndex idx{ctx, index}; + const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())}; + idx.Decorate(ctx, pointer); + const Id object{ctx.OpLoad(def.sampled_type, pointer)}; + idx.Decorate(ctx, object); + return object; } else { return ctx.OpLoad(def.sampled_type, def.id); } @@ -208,9 +244,14 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind } else { const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; if (def.count > 1) { - const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)}; - const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx)}; - return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, ptr)); + const DescriptorIndex idx{ctx, index}; + const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())}; + idx.Decorate(ctx, ptr); + const Id object{ctx.OpLoad(def.sampled_type, ptr)}; + idx.Decorate(ctx, object); + const Id image{ctx.OpImage(def.image_type, object)}; + idx.Decorate(ctx, image); + return image; } return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, def.id)); } diff --git a/src/shader_recompiler/host_translate_info.h b/src/shader_recompiler/host_translate_info.h index 1b53404fcc..4a9261d5f5 100644 --- a/src/shader_recompiler/host_translate_info.h +++ b/src/shader_recompiler/host_translate_info.h @@ -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 @@ -17,6 +20,9 @@ struct HostTranslateInfo { bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS u32 min_ssbo_alignment{}; ///< Minimum alignment supported by the device for SSBOs + u32 max_per_stage_descriptor_sampled_images{1024}; ///< maximum sampled descriptors per stage + u32 max_per_stage_resources{4096}; ///< maximum resources per stage + u32 max_descriptor_set_sampled_images{1024}; ///< maximum sampled descriptors per set bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry ///< passthrough shaders bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index 20b8591072..55b6f7213c 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -32,6 +32,71 @@ using TextureInstVector = boost::container::small_vector; constexpr u32 DESCRIPTOR_SIZE = 8; constexpr u32 DESCRIPTOR_SIZE_SHIFT = static_cast(std::countr_zero(DESCRIPTOR_SIZE)); +constexpr u32 DYNAMIC_DESCRIPTOR_CBUF_BYTES = 16 * 1024; +constexpr u32 MAX_DYNAMIC_DESCRIPTOR_COUNT = 1024; + +u32 DynamicDescriptorSizeShift(const IR::U32& dynamic_offset) { + const IR::Inst* const inst{dynamic_offset.InstRecursive()}; + if (!inst || inst->GetOpcode() != IR::Opcode::ShiftLeftLogical32) { + return DESCRIPTOR_SIZE_SHIFT; + } + const IR::Value shift{inst->Arg(1)}; + if (!shift.IsImmediate()) { + return DESCRIPTOR_SIZE_SHIFT; + } + const u32 size_shift{shift.U32()}; + return size_shift >= DESCRIPTOR_SIZE_SHIFT && size_shift < 31 ? size_shift + : DESCRIPTOR_SIZE_SHIFT; +} + +u32 DynamicDescriptorCount(u32 base_offset, u32 size_shift) { + if (size_shift >= 31 || base_offset >= DYNAMIC_DESCRIPTOR_CBUF_BYTES) { + return 1; + } + const u32 stride{1U << size_shift}; + const u32 available{DYNAMIC_DESCRIPTOR_CBUF_BYTES - base_offset}; + if (available < DESCRIPTOR_SIZE) { + return 1; + } + const u32 available_count{1U + (available - DESCRIPTOR_SIZE) / stride}; + return std::min(MAX_DYNAMIC_DESCRIPTOR_COUNT, available_count); +} + +u32 SaturatingSub(u32 lhs, u32 rhs) { + return lhs > rhs ? lhs - rhs : 0; +} + +template +u32 StaticDescriptorCount(const Descriptors& descriptors) { + u32 count{}; + for (const auto& desc : descriptors) { + if (desc.count <= 1) { + count += desc.count; + } + } + return count; +} + +u32 DynamicSampledTextureCap(const Info& info, const HostTranslateInfo& host_info, + u32 dynamic_arrays) { + if (dynamic_arrays == 0) { + return MAX_DYNAMIC_DESCRIPTOR_COUNT; + } + const u32 sampled_static_count{StaticDescriptorCount(info.texture_buffer_descriptors) + + StaticDescriptorCount(info.texture_descriptors)}; + const u32 resource_static_count{ + NumDescriptors(info.constant_buffer_descriptors) + + NumDescriptors(info.storage_buffers_descriptors) + sampled_static_count + + NumDescriptors(info.image_buffer_descriptors) + NumDescriptors(info.image_descriptors)}; + const u32 sampled_limit{std::min(host_info.max_per_stage_descriptor_sampled_images, + host_info.max_descriptor_set_sampled_images)}; + const u32 sampled_budget{SaturatingSub(sampled_limit, sampled_static_count)}; + const u32 resource_budget{SaturatingSub(host_info.max_per_stage_resources, + resource_static_count)}; + const u32 sampled_cap{sampled_budget / dynamic_arrays}; + const u32 resource_cap{resource_budget / dynamic_arrays}; + return std::max(1U, std::min({MAX_DYNAMIC_DESCRIPTOR_COUNT, sampled_cap, resource_cap})); +} IR::Opcode IndexedInstruction(const IR::Inst& inst) { switch (inst.GetOpcode()) { @@ -109,6 +174,39 @@ IR::Opcode IndexedInstruction(const IR::Inst& inst) { } } +bool IsStorageImageOpcode(IR::Opcode opcode) { + switch (opcode) { + case IR::Opcode::ImageRead: + case IR::Opcode::ImageAtomicIAdd32: + case IR::Opcode::ImageAtomicSMin32: + case IR::Opcode::ImageAtomicUMin32: + case IR::Opcode::ImageAtomicSMax32: + case IR::Opcode::ImageAtomicUMax32: + case IR::Opcode::ImageAtomicInc32: + case IR::Opcode::ImageAtomicDec32: + case IR::Opcode::ImageAtomicAnd32: + case IR::Opcode::ImageAtomicOr32: + case IR::Opcode::ImageAtomicXor32: + case IR::Opcode::ImageAtomicExchange32: + case IR::Opcode::ImageWrite: + return true; + default: + return false; + } +} + +u32 DynamicSampledTextureArrayCount(const TextureInstVector& to_replace) { + u32 count{}; + for (const TextureInst& inst : to_replace) { + const auto flags{inst.inst->Flags()}; + if (inst.cbuf.count > 1 && !IsStorageImageOpcode(IndexedInstruction(*inst.inst)) && + flags.type != TextureType::Buffer) { + ++count; + } + } + return count; +} + bool IsBindless(const IR::Inst& inst) { switch (inst.GetOpcode()) { case IR::Opcode::BindlessImageSampleImplicitLod: @@ -354,6 +452,7 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme } else { return std::nullopt; } + const u32 size_shift{DynamicDescriptorSizeShift(dynamic_offset)}; return ConstBufferAddr{ .index = index.U32(), .offset = base_offset, @@ -362,7 +461,7 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme .secondary_offset = 0, .secondary_shift_left = 0, .dynamic_offset = dynamic_offset, - .count = 8, + .count = DynamicDescriptorCount(base_offset, size_shift), .has_secondary = false, }; } @@ -589,6 +688,8 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo program.info.texture_descriptors, program.info.image_descriptors, }; + const u32 sampled_dynamic_cap{ + DynamicSampledTextureCap(program.info, host_info, DynamicSampledTextureArrayCount(to_replace))}; for (TextureInst& texture_inst : to_replace) { // TODO: Handle arrays IR::Inst* const inst{texture_inst.inst}; @@ -632,6 +733,9 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo break; } u32 index; + const u32 size_shift{cbuf.count > 1 ? DynamicDescriptorSizeShift(cbuf.dynamic_offset) + : DESCRIPTOR_SIZE_SHIFT}; + u32 count{cbuf.count}; switch (inst->GetOpcode()) { case IR::Opcode::ImageRead: case IR::Opcode::ImageAtomicIAdd32: @@ -660,8 +764,8 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo .is_integer = is_integer, .cbuf_index = cbuf.index, .cbuf_offset = cbuf.offset, - .count = cbuf.count, - .size_shift = DESCRIPTOR_SIZE_SHIFT, + .count = count, + .size_shift = size_shift, }); } else { index = descriptors.Add(ImageDescriptor{ @@ -672,8 +776,8 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo .is_integer = is_integer, .cbuf_index = cbuf.index, .cbuf_offset = cbuf.offset, - .count = cbuf.count, - .size_shift = DESCRIPTOR_SIZE_SHIFT, + .count = count, + .size_shift = size_shift, }); } break; @@ -688,10 +792,11 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo .secondary_cbuf_index = cbuf.secondary_index, .secondary_cbuf_offset = cbuf.secondary_offset, .secondary_shift_left = cbuf.secondary_shift_left, - .count = cbuf.count, - .size_shift = DESCRIPTOR_SIZE_SHIFT, + .count = count, + .size_shift = size_shift, }); } else { + count = std::min(count, sampled_dynamic_cap); index = descriptors.Add(TextureDescriptor{ .type = flags.type, .is_depth = flags.is_depth != 0, @@ -703,8 +808,8 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo .secondary_cbuf_index = cbuf.secondary_index, .secondary_cbuf_offset = cbuf.secondary_offset, .secondary_shift_left = cbuf.secondary_shift_left, - .count = cbuf.count, - .size_shift = DESCRIPTOR_SIZE_SHIFT, + .count = count, + .size_shift = size_shift, }); } break; @@ -712,12 +817,12 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo flags.descriptor_index.Assign(index); inst->SetFlags(flags); - if (cbuf.count > 1) { + if (count > 1) { const auto insert_point{IR::Block::InstructionList::s_iterator_to(*inst)}; IR::IREmitter ir{*texture_inst.block, insert_point}; - const IR::U32 shift{ir.Imm32(DESCRIPTOR_SIZE_SHIFT)}; + const IR::U32 shift{ir.Imm32(size_shift)}; inst->SetArg(0, ir.UMin(ir.ShiftRightLogical(cbuf.dynamic_offset, shift), - ir.Imm32(DESCRIPTOR_SIZE - 1))); + ir.Imm32(count - 1))); } else { inst->SetArg(0, IR::Value{}); } diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index 90e46bb1ba..ff19f0710f 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -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 @@ -45,6 +48,7 @@ struct Profile { bool support_scaled_attributes{}; bool support_multi_viewport{}; bool support_geometry_streams{}; + bool support_sampled_image_array_nonuniform_indexing{}; bool warp_size_potentially_larger_than_guest{}; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 25eb7846b4..be53b71393 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -323,7 +323,10 @@ if (ENABLE_OPENGL) endif() target_link_libraries(video_core PUBLIC common core) -target_link_libraries(video_core PUBLIC glad shader_recompiler stb bc_decoder gpu_logging) +target_link_libraries(video_core PUBLIC shader_recompiler stb bc_decoder gpu_logging) +if (ENABLE_OPENGL) + target_link_libraries(video_core PUBLIC glad) +endif() if (YUZU_USE_EXTERNAL_FFMPEG) add_dependencies(video_core ffmpeg-build) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index eaa6912f0d..60e0e8449b 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -811,7 +811,7 @@ void BufferCache

::BindHostVertexBuffers() { auto& flags = maxwell3d->dirty.flags; u32 enabled_mask = enabled_vertex_buffers_mask; HostBindings bindings{}; - u32 last_index = std::numeric_limits::max(); + u32 last_index = (std::numeric_limits::max)(); const auto flush_bindings = [&]() { if (bindings.buffers.empty()) { return; @@ -819,7 +819,7 @@ void BufferCache

::BindHostVertexBuffers() { bindings.max_index = bindings.min_index + static_cast(bindings.buffers.size()); runtime.BindVertexBuffers(bindings); bindings = HostBindings{}; - last_index = std::numeric_limits::max(); + last_index = (std::numeric_limits::max)(); }; while (enabled_mask != 0) { const u32 index = std::countr_zero(enabled_mask); diff --git a/src/video_core/macro.cpp b/src/video_core/macro.cpp index 606162ba8d..dbf011860e 100644 --- a/src/video_core/macro.cpp +++ b/src/video_core/macro.cpp @@ -11,17 +11,9 @@ #include #include + #ifdef ARCHITECTURE_x86_64 -// xbyak hates human beings -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wconversion" -#pragma GCC diagnostic ignored "-Wshadow" -#endif -#ifdef __clang__ -#pragma clang diagnostic ignored "-Wconversion" -#pragma clang diagnostic ignored "-Wshadow" -#endif -#include +#include "common/x64/xbyak.h" #endif #include "common/assert.h" @@ -39,10 +31,6 @@ #include "common/assert.h" #include "common/bit_field.h" #include "common/logging.h" -#ifdef ARCHITECTURE_x86_64 -#include "common/x64/xbyak_abi.h" -#include "common/x64/xbyak_util.h" -#endif #include "video_core/engines/maxwell_3d.h" namespace Tegra { diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 9275dbf01c..deedf1aa6b 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -54,7 +54,7 @@ using VideoCommon::LoadPipelines; using VideoCommon::SerializePipeline; using Context = ShaderContext::Context; -constexpr u32 CACHE_VERSION = 14; +constexpr u32 CACHE_VERSION = 15; template auto MakeSpan(Container& container) { diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index 930f06daf6..77ef8bf7bc 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -217,6 +217,12 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with SURFACE_FORMAT_ELEM(VK_FORMAT_ASTC_6x5_UNORM_BLOCK, 0, ASTC_2D_6X5_UNORM) \ SURFACE_FORMAT_ELEM(VK_FORMAT_ASTC_6x5_SRGB_BLOCK, 0, ASTC_2D_6X5_SRGB) \ SURFACE_FORMAT_ELEM(VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, 0, E5B9G9R9_FLOAT) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, 0, ETC2_RGB_UNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, 0, ETC2_RGBA_UNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK, 0, ETC2_RGB_PTA_UNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, 0, ETC2_RGB_SRGB) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, 0, ETC2_RGBA_SRGB) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, 0, ETC2_RGB_PTA_SRGB) \ /* Depth formats */ \ SURFACE_FORMAT_ELEM(VK_FORMAT_D32_SFLOAT, usage_attachable, D32_FLOAT) \ SURFACE_FORMAT_ELEM(VK_FORMAT_D16_UNORM, usage_attachable, D16_UNORM) \ @@ -253,8 +259,8 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with break; } } - // Transcode on hardware that doesn't support BCn natively if (!device.IsOptimalBcnSupported() && VideoCore::Surface::IsPixelFormatBCn(pixel_format)) { + // Transcode on hardware that doesn't support BCn natively if (pixel_format == PixelFormat::BC4_SNORM) { tuple.format = VK_FORMAT_R8_SNORM; } else if (pixel_format == PixelFormat::BC4_UNORM) { @@ -270,6 +276,9 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with } else { tuple.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32; } + } else if (!device.IsOptimalEtc2Supported() && VideoCore::Surface::IsPixelFormatETC2(pixel_format)) { + // Transcode on hardware that doesn't support ETC2 natively + tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32; } bool const attachable = (tuple.usage & usage_attachable) != 0; bool const storage = (tuple.usage & usage_storage) != 0; diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index e88b27b273..451a02c7e6 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h @@ -189,6 +189,7 @@ inline void PushImageDescriptors(TextureCache& texture_cache, views += num_texture_buffers; views += num_image_buffers; for (const auto& desc : info.texture_descriptors) { + bool is_rescaled{}; for (u32 index = 0; index < desc.count; ++index) { const VideoCommon::ImageViewId image_view_id{(views++)->id}; const VideoCommon::SamplerId sampler_id{*(samplers++)}; @@ -200,10 +201,13 @@ inline void PushImageDescriptors(TextureCache& texture_cache, const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() : sampler.Handle()}; guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler); - rescaling.PushTexture(texture_cache.IsRescaling(image_view)); + const bool element_rescaled{texture_cache.IsRescaling(image_view)}; + is_rescaled |= element_rescaled; } + rescaling.PushTexture(is_rescaled); } for (const auto& desc : info.image_descriptors) { + bool is_rescaled{}; for (u32 index = 0; index < desc.count; ++index) { ImageView& image_view{texture_cache.GetImageView((views++)->id)}; if (desc.is_written) { @@ -211,8 +215,10 @@ inline void PushImageDescriptors(TextureCache& texture_cache, } const VkImageView vk_image_view{image_view.StorageView(desc.type, desc.format)}; guest_descriptor_queue.AddImage(vk_image_view); - rescaling.PushImage(texture_cache.IsRescaling(image_view)); + const bool element_rescaled{texture_cache.IsRescaling(image_view)}; + is_rescaled |= element_rescaled; } + rescaling.PushImage(is_rescaled); } } diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 1a62324c95..490dd7acfe 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -127,9 +127,8 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, texture_cache.SynchronizeComputeDescriptors(); - static constexpr size_t max_elements = 64; - boost::container::static_vector views; - boost::container::static_vector samplers; + boost::container::small_vector views; + boost::container::small_vector samplers; const auto& qmd{kepler_compute.launch_description}; const auto& cbufs{qmd.const_buffer_config}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index adfe1614b1..d0f98091c1 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -45,6 +45,10 @@ #include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/gpu_logging/gpu_logging.h" +#ifdef ANDROID +#include "../../android/app/src/main/jni/android_settings.h" +#endif + namespace Vulkan { namespace { @@ -58,7 +62,7 @@ using VideoCommon::FileEnvironment; using VideoCommon::GenericEnvironment; using VideoCommon::GraphicsEnvironment; -constexpr u32 CACHE_VERSION = 16; +constexpr u32 CACHE_VERSION = 17; constexpr std::array VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'}; template @@ -325,13 +329,13 @@ size_t GetTotalPipelineWorkers() { const size_t max_core_threads = std::max(static_cast(std::thread::hardware_concurrency()), 2ULL) - 1ULL; #ifdef ANDROID - // Leave at least one core free on Android. Previously we reserved two, but - // shipping builds benefit from one extra compilation worker. - constexpr size_t free_cores = 1ULL; - if (max_core_threads <= free_cores) { + const int configured = AndroidSettings::values.pipeline_worker_count.GetValue(); + const int clamped = std::clamp(configured, 4, 8); + const size_t desired = static_cast(clamped); + if (desired == 0) { return 1ULL; } - return max_core_threads - free_cores; + return std::min(max_core_threads, desired); #else return max_core_threads; #endif @@ -412,6 +416,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_scaled_attributes = !device.MustEmulateScaledFormats(), .support_multi_viewport = device.SupportsMultiViewport(), .support_geometry_streams = device.AreTransformFeedbackGeometryStreamsSupported(), + .support_sampled_image_array_nonuniform_indexing = + device.IsSampledImageArrayNonUniformIndexingSupported(), .warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyBiggerThanGuest(), @@ -424,13 +430,12 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA, .has_broken_spirv_clamp = driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS, - .has_broken_spirv_position_input = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY, + .has_broken_spirv_position_input = driver_id == false, .has_broken_unsigned_image_offsets = false, .has_broken_signed_operations = false, .has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY, .ignore_nan_fp_comparisons = false, - .has_broken_spirv_subgroup_mask_vector_extract_dynamic = - driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY, + .has_broken_spirv_subgroup_mask_vector_extract_dynamic = false, .has_broken_robust = device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal, .min_ssbo_alignment = device.GetStorageBufferAlignment(), @@ -447,6 +452,9 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_snorm_render_buffer = true, .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(), .min_ssbo_alignment = static_cast(device.GetStorageBufferAlignment()), + .max_per_stage_descriptor_sampled_images = device.GetMaxPerStageDescriptorSampledImages(), + .max_per_stage_resources = device.GetMaxPerStageResources(), + .max_descriptor_set_sampled_images = device.GetMaxDescriptorSetSampledImages(), .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), .support_conditional_barrier = device.SupportsConditionalBarriers(), }; diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 9543c65bd4..98062a78dc 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -951,7 +951,7 @@ private: runtime.View3DRegs([this](Maxwell3D& maxwell3d) { buffers_count = 0; out_topology = maxwell3d.draw_manager.draw_state.topology; - patch_vertices = std::max(maxwell3d.regs.patch_vertices, 1U); + patch_vertices = (std::max)(maxwell3d.regs.patch_vertices, 1U); if (out_topology == Maxwell3D::Regs::PrimitiveTopology::Patches) { switch (maxwell3d.regs.tessellation.params.output_primitives.Value()) { case Maxwell3D::Regs::Tessellation::OutputPrimitives::Points: @@ -1145,7 +1145,7 @@ public: } new_query->stride = 1; runtime.View3DRegs([new_query, subreport](Maxwell3D& maxwell3d) { - new_query->patch_vertices = std::max(maxwell3d.regs.patch_vertices, 1U); + new_query->patch_vertices = (std::max)(maxwell3d.regs.patch_vertices, 1U); for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) { const auto& tf = maxwell3d.regs.transform_feedback; if (tf.buffers[i].enable == 0) { diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 1998849e84..69c1720943 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -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: 2014 Citra Emulator Project @@ -336,6 +336,20 @@ bool IsPixelFormatBCn(PixelFormat format) { } } +bool IsPixelFormatETC2(PixelFormat format) { + switch (format) { + case PixelFormat::ETC2_RGB_UNORM: + case PixelFormat::ETC2_RGBA_UNORM: + case PixelFormat::ETC2_RGB_PTA_UNORM: + case PixelFormat::ETC2_RGB_SRGB: + case PixelFormat::ETC2_RGBA_SRGB: + case PixelFormat::ETC2_RGB_PTA_SRGB: + return true; + default: + return false; + } +} + bool IsPixelFormatSRGB(PixelFormat format) { switch (format) { case PixelFormat::A8B8G8R8_SRGB: @@ -344,6 +358,9 @@ bool IsPixelFormatSRGB(PixelFormat format) { case PixelFormat::BC2_SRGB: case PixelFormat::BC3_SRGB: case PixelFormat::BC7_SRGB: + case PixelFormat::ETC2_RGB_SRGB: + case PixelFormat::ETC2_RGBA_SRGB: + case PixelFormat::ETC2_RGB_PTA_SRGB: case PixelFormat::ASTC_2D_4X4_SRGB: case PixelFormat::ASTC_2D_8X8_SRGB: case PixelFormat::ASTC_2D_8X5_SRGB: diff --git a/src/video_core/surface.h b/src/video_core/surface.h index a97bcb39b5..5611dbdffa 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -5,6 +5,8 @@ #pragma once +#undef PIXEL_FORMAT_LIST + #include #include #include "common/assert.h" @@ -111,6 +113,12 @@ namespace VideoCore::Surface { PIXEL_FORMAT_ELEM(ASTC_2D_6X5_UNORM, 6, 5, 128) \ PIXEL_FORMAT_ELEM(ASTC_2D_6X5_SRGB, 6, 5, 128) \ PIXEL_FORMAT_ELEM(E5B9G9R9_FLOAT, 1, 1, 32) \ + PIXEL_FORMAT_ELEM(ETC2_RGB_UNORM, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(ETC2_RGBA_UNORM, 4, 4, 128) \ + PIXEL_FORMAT_ELEM(ETC2_RGB_PTA_UNORM, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(ETC2_RGB_SRGB, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(ETC2_RGBA_SRGB, 4, 4, 128) \ + PIXEL_FORMAT_ELEM(ETC2_RGB_PTA_SRGB, 4, 4, 64) \ /* Depth formats */ \ PIXEL_FORMAT_ELEM(D32_FLOAT, 1, 1, 32) \ PIXEL_FORMAT_ELEM(D16_UNORM, 1, 1, 16) \ @@ -122,7 +130,7 @@ namespace VideoCore::Surface { PIXEL_FORMAT_ELEM(S8_UINT_D24_UNORM, 1, 1, 32) \ PIXEL_FORMAT_ELEM(D32_FLOAT_S8_UINT, 1, 1, 64) -enum class PixelFormat { +enum class PixelFormat : u32 { #define PIXEL_FORMAT_ELEM(name, ...) name, PIXEL_FORMAT_LIST #undef PIXEL_FORMAT_ELEM @@ -181,13 +189,18 @@ constexpr u32 BitsPerBlock(PixelFormat format) noexcept { } } -#undef PIXEL_FORMAT_LIST - /// Returns the sizer in bytes of the specified pixel format constexpr u32 BytesPerBlock(PixelFormat pixel_format) { return BitsPerBlock(pixel_format) / CHAR_BIT; } +} + +#include "video_core/gpu.h" +#include "video_core/textures/texture.h" + +namespace VideoCore::Surface { + SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_type); bool SurfaceTargetIsLayered(SurfaceTarget target); bool SurfaceTargetIsArray(SurfaceTarget target); @@ -198,6 +211,7 @@ SurfaceType GetFormatType(PixelFormat pixel_format); bool HasAlpha(PixelFormat pixel_format); bool IsPixelFormatASTC(PixelFormat format); bool IsPixelFormatBCn(PixelFormat format); +bool IsPixelFormatETC2(PixelFormat format); bool IsPixelFormatSRGB(PixelFormat format); bool IsPixelFormatInteger(PixelFormat format); bool IsPixelFormatSignedInteger(PixelFormat format); diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index a9e03b375d..ea68a1c063 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp @@ -191,6 +191,20 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, return PixelFormat::BC6H_SFLOAT; case Hash(TextureFormat::BC6H_U16, FLOAT): return PixelFormat::BC6H_UFLOAT; + /* ETC2 */ + case Hash(TextureFormat::ETC2_RGB, UNORM, LINEAR): + return PixelFormat::ETC2_RGB_UNORM; + case Hash(TextureFormat::ETC2_RGB_PTA, UNORM, LINEAR): + return PixelFormat::ETC2_RGB_PTA_UNORM; + case Hash(TextureFormat::ETC2_RGBA, UNORM, LINEAR): + return PixelFormat::ETC2_RGBA_UNORM; + case Hash(TextureFormat::ETC2_RGB, UNORM, SRGB): + return PixelFormat::ETC2_RGB_SRGB; + case Hash(TextureFormat::ETC2_RGB_PTA, UNORM, SRGB): + return PixelFormat::ETC2_RGB_PTA_SRGB; + case Hash(TextureFormat::ETC2_RGBA, UNORM, SRGB): + return PixelFormat::ETC2_RGBA_SRGB; + /* ASTC */ case Hash(TextureFormat::ASTC_2D_4X4, UNORM, LINEAR): return PixelFormat::ASTC_2D_4X4_UNORM; case Hash(TextureFormat::ASTC_2D_4X4, UNORM, SRGB): diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h index 33c32645a2..3e2e749274 100644 --- a/src/video_core/texture_cache/formatter.h +++ b/src/video_core/texture_cache/formatter.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -17,210 +20,9 @@ struct fmt::formatter : fmt::formatter::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag (std::max)((std::min)(device_local_memory - min_vacancy_critical, min_spacing_critical), DEFAULT_CRITICAL_MEMORY)); minimum_memory = static_cast((device_local_memory - mem_threshold) / 2); - - lowmemorydevice = false; } else { expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB; critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB; minimum_memory = 0; - - lowmemorydevice = true; } const bool gpu_unswizzle_enabled = Settings::values.gpu_unswizzle_enabled.GetValue(); @@ -122,102 +118,48 @@ void TextureCache

::RunGarbageCollector() { bool aggressive_mode = false; u64 ticks_to_destroy = 0; size_t num_iterations = 0; - const auto Configure = [&](bool allow_aggressive) { high_priority_mode = total_used_memory >= expected_memory; aggressive_mode = allow_aggressive && total_used_memory >= critical_memory; ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL; num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10); }; - - const auto Cleanup = [this, &num_iterations, &high_priority_mode, - &aggressive_mode](ImageId image_id) { + const auto Cleanup = [this, &num_iterations, &high_priority_mode, &aggressive_mode](ImageId image_id) { if (num_iterations == 0) { return true; } - --num_iterations; auto& image = slot_images[image_id]; - - // Never delete recently allocated sparse textures (within 3 frames) - const bool is_recently_allocated = image.allocation_tick >= frame_tick - 3; - if (is_recently_allocated && image.info.is_sparse) { - return false; - } - if (True(image.flags & ImageFlagBits::IsDecoding)) { - // This image is still being decoded, deleting it will invalidate the slot - // used by the async decoder thread. return false; } - - // Prioritize large sparse textures for cleanup - const bool is_large_sparse = lowmemorydevice && - image.info.is_sparse && - image.guest_size_bytes >= 256_MiB; - - if (!aggressive_mode && !is_large_sparse && - True(image.flags & ImageFlagBits::CostlyLoad)) { + const bool must_download = image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap); + if ((!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) || (!high_priority_mode && must_download)) { return false; } - - const bool must_download = - image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap); - if (!high_priority_mode && !is_large_sparse && must_download) { - return false; - } - - if (must_download && !is_large_sparse) { + --num_iterations; + if (must_download) { auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes); const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info)); image.DownloadMemory(map, copies); runtime.Finish(); - SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span, - swizzle_data_buffer); + SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span, swizzle_data_buffer); } - if (True(image.flags & ImageFlagBits::Tracked)) { UntrackImage(image, image_id); } UnregisterImage(image_id); DeleteImage(image_id, image.scale_tick > frame_tick + 5); - - if (total_used_memory < critical_memory) { - if (aggressive_mode) { - // Sink the aggresiveness. - num_iterations >>= 2; - aggressive_mode = false; - return false; - } - if (high_priority_mode && total_used_memory < expected_memory) { - num_iterations >>= 1; - high_priority_mode = false; - } + if (aggressive_mode && total_used_memory < critical_memory) { + num_iterations >>= 2; + aggressive_mode = false; + } else if (high_priority_mode && total_used_memory < expected_memory) { + num_iterations >>= 1; + high_priority_mode = false; } return false; }; - - // Aggressively clear massive sparse textures - if (total_used_memory >= expected_memory) { - lru_cache.ForEachItemBelow(frame_tick, [&](ImageId image_id) { - auto& image = slot_images[image_id]; - // Only target sparse textures that are old enough - if (lowmemorydevice && - image.info.is_sparse && - image.guest_size_bytes >= 256_MiB && - image.allocation_tick < frame_tick - 3) { - LOG_DEBUG(HW_GPU, "GC targeting old sparse texture at 0x{:X} ({} MiB, age: {} frames)", - image.gpu_addr, image.guest_size_bytes / (1024 * 1024), - frame_tick - image.allocation_tick); - return Cleanup(image_id); - } - return false; - }); - } - Configure(false); lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup); - - // If pressure is still too high, prune aggressively. if (total_used_memory >= critical_memory) { Configure(true); lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup); @@ -1196,9 +1138,6 @@ void TextureCache

::RefreshContents(Image& image, ImageId image_id) { } image.flags &= ~ImageFlagBits::CpuModified; - if( lowmemorydevice && image.info.format == PixelFormat::BC1_RGBA_UNORM && MapSizeBytes(image) >= 256_MiB ) { - return; - } TrackImage(image, image_id); @@ -1619,39 +1558,6 @@ ImageId TextureCache

::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr, } } ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr=0x{:x}", gpu_addr); - - // For large sparse textures, aggressively clean up old allocations at same address - if (lowmemorydevice && info.is_sparse && CalculateGuestSizeInBytes(info) >= 256_MiB) { - const auto alloc_it = image_allocs_table.find(gpu_addr); - if (alloc_it != image_allocs_table.end()) { - const ImageAllocId alloc_id = alloc_it->second; - auto& alloc_images = slot_image_allocs[alloc_id].images; - - // Collect old images at this address that were created more than 2 frames ago - boost::container::small_vector to_delete; - for (ImageId old_image_id : alloc_images) { - Image& old_image = slot_images[old_image_id]; - if (old_image.info.is_sparse && - old_image.gpu_addr == gpu_addr && - old_image.allocation_tick < frame_tick - 2) { // Try not to delete fresh textures - to_delete.push_back(old_image_id); - } - } - - // Delete old images immediately - for (ImageId old_id : to_delete) { - Image& old_image = slot_images[old_id]; - LOG_DEBUG(HW_GPU, "Immediately deleting old sparse texture at 0x{:X} ({} MiB)", - gpu_addr, old_image.guest_size_bytes / (1024 * 1024)); - if (True(old_image.flags & ImageFlagBits::Tracked)) { - UntrackImage(old_image, old_id); - } - UnregisterImage(old_id); - DeleteImage(old_id, true); - } - } - } - const ImageId image_id = JoinImages(info, gpu_addr, *cpu_addr); const Image& image = slot_images[image_id]; // Using "image.gpu_addr" instead of "gpu_addr" is important because it might be different @@ -1667,27 +1573,6 @@ template ImageId TextureCache

::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DAddr cpu_addr) { ImageInfo new_info = info; const size_t size_bytes = CalculateGuestSizeInBytes(new_info); - - // Proactive cleanup for large sparse texture allocations - if (lowmemorydevice && new_info.is_sparse && size_bytes >= 256_MiB) { - const u64 estimated_alloc_size = size_bytes; - - if (total_used_memory + estimated_alloc_size >= critical_memory) { - LOG_DEBUG(HW_GPU, "Large sparse texture allocation ({} MiB) - running aggressive GC. " - "Current memory: {} MiB, Critical: {} MiB", - size_bytes / (1024 * 1024), - total_used_memory / (1024 * 1024), - critical_memory / (1024 * 1024)); - RunGarbageCollector(); - - // If still over threshold after GC, try one more aggressive pass - if (total_used_memory + estimated_alloc_size >= critical_memory) { - LOG_DEBUG(HW_GPU, "Still critically low on memory, running second GC pass"); - RunGarbageCollector(); - } - } - } - const bool broken_views = runtime.HasBrokenTextureViewFormats(); const bool native_bgr = runtime.HasNativeBgr(); join_overlap_ids.clear(); diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 4b4061f21d..47f52c5c99 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -478,7 +478,6 @@ private: u64 minimum_memory; u64 expected_memory; u64 critical_memory; - bool lowmemorydevice = false; size_t gpu_unswizzle_maxsize = 0; size_t swizzle_chunk_size = 0; u32 swizzle_slices_per_batch = 0; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index a8a89aee89..7f2c29519f 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -33,6 +33,7 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE(KHR, VariablePointer, VARIABLE_POINTERS, variable_pointer) #define FOR_EACH_VK_FEATURE_1_2(FEATURE) \ + FEATURE(EXT, DescriptorIndexing, DESCRIPTOR_INDEXING, descriptor_indexing) \ FEATURE(EXT, HostQueryReset, HOST_QUERY_RESET, host_query_reset) \ FEATURE(KHR, 8BitStorage, 8BIT_STORAGE, bit8_storage) \ FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore) @@ -335,6 +336,18 @@ public: return properties.properties.limits.maxDescriptorSetUniformBuffersDynamic; } + u32 GetMaxPerStageDescriptorSampledImages() const { + return properties.properties.limits.maxPerStageDescriptorSampledImages; + } + + u32 GetMaxPerStageResources() const { + return properties.properties.limits.maxPerStageResources; + } + + u32 GetMaxDescriptorSetSampledImages() const { + return properties.properties.limits.maxDescriptorSetSampledImages; + } + /// Returns float control properties of the device. const VkPhysicalDeviceFloatControlsPropertiesKHR& FloatControlProperties() const { return properties.float_controls; @@ -350,11 +363,20 @@ public: return features.features.textureCompressionBC; } + /// Returns true if ETC2 is natively supported. + bool IsOptimalEtc2Supported() const { + return features.features.textureCompressionETC2; + } + /// Returns true if descriptor aliasing is natively supported. bool IsDescriptorAliasingSupported() const { return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY; } + bool IsSampledImageArrayNonUniformIndexingSupported() const { + return features.descriptor_indexing.shaderSampledImageArrayNonUniformIndexing; + } + /// Returns true if the device supports float64 natively. bool IsFloat64Supported() const { return features.features.shaderFloat64; diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 3adce1dd8b..0bddcca3e8 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -217,8 +217,6 @@ add_executable(yuzu util/url_request_interceptor.h util/util.cpp util/util.h - vk_device_info.cpp - vk_device_info.h compatdb.cpp compatdb.h user_data_migration.cpp @@ -245,9 +243,7 @@ add_executable(yuzu render/performance_overlay.h render/performance_overlay.cpp render/performance_overlay.ui libqt_common.h libqt_common.cpp - updater/update_dialog.h updater/update_dialog.cpp updater/update_dialog.ui - -) + updater/update_dialog.h updater/update_dialog.cpp updater/update_dialog.ui) set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden") @@ -411,8 +407,11 @@ endif() target_link_libraries(yuzu PRIVATE nlohmann_json::nlohmann_json) target_link_libraries(yuzu PRIVATE common core input_common frontend_common network video_core qt_common) -target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets Qt6::Charts Qt6::Concurrent) +target_link_libraries(yuzu PRIVATE Boost::headers Qt6::Widgets Qt6::Charts Qt6::Concurrent) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) +if (ENABLE_OPENGL) + target_link_libraries(yuzu PRIVATE glad) +endif() if (UNIX AND NOT APPLE) target_link_libraries(yuzu PRIVATE Qt6::DBus) @@ -463,6 +462,6 @@ if (NOT MSVC AND (APPLE OR NOT YUZU_STATIC_BUILD)) endif() # Remember that the linker is incredibly stupid. -target_link_libraries(yuzu PRIVATE OpenSSL::SSL OpenSSL::Crypto SDL2::SDL2) +target_link_libraries(yuzu PRIVATE OpenSSL::SSL OpenSSL::Crypto SDL3::SDL3) create_target_directory_groups(yuzu) diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 0c0ce6e90c..074b2360ee 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -6,13 +6,17 @@ #include #include #include -#include -#include + +#ifdef HAS_OPENGL #include +#endif #include #include "common/settings_enums.h" + #include "qt_common/config/uisettings.h" +#include "qt_common/abstract/frontend.h" + #if YUZU_USE_QT_MULTIMEDIA #include #include @@ -39,16 +43,13 @@ #include #endif -#include "common/polyfill_thread.h" #include "common/scm_rev.h" #include "common/settings.h" #include "common/settings_input.h" -#include "common/thread.h" #include "core/core.h" #include "core/cpu_manager.h" #include "core/frontend/framebuffer_layout.h" #include "core/frontend/graphics_context.h" -#include "input_common/drivers/camera.h" #include "input_common/drivers/keyboard.h" #include "input_common/drivers/mouse.h" #include "input_common/drivers/tas_input.h" @@ -56,189 +57,19 @@ #include "input_common/main.h" #include "qt_common/qt_common.h" #include "video_core/gpu.h" -#include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" #include "yuzu/bootmanager.h" #include "yuzu/main_window.h" +#include "qt_common/render/context.h" +#include "qt_common/render/emu_thread.h" + class QObject; class QPaintEngine; class QSurface; constexpr int default_mouse_constrain_timeout = 10; -EmuThread::EmuThread(Core::System& system) : m_system{system} {} - -EmuThread::~EmuThread() = default; - -void EmuThread::run() { - Common::SetCurrentThreadName("EmuControlThread"); - - auto& gpu = m_system.GPU(); - auto stop_token = m_stop_source.get_token(); - - m_system.RegisterHostThread(); - - // Main process has been loaded. Make the context current to this thread and begin GPU and CPU - // execution. - gpu.ObtainContext(); - - emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); - if (Settings::values.use_disk_shader_cache.GetValue()) { - m_system.Renderer().ReadRasterizer()->LoadDiskResources( - m_system.GetApplicationProcessProgramID(), stop_token, - [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { - emit LoadProgress(stage, value, total); - }); - } - emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); - - gpu.ReleaseContext(); - gpu.Start(); - - m_system.GetCpuManager().OnGpuReady(); - - if (m_system.DebuggerEnabled()) { - m_system.InitializeDebugger(); - } - - while (!stop_token.stop_requested()) { - std::unique_lock lk{m_should_run_mutex}; - if (m_should_run) { - m_system.Run(); - m_stopped.Reset(); - - m_should_run_cv.wait(lk, stop_token, [&] { return !m_should_run; }); - } else { - m_system.Pause(); - m_stopped.Set(); - - EmulationPaused(lk); - m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; }); - EmulationResumed(lk); - } - } - - // Shutdown the main emulated process - m_system.DetachDebugger(); - m_system.ShutdownMainProcess(); -} - -// Unlock while emitting signals so that the main thread can -// continue pumping events. - -void EmuThread::EmulationPaused(std::unique_lock& lk) { - lk.unlock(); - emit DebugModeEntered(); - lk.lock(); -} - -void EmuThread::EmulationResumed(std::unique_lock& lk) { - lk.unlock(); - emit DebugModeLeft(); - lk.lock(); -} - -#ifdef HAS_OPENGL -class OpenGLSharedContext : public Core::Frontend::GraphicsContext { -public: - /// Create the original context that should be shared from - explicit OpenGLSharedContext(QSurface* surface_) : surface{surface_} { - QSurfaceFormat format; - format.setVersion(4, 6); - format.setProfile(QSurfaceFormat::CompatibilityProfile); - format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); - if (Settings::values.renderer_debug) { - format.setOption(QSurfaceFormat::FormatOption::DebugContext); - } - // TODO: expose a setting for buffer value (ie default/single/double/triple) - format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); - format.setSwapInterval(0); - - context = std::make_unique(); - context->setFormat(format); - if (!context->create()) { - LOG_ERROR(Frontend, "Unable to create main openGL context"); - } - } - - /// Create the shared contexts for rendering and presentation - explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) { - - // disable vsync for any shared contexts - auto format = share_context->format(); - const int swap_interval = - Settings::values.vsync_mode.GetValue() == Settings::VSyncMode::Immediate ? 0 : 1; - - format.setSwapInterval(main_surface ? swap_interval : 0); - - context = std::make_unique(); - context->setShareContext(share_context); - context->setFormat(format); - if (!context->create()) { - LOG_ERROR(Frontend, "Unable to create shared openGL context"); - } - - if (!main_surface) { - offscreen_surface = std::make_unique(nullptr); - offscreen_surface->setFormat(format); - offscreen_surface->create(); - surface = offscreen_surface.get(); - } else { - surface = main_surface; - } - } - - ~OpenGLSharedContext() { - DoneCurrent(); - } - - void SwapBuffers() override { - if (auto window = static_cast(surface)) { - if (!window->isExposed()) { - LOG_DEBUG(Frontend, "SwapBuffers ignored: window not exposed"); - return; - } - } - - context->swapBuffers(surface); - } - - void MakeCurrent() override { - // We can't track the current state of the underlying context in this wrapper class because - // Qt may make the underlying context not current for one reason or another. In particular, - // the WebBrowser uses GL, so it seems to conflict if we aren't careful. - // Instead of always just making the context current (which does not have any caching to - // check if the underlying context is already current) we can check for the current context - // in the thread local data by calling `currentContext()` and checking if its ours. - if (QOpenGLContext::currentContext() != context.get()) { - context->makeCurrent(surface); - } - } - - void DoneCurrent() override { - context->doneCurrent(); - } - - QOpenGLContext* GetShareContext() { - return context.get(); - } - - const QOpenGLContext* GetShareContext() const { - return context.get(); - } - -private: - // Avoid using Qt parent system here since we might move the QObjects to new threads - // As a note, this means we should avoid using slots/signals with the objects too - std::unique_ptr context; - std::unique_ptr offscreen_surface{}; - QSurface* surface; -}; -#endif - -class DummyContext : public Core::Frontend::GraphicsContext {}; - class RenderWidget : public QWidget { public: explicit RenderWidget(GRenderWindow* parent) : QWidget(parent) { @@ -279,11 +110,9 @@ struct NullRenderWidget : public RenderWidget { explicit NullRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {} }; -GRenderWindow::GRenderWindow(MainWindow* parent, EmuThread* emu_thread_, - std::shared_ptr input_subsystem_, - Core::System& system_) - : QWidget(parent), emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)}, - system{system_} { +GRenderWindow::GRenderWindow(MainWindow* parent, + std::shared_ptr input_subsystem_) + : QWidget(parent), input_subsystem{std::move(input_subsystem_)} { setWindowTitle(QStringLiteral("Eden %1 | %2-%3") .arg(QString::fromUtf8(Common::g_build_name), QString::fromUtf8(Common::g_scm_branch), @@ -702,10 +531,11 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { } void GRenderWindow::ConstrainMouse() { - if (emu_thread == nullptr || !Settings::values.mouse_panning) { + if (QtCommon::emu_thread == nullptr || !Settings::values.mouse_panning) { mouse_constrain_timer.stop(); return; } + if (!this->isActiveWindow()) { mouse_constrain_timer.stop(); return; @@ -958,7 +788,7 @@ void GRenderWindow::ReleaseRenderTarget() { } void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) { - auto& renderer = system.Renderer(); + auto& renderer = QtCommon::system->Renderer(); if (renderer.IsScreenshotPending()) { LOG_WARNING(Render, @@ -983,7 +813,12 @@ void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) { screenshot_image.bits(), [=, this](bool invert_y) { const std::string std_screenshot_path = screenshot_path.toStdString(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + if (screenshot_image.flipped(invert_y ? Qt::Vertical : (Qt::Orientations)0) + .save(screenshot_path)) { +#else if (screenshot_image.mirrored(false, invert_y).save(screenshot_path)) { +#endif LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path); } else { LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path); @@ -1020,7 +855,8 @@ bool GRenderWindow::InitializeOpenGL() { return true; #else - QMessageBox::warning(this, tr("OpenGL not available!"), tr("Eden has not been compiled with OpenGL support.")); + QMessageBox::warning(this, tr("OpenGL not available!"), + tr("Eden has not been compiled with OpenGL support.")); return false; #endif } @@ -1039,11 +875,12 @@ void GRenderWindow::InitializeNull() { } bool GRenderWindow::LoadOpenGL() { +#ifdef HAS_OPENGL auto context = CreateSharedContext(); auto scope = context->Acquire(); if (!gladLoadGL()) { - QMessageBox::warning( - this, tr("Error while initializing OpenGL!"), + QtCommon::Frontend::Warning( + tr("Error while initializing OpenGL!"), tr("Your GPU may not support OpenGL, or you do not have the latest graphics driver.")); return false; } @@ -1052,27 +889,34 @@ bool GRenderWindow::LoadOpenGL() { const QString renderer = QString::fromUtf8(reinterpret_cast(glGetString(GL_RENDERER))); if (!GLAD_GL_VERSION_4_6) { - QMessageBox::warning(this, tr("Error while initializing OpenGL 4.6!"), - tr("Your GPU may not support OpenGL 4.6, or you do not have the " - "latest graphics driver.

GL Renderer:
%1") - .arg(renderer)); + QtCommon::Frontend::Warning( + tr("Error while initializing OpenGL 4.6!"), + tr("Your GPU may not support OpenGL 4.6, or you do not have the " + "latest graphics driver.

GL Renderer:
%1") + .arg(renderer)); return false; } if (QStringList missing_ext = GetUnsupportedGLExtensions(); !missing_ext.empty()) { - QMessageBox::warning( - this, tr("Error while initializing OpenGL!"), + QtCommon::Frontend::Warning( + tr("Error while initializing OpenGL!"), tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you " "have the latest graphics driver.

GL Renderer:
%1

Unsupported " "extensions:
%2") - .arg(renderer) - .arg(missing_ext.join(QStringLiteral("
")))); + .arg(renderer, missing_ext.join(QStringLiteral("
")))); // Non fatal } return true; +#else + QtCommon::Frontend::Warning( + tr("Error while initializing OpenGL!"), + tr("This build doesn't have OpenGL support.")); + return false; +#endif } QStringList GRenderWindow::GetUnsupportedGLExtensions() const { - QStringList missing_ext; + QStringList missing_ext{}; +#ifdef HAS_OPENGL // Extensions required to support some texture formats. if (!GLAD_GL_EXT_texture_compression_s3tc) missing_ext.append(QStringLiteral("EXT_texture_compression_s3tc")); @@ -1082,17 +926,10 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const { LOG_ERROR(Frontend, "GPU does not support all required extensions"); for (const QString& ext : missing_ext) LOG_ERROR(Frontend, "Unsupported GL extension: {}", ext.toStdString()); +#endif return missing_ext; } -void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread_) { - emu_thread = emu_thread_; -} - -void GRenderWindow::OnEmulationStopping() { - emu_thread = nullptr; -} - void GRenderWindow::showEvent(QShowEvent* event) { QWidget::showEvent(event); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index e763cd9868..1ed61a8191 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -6,12 +6,9 @@ #pragma once -#include #include #include -#include #include -#include #include #include @@ -27,9 +24,6 @@ #include #include "common/common_types.h" -#include "common/logging.h" -#include "common/polyfill_thread.h" -#include "common/thread.h" #include "core/frontend/emu_window.h" class MainWindow; @@ -46,6 +40,7 @@ class QShowEvent; class QTouchEvent; class QWheelEvent; +class EmuThread; namespace Core { class System; } // namespace Core @@ -63,96 +58,12 @@ namespace VideoCore { enum class LoadCallbackStage; } // namespace VideoCore -class EmuThread final : public QThread { - Q_OBJECT - -public: - explicit EmuThread(Core::System& system); - ~EmuThread() override; - - /** - * Start emulation (on new thread) - * @warning Only call when not running! - */ - void run() override; - - /** - * Sets whether the emulation thread should run or not - * @param should_run Boolean value, set the emulation thread to running if true - */ - void SetRunning(bool should_run) { - // TODO: Prevent other threads from modifying the state until we finish. - { - // Notify the running thread to change state. - std::unique_lock run_lk{m_should_run_mutex}; - m_should_run = should_run; - m_should_run_cv.notify_one(); - } - - // Wait until paused, if pausing. - if (!should_run) { - m_stopped.Wait(); - } - } - - /** - * Check if the emulation thread is running or not - * @return True if the emulation thread is running, otherwise false - */ - bool IsRunning() const { - return m_should_run; - } - - /** - * Requests for the emulation thread to immediately stop running - */ - void ForceStop() { - LOG_WARNING(Frontend, "Force stopping EmuThread"); - m_stop_source.request_stop(); - } - -private: - void EmulationPaused(std::unique_lock& lk); - void EmulationResumed(std::unique_lock& lk); - -private: - Core::System& m_system; - - std::stop_source m_stop_source; - std::mutex m_should_run_mutex; - std::condition_variable_any m_should_run_cv; - Common::Event m_stopped; - bool m_should_run{true}; - -signals: - /** - * Emitted when the CPU has halted execution - * - * @warning When connecting to this signal from other threads, make sure to specify either - * Qt::QueuedConnection (invoke slot within the destination object's message thread) or even - * Qt::BlockingQueuedConnection (additionally block source thread until slot returns) - */ - void DebugModeEntered(); - - /** - * Emitted right before the CPU continues execution - * - * @warning When connecting to this signal from other threads, make sure to specify either - * Qt::QueuedConnection (invoke slot within the destination object's message thread) or even - * Qt::BlockingQueuedConnection (additionally block source thread until slot returns) - */ - void DebugModeLeft(); - - void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); -}; - class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow { Q_OBJECT public: - explicit GRenderWindow(MainWindow* parent, EmuThread* emu_thread_, - std::shared_ptr input_subsystem_, - Core::System& system_); + explicit GRenderWindow(MainWindow* parent, + std::shared_ptr input_subsystem_); ~GRenderWindow() override; // EmuWindow implementation. @@ -217,8 +128,6 @@ public: void Exit(); public slots: - void OnEmulationStarting(EmuThread* emu_thread_); - void OnEmulationStopping(); void OnFramebufferSizeChanged(); signals: @@ -247,7 +156,6 @@ private: bool LoadOpenGL(); QStringList GetUnsupportedGLExtensions() const; - EmuThread* emu_thread; std::shared_ptr input_subsystem; // Main context that will be shared with all other contexts that are requested. @@ -277,8 +185,6 @@ private: QTimer mouse_constrain_timer; - Core::System& system; - protected: void showEvent(QShowEvent* event) override; bool eventFilter(QObject* object, QEvent* event) override; diff --git a/src/yuzu/configuration/addon/mod_select_dialog.cpp b/src/yuzu/configuration/addon/mod_select_dialog.cpp index 49f556a66a..f025a3b390 100644 --- a/src/yuzu/configuration/addon/mod_select_dialog.cpp +++ b/src/yuzu/configuration/addon/mod_select_dialog.cpp @@ -33,8 +33,8 @@ ModSelectDialog::ModSelectDialog(const QStringList& mods, QWidget* parent) ui->treeView->expandAll(); int rows = item_model->rowCount(); - int height = - 4 + ui->treeView->contentsMargins().top() * 4 + ui->treeView->contentsMargins().bottom() * 4; + int height = 4 + ui->treeView->contentsMargins().top() * 4 + + ui->treeView->contentsMargins().bottom() * 4; int width = 0; for (int i = 0; i < rows; ++i) { diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp index b31ca849a6..0511465ee6 100644 --- a/src/yuzu/configuration/configure_cpu.cpp +++ b/src/yuzu/configuration/configure_cpu.cpp @@ -76,7 +76,8 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) { } else if (setting->Id() == Settings::values.cpu_backend.Id()) { backend_layout->addWidget(widget); backend_combobox = widget->combobox; - } else if (setting->Id() == Settings::values.fast_cpu_time.Id() || setting->Id() == Settings::values.cpu_ticks.Id()) { + } else if (setting->Id() == Settings::values.fast_cpu_time.Id() || + setting->Id() == Settings::values.cpu_ticks.Id()) { ui->general_layout->addWidget(widget); } else { // Presently, all other settings here are unsafe checkboxes diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 9db48c084b..c7ca85ce99 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -51,8 +51,10 @@ void ConfigureDebug::SetConfiguration() { ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue()); ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue()); ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.GetValue()); - ui->serial_battery_edit->setText(QString::fromStdString(std::to_string(Settings::values.serial_battery.GetValue()))); - ui->serial_board_edit->setText(QString::fromStdString(std::to_string(Settings::values.serial_unit.GetValue()))); + ui->serial_battery_edit->setText( + QString::fromStdString(std::to_string(Settings::values.serial_battery.GetValue()))); + ui->serial_board_edit->setText( + QString::fromStdString(std::to_string(Settings::values.serial_unit.GetValue()))); #ifdef YUZU_USE_QT_WEB_ENGINE ui->disable_web_applet->setChecked(Settings::values.disable_web_applet.GetValue()); #else diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index d4a16e9f73..547008262e 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -10,8 +10,8 @@ #include "common/settings_enums.h" #include "core/core.h" #include "qt_common/config/uisettings.h" +#include "qt_common/util/vk.h" #include "ui_configure.h" -#include "vk_device_info.h" #include "yuzu/configuration/configure_applets.h" #include "yuzu/configuration/configure_audio.h" #include "yuzu/configuration/configure_cpu.h" diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index 5c504c9a16..73db11238e 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -11,8 +11,8 @@ #include #include "configuration/shared_widget.h" #include "qt_common/config/shared_translation.h" +#include "qt_common/util/vk.h" #include "yuzu/configuration/configuration_shared.h" -#include "yuzu/vk_device_info.h" namespace Core { class System; diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 54835aea5a..344cbe2406 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -4,14 +4,7 @@ // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include #include -#include -#include -#include -#include -#include -#include #include #include #include @@ -34,52 +27,15 @@ #include #include "common/common_types.h" -#include "common/dynamic_library.h" -#include "common/logging.h" #include "common/settings.h" #include "common/settings_enums.h" #include "core/core.h" #include "qt_common/config/uisettings.h" -#include "qt_common/qt_common.h" +#include "qt_common/util/vk.h" #include "ui_configure_graphics.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics.h" #include "yuzu/configuration/shared_widget.h" -#include "yuzu/vk_device_info.h" - -static const std::vector default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR, - VK_PRESENT_MODE_FIFO_KHR}; - -// Converts a setting to a present mode (or vice versa) -static constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) { - switch (mode) { - case Settings::VSyncMode::Immediate: - return VK_PRESENT_MODE_IMMEDIATE_KHR; - case Settings::VSyncMode::Mailbox: - return VK_PRESENT_MODE_MAILBOX_KHR; - case Settings::VSyncMode::Fifo: - return VK_PRESENT_MODE_FIFO_KHR; - case Settings::VSyncMode::FifoRelaxed: - return VK_PRESENT_MODE_FIFO_RELAXED_KHR; - default: - return VK_PRESENT_MODE_FIFO_KHR; - } -} - -static constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) { - switch (mode) { - case VK_PRESENT_MODE_IMMEDIATE_KHR: - return Settings::VSyncMode::Immediate; - case VK_PRESENT_MODE_MAILBOX_KHR: - return Settings::VSyncMode::Mailbox; - case VK_PRESENT_MODE_FIFO_KHR: - return Settings::VSyncMode::Fifo; - case VK_PRESENT_MODE_FIFO_RELAXED_KHR: - return Settings::VSyncMode::FifoRelaxed; - default: - return Settings::VSyncMode::Fifo; - } -} ConfigureGraphics::ConfigureGraphics( const Core::System& system_, std::vector& records_, diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 3009edf4b6..9002af437b 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.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: 2016 Citra Emulator Project @@ -19,7 +19,7 @@ #include "common/common_types.h" #include "common/settings_enums.h" #include "qt_common/config/shared_translation.h" -#include "vk_device_info.h" +#include "qt_common/util/vk.h" #include "yuzu/configuration/configuration_shared.h" class QPushButton; diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index dcf40d678f..f84448dd29 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -30,6 +30,7 @@ #include "core/loader/loader.h" #include "frontend_common/config.h" #include "qt_common/config/uisettings.h" +#include "qt_common/util/vk.h" #include "ui_configure_per_game.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_applets.h" @@ -44,7 +45,6 @@ #include "yuzu/configuration/configure_per_game_addons.h" #include "yuzu/configuration/configure_system.h" #include "yuzu/util/util.h" -#include "yuzu/vk_device_info.h" ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, std::vector& vk_device_records, diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h index ca973db8b0..bc6561235e 100644 --- a/src/yuzu/configuration/configure_per_game.h +++ b/src/yuzu/configuration/configure_per_game.h @@ -18,7 +18,7 @@ #include "frontend_common/config.h" #include "qt_common/config/qt_config.h" #include "qt_common/config/shared_translation.h" -#include "vk_device_info.h" +#include "qt_common/util/vk.h" #include "yuzu/configuration/configuration_shared.h" namespace Core { diff --git a/src/yuzu/data_dialog.cpp b/src/yuzu/data_dialog.cpp index 8d50843eb3..ffdedcf1c2 100644 --- a/src/yuzu/data_dialog.cpp +++ b/src/yuzu/data_dialog.cpp @@ -63,27 +63,31 @@ DataWidget::DataWidget(FrontendCommon::DataManager::DataDir data_dir, void DataWidget::clear() { std::optional user_id = selectProfile(); - if (!user_id) return; + if (!user_id) + return; QtCommon::Content::ClearDataDir(m_dir, user_id.value()); scan(); } void DataWidget::open() { std::optional user_id = selectProfile(); - if (!user_id) return; - QDesktopServices::openUrl(QUrl::fromLocalFile( - QString::fromStdString(FrontendCommon::DataManager::GetDataDirString(m_dir, user_id.value())))); + if (!user_id) + return; + QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString( + FrontendCommon::DataManager::GetDataDirString(m_dir, user_id.value())))); } void DataWidget::upload() { std::optional user_id = selectProfile(); - if (!user_id) return; + if (!user_id) + return; QtCommon::Content::ExportDataDir(m_dir, user_id.value(), m_exportName); } void DataWidget::download() { std::optional user_id = selectProfile(); - if (!user_id) return; + if (!user_id) + return; QtCommon::Content::ImportDataDir(m_dir, user_id.value(), std::bind(&DataWidget::scan, this)); } @@ -107,7 +111,8 @@ std::optional DataWidget::selectProfile() { std::string user_id{}; if (m_dir == FrontendCommon::DataManager::DataDir::Saves) { user_id = GetProfileIDString(); - if (user_id.empty()) return std::nullopt; + if (user_id.empty()) + return std::nullopt; } return user_id; diff --git a/src/yuzu/debugger/console.cpp b/src/yuzu/debugger/console.cpp index 8965c7a08f..3506f1edf3 100644 --- a/src/yuzu/debugger/console.cpp +++ b/src/yuzu/debugger/console.cpp @@ -11,7 +11,6 @@ #endif #include "common/logging.h" -#include "yuzu/debugger/console.h" #include "qt_common/config/uisettings.h" #include "yuzu/debugger/console.h" diff --git a/src/yuzu/game/game_card.cpp b/src/yuzu/game/game_card.cpp index 05167fe455..03c71449f8 100644 --- a/src/yuzu/game/game_card.cpp +++ b/src/yuzu/game/game_card.cpp @@ -36,7 +36,8 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, const auto x_pos = option.rect.left() - (column * cell_width) + static_cast(relative_x); // also, add some additional padding here to prevent card overlap - QRect cardRect(x_pos + 4, option.rect.top() + 4, fixed_card_width - 8, option.rect.height() - margins); + QRect cardRect(x_pos + 4, option.rect.top() + 4, fixed_card_width - 8, + option.rect.height() - margins); // colors QPalette palette = option.palette; diff --git a/src/yuzu/game/game_list.cpp b/src/yuzu/game/game_list.cpp index 87b5f87474..b5967bacca 100644 --- a/src/yuzu/game/game_list.cpp +++ b/src/yuzu/game/game_list.cpp @@ -1113,10 +1113,6 @@ void GameList::LoadInterfaceLayout() { header->resizeSection(COLUMN_NAME, 840); } -const QStringList GameList::supported_file_extensions = { - QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"), - QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; - void GameList::RefreshGameDirectory() { // Reset the externals watcher whenever the game list is reloaded, // primarily ensures that new titles and external dirs are caught. diff --git a/src/yuzu/game/game_list.h b/src/yuzu/game/game_list.h index 42efcbdc3b..6cf857c4e1 100644 --- a/src/yuzu/game/game_list.h +++ b/src/yuzu/game/game_list.h @@ -95,8 +95,6 @@ public: /// Disables events from the emulated controller void UnloadController(); - static const QStringList supported_file_extensions; - bool IsTreeMode(); void ResetViewMode(); diff --git a/src/yuzu/game/game_list_worker.cpp b/src/yuzu/game/game_list_worker.cpp index 777122e135..df3560a813 100644 --- a/src/yuzu/game/game_list_worker.cpp +++ b/src/yuzu/game/game_list_worker.cpp @@ -20,17 +20,18 @@ #include "common/settings.h" #include "core/core.h" #include "core/file_sys/card_image.h" -#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/fs_filesystem.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" -#include "core/file_sys/romfs.h" #include "core/file_sys/submission_package.h" #include "core/loader/loader.h" + #include "qt_common/config/uisettings.h" +#include "qt_common/qt_common.h" + #include "yuzu/compatibility_list.h" #include "yuzu/game/game_list.h" #include "yuzu/game/game_list_p.h" @@ -148,7 +149,7 @@ void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const bool HasSupportedFileExtension(const std::string& file_name) { const QFileInfo file = QFileInfo(QString::fromStdString(file_name)); - return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); + return QtCommon::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); } bool IsExtractedNCAMain(const std::string& file_name) { diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 7549325567..3ca4b40a6d 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -2,17 +2,18 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Qt on macOS doesn't define VMA shit -#include -#include "common/settings.h" -#include "common/settings_enums.h" -#include "frontend_common/settings_generator.h" -#include "qt_common/qt_string_lookup.h" -#include "render/performance_overlay.h" -#include "updater/update_dialog.h" #if defined(QT_STATICPLUGIN) && !defined(__APPLE__) #undef VMA_IMPLEMENTATION #endif +#include +#include "common/fs/path_util.h" +#include "common/settings.h" +#include "common/settings_enums.h" +#include "frontend_common/settings_generator.h" +#include "render/performance_overlay.h" +#include "updater/update_dialog.h" + #include "common/fs/ryujinx_compat.h" #include "main_window.h" #include "network/network.h" @@ -30,10 +31,10 @@ #include "bootmanager.h" #include "loading_screen.h" +#include "qt_common/util/vk.h" #include "ryujinx_dialog.h" #include "set_play_time_dialog.h" #include "util/util.h" -#include "vk_device_info.h" #include "yuzu/game/game_list.h" #include "applets/qt_amiibo_settings.h" @@ -85,6 +86,7 @@ #include "qt_common/abstract/frontend.h" #include "qt_common/qt_common.h" +#include "qt_common/qt_string_lookup.h" #include "qt_common/util/content.h" #include "qt_common/util/fs.h" @@ -92,6 +94,8 @@ #include "qt_common/util/mod.h" #include "qt_common/util/path.h" +#include "qt_common/render/emu_thread.h" + // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper(const std::string& path, @@ -153,7 +157,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "video_core/renderer_base.h" #include "video_core/shader_notify.h" -#include +#include #include @@ -314,73 +318,6 @@ enum class CalloutFlag : uint32_t { const int MainWindow::max_recent_files_item; -static void RemoveCachedContents() { - const auto cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir); - const auto offline_fonts = cache_dir / "fonts"; - const auto offline_manual = cache_dir / "offline_web_applet_manual"; - const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information"; - const auto offline_system_data = cache_dir / "offline_web_applet_system_data"; - - Common::FS::RemoveDirRecursively(offline_fonts); - Common::FS::RemoveDirRecursively(offline_manual); - Common::FS::RemoveDirRecursively(offline_legal_information); - Common::FS::RemoveDirRecursively(offline_system_data); -} - -static void LogRuntimes() { -#ifdef _MSC_VER - // It is possible that the name of the dll will change. - // vcruntime140.dll is for 2015 and onwards - static constexpr char runtime_dll_name[] = "vcruntime140.dll"; - UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr); - bool runtime_version_inspection_worked = false; - if (sz > 0) { - std::vector buf(sz); - if (GetFileVersionInfoA(runtime_dll_name, 0, sz, buf.data())) { - VS_FIXEDFILEINFO* pvi; - sz = sizeof(VS_FIXEDFILEINFO); - if (VerQueryValueA(buf.data(), "\\", reinterpret_cast(&pvi), &sz)) { - if (pvi->dwSignature == VS_FFI_SIGNATURE) { - runtime_version_inspection_worked = true; - LOG_INFO(Frontend, "MSVC Compiler: {} Runtime: {}.{}.{}.{}", _MSC_VER, - pvi->dwProductVersionMS >> 16, pvi->dwProductVersionMS & 0xFFFF, - pvi->dwProductVersionLS >> 16, pvi->dwProductVersionLS & 0xFFFF); - } - } - } - } - if (!runtime_version_inspection_worked) { - LOG_INFO(Frontend, "Unable to inspect {}", runtime_dll_name); - } -#endif - LOG_INFO(Frontend, "Qt Compile: {} Runtime: {}", QT_VERSION_STR, qVersion()); -} - -static QString PrettyProductName() { -#ifdef _WIN32 - // After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2 - // With that notation change they changed the registry key used to denote the current version - QSettings windows_registry( - QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), - QSettings::NativeFormat); - const QString release_id = windows_registry.value(QStringLiteral("ReleaseId")).toString(); - if (release_id == QStringLiteral("2009")) { - const u32 current_build = windows_registry.value(QStringLiteral("CurrentBuild")).toUInt(); - const QString display_version = - windows_registry.value(QStringLiteral("DisplayVersion")).toString(); - const u32 ubr = windows_registry.value(QStringLiteral("UBR")).toUInt(); - u32 version = 10; - if (current_build >= 22000) { - version = 11; - } - return QStringLiteral("Windows %1 Version %2 (Build %3.%4)") - .arg(QString::number(version), display_version, QString::number(current_build), - QString::number(ubr)); - } -#endif - return QSysInfo::prettyProductName(); -} - namespace { constexpr std::array, 5> default_game_icon_sizes{ @@ -446,11 +383,6 @@ MainWindow::MainWindow(bool has_broken_vulkan) UISettings::RestoreWindowState(config); - QtCommon::system->Initialize(); - - Common::Log::Initialize(); - Common::Log::Start(); - LoadTranslation(); FrontendCommon::GenerateSettings(); @@ -471,10 +403,6 @@ MainWindow::MainWindow(bool has_broken_vulkan) play_time_manager = std::make_unique(); - Network::Init(); - - QtCommon::Meta::RegisterMetaTypes(); - InitializeWidgets(); InitializeDebugWidgets(); InitializeRecentFileMenuActions(); @@ -486,53 +414,9 @@ MainWindow::MainWindow(bool has_broken_vulkan) ConnectMenuEvents(); ConnectWidgetEvents(); - QtCommon::system->HIDCore().ReloadInputDevices(); + QtCommon::SetupHID(); controller_dialog->refreshConfiguration(); - const auto branch_name = std::string(Common::g_scm_branch); - const auto description = std::string(Common::g_scm_desc); - const auto build_id = std::string(Common::g_build_id); - - const auto yuzu_build = fmt::format("Eden Development Build | {}-{}", branch_name, description); - const auto override_build = - fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id); - const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build; - const auto processor_count = std::thread::hardware_concurrency(); - - LOG_INFO(Frontend, "Eden Version: {}", yuzu_build_version); - LogRuntimes(); -#ifdef ARCHITECTURE_x86_64 - const auto& caps = Common::GetCPUCaps(); - std::string cpu_string = caps.cpu_string; - if (caps.avx || caps.avx2 || caps.avx512f) { - cpu_string += " | AVX"; - if (caps.avx512f) { - cpu_string += "512"; - } else if (caps.avx2) { - cpu_string += '2'; - } - if (caps.fma || caps.fma4) { - cpu_string += " | FMA"; - } - } - LOG_INFO(Frontend, "Host CPU: {}", cpu_string); - if (std::optional processor_core = Common::GetProcessorCount()) { - LOG_INFO(Frontend, "Host CPU Cores: {}", *processor_core); - } -#endif - LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count); - LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString()); - LOG_INFO(Frontend, "Host RAM: {:.2f} GiB", - Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB}); - LOG_INFO(Frontend, "Host Swap: {:.2f} GiB", Common::GetMemInfo().TotalSwapMemory / f64{1_GiB}); -#ifdef _WIN32 - LOG_INFO(Frontend, "Host Timer Resolution: {:.4f} ms", - std::chrono::duration_cast>( - Common::Windows::SetCurrentTimerResolutionToMaximum()) - .count()); - QtCommon::system->CoreTiming().SetTimerResolutionNs( - Common::Windows::GetCurrentTimerResolution()); -#endif UpdateWindowTitle(); show(); @@ -547,13 +431,8 @@ MainWindow::MainWindow(bool has_broken_vulkan) } #endif - QtCommon::system->SetContentProvider(std::make_unique()); - QtCommon::system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, - QtCommon::provider.get()); - QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); - - // Remove cached contents generated during the previous session - RemoveCachedContents(); + // Setup content providers. + QtCommon::SetupContentProviders(); // Gen keys if necessary OnCheckFirmwareDecryption(); @@ -635,6 +514,7 @@ MainWindow::MainWindow(bool has_broken_vulkan) QString game_path; bool should_launch_qlaunch = false; + bool should_launch_hlaunch = false; bool should_launch_setup = false; bool has_gamepath = false; bool is_fullscreen = false; @@ -676,6 +556,8 @@ MainWindow::MainWindow(bool has_broken_vulkan) players[0].profile_name = args[++i].toStdString(); } else if (args[i] == QStringLiteral("-qlaunch")) { should_launch_qlaunch = true; + } else if (args[i] == QStringLiteral("-hlaunch")) { + should_launch_hlaunch = true; } else if (args[i] == QStringLiteral("-setup")) { should_launch_setup = true; } else { @@ -694,10 +576,12 @@ MainWindow::MainWindow(bool has_broken_vulkan) } else { if (!game_path.isEmpty()) { BootGame(game_path, ApplicationAppletParameters()); - } else { - if (should_launch_qlaunch) { - LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt); - } + } else if (should_launch_qlaunch) { + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt); + } else if (should_launch_hlaunch) { + std::filesystem::path const sd_dir = Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir); + auto const hbl_path = (sd_dir / "atmosphere" / "hbl.nsp").string(); + BootGame(QString::fromStdString(hbl_path), ApplicationAppletParameters()); } } } @@ -1073,7 +957,7 @@ void MainWindow::InitializeWidgets() { #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING ui->action_Report_Compatibility->setVisible(true); #endif - render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *QtCommon::system); + render_window = new GRenderWindow(this, input_subsystem); render_window->hide(); game_list = new GameList(QtCommon::vfs, QtCommon::provider.get(), *play_time_manager, @@ -1533,11 +1417,12 @@ void MainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) { return; } if (UISettings::values.pause_when_in_background) { - if (emu_thread->IsRunning() && + if (QtCommon::emu_thread->IsRunning() && (state & (Qt::ApplicationHidden | Qt::ApplicationInactive))) { auto_paused = true; OnPauseGame(); - } else if (!emu_thread->IsRunning() && auto_paused && (state & Qt::ApplicationActive)) { + } else if (!QtCommon::emu_thread->IsRunning() && auto_paused && + (state & Qt::ApplicationActive)) { auto_paused = false; OnStartGame(); } @@ -1589,11 +1474,6 @@ void MainWindow::ConnectWidgetEvents() { connect(this, &MainWindow::UpdateInstallProgress, this, &MainWindow::IncrementInstallProgress); - connect(this, &MainWindow::EmulationStarting, render_window, - &GRenderWindow::OnEmulationStarting); - connect(this, &MainWindow::EmulationStopping, render_window, - &GRenderWindow::OnEmulationStopping); - // Software Keyboard Applet connect(this, &MainWindow::EmulationStarting, this, &MainWindow::SoftwareKeyboardExit); connect(this, &MainWindow::EmulationStopping, this, &MainWindow::SoftwareKeyboardExit); @@ -1747,7 +1627,7 @@ void MainWindow::ConnectMenuEvents() { } void MainWindow::UpdateMenuState() { - const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning(); + const bool is_paused = QtCommon::emu_thread == nullptr || !QtCommon::emu_thread->IsRunning(); const bool is_firmware_available = CheckFirmwarePresence(); const std::array running_actions{ @@ -1815,17 +1695,16 @@ void MainWindow::SetupPrepareForSleep() { } void MainWindow::OnPrepareForSleep(bool prepare_sleep) { - if (emu_thread == nullptr) { + if (QtCommon::emu_thread == nullptr) return; - } if (prepare_sleep) { - if (emu_thread->IsRunning()) { + if (QtCommon::emu_thread->IsRunning()) { auto_paused = true; OnPauseGame(); } } else { - if (!emu_thread->IsRunning() && auto_paused) { + if (!QtCommon::emu_thread->IsRunning() && auto_paused) { auto_paused = false; OnStartGame(); } @@ -1898,19 +1777,16 @@ void MainWindow::AllowOSSleep() { bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletParameters params) { // Shutdown previous session if the emu thread is still active... - if (emu_thread != nullptr) { + if (QtCommon::emu_thread != nullptr) ShutdownGame(); - } - if (!render_window->InitRenderTarget()) { + if (!render_window->InitRenderTarget()) return false; - } QtCommon::system->SetFilesystem(QtCommon::vfs); - if (params.launch_type == Service::AM::LaunchType::FrontendInitiated) { + if (params.launch_type == Service::AM::LaunchType::FrontendInitiated) QtCommon::system->GetUserChannel().clear(); - } QtCommon::system->SetFrontendAppletSet({ std::make_unique(*this), // Amiibo Settings @@ -2017,36 +1893,6 @@ bool MainWindow::SelectAndSetCurrentUser( return true; } -void MainWindow::ConfigureFilesystemProvider(const std::string& filepath) { - // Ensure all NCAs are registered before launching the game - const auto file = QtCommon::vfs->OpenFile(filepath, FileSys::OpenMode::Read); - if (!file) { - return; - } - - if (QtCommon::provider->AddEntriesFromContainer(file)) { - return; - } - - auto loader = Loader::GetLoader(*QtCommon::system, file); - if (!loader) { - return; - } - - const auto file_type = loader->GetFileType(); - if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { - return; - } - - u64 program_id = 0; - const auto res2 = loader->ReadProgramId(program_id); - if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { - QtCommon::provider->AddEntry(FileSys::TitleType::Application, - FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), - program_id, file); - } -} - void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletParameters params, StartGameType type) { LOG_INFO(Frontend, "Eden starting..."); @@ -2065,7 +1911,7 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa last_filename_booted = filename; - ConfigureFilesystemProvider(filename.toStdString()); + QtCommon::Content::configureFilesystemProvider(filename.toStdString()); const auto v_file = Core::GetGameFileFromPath(QtCommon::vfs, filename.toUtf8().constData()); const auto loader = Loader::GetLoader(*QtCommon::system, v_file, params.program_id, params.program_index); @@ -2110,23 +1956,23 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa game_list->setDisabled(true); // Create and start the emulation thread - emu_thread = std::make_unique(*QtCommon::system); - emit EmulationStarting(emu_thread.get()); - emu_thread->start(); + QtCommon::emu_thread = std::make_unique(); + emit EmulationStarting(); + QtCommon::emu_thread->start(); // Register an ExecuteProgram callback such that Core can execute a sub-program QtCommon::system->RegisterExecuteProgramCallback( [this](std::size_t program_index_) { render_window->ExecuteProgram(program_index_); }); QtCommon::system->RegisterExitCallback([this] { - emu_thread->ForceStop(); + QtCommon::emu_thread->ForceStop(); render_window->Exit(); }); connect(render_window, &GRenderWindow::Closed, this, &MainWindow::OnStopGame); connect(render_window, &GRenderWindow::MouseActivity, this, &MainWindow::OnMouseActivity); - connect(emu_thread.get(), &EmuThread::LoadProgress, loading_screen, + connect(QtCommon::emu_thread.get(), &EmuThread::LoadProgress, loading_screen, &LoadingScreen::OnLoadProgress, Qt::QueuedConnection); // Update the GUI @@ -2221,9 +2067,10 @@ bool MainWindow::OnShutdownBegin() { discord_rpc->Pause(); RequestGameExit(); - emu_thread->disconnect(); - emu_thread->SetRunning(true); + QtCommon::emu_thread->disconnect(); + QtCommon::emu_thread->SetRunning(true); + connect(QtCommon::emu_thread.get(), &QThread::finished, this, &MainWindow::OnEmulationStopped); emit EmulationStopping(); int shutdown_time = 1000; @@ -2237,7 +2084,6 @@ bool MainWindow::OnShutdownBegin() { shutdown_timer.setSingleShot(true); shutdown_timer.start(shutdown_time); connect(&shutdown_timer, &QTimer::timeout, this, &MainWindow::OnEmulationStopTimeExpired); - connect(emu_thread.get(), &QThread::finished, this, &MainWindow::OnEmulationStopped); // Disable everything to prevent anything from being triggered here ui->action_Pause->setEnabled(false); @@ -2255,17 +2101,17 @@ void MainWindow::OnShutdownBeginDialog() { } void MainWindow::OnEmulationStopTimeExpired() { - if (emu_thread) { - emu_thread->ForceStop(); + if (QtCommon::emu_thread) { + QtCommon::emu_thread->ForceStop(); } } void MainWindow::OnEmulationStopped() { shutdown_timer.stop(); - if (emu_thread) { - emu_thread->disconnect(); - emu_thread->wait(); - emu_thread.reset(); + if (QtCommon::emu_thread) { + QtCommon::emu_thread->disconnect(); + QtCommon::emu_thread->wait(); + QtCommon::emu_thread.reset(); } if (shutdown_dialog) { @@ -2906,7 +2752,7 @@ void MainWindow::OnMenuLoadFile() { is_load_file_select_active = true; const QString extensions = QStringLiteral("*.") - .append(GameList::supported_file_extensions.join(QStringLiteral(" *."))) + .append(QtCommon::supported_file_extensions.join(QStringLiteral(" *."))) .append(QStringLiteral(" main")); const QString file_filter = tr("Switch Executable (%1);;All Files (*.*)", "%1 is an identifier for the Switch executable file extensions.") @@ -3142,7 +2988,7 @@ void MainWindow::OnMenuRecentFile() { void MainWindow::OnStartGame() { PreventOSSleep(); - emu_thread->SetRunning(true); + QtCommon::emu_thread->SetRunning(true); UpdateMenuState(); OnTasStateChanged(); @@ -3168,7 +3014,7 @@ void MainWindow::OnRestartGame() { } void MainWindow::OnPauseGame() { - emu_thread->SetRunning(false); + QtCommon::emu_thread->SetRunning(false); play_time_manager->Stop(); UpdateMenuState(); AllowOSSleep(); @@ -3177,7 +3023,7 @@ void MainWindow::OnPauseGame() { void MainWindow::OnPauseContinueGame() { if (emulation_running) { - if (emu_thread->IsRunning()) { + if (QtCommon::emu_thread->IsRunning()) { OnPauseGame(); } else { OnStartGame(); @@ -3857,12 +3703,9 @@ void MainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_ } void MainWindow::OnLoadAmiibo() { - if (emu_thread == nullptr || !emu_thread->IsRunning()) { + if (QtCommon::emu_thread == nullptr || !QtCommon::emu_thread->IsRunning() || + is_amiibo_file_select_active) return; - } - if (is_amiibo_file_select_active) { - return; - } auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); @@ -3966,79 +3809,21 @@ void MainWindow::OnVerifyInstalledContents() { QtCommon::Content::VerifyInstalledContents(); } -void MainWindow::InstallFirmware(const QString& location, bool recursive) { - QtCommon::Content::InstallFirmware(location, recursive); +void MainWindow::OnInstallFirmware() { + QtCommon::Content::InstallFirmware(); OnCheckFirmwareDecryption(); } -void MainWindow::OnInstallFirmware() { - // Don't do this while emulation is running, that'd probably be a bad idea. - if (emu_thread != nullptr && emu_thread->IsRunning()) { - return; - } - - // Check for installed keys, error out, suggest restart? - if (!ContentManager::AreKeysPresent()) { - QMessageBox::information( - this, tr("Keys not installed"), - tr("Install decryption keys and restart Eden before attempting to install firmware.")); - return; - } - - const QString firmware_source_location = QFileDialog::getExistingDirectory( - this, tr("Select Dumped Firmware Source Location"), {}, QFileDialog::ShowDirsOnly); - if (firmware_source_location.isEmpty()) { - return; - } - - InstallFirmware(firmware_source_location); -} - void MainWindow::OnInstallFirmwareFromZIP() { - // Don't do this while emulation is running, that'd probably be a bad idea. - if (emu_thread != nullptr && emu_thread->IsRunning()) { - return; - } - - // Check for installed keys, error out, suggest restart? - if (!ContentManager::AreKeysPresent()) { - QMessageBox::information( - this, tr("Keys not installed"), - tr("Install decryption keys and restart Eden before attempting to install firmware.")); - return; - } - - const QString firmware_zip_location = QFileDialog::getOpenFileName( - this, tr("Select Dumped Firmware ZIP"), {}, tr("Zipped Archives (*.zip)")); - if (firmware_zip_location.isEmpty()) { - return; - } - - const QString qCacheDir = QtCommon::Content::UnzipFirmwareToTmp(firmware_zip_location); - - // In this case, it has to be done recursively, since sometimes people - // will pack it into a subdirectory after dumping - if (!qCacheDir.isEmpty()) { - InstallFirmware(qCacheDir, true); - std::error_code ec; - std::filesystem::remove_all(std::filesystem::temp_directory_path() / "eden" / "firmware", - ec); - - if (ec) { - QMessageBox::warning(this, tr("Firmware cleanup failed"), - tr("Failed to clean up extracted firmware cache.\n" - "Check write permissions in the system temp directory and try " - "again.\nOS reported error: %1") - .arg(QString::fromStdString(ec.message()))); - } - } + QtCommon::Content::InstallFirmwareZip(); + OnCheckFirmwareDecryption(); } +// TODO(crueter): QtCommon this: game list populate can be a signal? void MainWindow::OnInstallDecryptionKeys() { // Don't do this while emulation is running. - if (emu_thread != nullptr && emu_thread->IsRunning()) { + if (QtCommon::emu_thread != nullptr && QtCommon::emu_thread->IsRunning()) return; - } QtCommon::Content::InstallKeys(); @@ -4066,11 +3851,10 @@ void MainWindow::OnDataDialog() { void MainWindow::OnToggleFilterBar() { game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked()); - if (ui->action_Show_Filter_Bar->isChecked()) { + if (ui->action_Show_Filter_Bar->isChecked()) game_list->SetFilterFocus(); - } else { + else game_list->ClearFilter(); - } } void MainWindow::OnToggleStatusBar() { @@ -4078,9 +3862,8 @@ void MainWindow::OnToggleStatusBar() { } void MainWindow::OnTogglePerfOverlay() { - if (perf_overlay) { + if (perf_overlay) perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked()); - } } void MainWindow::OnGameListRefresh() { @@ -4185,9 +3968,8 @@ void MainWindow::OnCreateHomeMenuApplicationMenuShortcut() { } void MainWindow::OnCaptureScreenshot() { - if (emu_thread == nullptr || !emu_thread->IsRunning()) { + if (QtCommon::emu_thread == nullptr || !QtCommon::emu_thread->IsRunning()) return; - } const u64 title_id = QtCommon::system->GetApplicationProcessProgramID(); const auto screenshot_path = @@ -4199,9 +3981,8 @@ void MainWindow::OnCaptureScreenshot() { .arg(title_id, 16, 16, QLatin1Char{'0'}) .arg(date); - if (!Common::FS::CreateDir(screenshot_path.toStdString())) { + if (!Common::FS::CreateDir(screenshot_path.toStdString())) return; - } #ifdef _WIN32 if (UISettings::values.enable_screenshot_save_as) { @@ -4307,16 +4088,15 @@ void MainWindow::OnTasStateChanged() { } void MainWindow::UpdateStatusBar() { - if (emu_thread == nullptr || !QtCommon::system->IsPoweredOn()) { + if (QtCommon::emu_thread == nullptr || !QtCommon::system->IsPoweredOn()) { status_bar_update_timer.stop(); return; } - if (Settings::values.tas_enable) { + if (Settings::values.tas_enable) tas_label->setText(GetTasStateDescription()); - } else { + else tas_label->clear(); - } auto results = QtCommon::system->GetAndResetPerfStats(); auto& shader_notify = QtCommon::system->GPU().ShaderNotify(); @@ -4438,14 +4218,13 @@ void MainWindow::UpdateUISettings() { } void MainWindow::UpdateInputDrivers() { - if (!input_subsystem) { + if (!input_subsystem) return; - } input_subsystem->PumpEvents(); } void MainWindow::HideMouseCursor() { - if (emu_thread == nullptr && UISettings::values.hide_mouse) { + if (QtCommon::emu_thread == nullptr && UISettings::values.hide_mouse) { mouse_hide_timer.stop(); ShowMouseCursor(); return; @@ -4455,9 +4234,8 @@ void MainWindow::HideMouseCursor() { void MainWindow::ShowMouseCursor() { render_window->unsetCursor(); - if (emu_thread != nullptr && UISettings::values.hide_mouse) { + if (QtCommon::emu_thread != nullptr && UISettings::values.hide_mouse) mouse_hide_timer.start(); - } } void MainWindow::OnMouseActivity() { @@ -4637,14 +4415,14 @@ bool MainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed } bool MainWindow::ConfirmClose() { - if (emu_thread == nullptr || - UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Never) { + if (QtCommon::emu_thread == nullptr || + UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Never) return true; - } + if (!QtCommon::system->GetExitLocked() && - UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Based_On_Game) { + UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Based_On_Game) return true; - } + const auto text = tr("Are you sure you want to close Eden?"); return question(this, tr("Eden"), text); } @@ -4665,9 +4443,8 @@ void MainWindow::closeEvent(QCloseEvent* event) { game_list->UnloadController(); // Shutdown session if the emu thread is active... - if (emu_thread != nullptr) { + if (QtCommon::emu_thread != nullptr) ShutdownGame(); - } render_window->close(); multiplayer_state->Close(); @@ -4730,7 +4507,7 @@ void MainWindow::dragMoveEvent(QDragMoveEvent* event) { } bool MainWindow::ConfirmChangeGame() { - if (emu_thread == nullptr) + if (QtCommon::emu_thread == nullptr) return true; // Use custom question to link controller navigation @@ -4741,9 +4518,9 @@ bool MainWindow::ConfirmChangeGame() { } bool MainWindow::ConfirmForceLockedExit() { - if (emu_thread == nullptr) { + if (QtCommon::emu_thread == nullptr) return true; - } + const auto text = tr("The currently running application has requested Eden to not exit.\n\n" "Would you like to bypass this and exit anyway?"); @@ -4751,9 +4528,8 @@ bool MainWindow::ConfirmForceLockedExit() { } void MainWindow::RequestGameExit() { - if (!QtCommon::system->IsPoweredOn()) { + if (!QtCommon::system->IsPoweredOn()) return; - } QtCommon::system->SetExitRequested(true); QtCommon::system->GetAppletManager().RequestExit(); @@ -4766,14 +4542,14 @@ void MainWindow::filterBarSetChecked(bool state) { static void AdjustLinkColor() { QPalette new_pal(qApp->palette()); - if (UISettings::IsDarkTheme()) { + + if (UISettings::IsDarkTheme()) new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255)); - } else { + else new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255)); - } - if (qApp->palette().color(QPalette::Link) != new_pal.color(QPalette::Link)) { + + if (qApp->palette().color(QPalette::Link) != new_pal.color(QPalette::Link)) qApp->setPalette(new_pal); - } } void MainWindow::UpdateUITheme() { @@ -4781,9 +4557,8 @@ void MainWindow::UpdateUITheme() { UISettings::themes[static_cast(UISettings::default_theme)].second); QString current_theme = QString::fromStdString(UISettings::values.theme); - if (current_theme.isEmpty()) { + if (current_theme.isEmpty()) current_theme = default_theme; - } #ifdef _WIN32 QIcon::setThemeName(current_theme); @@ -4844,17 +4619,15 @@ void MainWindow::LoadTranslation() { QStringLiteral(":/languages/")); } - if (loaded) { + if (loaded) qApp->installTranslator(&translator); - } else { + else UISettings::values.language = std::string("en"); - } } void MainWindow::OnLanguageChanged(const QString& locale) { - if (UISettings::values.language.GetValue() != std::string("en")) { + if (UISettings::values.language.GetValue() != std::string("en")) qApp->removeTranslator(&translator); - } QList actions = game_size_actions->actions(); for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { @@ -4870,11 +4643,10 @@ void MainWindow::OnLanguageChanged(const QString& locale) { void MainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { #ifdef USE_DISCORD_PRESENCE - if (state) { + if (state) discord_rpc = std::make_unique(*QtCommon::system); - } else { + else discord_rpc = std::make_unique(); - } #else discord_rpc = std::make_unique(); #endif diff --git a/src/yuzu/main_window.h b/src/yuzu/main_window.h index b676c0534a..65da01e9ca 100644 --- a/src/yuzu/main_window.h +++ b/src/yuzu/main_window.h @@ -20,9 +20,9 @@ #include "common/common_types.h" #include "common/settings_enums.h" #include "frontend_common/content_manager.h" -#include "frontend_common/update_checker.h" #include "input_common/drivers/tas_input.h" #include "qt_common/config/qt_config.h" +#include "qt_common/qt_common.h" #include "qt_common/util/game.h" #include "yuzu/compatibility_list.h" #include "yuzu/hotkeys.h" @@ -38,6 +38,7 @@ #ifdef ENABLE_UPDATE_CHECKER #include #include +#include "common/net/net.h" #endif class QtConfig; @@ -66,11 +67,6 @@ class QtProfileSelectionDialog; class QtSoftwareKeyboardDialog; class QtNXWebEngineView; -enum class StartGameType { - Normal, // Can use custom configuration - Global, // Only uses global configuration -}; - namespace VideoCore { class ShaderNotify; } @@ -184,11 +180,8 @@ signals: * Signal that is emitted when a new EmuThread has been created and an emulation session is * about to start. At this time, the core system emulation has been initialized, and all * emulation handles and memory should be valid. - * - * @param emu_thread Pointer to the newly created EmuThread (to be used by widgets that need to - * access/change emulation state). */ - void EmulationStarting(EmuThread* emu_thread); + void EmulationStarting(); /** * Signal that is emitted when emulation is about to stop. At this time, the EmuThread and core @@ -465,7 +458,6 @@ private: bool CheckFirmwarePresence(); void SetFirmwareVersion(); void SetFPSSuffix(); - void ConfigureFilesystemProvider(const std::string& filepath); /** * Open (or not) the right confirm dialog based on current setting and game exit lock * @returns true if the player confirmed or the settings do no require it @@ -538,7 +530,6 @@ private: // Whether emulation is currently running in yuzu. bool emulation_running = false; - std::unique_ptr emu_thread; // The path to the game currently running QString current_game_path; // Whether a user was set on the command line (skips UserSelector if it's forced to show up) @@ -599,8 +590,6 @@ private: const std::string& game_title, QtCommon::Game::ShortcutTarget target, std::string arguments, const bool needs_title); - void InstallFirmware(const QString& location, bool recursive = false); - protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; diff --git a/src/yuzu/updater/update_dialog.h b/src/yuzu/updater/update_dialog.h index 692481b710..e821ef660d 100644 --- a/src/yuzu/updater/update_dialog.h +++ b/src/yuzu/updater/update_dialog.h @@ -15,7 +15,7 @@ class UpdateDialog : public QDialog { Q_OBJECT public: - explicit UpdateDialog(const Common::Net::Release &release, QWidget* parent = nullptr); + explicit UpdateDialog(const Common::Net::Release& release, QWidget* parent = nullptr); ~UpdateDialog(); private slots: @@ -23,6 +23,6 @@ private slots: private: Ui::UpdateDialog* ui; - QList m_buttons; + QList m_buttons; Common::Net::Asset m_asset; }; diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp index a3933d9b63..e37d9d03e4 100644 --- a/src/yuzu/util/util.cpp +++ b/src/yuzu/util/util.cpp @@ -43,111 +43,6 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) { return circle_pixmap; } -bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) { -#if defined(WIN32) -#pragma pack(push, 2) - struct IconDir { - WORD id_reserved; - WORD id_type; - WORD id_count; - }; - - struct IconDirEntry { - BYTE width; - BYTE height; - BYTE color_count; - BYTE reserved; - WORD planes; - WORD bit_count; - DWORD bytes_in_res; - DWORD image_offset; - }; -#pragma pack(pop) - - const QImage source_image = image.convertToFormat(QImage::Format_RGB32); - constexpr std::array scale_sizes{256, 128, 64, 48, 32, 24, 16}; - constexpr int bytes_per_pixel = 4; - - const IconDir icon_dir{ - .id_reserved = 0, - .id_type = 1, - .id_count = static_cast(scale_sizes.size()), - }; - - Common::FS::IOFile icon_file(icon_path.string(), Common::FS::FileAccessMode::Write, - Common::FS::FileType::BinaryFile); - if (!icon_file.IsOpen()) { - return false; - } - - if (!icon_file.Write(icon_dir)) { - return false; - } - - std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size()); - for (std::size_t i = 0; i < scale_sizes.size(); i++) { - const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel; - const IconDirEntry icon_entry{ - .width = static_cast(scale_sizes[i]), - .height = static_cast(scale_sizes[i]), - .color_count = 0, - .reserved = 0, - .planes = 1, - .bit_count = bytes_per_pixel * 8, - .bytes_in_res = static_cast(sizeof(BITMAPINFOHEADER) + image_size), - .image_offset = static_cast(image_offset), - }; - image_offset += icon_entry.bytes_in_res; - if (!icon_file.Write(icon_entry)) { - return false; - } - } - - for (std::size_t i = 0; i < scale_sizes.size(); i++) { - const QImage scaled_image = source_image.scaled( - scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - const BITMAPINFOHEADER info_header{ - .biSize = sizeof(BITMAPINFOHEADER), - .biWidth = scaled_image.width(), - .biHeight = scaled_image.height() * 2, - .biPlanes = 1, - .biBitCount = bytes_per_pixel * 8, - .biCompression = BI_RGB, - .biSizeImage{}, - .biXPelsPerMeter{}, - .biYPelsPerMeter{}, - .biClrUsed{}, - .biClrImportant{}, - }; - - if (!icon_file.Write(info_header)) { - return false; - } - - for (int y = 0; y < scaled_image.height(); y++) { - const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y); - std::vector line_data(scaled_image.width() * bytes_per_pixel); - std::memcpy(line_data.data(), line, line_data.size()); - if (!icon_file.Write(line_data)) { - return false; - } - } - } - icon_file.Close(); - - return true; -#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) - // Convert and write the icon as a PNG - if (!image.save(QString::fromStdString(icon_path.string()))) { - LOG_ERROR(Frontend, "Could not write icon as PNG to file"); - } else { - LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); - } - return true; -#else - return false; -#endif -} const std::optional GetProfileID() { // if there's only a single profile, the user probably wants to use that... right? const auto& profiles = QtCommon::system->GetProfileManager().FindExistingProfileUUIDs(); diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h index 7b482aa11d..9c3d5aa84a 100644 --- a/src/yuzu/util/util.h +++ b/src/yuzu/util/util.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: 2015 Citra Emulator Project @@ -6,7 +6,6 @@ #pragma once -#include #include #include #include "common/uuid.h" @@ -24,14 +23,6 @@ */ [[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color); -/** - * Saves a windows icon to a file - * @param path The icons path - * @param image The image to save - * @return bool If the operation succeeded - */ -[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image); - /** * Prompt the user for a profile ID. If there is only one valid profile, returns that profile. * @return The selected profile, or an std::nullopt if none were selected diff --git a/src/yuzu/vk_device_info.h b/src/yuzu/vk_device_info.h deleted file mode 100644 index bda8262f4e..0000000000 --- a/src/yuzu/vk_device_info.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include -#include -#include "common/common_types.h" -#include "vulkan/vulkan_core.h" - -class QWindow; - -namespace Settings { -enum class VSyncMode : u32; -} -// #include "common/settings.h" - -namespace VkDeviceInfo { -// Short class to record Vulkan driver information for configuration purposes -class Record { -public: - explicit Record(std::string_view name, const std::vector& vsync_modes, - bool has_broken_compute); - ~Record(); - - const std::string name; - const std::vector vsync_support; - const bool has_broken_compute; -}; - -void PopulateRecords(std::vector& records, QWindow* window); -} // namespace VkDeviceInfo diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt index 8f92525ad6..09a9bd6da1 100644 --- a/src/yuzu_cmd/CMakeLists.txt +++ b/src/yuzu_cmd/CMakeLists.txt @@ -15,23 +15,33 @@ function(create_resource file output filename) file(WRITE "${PROJECT_BINARY_DIR}/dist/${output}" "const unsigned char ${filename}[] = {${filedata}};\nconst unsigned ${filename}_size = sizeof(${filename});\n") endfunction() +if (ENABLE_OPENGL) + list(APPEND OPENGL_SOURCES + emu_window/emu_window_sdl3_gl.cpp + emu_window/emu_window_sdl3_gl.h + ) +else() + set(OPENGL_SOURCES "") +endif() + add_executable(yuzu-cmd - emu_window/emu_window_sdl2.cpp - emu_window/emu_window_sdl2.h - emu_window/emu_window_sdl2_gl.cpp - emu_window/emu_window_sdl2_gl.h - emu_window/emu_window_sdl2_null.cpp - emu_window/emu_window_sdl2_null.h - emu_window/emu_window_sdl2_vk.cpp - emu_window/emu_window_sdl2_vk.h + emu_window/emu_window_sdl3.cpp + emu_window/emu_window_sdl3.h + emu_window/emu_window_sdl3_null.cpp + emu_window/emu_window_sdl3_null.h + emu_window/emu_window_sdl3_vk.cpp + emu_window/emu_window_sdl3_vk.h sdl_config.cpp sdl_config.h yuzu.cpp yuzu.rc + ${OPENGL_SOURCES} ) target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common video_core) -target_link_libraries(yuzu-cmd PRIVATE glad) +if (ENABLE_OPENGL) + target_link_libraries(yuzu-cmd PRIVATE glad) +endif() if (MSVC) target_link_libraries(yuzu-cmd PRIVATE getopt) endif() @@ -40,7 +50,7 @@ target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) create_resource("../../dist/eden.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon") target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR}) -target_link_libraries(yuzu-cmd PRIVATE SDL2::SDL2) +target_link_libraries(yuzu-cmd PRIVATE SDL3::SDL3) if(UNIX AND NOT APPLE) install(TARGETS yuzu-cmd) diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp deleted file mode 100644 index c54e5b4fda..0000000000 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include - -#include - -#include "common/logging.h" -#include "common/scm_rev.h" -#include "video_core/renderer_vulkan/renderer_vulkan.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h" - -#include -#include - -EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem_, - Core::System& system_, bool fullscreen) - : EmuWindow_SDL2{input_subsystem_, system_} { - const std::string window_title = fmt::format("Eden {} | {}-{} (Vulkan)", - Common::g_build_name, - Common::g_scm_branch, - Common::g_scm_desc); - render_window = - SDL_CreateWindow(window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, - SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - - SDL_SysWMinfo wm; - SDL_VERSION(&wm.version); - if (SDL_GetWindowWMInfo(render_window, &wm) == SDL_FALSE) { - LOG_CRITICAL(Frontend, "Failed to get information from the window manager: {}", - SDL_GetError()); - std::exit(EXIT_FAILURE); - } - - SetWindowIcon(); - - if (fullscreen) { - Fullscreen(); - ShowCursor(false); - } - - switch (wm.subsystem) { -#ifdef SDL_VIDEO_DRIVER_WINDOWS - case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS: - window_info.type = Core::Frontend::WindowSystemType::Windows; - window_info.render_surface = reinterpret_cast(wm.info.win.window); - break; -#endif -#ifdef SDL_VIDEO_DRIVER_X11 - case SDL_SYSWM_TYPE::SDL_SYSWM_X11: - window_info.type = Core::Frontend::WindowSystemType::X11; - window_info.display_connection = wm.info.x11.display; - window_info.render_surface = reinterpret_cast(wm.info.x11.window); - break; -#endif -#ifdef SDL_VIDEO_DRIVER_WAYLAND - case SDL_SYSWM_TYPE::SDL_SYSWM_WAYLAND: - window_info.type = Core::Frontend::WindowSystemType::Wayland; - window_info.display_connection = wm.info.wl.display; - window_info.render_surface = wm.info.wl.surface; - break; -#endif -#ifdef SDL_VIDEO_DRIVER_COCOA - case SDL_SYSWM_TYPE::SDL_SYSWM_COCOA: - window_info.type = Core::Frontend::WindowSystemType::Cocoa; - window_info.render_surface = SDL_Metal_CreateView(render_window); - break; -#endif -#ifdef SDL_VIDEO_DRIVER_ANDROID - case SDL_SYSWM_TYPE::SDL_SYSWM_ANDROID: - window_info.type = Core::Frontend::WindowSystemType::Android; - window_info.render_surface = reinterpret_cast(wm.info.android.window); - break; -#endif - default: - LOG_CRITICAL(Frontend, "Window manager subsystem {} not implemented", wm.subsystem); - std::exit(EXIT_FAILURE); - break; - } - - OnResize(); - OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); - SDL_PumpEvents(); - LOG_INFO(Frontend, "Eden Version: {} | {}-{} (Vulkan)", Common::g_build_name, - Common::g_scm_branch, Common::g_scm_desc); -} - -EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() = default; - -std::unique_ptr EmuWindow_SDL2_VK::CreateSharedContext() const { - return std::make_unique(); -} diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp similarity index 68% rename from src/yuzu_cmd/emu_window/emu_window_sdl2.cpp rename to src/yuzu_cmd/emu_window/emu_window_sdl3.cpp index c00734216e..cd390d29f9 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp @@ -1,9 +1,10 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include #include "common/logging.h" #include "common/scm_rev.h" @@ -15,26 +16,25 @@ #include "input_common/drivers/mouse.h" #include "input_common/drivers/touch_screen.h" #include "input_common/main.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3.h" #include "yuzu_cmd/yuzu_icon.h" -EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) +EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) : input_subsystem{input_subsystem_}, system{system_} { input_subsystem->Initialize(); - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { - LOG_CRITICAL(Frontend, "Failed to initialize SDL2: {}, Exiting...", SDL_GetError()); + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) { + LOG_CRITICAL(Frontend, "Failed to initialize SDL3: {}, Exiting...", SDL_GetError()); exit(1); } - SDL_SetMainReady(); } -EmuWindow_SDL2::~EmuWindow_SDL2() { +EmuWindow_SDL3::~EmuWindow_SDL3() { system.HIDCore().UnloadInputDevices(); input_subsystem->Shutdown(); SDL_Quit(); } -InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) const { +InputCommon::MouseButton EmuWindow_SDL3::SDLButtonToMouseButton(u32 button) const { switch (button) { case SDL_BUTTON_LEFT: return InputCommon::MouseButton::Left; @@ -52,7 +52,7 @@ InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) cons } /// @brief Translates pixel position to float position -EmuWindow_SDL2::FloatPairNonHFA EmuWindow_SDL2::MouseToTouchPos(s32 touch_x, s32 touch_y) const { +EmuWindow_SDL3::FloatPairNonHFA EmuWindow_SDL3::MouseToTouchPos(s32 touch_x, s32 touch_y) const { int w = 0, h = 0; SDL_GetWindowSize(render_window, &w, &h); const float fx = float(touch_x) / w; @@ -64,9 +64,9 @@ EmuWindow_SDL2::FloatPairNonHFA EmuWindow_SDL2::MouseToTouchPos(s32 touch_x, s32 }; } -void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { +void EmuWindow_SDL3::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { const auto mouse_button = SDLButtonToMouseButton(button); - if (state == SDL_PRESSED) { + if (state != 0) { auto const [touch_x, touch_y, _] = MouseToTouchPos(x, y); input_subsystem->GetMouse()->PressButton(x, y, mouse_button); input_subsystem->GetMouse()->PressMouseButton(mouse_button); @@ -76,64 +76,70 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { } } -void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { +void EmuWindow_SDL3::OnMouseMotion(s32 x, s32 y) { auto const [touch_x, touch_y, _] = MouseToTouchPos(x, y); input_subsystem->GetMouse()->Move(x, y, 0, 0); input_subsystem->GetMouse()->MouseMove(touch_x, touch_y); input_subsystem->GetMouse()->TouchMove(touch_x, touch_y); } -void EmuWindow_SDL2::OnFingerDown(float x, float y, std::size_t id) { +void EmuWindow_SDL3::OnFingerDown(float x, float y, std::size_t id) { input_subsystem->GetTouchScreen()->TouchPressed(x, y, id); } -void EmuWindow_SDL2::OnFingerMotion(float x, float y, std::size_t id) { +void EmuWindow_SDL3::OnFingerMotion(float x, float y, std::size_t id) { input_subsystem->GetTouchScreen()->TouchMoved(x, y, id); } -void EmuWindow_SDL2::OnFingerUp() { +void EmuWindow_SDL3::OnFingerUp() { input_subsystem->GetTouchScreen()->ReleaseAllTouch(); } -void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { - if (state == SDL_PRESSED) { +void EmuWindow_SDL3::OnKeyEvent(int key, u8 state) { + if (state != 0) { input_subsystem->GetKeyboard()->PressKey(static_cast(key)); - } else if (state == SDL_RELEASED) { + } else { input_subsystem->GetKeyboard()->ReleaseKey(static_cast(key)); } } -bool EmuWindow_SDL2::IsOpen() const { +bool EmuWindow_SDL3::IsOpen() const { return is_open; } -bool EmuWindow_SDL2::IsShown() const { +bool EmuWindow_SDL3::IsShown() const { return is_shown; } -void EmuWindow_SDL2::OnResize() { +void EmuWindow_SDL3::OnResize() { int width, height; - SDL_GL_GetDrawableSize(render_window, &width, &height); + SDL_GetWindowSizeInPixels(render_window, &width, &height); UpdateCurrentFramebufferLayout(width, height); } -void EmuWindow_SDL2::ShowCursor(bool show_cursor) { - SDL_ShowCursor(show_cursor ? SDL_ENABLE : SDL_DISABLE); +void EmuWindow_SDL3::ShowCursor(bool show_cursor) { + if (show_cursor) { + SDL_ShowCursor(); + } else { + SDL_HideCursor(); + } } -void EmuWindow_SDL2::Fullscreen() { +void EmuWindow_SDL3::Fullscreen() { SDL_DisplayMode display_mode; switch (Settings::values.fullscreen_mode.GetValue()) { case Settings::FullscreenMode::Exclusive: - // Set window size to render size before entering fullscreen -- SDL2 does not resize window - // to display dimensions automatically in this mode. - if (SDL_GetDesktopDisplayMode(0, &display_mode) == 0) { + // 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))) { + display_mode = *display_mode_ptr; SDL_SetWindowSize(render_window, display_mode.w, display_mode.h); + SDL_SetWindowFullscreenMode(render_window, &display_mode); } else { LOG_ERROR(Frontend, "SDL_GetDesktopDisplayMode failed: {}", SDL_GetError()); } - if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN) == 0) { + if (SDL_SetWindowFullscreen(render_window, true)) { return; } @@ -141,7 +147,8 @@ void EmuWindow_SDL2::Fullscreen() { LOG_INFO(Frontend, "Attempting to use borderless fullscreen..."); [[fallthrough]]; case Settings::FullscreenMode::Borderless: - if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) { + SDL_SetWindowFullscreenMode(render_window, nullptr); + if (SDL_SetWindowFullscreen(render_window, true)) { return; } @@ -156,7 +163,7 @@ void EmuWindow_SDL2::Fullscreen() { } } -void EmuWindow_SDL2::WaitEvent() { +void EmuWindow_SDL3::WaitEvent() { // Called on main thread SDL_Event event; @@ -174,52 +181,52 @@ void EmuWindow_SDL2::WaitEvent() { } switch (event.type) { - case SDL_WINDOWEVENT: - switch (event.window.event) { - case SDL_WINDOWEVENT_SIZE_CHANGED: - case SDL_WINDOWEVENT_RESIZED: - case SDL_WINDOWEVENT_MAXIMIZED: - case SDL_WINDOWEVENT_RESTORED: - OnResize(); - break; - case SDL_WINDOWEVENT_MINIMIZED: - case SDL_WINDOWEVENT_EXPOSED: - is_shown = event.window.event == SDL_WINDOWEVENT_EXPOSED; - OnResize(); - break; - case SDL_WINDOWEVENT_CLOSE: - is_open = false; - break; - } + case SDL_EVENT_WINDOW_RESIZED: + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: + case SDL_EVENT_WINDOW_MAXIMIZED: + case SDL_EVENT_WINDOW_RESTORED: + OnResize(); break; - case SDL_KEYDOWN: - case SDL_KEYUP: - OnKeyEvent(static_cast(event.key.keysym.scancode), event.key.state); + case SDL_EVENT_WINDOW_MINIMIZED: + is_shown = false; + OnResize(); break; - case SDL_MOUSEMOTION: + case SDL_EVENT_WINDOW_EXPOSED: + is_shown = true; + OnResize(); + break; + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: + is_open = false; + break; + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + OnKeyEvent(static_cast(event.key.scancode), event.key.down ? 1 : 0); + break; + 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; - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: + 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.state, event.button.x, event.button.y); + OnMouseButton(event.button.button, event.button.down ? 1 : 0, + static_cast(event.button.x), static_cast(event.button.y)); } break; - case SDL_FINGERDOWN: + case SDL_EVENT_FINGER_DOWN: OnFingerDown(event.tfinger.x, event.tfinger.y, - static_cast(event.tfinger.touchId)); + static_cast(event.tfinger.touchID)); break; - case SDL_FINGERMOTION: + case SDL_EVENT_FINGER_MOTION: OnFingerMotion(event.tfinger.x, event.tfinger.y, - static_cast(event.tfinger.touchId)); + static_cast(event.tfinger.touchID)); break; - case SDL_FINGERUP: + case SDL_EVENT_FINGER_UP: OnFingerUp(); break; - case SDL_QUIT: + case SDL_EVENT_QUIT: is_open = false; break; default: @@ -241,22 +248,22 @@ void EmuWindow_SDL2::WaitEvent() { } // Credits to Samantas5855 and others for this function. -void EmuWindow_SDL2::SetWindowIcon() { - SDL_RWops* const yuzu_icon_stream = SDL_RWFromConstMem((void*)yuzu_icon, yuzu_icon_size); +void EmuWindow_SDL3::SetWindowIcon() { + 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."); return; } - SDL_Surface* const window_icon = SDL_LoadBMP_RW(yuzu_icon_stream, 1); + SDL_Surface* const window_icon = SDL_LoadBMP_IO(yuzu_icon_stream, true); if (window_icon == nullptr) { LOG_WARNING(Frontend, "Failed to read BMP from stream."); return; } // The icon is attached to the window pointer SDL_SetWindowIcon(render_window, window_icon); - SDL_FreeSurface(window_icon); + SDL_DestroySurface(window_icon); } -void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair minimal_size) { +void EmuWindow_SDL3::OnMinimalClientAreaChangeRequest(std::pair minimal_size) { SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl3.h similarity index 92% rename from src/yuzu_cmd/emu_window/emu_window_sdl2.h rename to src/yuzu_cmd/emu_window/emu_window_sdl3.h index 8aec1efda0..8e809a3f02 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3.h @@ -1,5 +1,6 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -22,10 +23,10 @@ class InputSubsystem; enum class MouseButton; } // namespace InputCommon -class EmuWindow_SDL2 : public Core::Frontend::EmuWindow { +class EmuWindow_SDL3 : public Core::Frontend::EmuWindow { public: - explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_); - ~EmuWindow_SDL2(); + explicit EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_); + ~EmuWindow_SDL3(); /// Whether the window is still open, and a close request hasn't yet been sent bool IsOpen() const; @@ -85,7 +86,7 @@ protected: /// Is the window being shown? bool is_shown = true; - /// Internal SDL2 render window + /// Internal SDL3 render window SDL_Window* render_window{}; /// Keeps track of how often to update the title bar during gameplay diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl3_gl.cpp similarity index 74% rename from src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp rename to src/yuzu_cmd/emu_window/emu_window_sdl3_gl.cpp index 94a6a69776..e86858a232 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3_gl.cpp @@ -9,7 +9,7 @@ #include #define SDL_MAIN_HANDLED -#include +#include #include #include @@ -20,7 +20,13 @@ #include "core/core.h" #include "input_common/main.h" #include "video_core/renderer_base.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3_gl.h" + +namespace { +void* SDLGLGetProcAddress(const char* proc_name) { + return reinterpret_cast(SDL_GL_GetProcAddress(proc_name)); +} +} // Anonymous namespace class SDLGLContext : public Core::Frontend::GraphicsContext { public: @@ -30,7 +36,7 @@ public: ~SDLGLContext() { DoneCurrent(); - SDL_GL_DeleteContext(context); + SDL_GL_DestroyContext(context); } void SwapBuffers() override { @@ -58,27 +64,23 @@ private: bool is_current = false; }; -bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() { - std::vector unsupported_ext; - +bool EmuWindow_SDL3_GL::SupportsRequiredGLExtensions() { + std::vector unsupported_ext{}; +#ifdef HAS_OPENGL // Extensions required to support some texture formats. - if (!GLAD_GL_EXT_texture_compression_s3tc) { + if (!GLAD_GL_EXT_texture_compression_s3tc) unsupported_ext.push_back("EXT_texture_compression_s3tc"); - } - if (!GLAD_GL_ARB_texture_compression_rgtc) { + if (!GLAD_GL_ARB_texture_compression_rgtc) unsupported_ext.push_back("ARB_texture_compression_rgtc"); - } - - for (const auto& extension : unsupported_ext) { + for (const auto& extension : unsupported_ext) LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", extension); - } - +#endif return unsupported_ext.empty(); } -EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem_, +EmuWindow_SDL3_GL::EmuWindow_SDL3_GL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_, bool fullscreen) - : EmuWindow_SDL2{input_subsystem_, system_} { + : EmuWindow_SDL3{input_subsystem_, system_} { SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); @@ -96,14 +98,13 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste std::string window_title = fmt::format("{} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc); render_window = - SDL_CreateWindow(window_title.c_str(), - SDL_WINDOWPOS_UNDEFINED, // x position - SDL_WINDOWPOS_UNDEFINED, // y position - Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, - SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width, + Layout::ScreenUndocked::Height, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | + SDL_WINDOW_HIGH_PIXEL_DENSITY); if (render_window == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError()); + LOG_CRITICAL(Frontend, "Failed to create SDL3 window! {}", SDL_GetError()); exit(1); } @@ -120,15 +121,15 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste core_context = CreateSharedContext(); if (window_context == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError()); + LOG_CRITICAL(Frontend, "Failed to create SDL3 GL context: {}", SDL_GetError()); exit(1); } if (core_context == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError()); + LOG_CRITICAL(Frontend, "Failed to create shared SDL3 GL context: {}", SDL_GetError()); exit(1); } - if (!gladLoadGLLoader(static_cast(SDL_GL_GetProcAddress))) { + if (!gladLoadGLLoader(static_cast(SDLGLGetProcAddress))) { LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError()); exit(1); } @@ -146,11 +147,11 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste Settings::LogSettings(); } -EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() { +EmuWindow_SDL3_GL::~EmuWindow_SDL3_GL() { core_context.reset(); - SDL_GL_DeleteContext(window_context); + SDL_GL_DestroyContext(window_context); } -std::unique_ptr EmuWindow_SDL2_GL::CreateSharedContext() const { +std::unique_ptr EmuWindow_SDL3_GL::CreateSharedContext() const { return std::make_unique(render_window); } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl3_gl.h similarity index 70% rename from src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h rename to src/yuzu_cmd/emu_window/emu_window_sdl3_gl.h index 39346e7047..73a7d3d482 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3_gl.h @@ -1,11 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include +#include #include "core/frontend/emu_window.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3.h" namespace Core { class System; @@ -15,11 +19,11 @@ namespace InputCommon { class InputSubsystem; } -class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 { +class EmuWindow_SDL3_GL final : public EmuWindow_SDL3 { public: - explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_, + explicit EmuWindow_SDL3_GL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_, bool fullscreen); - ~EmuWindow_SDL2_GL(); + ~EmuWindow_SDL3_GL(); std::unique_ptr CreateSharedContext() const override; @@ -27,8 +31,6 @@ private: /// Whether the GPU and driver supports the OpenGL extension required bool SupportsRequiredGLExtensions(); - using SDL_GLContext = void*; - /// The OpenGL context associated with the window SDL_GLContext window_context; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_null.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl3_null.cpp similarity index 67% rename from src/yuzu_cmd/emu_window/emu_window_sdl2_null.cpp rename to src/yuzu_cmd/emu_window/emu_window_sdl3_null.cpp index 4947080313..f5e4e19a30 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_null.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3_null.cpp @@ -13,25 +13,25 @@ #include "common/logging.h" #include "common/scm_rev.h" #include "video_core/renderer_null/renderer_null.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2_null.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3_null.h" -#ifdef YUZU_USE_EXTERNAL_SDL2 +#ifdef YUZU_USE_EXTERNAL_SDL3 // Include this before SDL.h to prevent the external from including a dummy #define USING_GENERATED_CONFIG_H -#include +#include #endif -#include +#include -EmuWindow_SDL2_Null::EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subsystem_, +EmuWindow_SDL3_Null::EmuWindow_SDL3_Null(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_, bool fullscreen) - : EmuWindow_SDL2{input_subsystem_, system_} { + : EmuWindow_SDL3{input_subsystem_, system_} { const std::string window_title = fmt::format("Eden {} | {}-{} (Vulkan)", Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc); render_window = - SDL_CreateWindow(window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, - SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width, + Layout::ScreenUndocked::Height, + SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY); SetWindowIcon(); @@ -47,8 +47,8 @@ EmuWindow_SDL2_Null::EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subs Common::g_scm_branch, Common::g_scm_desc); } -EmuWindow_SDL2_Null::~EmuWindow_SDL2_Null() = default; +EmuWindow_SDL3_Null::~EmuWindow_SDL3_Null() = default; -std::unique_ptr EmuWindow_SDL2_Null::CreateSharedContext() const { +std::unique_ptr EmuWindow_SDL3_Null::CreateSharedContext() const { return std::make_unique(); } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_null.h b/src/yuzu_cmd/emu_window/emu_window_sdl3_null.h similarity index 58% rename from src/yuzu_cmd/emu_window/emu_window_sdl2_null.h rename to src/yuzu_cmd/emu_window/emu_window_sdl3_null.h index 35aee286df..7c046e72b8 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_null.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3_null.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -6,7 +9,7 @@ #include #include "core/frontend/emu_window.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3.h" namespace Core { class System; @@ -16,11 +19,11 @@ namespace InputCommon { class InputSubsystem; } -class EmuWindow_SDL2_Null final : public EmuWindow_SDL2 { +class EmuWindow_SDL3_Null final : public EmuWindow_SDL3 { public: - explicit EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subsystem_, + explicit EmuWindow_SDL3_Null(InputCommon::InputSubsystem* input_subsystem_, Core::System& system, bool fullscreen); - ~EmuWindow_SDL2_Null() override; + ~EmuWindow_SDL3_Null() override; std::unique_ptr CreateSharedContext() const override; }; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp new file mode 100644 index 0000000000..9019cbbba0 --- /dev/null +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + +#include + +#include "common/logging.h" +#include "common/scm_rev.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h" + +#include +#include + +EmuWindow_SDL3_VK::EmuWindow_SDL3_VK(InputCommon::InputSubsystem* input_subsystem_, + Core::System& system_, bool fullscreen) + : EmuWindow_SDL3{input_subsystem_, system_} { + const std::string window_title = fmt::format("Eden {} | {}-{} (Vulkan)", + Common::g_build_name, + Common::g_scm_branch, + Common::g_scm_desc); + render_window = + SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width, + Layout::ScreenUndocked::Height, + SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY); + + const SDL_PropertiesID window_props = SDL_GetWindowProperties(render_window); + + SetWindowIcon(); + + if (fullscreen) { + Fullscreen(); + ShowCursor(false); + } + + if (void* hwnd = + SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr)) { + window_info.type = Core::Frontend::WindowSystemType::Windows; + window_info.render_surface = hwnd; + } else if (void* wl_display = + SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, + nullptr); + wl_display != nullptr) { + void* wl_surface = + SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr); + if (wl_surface == nullptr) { + LOG_CRITICAL(Frontend, "Wayland surface is unavailable"); + std::exit(EXIT_FAILURE); + } + window_info.type = Core::Frontend::WindowSystemType::Wayland; + window_info.display_connection = wl_display; + window_info.render_surface = wl_surface; + } else if (void* x11_display = + SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, + nullptr); + x11_display != nullptr) { + const auto x11_window = + SDL_GetNumberProperty(window_props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); + if (x11_window == 0) { + LOG_CRITICAL(Frontend, "X11 window handle is unavailable"); + std::exit(EXIT_FAILURE); + } + window_info.type = Core::Frontend::WindowSystemType::X11; + window_info.display_connection = x11_display; + window_info.render_surface = reinterpret_cast(static_cast(x11_window)); + } else if (SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, + nullptr) != nullptr) { + window_info.type = Core::Frontend::WindowSystemType::Cocoa; + window_info.render_surface = SDL_Metal_CreateView(render_window); + } else if (void* android_window = + SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, + nullptr); + android_window != nullptr) { + window_info.type = Core::Frontend::WindowSystemType::Android; + window_info.render_surface = android_window; + } else { + LOG_CRITICAL(Frontend, "Unable to determine native window backend from SDL properties"); + std::exit(EXIT_FAILURE); + } + + OnResize(); + OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); + SDL_PumpEvents(); + LOG_INFO(Frontend, "Eden Version: {} | {}-{} (Vulkan)", Common::g_build_name, + Common::g_scm_branch, Common::g_scm_desc); +} + +EmuWindow_SDL3_VK::~EmuWindow_SDL3_VK() = default; + +std::unique_ptr EmuWindow_SDL3_VK::CreateSharedContext() const { + return std::make_unique(); +} diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.h similarity index 59% rename from src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h rename to src/yuzu_cmd/emu_window/emu_window_sdl3_vk.h index 9467d164a4..5eb2626dd7 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -6,7 +9,7 @@ #include #include "core/frontend/emu_window.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3.h" namespace Core { class System; @@ -16,11 +19,11 @@ namespace InputCommon { class InputSubsystem; } -class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 { +class EmuWindow_SDL3_VK final : public EmuWindow_SDL3 { public: - explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem_, Core::System& system, + explicit EmuWindow_SDL3_VK(InputCommon::InputSubsystem* input_subsystem_, Core::System& system, bool fullscreen); - ~EmuWindow_SDL2_VK() override; + ~EmuWindow_SDL3_VK() override; std::unique_ptr CreateSharedContext() const override; }; diff --git a/src/yuzu_cmd/sdl_config.cpp b/src/yuzu_cmd/sdl_config.cpp index 136043dc9e..b06a5a698c 100644 --- a/src/yuzu_cmd/sdl_config.cpp +++ b/src/yuzu_cmd/sdl_config.cpp @@ -6,7 +6,7 @@ // SDL will break our main function in yuzu-cmd if we don't define this before adding SDL.h #define SDL_MAIN_HANDLED -#include +#include #include "common/logging.h" #include "input_common/main.h" diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 8e518d816c..d20c126159 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -30,10 +30,12 @@ #include "network/network.h" #include "sdl_config.h" #include "video_core/renderer_base.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2_null.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3.h" +#ifdef HAS_OPENGL +#include "yuzu_cmd/emu_window/emu_window_sdl3_gl.h" +#endif +#include "yuzu_cmd/emu_window/emu_window_sdl3_null.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h" #ifdef _WIN32 // windows.h needs to be included before shellapi.h @@ -347,19 +349,24 @@ int main(int argc, char** argv) { // Apply the command line arguments system.ApplySettings(); - std::unique_ptr emu_window; + std::unique_ptr 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(&input_subsystem, system, fullscreen); + emu_window = std::make_unique(&input_subsystem, system, fullscreen); break; +#endif case Settings::RendererBackend::Vulkan: - emu_window = std::make_unique(&input_subsystem, system, fullscreen); + emu_window = std::make_unique(&input_subsystem, system, fullscreen); break; case Settings::RendererBackend::Null: - emu_window = std::make_unique(&input_subsystem, system, fullscreen); + emu_window = std::make_unique(&input_subsystem, system, fullscreen); break; + default: + LOG_CRITICAL(Frontend, "Invalid renderer backend"); + return -1; } #ifdef _WIN32 diff --git a/tools/dtrace-tool.pl b/tools/dtrace-tool.pl index 2c4e7d6876..d143d36f3e 100755 --- a/tools/dtrace-tool.pl +++ b/tools/dtrace-tool.pl @@ -9,9 +9,11 @@ use warnings; use POSIX qw(strftime); my $input; -my $sampling_hz = '4000'; -my $sampling_time = '5'; +my $sampling_hz = '997'; +my $sampling_time = '60'; my $sampling_pid = `pgrep eden`; +chomp($sampling_pid); + my $sampling_program = 'eden'; my $sampling_type = 0; @@ -39,43 +41,27 @@ sub dtrace_ask_params { sub dtrace_probe_profiling { if ($sampling_type eq 0) { + # profile both kernel stacks and user stacks return " -profile-".$sampling_hz." /pid == ".$sampling_pid." && arg0/ { - @[stack(100)] = count(); -} -profile-".$sampling_hz." /pid == ".$sampling_pid." && arg1/ { - @[ustack(100)] = count(); -} -tick-".$sampling_time."s { - exit(0); -}"; +profile-".$sampling_hz." /pid == ".$sampling_pid." && arg0/ { @[stack(100)] = count(); } +profile-".$sampling_hz." /pid == ".$sampling_pid." && arg1/ { @[ustack(100)] = count(); } +tick-".$sampling_time."s { exit(0); }"; } elsif ($sampling_type eq 1) { + # trace syscall entries return " -syscall:::entry /pid == ".$sampling_pid."/ { - \@traces[ustack(100)] = count(); -} -tick-".$sampling_time."s { - exit(0); -}"; +syscall:::entry /pid == ".$sampling_pid."/ { \@traces[ustack(100)] = count(); } +tick-".$sampling_time."s { exit(0); }"; } elsif ($sampling_type eq 2) { + # profile both kernel and user stacks with thread names attached return " -profile-".$sampling_hz." /pid == ".$sampling_pid." && arg0/ { - @[stringof(curthread->td_name), stack(100)] = count(); -} -profile-".$sampling_hz." /pid == ".$sampling_pid." && arg1/ { - @[stringof(curthread->td_name), ustack(100)] = count(); -} -tick-".$sampling_time."s { - exit(0); -}"; +profile-".$sampling_hz." /pid == ".$sampling_pid." && arg0/ { @[stringof(curthread->td_name), stack(100)] = count(); } +profile-".$sampling_hz." /pid == ".$sampling_pid." && arg1/ { @[stringof(curthread->td_name), ustack(100)] = count(); } +tick-".$sampling_time."s { exit(0); }"; } elsif ($sampling_type eq 3) { + # trace I/O requests return " -io::start /pid == ".$sampling_pid."/ { - @[ustack(100)] = count(); -} -tick-".$sampling_time."s { - exit(0); -}"; +io::start /pid == ".$sampling_pid."/ { @[ustack(100)] = count(); } +tick-".$sampling_time."s { exit(0); }"; } else { die "idk"; } @@ -111,11 +97,17 @@ sub dtrace_generate { foreach my $i (0 .. $#ARGV) { if ($ARGV[$i] eq '-h') { - print "Usage: $0\n"; - printf "%-20s%s\n", "-p", "Prompt for parameters"; - printf "%-20s%s\n", "-g", "Generate dtrace output"; - printf "%-20s%s\n", "-s", "Continously generate output until Ctrl^C"; - printf "%-20s%s\n", "-", "Select dtrace type"; + print " +Usage: $0\n +-p Prompt for parameters\n +-g Generate dtrace output\n +-s Continously generate output until Ctrl^C\n +- Select dtrace type\n + 0: Profile kernel + user stacks (default)\n + 1: Trace syscall entries\n + 2: Profile kernel + user stacks with thread names\n + 3: Trace I/O requests\n +"; } elsif ($ARGV[$i] eq '-g') { dtrace_generate; } elsif ($ARGV[$i] eq '-s') {