From 267ba83d407df2330a1ccd71af8525e4535733e6 Mon Sep 17 00:00:00 2001 From: administrator Date: Tue, 21 May 2024 02:12:22 +0200 Subject: [PATCH 01/26] Remove unsanctioned Discord invite Having a Discord server linked to Suyu poses a risk to the accounts of its members. Moreover, many of the members of this server have quit the Suyu project and do not wish to continue its development. --- MIGRATION.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 425e301136..3963b5004f 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,3 +1,7 @@ + # Migrating from yuzu When coming from yuzu, the migration is as easy as renaming some directories. From 433bcabb72ef52f6f09ae769d7dd51a6fd274538 Mon Sep 17 00:00:00 2001 From: Crimson Hawk Date: Wed, 29 May 2024 08:53:17 +0800 Subject: [PATCH 02/26] make pipeline run on every branch --- .forgejo/workflows/verify.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.forgejo/workflows/verify.yml b/.forgejo/workflows/verify.yml index c858448468..aebb79e616 100644 --- a/.forgejo/workflows/verify.yml +++ b/.forgejo/workflows/verify.yml @@ -8,7 +8,7 @@ name: 'suyu verify' on: pull_request: - branches: [ "dev" ] + # branches: [ "dev" ] paths: - 'src/**' - 'CMakeModules/**' @@ -19,7 +19,7 @@ on: # paths-ignore: # - 'src/android/**' push: - branches: [ "dev" ] + # branches: [ "dev" ] paths: - 'src/**' - 'CMakeModules/**' From b95cfe64830033280e14c57993ce4b54c4933963 Mon Sep 17 00:00:00 2001 From: Crimson-Hawk Date: Wed, 29 May 2024 16:51:35 +0800 Subject: [PATCH 03/26] fixed reference to gitlab in ci --- .ci/scripts/linux/docker.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh index 82432bd835..43492940c3 100755 --- a/.ci/scripts/linux/docker.sh +++ b/.ci/scripts/linux/docker.sh @@ -52,9 +52,9 @@ DESTDIR="$PWD/AppDir" ninja install rm -vf AppDir/usr/bin/suyu-cmd AppDir/usr/bin/suyu-tester # Download tools needed to build an AppImage -wget -nc https://gitlab.com/suyu-emu/ext-linux-bin/-/raw/main/appimage/deploy-linux.sh -wget -nc https://gitlab.com/suyu-emu/ext-linux-bin/-/raw/main/appimage/exec-x86_64.so -wget -nc https://gitlab.com/suyu-emu/AppImageKit-checkrt/-/raw/old/AppRun.sh +wget -nc https://git.suyu.dev/suyu/ext-linux-bin/raw/branch/main/appimage/deploy-linux.sh +wget -nc https://git.suyu.dev/suyu/ext-linux-bin/raw/branch/main/appimage/exec-x86_64.so +wget -nc https://git.suyu.dev/suyu/AppImageKit-checkrt/raw/branch/gh-workflow/AppRun # Set executable bit chmod 755 \ From e1f809079ed36a6d094f38bf8ba44b27c4332afb Mon Sep 17 00:00:00 2001 From: Crimson-Hawk Date: Wed, 29 May 2024 16:51:35 +0800 Subject: [PATCH 04/26] fixed reference to gitlab in ci --- .ci/scripts/clang/docker.sh | 2 +- .ci/scripts/linux/docker.sh | 2 +- .ci/scripts/windows/docker.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.ci/scripts/clang/docker.sh b/.ci/scripts/clang/docker.sh index 57fbb97544..2fd100b8f8 100755 --- a/.ci/scripts/clang/docker.sh +++ b/.ci/scripts/clang/docker.sh @@ -7,7 +7,7 @@ # Exit on error, rather than continuing with the rest of the script. set -e -ccache -sv +ccache -s mkdir build || true && cd build cmake .. \ diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh index 43492940c3..c932b5a88a 100755 --- a/.ci/scripts/linux/docker.sh +++ b/.ci/scripts/linux/docker.sh @@ -6,7 +6,7 @@ # Exit on error, rather than continuing with the rest of the script. set -e -ccache -sv +ccache -s mkdir build || true && cd build cmake .. \ diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh index 73e000324c..12264576e9 100755 --- a/.ci/scripts/windows/docker.sh +++ b/.ci/scripts/windows/docker.sh @@ -8,7 +8,7 @@ set -e #cd /suyu -ccache -sv +ccache -s rm -rf build mkdir -p build && cd build From 7b13512b41682ec85395f4a9144f67da1a7f1c53 Mon Sep 17 00:00:00 2001 From: Crimson-Hawk Date: Wed, 29 May 2024 16:51:35 +0800 Subject: [PATCH 05/26] fixed reference to gitlab in ci --- .ci/scripts/clang/docker.sh | 2 + .ci/scripts/linux/docker.sh | 2 + .ci/scripts/windows/docker.sh | 2 + temp.sh | 75 +++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 temp.sh diff --git a/.ci/scripts/clang/docker.sh b/.ci/scripts/clang/docker.sh index 2fd100b8f8..d59f672087 100755 --- a/.ci/scripts/clang/docker.sh +++ b/.ci/scripts/clang/docker.sh @@ -9,6 +9,8 @@ set -e ccache -s +git submodule update --init --recursive + mkdir build || true && cd build cmake .. \ -DCMAKE_BUILD_TYPE=Release \ diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh index c932b5a88a..9854429257 100755 --- a/.ci/scripts/linux/docker.sh +++ b/.ci/scripts/linux/docker.sh @@ -8,6 +8,8 @@ set -e ccache -s +git submodule update --init --recursive + mkdir build || true && cd build cmake .. \ -DBoost_USE_STATIC_LIBS=ON \ diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh index 12264576e9..ba40e5dbbb 100755 --- a/.ci/scripts/windows/docker.sh +++ b/.ci/scripts/windows/docker.sh @@ -10,6 +10,8 @@ set -e ccache -s +git submodule update --init --recursive + rm -rf build mkdir -p build && cd build /usr/bin/x86_64-w64-mingw32-cmake .. \ diff --git a/temp.sh b/temp.sh new file mode 100644 index 0000000000..61aff76cbf --- /dev/null +++ b/temp.sh @@ -0,0 +1,75 @@ +ccache -sv + +mkdir build || true && cd build +cmake .. \ + -DBoost_USE_STATIC_LIBS=ON \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DSUYU_USE_PRECOMPILED_HEADERS=OFF \ + -DDYNARMIC_USE_PRECOMPILED_HEADERS=OFF \ + -DCMAKE_CXX_FLAGS="-march=x86-64-v2" \ + -DCMAKE_CXX_COMPILER=/usr/local/bin/g++ \ + -DCMAKE_C_COMPILER=/usr/local/bin/gcc \ + -DCMAKE_INSTALL_PREFIX="/usr" \ + -DDISPLAY_VERSION=$1 \ + -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=OFF \ + -DENABLE_QT_TRANSLATION=OFF \ + -DUSE_DISCORD_PRESENCE=ON \ + -DSUYU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \ + -DSUYU_USE_BUNDLED_FFMPEG=ON \ + -DSUYU_ENABLE_LTO=OFF \ + -DSUYU_CRASH_DUMPS=ON \ + -DSUYU_USE_FASTER_LD=ON \ + -GNinja + +ninja + +ccache -sv + +ctest -VV -C Release + +# Separate debug symbols from specified executables +for EXE in suyu; do + EXE_PATH="bin/$EXE" + # Copy debug symbols out + objcopy --only-keep-debug $EXE_PATH $EXE_PATH.debug + # Add debug link and strip debug symbols + objcopy -g --add-gnu-debuglink=$EXE_PATH.debug $EXE_PATH $EXE_PATH.out + # Overwrite original with stripped copy + mv $EXE_PATH.out $EXE_PATH +done +# Strip debug symbols from all executables +find bin/ -type f -not -regex '.*.debug' -exec strip -g {} ';' + +DESTDIR="$PWD/AppDir" ninja install +rm -vf AppDir/usr/bin/suyu-cmd AppDir/usr/bin/suyu-tester + +# Download tools needed to build an AppImage +wget -nc https://git.suyu.dev/suyu/ext-linux-bin/raw/branch/main/appimage/deploy-linux.sh +wget -nc https://git.suyu.dev/suyu/ext-linux-bin/raw/branch/main/appimage/exec-x86_64.so +wget -nc https://git.suyu.dev/suyu/AppImageKit-checkrt/raw/branch/gh-workflow/AppRun + +# Set executable bit +chmod 755 \ + deploy-linux.sh \ + AppRun.sh \ + exec-x86_64.so \ + +# Workaround for https://github.com/AppImage/AppImageKit/issues/828 +export APPIMAGE_EXTRACT_AND_RUN=1 + +mkdir -p AppDir/usr/optional +mkdir -p AppDir/usr/optional/libstdc++ +mkdir -p AppDir/usr/optional/libgcc_s + +# Deploy suyu's needed dependencies +DEPLOY_QT=1 ./deploy-linux.sh AppDir/usr/bin/suyu AppDir + +# Workaround for libQt5MultimediaGstTools indirectly requiring libwayland-client and breaking Vulkan usage on end-user systems +find AppDir -type f -regex '.*libwayland-client\.so.*' -delete -print + +# Workaround for building suyu with GCC 10 but also trying to distribute it to Ubuntu 18.04 et al. +# See https://github.com/darealshinji/AppImageKit-checkrt +cp exec-x86_64.so AppDir/usr/optional/exec.so +cp AppRun.sh AppDir/AppRun +cp --dereference /usr/lib/x86_64-linux-gnu/libstdc++.so.6 AppDir/usr/optional/libstdc++/libstdc++.so.6 +cp --dereference /lib/x86_64-linux-gnu/libgcc_s.so.1 AppDir/usr/optional/libgcc_s/libgcc_s.so.1 From 5f351bf2b37d380371fef5c357eaf571c760ed46 Mon Sep 17 00:00:00 2001 From: Crimson-Hawk Date: Wed, 29 May 2024 17:30:20 +0800 Subject: [PATCH 06/26] remove temp.sh --- temp.sh | 75 --------------------------------------------------------- 1 file changed, 75 deletions(-) delete mode 100644 temp.sh diff --git a/temp.sh b/temp.sh deleted file mode 100644 index 61aff76cbf..0000000000 --- a/temp.sh +++ /dev/null @@ -1,75 +0,0 @@ -ccache -sv - -mkdir build || true && cd build -cmake .. \ - -DBoost_USE_STATIC_LIBS=ON \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DSUYU_USE_PRECOMPILED_HEADERS=OFF \ - -DDYNARMIC_USE_PRECOMPILED_HEADERS=OFF \ - -DCMAKE_CXX_FLAGS="-march=x86-64-v2" \ - -DCMAKE_CXX_COMPILER=/usr/local/bin/g++ \ - -DCMAKE_C_COMPILER=/usr/local/bin/gcc \ - -DCMAKE_INSTALL_PREFIX="/usr" \ - -DDISPLAY_VERSION=$1 \ - -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=OFF \ - -DENABLE_QT_TRANSLATION=OFF \ - -DUSE_DISCORD_PRESENCE=ON \ - -DSUYU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \ - -DSUYU_USE_BUNDLED_FFMPEG=ON \ - -DSUYU_ENABLE_LTO=OFF \ - -DSUYU_CRASH_DUMPS=ON \ - -DSUYU_USE_FASTER_LD=ON \ - -GNinja - -ninja - -ccache -sv - -ctest -VV -C Release - -# Separate debug symbols from specified executables -for EXE in suyu; do - EXE_PATH="bin/$EXE" - # Copy debug symbols out - objcopy --only-keep-debug $EXE_PATH $EXE_PATH.debug - # Add debug link and strip debug symbols - objcopy -g --add-gnu-debuglink=$EXE_PATH.debug $EXE_PATH $EXE_PATH.out - # Overwrite original with stripped copy - mv $EXE_PATH.out $EXE_PATH -done -# Strip debug symbols from all executables -find bin/ -type f -not -regex '.*.debug' -exec strip -g {} ';' - -DESTDIR="$PWD/AppDir" ninja install -rm -vf AppDir/usr/bin/suyu-cmd AppDir/usr/bin/suyu-tester - -# Download tools needed to build an AppImage -wget -nc https://git.suyu.dev/suyu/ext-linux-bin/raw/branch/main/appimage/deploy-linux.sh -wget -nc https://git.suyu.dev/suyu/ext-linux-bin/raw/branch/main/appimage/exec-x86_64.so -wget -nc https://git.suyu.dev/suyu/AppImageKit-checkrt/raw/branch/gh-workflow/AppRun - -# Set executable bit -chmod 755 \ - deploy-linux.sh \ - AppRun.sh \ - exec-x86_64.so \ - -# Workaround for https://github.com/AppImage/AppImageKit/issues/828 -export APPIMAGE_EXTRACT_AND_RUN=1 - -mkdir -p AppDir/usr/optional -mkdir -p AppDir/usr/optional/libstdc++ -mkdir -p AppDir/usr/optional/libgcc_s - -# Deploy suyu's needed dependencies -DEPLOY_QT=1 ./deploy-linux.sh AppDir/usr/bin/suyu AppDir - -# Workaround for libQt5MultimediaGstTools indirectly requiring libwayland-client and breaking Vulkan usage on end-user systems -find AppDir -type f -regex '.*libwayland-client\.so.*' -delete -print - -# Workaround for building suyu with GCC 10 but also trying to distribute it to Ubuntu 18.04 et al. -# See https://github.com/darealshinji/AppImageKit-checkrt -cp exec-x86_64.so AppDir/usr/optional/exec.so -cp AppRun.sh AppDir/AppRun -cp --dereference /usr/lib/x86_64-linux-gnu/libstdc++.so.6 AppDir/usr/optional/libstdc++/libstdc++.so.6 -cp --dereference /lib/x86_64-linux-gnu/libgcc_s.so.1 AppDir/usr/optional/libgcc_s/libgcc_s.so.1 From daf2c1f49658ebe88d9038baf35d4e3c3703a454 Mon Sep 17 00:00:00 2001 From: Crimson-Hawk Date: Wed, 29 May 2024 17:43:46 +0800 Subject: [PATCH 07/26] fix android build --- .ci/scripts/android/build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.ci/scripts/android/build.sh b/.ci/scripts/android/build.sh index 935919b6de..885ebfee4c 100755 --- a/.ci/scripts/android/build.sh +++ b/.ci/scripts/android/build.sh @@ -7,6 +7,8 @@ export NDK_CCACHE="$(which ccache)" ccache -s +git submodule update --init --recursive + BUILD_FLAVOR="mainline" BUILD_TYPE="release" From 4eb41467f8cf39d666372b5ea78694df970252a3 Mon Sep 17 00:00:00 2001 From: Crimson-Hawk Date: Thu, 4 Jul 2024 12:22:04 +0800 Subject: [PATCH 08/26] correct the false information in readme regarding rewrite --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6fa5ed4d2e..c119a5849d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later **Note**: We do not support or condone piracy in any form. In order to use suyu, you'll need keys from your real Switch system, and games which you have legally obtained and paid for. We do not intend to make money or profit from this project. We're in need of developers. Please join our chat below if you want to contribute! -This repo was based on Yuzu EA 4176 but the code is being rewritten from the ground up for legal and performance reasons. +This repo was based on Yuzu EA 4176
From 5f485a5863aa35061c796a3e69e83039eb92f577 Mon Sep 17 00:00:00 2001 From: chaphidoesstuff Date: Sun, 15 Sep 2024 16:41:53 +0200 Subject: [PATCH 09/26] Updated links --- README.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c119a5849d..c18d313552 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ SPDX-License-Identifier: GPL-3.0-or-later **Note**: We do not support or condone piracy in any form. In order to use suyu, you'll need keys from your real Switch system, and games which you have legally obtained and paid for. We do not intend to make money or profit from this project. -We're in need of developers. Please join our chat below if you want to contribute! -This repo was based on Yuzu EA 4176 +We're in need of developers. Please join our chat below or DM a dev if you want to contribute! +This repo is currently based on Yuzu EA 4176 but the code will be rewritten from the ground up for legal and performance reasons.
@@ -22,12 +22,13 @@ This repo was based on Yuzu EA 4176

suyu was the continuation of the world's most popular, open-source Nintendo Switch emulator, yuzu, but is now something more.
-It is written in C++ with portability in mind, and we actively provide builds for Windows, Linux, Android and iOS potentially coming soon. +It is written in C++ with portability in mind, and we actively provide builds for Windows, Linux and Android, iOS may come later.

Chat | + Reddit | Status | Development | Downloads | @@ -54,7 +55,7 @@ We currently have builds over at the [Releases](https://git.suyu.dev/suyu/suyu/r This project is completely free and open source, and anyone can contribute to help improve suyu. -Most of the development happens on the Git. For development discussion, please join us in our [Chat](https://chat.suyu.dev) or contact a developer. +Most of the development happens on Git. For development discussion, please join us in our [Chat](https://chat.suyu.dev) or [Subreddit](reddit.com/r/suyu/), you can also contact a developer. If you want to contribute, please take a look at the [Contributor's Guide](https://git.suyu.dev/suyu/suyu/wiki/Contributing) and [Developer Information](https://git.suyu.dev/suyu/suyu/wiki/Developer-Information). You can also contact any of the developers on the Chat to learn more about the current state of suyu. @@ -65,25 +66,27 @@ You can also contact any of the developers on the Chat to learn more about the c * __Linux__: [Releases](https://git.suyu.dev/suyu/suyu/releases) * __macOS__: [Releases](https://git.suyu.dev/suyu/suyu/releases) * __Android__: [Releases](https://git.suyu.dev/suyu/suyu/releases) -###### We currently do not provide builds for iOS, however if you would like, you could try the experimental [Sudachi](https://github.com/emuPlace/Sudachi/releases)/[Folium](https://github.com/jarrodnorwell/Folium/releases). +###### We currently do not provide builds for iOS, however if you would like, you could try the experimental Sudachi Emulator and it's bigger project: [Folium](https://apps.apple.com/us/app/folium/id6498623389). If you want daily builds then [Click here](https://git.suyu.dev/suyu/suyu/actions). If you don't know how to download the daily builds then [Click here](https://git.suyu.dev/suyu/suyu/raw/branch/dev/img/daily-builds.png) -We have official builds [here.](https://git.suyu.dev/suyu/suyu/releases)
If any website or person is claiming to have a build for suyu, take that with a grain of salt. +We have official builds [here.](https://git.suyu.dev/suyu/suyu/releases)
If any website or person is claiming to have a build for suyu, take that with a grain of salt and let us know. + +For Multiplayer, we recommend using the "Yuzu Online" patch, install instructions can be found on Reddit and their Discord. ## Building * __Windows__: [Windows Build](https://git.suyu.dev/suyu/suyu/wiki/Building-For-Windows) * __Linux__: [Linux Build](https://git.suyu.dev/suyu/suyu/wiki/Building-For-Linux) * __Android__: [Android Build](https://git.suyu.dev/suyu/suyu/wiki/Building-For-Android) -* __macOS__: [macOS Build](https://git.suyu.dev/suyu/suyu/wiki/Building-for-macOS) +* __MacOS__: [MacOS Build](https://git.suyu.dev/suyu/suyu/wiki/Building-for-macOS) ## Support -If you have any questions, don't hesitate to ask us in our [chat](https://chat.suyu.dev), make an issue or contact a developer. We don't bite! +If you have any questions, don't hesitate to ask us in our [Chat](https://chat.suyu.dev) or Subreddit, make an issue or contact a developer. We don't bite! ## License From 9490b5264e82cc22251bd4afd7fe5fb1611faf35 Mon Sep 17 00:00:00 2001 From: chaphidoesstuff Date: Sun, 15 Sep 2024 17:18:09 +0200 Subject: [PATCH 10/26] Corrected Mistake --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c18d313552..293d49b22d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later **Note**: We do not support or condone piracy in any form. In order to use suyu, you'll need keys from your real Switch system, and games which you have legally obtained and paid for. We do not intend to make money or profit from this project. We're in need of developers. Please join our chat below or DM a dev if you want to contribute! -This repo is currently based on Yuzu EA 4176 but the code will be rewritten from the ground up for legal and performance reasons. +This repo is currently based on Yuzu EA 4176 but the code will be rewritten for legal and performance reasons.


From e886f27816eff3c8b869169740eb3b47298ade16 Mon Sep 17 00:00:00 2001 From: Herman Semenov Date: Fri, 12 Apr 2024 15:42:47 +0300 Subject: [PATCH 11/26] Using reserve() for optimization inserts, marked unused pair items and minor code refactor --- src/audio_core/device/audio_buffers.h | 4 +++- src/core/core.cpp | 1 + src/core/debugger/gdbstub.cpp | 1 + src/core/file_sys/registered_cache.cpp | 2 +- src/core/file_sys/submission_package.cpp | 2 ++ src/core/file_sys/system_archive/ng_word.cpp | 4 ++-- src/core/file_sys/system_archive/time_zone_binary.cpp | 3 +++ src/core/file_sys/vfs/vfs_cached.cpp | 6 ++++-- src/core/hle/service/am/window_system.cpp | 6 +++--- src/core/hle/service/ldn/lan_discovery.cpp | 2 +- src/core/hle/service/ns/application_manager_interface.cpp | 2 +- src/core/hle/service/sm/sm.cpp | 2 +- src/input_common/drivers/sdl_driver.cpp | 4 ++-- src/suyu/configuration/configure_applets.cpp | 2 +- src/suyu/configuration/configure_audio.cpp | 2 +- src/suyu/configuration/configure_cpu.cpp | 2 +- src/suyu/configuration/configure_general.cpp | 4 ++-- src/suyu/configuration/configure_graphics.cpp | 2 +- src/suyu/configuration/configure_graphics_advanced.cpp | 2 +- src/suyu/configuration/configure_linux_tab.cpp | 2 +- src/suyu/configuration/configure_system.cpp | 4 ++-- src/suyu/configuration/configure_ui.cpp | 2 +- src/suyu/configuration/input_profiles.cpp | 2 +- src/suyu/configuration/shared_widget.cpp | 4 ++-- src/suyu/play_time_manager.cpp | 2 +- src/tests/video_core/memory_tracker.cpp | 2 +- src/video_core/host1x/host1x.h | 4 ++-- 27 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h index 9e84a9c059..6e5e27ae3d 100644 --- a/src/audio_core/device/audio_buffers.h +++ b/src/audio_core/device/audio_buffers.h @@ -54,7 +54,8 @@ public: const s32 to_register{std::min(std::min(appended_count, BufferAppendLimit), BufferAppendLimit - registered_count)}; - for (s32 i = 0; i < to_register; i++) { + out_buffers.reserve(to_register); + for (s32 i = 0; i < to_register; ++i) { s32 index{appended_index - appended_count}; if (index < 0) { index += N; @@ -180,6 +181,7 @@ public: return 0; } + buffers_flushed.reserve(registered_count + appended_count); while (registered_count > 0) { auto index{registered_index - registered_count}; if (index < 0) { diff --git a/src/core/core.cpp b/src/core/core.cpp index 0cb81d6d8f..83517d46cc 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -80,6 +80,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, if (filename == "00") { const auto dir = vfs->OpenDirectory(dir_name, FileSys::OpenMode::Read); std::vector concat; + concat.reserve(0x10); for (u32 i = 0; i < 0x10; ++i) { const auto file_name = fmt::format("{:02X}", i); diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index 80091cc7e0..27d45fca5f 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp @@ -481,6 +481,7 @@ void GDBStub::HandleQuery(std::string_view command) { // beginning of list const auto& threads = GetProcess()->GetThreadList(); std::vector thread_ids; + thread_ids.reserve(threads.size()); for (const auto& thread : threads) { thread_ids.push_back(fmt::format("{:x}", thread.GetThreadId())); } diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index c208be83f2..fae8e74e44 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -261,7 +261,7 @@ std::vector PlaceholderCache::List() const { std::vector out; for (const auto& sdir : dir->GetSubdirectories()) { for (const auto& file : sdir->GetFiles()) { - const auto name = file->GetName(); + const auto& name = file->GetName(); if (name.length() == 36 && name.ends_with(".nca")) { out.push_back(Common::HexStringToArray<0x10>(name.substr(0, 32))); } diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index 68e8ec22fc..4ab7e03590 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -117,7 +117,9 @@ std::vector> NSP::GetNCAsCollapsed() const { if (extracted) LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); std::vector> out; + out.reserve(ncas.size()); for (const auto& map : ncas) { + out.reserve(map.second.size()); for (const auto& inner_map : map.second) out.push_back(inner_map.second); } diff --git a/src/core/file_sys/system_archive/ng_word.cpp b/src/core/file_sys/system_archive/ng_word.cpp index 1fa67877dd..13ae1999ee 100644 --- a/src/core/file_sys/system_archive/ng_word.cpp +++ b/src/core/file_sys/system_archive/ng_word.cpp @@ -24,7 +24,7 @@ constexpr std::array WORD_TXT{ VirtualDir NgWord1() { std::vector files; - files.reserve(NgWord1Data::NUMBER_WORD_TXT_FILES); + files.reserve(files.size() + 2); for (std::size_t i = 0; i < files.size(); ++i) { files.push_back(MakeArrayFile(NgWord1Data::WORD_TXT, fmt::format("{}.txt", i))); @@ -54,7 +54,7 @@ constexpr std::array AC_NX_DATA{ VirtualDir NgWord2() { std::vector files; - files.reserve(NgWord2Data::NUMBER_AC_NX_FILES * 3); + files.reserve(NgWord2Data::NUMBER_AC_NX_FILES + 4); for (std::size_t i = 0; i < NgWord2Data::NUMBER_AC_NX_FILES; ++i) { files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b1_nx", i))); 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..3fa703a6fa 100644 --- a/src/core/file_sys/system_archive/time_zone_binary.cpp +++ b/src/core/file_sys/system_archive/time_zone_binary.cpp @@ -37,6 +37,7 @@ const static std::map& directory, const std::map>& files) { + directory.reserve(files.size()); for (const auto& [filename, data] : files) { const auto data_copy{data}; const std::string filename_copy{filename}; @@ -54,6 +55,7 @@ static std::vector GenerateZoneinfoFiles() { VirtualDir TimeZoneBinary() { std::vector america_sub_dirs; + america_sub_dirs.reserve(tzdb_america_dirs.size()); for (const auto& [dir_name, files] : tzdb_america_dirs) { std::vector vfs_files; GenerateFiles(vfs_files, files); @@ -62,6 +64,7 @@ VirtualDir TimeZoneBinary() { } std::vector zoneinfo_sub_dirs; + zoneinfo_sub_dirs.reserve(tzdb_zoneinfo_dirs.size()); for (const auto& [dir_name, files] : tzdb_zoneinfo_dirs) { std::vector vfs_files; GenerateFiles(vfs_files, files); diff --git a/src/core/file_sys/vfs/vfs_cached.cpp b/src/core/file_sys/vfs/vfs_cached.cpp index 01cd0f1e08..9f570520bb 100644 --- a/src/core/file_sys/vfs/vfs_cached.cpp +++ b/src/core/file_sys/vfs/vfs_cached.cpp @@ -38,7 +38,8 @@ VirtualDir CachedVfsDirectory::GetSubdirectory(std::string_view dir_name) const std::vector CachedVfsDirectory::GetFiles() const { std::vector out; - for (auto& [file_name, file] : files) { + out.reserve(files.size()); + for (const auto& [_, file] : files) { out.push_back(file); } return out; @@ -46,7 +47,8 @@ std::vector CachedVfsDirectory::GetFiles() const { std::vector CachedVfsDirectory::GetSubdirectories() const { std::vector out; - for (auto& [dir_name, dir] : dirs) { + out.reserve(dirs.size()); + for (auto& [_, dir] : dirs) { out.push_back(dir); } return out; diff --git a/src/core/hle/service/am/window_system.cpp b/src/core/hle/service/am/window_system.cpp index 5cf24007cc..ca289a84d1 100644 --- a/src/core/hle/service/am/window_system.cpp +++ b/src/core/hle/service/am/window_system.cpp @@ -121,7 +121,7 @@ void WindowSystem::RequestAppletVisibilityState(Applet& applet, bool visible) { void WindowSystem::OnOperationModeChanged() { std::scoped_lock lk{m_lock}; - for (const auto& [aruid, applet] : m_applets) { + for (const auto& [_, applet] : m_applets) { std::scoped_lock lk2{applet->lock}; applet->lifecycle_manager.OnOperationAndPerformanceModeChanged(); } @@ -130,7 +130,7 @@ void WindowSystem::OnOperationModeChanged() { void WindowSystem::OnExitRequested() { std::scoped_lock lk{m_lock}; - for (const auto& [aruid, applet] : m_applets) { + for (const auto& [_, applet] : m_applets) { std::scoped_lock lk2{applet->lock}; applet->lifecycle_manager.RequestExit(); } @@ -156,7 +156,7 @@ void WindowSystem::OnHomeButtonPressed(ButtonPressDuration type) { void WindowSystem::PruneTerminatedAppletsLocked() { for (auto it = m_applets.begin(); it != m_applets.end(); /* ... */) { - const auto& [aruid, applet] = *it; + const auto& [_, applet] = *it; std::scoped_lock lk{applet->lock}; diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp index b9db19618a..e947a3c2a0 100644 --- a/src/core/hle/service/ldn/lan_discovery.cpp +++ b/src/core/hle/service/ldn/lan_discovery.cpp @@ -119,7 +119,7 @@ Result LANDiscovery::Scan(std::span out_networks, s16& out_count, std::this_thread::sleep_for(std::chrono::seconds(1)); std::scoped_lock lock{packet_mutex}; - for (const auto& [key, info] : scan_results) { + for (const auto& [_, info] : scan_results) { if (out_count >= static_cast(out_networks.size())) { break; } diff --git a/src/core/hle/service/ns/application_manager_interface.cpp b/src/core/hle/service/ns/application_manager_interface.cpp index 7a91727f97..df0bd8acce 100644 --- a/src/core/hle/service/ns/application_manager_interface.cpp +++ b/src/core/hle/service/ns/application_manager_interface.cpp @@ -348,7 +348,7 @@ Result IApplicationManagerInterface::ListApplicationRecord( size_t i = 0; u8 ii = 24; - for (const auto& [slot, game] : installed_games) { + for (const auto& [_, game] : installed_games) { if (i >= limit) { break; } diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 1095dcf6c3..2cf12aba52 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -28,7 +28,7 @@ ServiceManager::ServiceManager(Kernel::KernelCore& kernel_) : kernel{kernel_} { } ServiceManager::~ServiceManager() { - for (auto& [name, port] : service_ports) { + for (auto& [_, port] : service_ports) { port->Close(); } diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index eea607b66d..20aecf4c76 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -571,7 +571,7 @@ SDLDriver::~SDLDriver() { std::vector SDLDriver::GetInputDevices() const { std::vector devices; std::unordered_map> joycon_pairs; - for (const auto& [key, value] : joystick_map) { + for (const auto& [_, value] : joystick_map) { for (const auto& joystick : value) { if (!joystick->GetSDLJoystick()) { continue; @@ -591,7 +591,7 @@ std::vector SDLDriver::GetInputDevices() const { } // Add dual controllers - for (const auto& [key, value] : joystick_map) { + for (const auto& [_, value] : joystick_map) { for (const auto& joystick : value) { if (joystick->IsJoyconRight()) { if (!joycon_pairs.contains(joystick->GetPort())) { diff --git a/src/suyu/configuration/configure_applets.cpp b/src/suyu/configuration/configure_applets.cpp index a607fa3af8..d5e3520718 100644 --- a/src/suyu/configuration/configure_applets.cpp +++ b/src/suyu/configuration/configure_applets.cpp @@ -69,7 +69,7 @@ void ConfigureApplets::Setup(const ConfigurationShared::Builder& builder) { applets_hold.emplace(setting->Id(), widget); } - for (const auto& [label, widget] : applets_hold) { + for (const auto& [_, widget] : applets_hold) { library_applets_layout.addWidget(widget); } } diff --git a/src/suyu/configuration/configure_audio.cpp b/src/suyu/configuration/configure_audio.cpp index 2341131585..5ecd79ae31 100644 --- a/src/suyu/configuration/configure_audio.cpp +++ b/src/suyu/configuration/configure_audio.cpp @@ -164,7 +164,7 @@ void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) { } } - for (const auto& [id, widget] : hold) { + for (const auto& [_, widget] : hold) { layout.addWidget(widget); } } diff --git a/src/suyu/configuration/configure_cpu.cpp b/src/suyu/configuration/configure_cpu.cpp index ce266642ff..0a26f531fb 100644 --- a/src/suyu/configuration/configure_cpu.cpp +++ b/src/suyu/configuration/configure_cpu.cpp @@ -79,7 +79,7 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) { } } - for (const auto& [label, widget] : unsafe_hold) { + for (const auto& [_, widget] : unsafe_hold) { unsafe_layout->addWidget(widget); } diff --git a/src/suyu/configuration/configure_general.cpp b/src/suyu/configuration/configure_general.cpp index 689d9be2b8..f8007574d4 100644 --- a/src/suyu/configuration/configure_general.cpp +++ b/src/suyu/configuration/configure_general.cpp @@ -81,10 +81,10 @@ void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) { } } - for (const auto& [id, widget] : general_hold) { + for (const auto& [_, widget] : general_hold) { general_layout.addWidget(widget); } - for (const auto& [id, widget] : linux_hold) { + for (const auto& [_, widget] : linux_hold) { linux_layout.addWidget(widget); } } diff --git a/src/suyu/configuration/configure_graphics.cpp b/src/suyu/configuration/configure_graphics.cpp index d11110a74a..54cdd8d25f 100644 --- a/src/suyu/configuration/configure_graphics.cpp +++ b/src/suyu/configuration/configure_graphics.cpp @@ -358,7 +358,7 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) { } } - for (const auto& [id, widget] : hold_graphics) { + for (const auto& [_, widget] : hold_graphics) { graphics_layout.addWidget(widget); } diff --git a/src/suyu/configuration/configure_graphics_advanced.cpp b/src/suyu/configuration/configure_graphics_advanced.cpp index 8cdae0a65d..28b3f7c3c8 100644 --- a/src/suyu/configuration/configure_graphics_advanced.cpp +++ b/src/suyu/configuration/configure_graphics_advanced.cpp @@ -53,7 +53,7 @@ void ConfigureGraphicsAdvanced::Setup(const ConfigurationShared::Builder& builde checkbox_enable_compute_pipelines = widget; } } - for (const auto& [id, widget] : hold) { + for (const auto& [_, widget] : hold) { layout.addWidget(widget); } } diff --git a/src/suyu/configuration/configure_linux_tab.cpp b/src/suyu/configuration/configure_linux_tab.cpp index 1db9893b71..488db7b932 100644 --- a/src/suyu/configuration/configure_linux_tab.cpp +++ b/src/suyu/configuration/configure_linux_tab.cpp @@ -50,7 +50,7 @@ void ConfigureLinuxTab::Setup(const ConfigurationShared::Builder& builder) { linux_hold.insert({setting->Id(), widget}); } - for (const auto& [id, widget] : linux_hold) { + for (const auto& [_, widget] : linux_hold) { linux_layout.addWidget(widget); } } diff --git a/src/suyu/configuration/configure_system.cpp b/src/suyu/configuration/configure_system.cpp index 3204303e98..e0312eb6fc 100644 --- a/src/suyu/configuration/configure_system.cpp +++ b/src/suyu/configuration/configure_system.cpp @@ -174,10 +174,10 @@ void ConfigureSystem::Setup(const ConfigurationShared::Builder& builder) { widget->deleteLater(); } } - for (const auto& [label, widget] : core_hold) { + for (const auto& [_, widget] : core_hold) { core_layout.addWidget(widget); } - for (const auto& [id, widget] : system_hold) { + for (const auto& [_, widget] : system_hold) { system_layout.addWidget(widget); } } diff --git a/src/suyu/configuration/configure_ui.cpp b/src/suyu/configuration/configure_ui.cpp index 589c035589..74add9bbd3 100644 --- a/src/suyu/configuration/configure_ui.cpp +++ b/src/suyu/configuration/configure_ui.cpp @@ -83,7 +83,7 @@ static void PopulateResolutionComboBox(QComboBox* screenshot_height, QWidget* pa const auto& enumeration = Settings::EnumMetadata::Canonicalizations(); std::set resolutions{}; - for (const auto& [name, value] : enumeration) { + for (const auto& [_, value] : enumeration) { const float up_factor = GetUpFactor(value); u32 height_undocked = Layout::ScreenUndocked::Height * up_factor; u32 height_docked = Layout::ScreenDocked::Height * up_factor; diff --git a/src/suyu/configuration/input_profiles.cpp b/src/suyu/configuration/input_profiles.cpp index a2ca806899..5add5f057b 100644 --- a/src/suyu/configuration/input_profiles.cpp +++ b/src/suyu/configuration/input_profiles.cpp @@ -61,7 +61,7 @@ std::vector InputProfiles::GetInputProfileNames() { auto it = map_profiles.cbegin(); while (it != map_profiles.cend()) { - const auto& [profile_name, config] = *it; + const auto& [profile_name, _] = *it; if (!ProfileExistsInFilesystem(profile_name)) { it = map_profiles.erase(it); continue; diff --git a/src/suyu/configuration/shared_widget.cpp b/src/suyu/configuration/shared_widget.cpp index 76a6b417cd..8a552d68c9 100644 --- a/src/suyu/configuration/shared_widget.cpp +++ b/src/suyu/configuration/shared_widget.cpp @@ -135,7 +135,7 @@ QWidget* Widget::CreateCombobox(std::function& serializer, const ComboboxTranslations* enumeration{nullptr}; if (combobox_enumerations.contains(type)) { enumeration = &combobox_enumerations.at(type); - for (const auto& [id, name] : *enumeration) { + for (const auto& [_, name] : *enumeration) { combobox->addItem(name); } } else { @@ -223,7 +223,7 @@ QWidget* Widget::CreateRadioGroup(std::function& serializer, }; if (!Settings::IsConfiguringGlobal()) { - for (const auto& [id, button] : radio_buttons) { + for (const auto& [_, button] : radio_buttons) { QObject::connect(button, &QAbstractButton::clicked, [touch]() { touch(); }); } } diff --git a/src/suyu/play_time_manager.cpp b/src/suyu/play_time_manager.cpp index 9a046c69a1..ede966da6e 100644 --- a/src/suyu/play_time_manager.cpp +++ b/src/suyu/play_time_manager.cpp @@ -87,7 +87,7 @@ std::optional GetCurrentUserPlayTimePath( std::vector elements; elements.reserve(play_time_db.size()); - for (auto& [program_id, play_time] : play_time_db) { + for (const auto& [program_id, play_time] : play_time_db) { if (program_id != 0) { elements.push_back(PlayTimeElement{program_id, play_time}); } diff --git a/src/tests/video_core/memory_tracker.cpp b/src/tests/video_core/memory_tracker.cpp index 45b1a91dc5..bfdcc8a16c 100644 --- a/src/tests/video_core/memory_tracker.cpp +++ b/src/tests/video_core/memory_tracker.cpp @@ -45,7 +45,7 @@ public: [[nodiscard]] unsigned Count() const noexcept { unsigned count = 0; - for (const auto& [index, value] : page_table) { + for (const auto& [_, value] : page_table) { count += value; } return count; diff --git a/src/video_core/host1x/host1x.h b/src/video_core/host1x/host1x.h index 8debac93dd..6de360d363 100644 --- a/src/video_core/host1x/host1x.h +++ b/src/video_core/host1x/host1x.h @@ -45,7 +45,7 @@ public: // Vic does not know which nvdec is producing frames for it, so search all the fds here for // the given offset. for (auto& map : m_presentation_order) { - for (auto& [offset, frame] : map.second) { + for (auto& [offset, _] : map.second) { if (offset == search_offset) { return map.first; } @@ -53,7 +53,7 @@ public: } for (auto& map : m_decode_order) { - for (auto& [offset, frame] : map.second) { + for (auto& [offset, _] : map.second) { if (offset == search_offset) { return map.first; } From ae65020815ae3e88f79fd4fd2b2493f8427c420f Mon Sep 17 00:00:00 2001 From: chaphidoesstuff Date: Sun, 15 Sep 2024 17:40:10 +0200 Subject: [PATCH 12/26] Re-added credit to OG devs --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 293d49b22d..fb7aa3ed16 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ SPDX-License-Identifier: GPL-3.0-or-later We're in need of developers. Please join our chat below or DM a dev if you want to contribute! This repo is currently based on Yuzu EA 4176 but the code will be rewritten for legal and performance reasons. +Support the original suyu developer team [here](https://discord.gg/79B6wqFPnc). +
From 6be886d0ff5cc8bd2617c8cfc68b8934f0fc1317 Mon Sep 17 00:00:00 2001 From: chaphidoesstuff Date: Sun, 15 Sep 2024 17:50:09 +0200 Subject: [PATCH 13/26] audio_core: increment current revision, Courtesy of Sudachi Dev Originally from https://git.suyu.dev/chaphidoesstuff/suyu/src/commit/39effa10110aa6b29708c5849cbd611c855e798c/src/audio_core/common/feature_support.h# and my mirror --- src/audio_core/common/feature_support.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio_core/common/feature_support.h b/src/audio_core/common/feature_support.h index e71905ae84..e2e00769c2 100644 --- a/src/audio_core/common/feature_support.h +++ b/src/audio_core/common/feature_support.h @@ -13,7 +13,7 @@ #include "common/polyfill_ranges.h" namespace AudioCore { -constexpr u32 CurrentRevision = 11; +constexpr u32 CurrentRevision = 12; enum class SupportTags { CommandProcessingTimeEstimatorVersion4, From 66993e26039ec5afb05c4fdc4eee92bc20605389 Mon Sep 17 00:00:00 2001 From: Exverge Date: Sat, 30 Mar 2024 19:34:32 -0400 Subject: [PATCH 14/26] Comment out unimplemented check In my testing on macOS, MK8 sometimes crashed at this function, giving a void type instead of u32. I've temporarily commented this out until (if) this is implemented and added a check for if it is implemented --- .../backend/spirv/emit_spirv_image.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 945cdb42bc..75767448c3 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -196,8 +196,11 @@ Id Texture(EmitContext& ctx, IR::TextureInstInfo info, [[maybe_unused]] const IR } Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& index) { - if (!index.IsImmediate() || index.U32() != 0) { - throw NotImplementedException("Indirect image indexing"); + // if (!index.IsImmediate() || index.Type() != Shader::IR::Type::U32 || index.U32() != 0) { + // throw NotImplementedException("Indirect image indexing"); + // } + if (index.Type() != Shader::IR::Type::U32) { + LOG_WARNING(Shader_SPIRV, "Non-U32 type provided as index: {}", index.Type()); } if (info.type == TextureType::Buffer) { const TextureBufferDefinition& def{ctx.texture_buffers.at(info.descriptor_index)}; @@ -215,8 +218,11 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind } std::pair Image(EmitContext& ctx, const IR::Value& index, IR::TextureInstInfo info) { - if (!index.IsImmediate() || index.U32() != 0) { - throw NotImplementedException("Indirect image indexing"); + // if (!index.IsImmediate() || index.Type() != Shader::IR::Type::U32 || index.U32() != 0) { + // throw NotImplementedException("Indirect image indexing"); + // } + if (index.Type() != Shader::IR::Type::U32) { + LOG_WARNING(Shader_SPIRV, "Non-U32 type provided as index: {}", index.Type()); } if (info.type == TextureType::Buffer) { const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)}; From 42ade6f62a17a87f53bdbd3d2d97ed543db82fb8 Mon Sep 17 00:00:00 2001 From: chaphidoesstuff Date: Tue, 17 Sep 2024 10:22:27 +0200 Subject: [PATCH 15/26] need to fix bugs people! --- img/need to fix bugs.png | Bin 0 -> 255278 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 img/need to fix bugs.png diff --git a/img/need to fix bugs.png b/img/need to fix bugs.png new file mode 100644 index 0000000000000000000000000000000000000000..124c55c91acc26037e203c201f57715ad8eb38d7 GIT binary patch literal 255278 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%0DoXBvzDF*_RN||c{8_HdrB~eo96`i~PDwOI& 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%0A?d)% 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 z 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 z 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

c5?!*X_cIe(cS~uOjmvw!$YL+8h z?XVcMl1)%4V%wGZ>o%#}}E5ptGKvG0C zX6o^7bZ>CB)yyveFB>rj69XMO29ueMTVs~*Go>to*MGlz5?peT9l!jGhJN|9&l_op z(Ra6*BwD`i|4SZL-gRjamT$0mr3a2(imlgb`*oZd_)N+#**U2mbfs~0PY&ugS$|KD z&ujWZvR15e>ys?xZOY11*8E4c4)-Rgs&c=|YB~(8-1J*Hjd%WhlB5uf`Y|~BnB)2G z9EHFP#RSpAarV+unVqKS(r^le0nOZ8c?>$ncqZTV`FEGsEv{LK@9x&sIao5y?{=?y z4f@U>#;yAg^?GMs;BIfJ3p}4Jd(!>1tK}<=pYlE=I>{{bVxzA#1&dg))6~_~1q6W6 zeQXHW$3I=1m2W zl8kI>dOF}}MCmmYY62WRuGh+pzK23yc?DPpAc)5O zF)@+M#l*~f1=jhjWEs5kgd6w&g&q8!si}lb4r8=@E~NDx^}=fdjD7mfx-TPR)cgfZ zyo{L(tTLT=RU29}dN=7#jxlu7gj+65KWs%a?@ai=-6Txuz2NV<_j~wpQq}r?tp+3S zaxI-&`s+f4hPs*|dxd)nxXcEn-V%<&R&)BL zwSW{US<9M~wnDEbwW`oh@qUx)5yLLqsi6bSKHkdQUAc7O!IGMHoZVrUzS%EJLnV&} zRgaG{@a1`HY;)8Ap=i{Z@&rb!+Y? zOKvi3^t`E;Zf<2_qT7@d56g~8La=Cd^rtWTcuh_DW8buyG#YakJz!#BVOfWU7_iJh zDl)R-%1YxTu?{fnhg5KUkKK7{kcW#UwyWNH@Z{rTam#@>Rs8ej;4^#u#G_1jx3 ztRRQ0+ajNtq+u$|+g5@8Re+>rJ9E}d{4>|0pK_LV3;Z=|1VPy7A#Em?CvV;wv`kPl zbdUC-XHlK@6y>`}(_}{0a(c1N;$KsfoN) z5yAQL^jeCF=$~$2k#OL1v7)5xz(lWQC;gMlK}bmG`e;l8pi598>4l@|;C#Zu#?Fsq zhslKQ0Q#jXXn!`@t*5Klma`SxMe_RS91St7Z83zK1O86W%)C2a3cW=L#S-akwKoya z&q-*xMy&9gH(H-u2{IWb;FLRgWVOA*L<(n^9)DC<7(+qj)iV_M$<;B_XLt6=Sxfc} zcSA;(-=R4s;SM{B7-%b0mg65+g>B4xaOkq};~RdXN5}8(?ce!g*p5O|(BIqhp^T5N zvYC)V)W+FRcJ=)ywlB|T2euW%sr3b4xTPXV&D~#gdtO5^EAO36&!mY4cafXSA}}*3 zh_*x~k7Q>bE%;s{m}f-z$MKTwQVy``h_ud#{M#3Fb#|g2fV~2cY3U7h(2cgi*#gFe zixX1AXF0(#_W|hyP#M_s%GH770H#|Svj5Uzm)cYM4ReN~Da74rp;LU|vmVZdqeLqd zs=Qj3WQLu)VgdRb_G@W67gp4{z(6}|>(j+HlmtLx!L(ujWms5P>>Y`E=q50#KDfQN zG{KZ1kH2}1OX6zySLVXQtMYvGV$z?G(xqTtaB1;}*e$B{asRbSgUJA%7q_y0#aCy9 zML&1(_=`p~P~0WoY+}qYUu(8q%zSQm^ewMJ7klf66B}!Y>w%p^f0tCFZk0y(m<{i< zh?D0}+7tzNS3m7Oou$$f?9%)8dW~|B)|l~1;f0WE9AS5JZIQTU{Oyup#0`wm&b#6! zf1TcUJ%7s-Y8_F#3HLnB8=XQI+GLfy++5fz(Cvb)8fe~24Q`ka3OG~+edr!#$ zWFf#H2=oINDykB);U!2HG}rPHbeIK$2e~h`4~V7dQWae+sD?Q@tly$@Dlj_cK4mKRN7WM#7NNGD#GCCpb;zmwrpzllmM zL89wZGaqlG<7tSgx;(J`Wpk!UNyAf>>$r=u;cbb`bU2Su#AW-aJnQ2H^=}!qr`fJ$ zMT3u)o+DMw-s@I4-Ie6T(p29_Tad+}Jl@z`vSW>F^fy0yrVqsVsP_py#f(~iQzTw{ zS+Q`P@BbiHZH&h73;DmB6s5QjxNx=)qq8uk41A}51BJ9uDAxf~%W7o~zTz4(XXf2^ zK zm!mH@)KAxVOlJ_EQA#~x)-RX*+*qJ6?D*G;)mYPV{9uevpDg^2y_8)UK0H`BtmQ-m zwlT6&(Ke5Fva)PwHd;pPheR7xJD4*ar)rcY@n!`5-MrUXvIbf``=N)O0SW^hZ%CUL zo(Y4+yt|1huzAMJ3m_|i8Vm-!%`s6pd+i++@L5TsZO4lV2-=1oV&LL(H7+{C)NWdk z7;_3!Z^2oRtPKwtF*@j3g1$fP>*?`2+90ULsAlWEy1I-)AVRkz5fHu0s{yDH(0Iel zn$;)4D=pWp{CYh5YWZiih&mOuj{!!pVa(RLLtA8s<6pgStTx0I!H-J}|4tIgbF+@i z6X4WlL>rjy#7NLY^o)fD%~)D&4a zn8E`|%KpbE1&@sG5)TydkoHLJ@QS&0*Y+9T3DU#K3Q3odcMCT~|9t;3!G6*ea5Ig^ zu|si}P)DE4#S{b}=F#u}UAGq3gISM*$+9_W;di)?H1HgXt;TE)3zONReg2TY;CNt| zhu*?0(X0wNyz)}KBN9-l`#Lx-J>PIScgxT2G1O(hcHV;{@L^r>mzTq)jMfWxZqnXw3Uf>9%+sT1hH3(s@Am#)C9g8c; zEsRw1H9Q+JICNnJ34+?!uc*aRz%U^#Em9T8`e5sVAtO9Zx^(&*q{oC@V0pq})aV5` z5U{|GI!7)AHVsJ0RN^!iIktS=wq}~t2vxQ8?LNFZsbW$JjZVJFP^9>6o6^-ZV4g4K z;+E;ZQp(-;#-8xvK?@P5x0Ctf3oL@!Xsn%!2N}LbwdlV%Irg8p{GJtwSJn-7)Itx@ zx^HUR*O-_66j4IqNwkf_mI6@OH+^sZ&|;} zM1%1Id>z3Eqf3i;D{uxOGyn?+-AW_s!T`YD2&RDK_Lifgm~0sHb|fPhq@1@nhb$a< z!C?sAcpyl~*Rr(M;n#HIZwYeM)rBDEnwMSA_|G9643|o{wJ6wOag~DbxU;ZcqUdSN zUk>vBuI?rM_aySV9rK3M-NJeDddGAx+e);_&|#VrHOGY7y7ic$E7ACO@1pUDAr9zx zf5@bfWM z$RZS|j<8+?WqmEd&-2Z(Qjct{z6hECjD9H*kvd3ZIt_VlWpx6Z9>|eEO?}PWWtt>p z9sKg;ORxb1#tT*vARirsot{|afOK!LV<;Jn6{^BP$HT(|hTtx*V*F$Eg+(VlVk3Q$ zDTYWMoBsC>qw{Bd{=L;3fb+*r|HaAn5i>f;H_4WITRo*E`d9bA5G}Cde{o!hV}Ony z-rjV$FkmeJ4I=a*o`|5}m%a9?hd0vP2NK`C!`2(1%Nakme>ph_;*Q9p4-@raQ28oNfI~*PXL6$(x*}iIRQ}}OLg96Qf(1^%AQ4M6Nc`F!4{;YGPPg&!0^_OfX zM1u#F^|q_k#L;o;GxN_WR~RJS>Fs71$Wit-w%H52G*0dYZ4!ZSnB%AUE=IB1eV` zYKrjNH*dzFF$A9rcx9L1dT2;VPY%{pT|6{DB_+|jl|Ty#c6mSQ-S0+?LmLbatvHc@ zJ_N`DA0MYDCWaVj>O+7X&5KKdM^dMYArK(h+&LhiGYC!(utZ~IV#>Lf<2iz-2C$*R zwsb3jnzV1)h!P%ZRVujifio2yikumtcctxOn`6KGbyiC}QP_Wnvq{pMcn`~cExcL> zE|2A@o0Xx`3RMb2TpBVMZ%cwLWH!mjnNCv2GrZ8tR6O6 zKk*CS^(Dpmc1c{lLNDi-%NV29TeeJqS|u~s9`su*KowraduzvowV zV~GMD{ubilqsAP=G7zjnZQw~lYsKKYpAmc|3koGE__SsqJL`Q4yOOGE8B9CiECz=% zJPW9@+pxnosIm?+SalC7_e#QA&5JjtC+~&_2T$`O7&`Xgo(IvzJ^-W}%TX8jA44)B z=mHM2Qym>BLi8Chq{b_U8m!ilVwp!g0LBWQH;74r5T}=c>mr|w0F*(IlbucLy$TNk z-jUhaSvc9}ntVx@O2xUN$WvLnh{MF^Zo`%wrJ0yC1v-@^dd8g(u2S2x!ta5HA zS5JMqQp|CJamQ>dWmtPi9~VU`lXsZ!Vs^aX(nTxXqd)UAi=XbKZuAOod&RC{{qLL) z@j|Ahlq?@{YvNKy>GMnET_~C zO-Ak{-ZMIha{H%U6IXf?fuDfApQ>Iw{cnXdvF=ngE7rvP>9~_j2WO-C-or)TO4|>g zI!C0b&H9Wcy5ziSL%UTr_?UKF;7lvk&huz`J`wd-Cp|KL=}hWa4x+6m$Hwxcp;%R$ zN=;wi9KOvS)tagC;6Y4WT(jHeZFN#k{iEMgJFJudRY*!oLha->hBsip15c{8IRn<- zU{?bq`h6=D0-%E-qo9CM_RBXw4h!Clexoe&k&E}0yud8h>!cKH*srIv8_|>R!=aYS zX#kbK3c9`v*dD`C;tvL22f?>+u*8Lhy#QhXmzwuRP3KVYC81tG;bc^1>!6^4xe1(C zWmohxvDic2W|P6jCyWQr8&T*T@5&C}canc6`Qj>FoVWG+U_7eG==J_z zZXqT1QSA%+ADwO5w+XM_@OgX|it{E8#6KJiv2=V|ZC6_q;upz!QDJ0!^u8a&cZc6y zE9KSgiw3BQYgd}|5;tk;)+<@oro1y#Prj%5XT(mtQ$h>zBXk_Y%@EZ)cKkyaEodKR zyLNx?yR*(A6}cZdNlbjCW8hwM3X)Vm=1;IRRIL15kvuoEWz4s# zoj&p?*L_qk&d60#d}97bOG}WcR_yV?6)k(m@b#3MW|vp(v%#q-CkB%2V)YAV`)-Yc zmtx_HsmUKs*mH)sUtuHq*#|#P6rJy@tK~T=`&GPdH38ZL%(EluU|R|;B{v470j^f+ zPepOhQBzTERKZ8ic)9-i-pZJUl*0b`$!=D9I>MS4LjS-uQBhtVXsC$9FYwfZTD*#h zG|fwkaX$&IG2*A%Jz{m(R)BjSe5^h&$q}J@2qYtXCcr$IN#$KqlH1Y7$i3#%b_g@< zqJ#~UgFLEqWN|pJyQ{0*C+qmmPI>U?hir!S5Vx10A~nC61Je zb^%=0OOWb2S?^{$9o_Ww3`pMtQpc)V%vU%cZiK=cr5(NaQZec2>aOf|x`-T!#H*ah z1Q8X>pO?8K`fnYNq=|d{dG4qevJnLNd6xVh$+z^`%Q09YLJ`K|-skAo2GSaPaV0?h-KjC@Ox1dVMdt-OSpWRr#8LTTcd#cc6@epd403 zk1^m>~@6K;LYFZ6LHS8MFKVdow7;NS^|=hD{wx zsA*YV@TZ1_U=I7(o}M0fN9Y7pRr_JUByJXhphU}b89qhDOHBKAv^3Hi`+xubbxu-d zL>TJaCaU03!KqSF=MobWqZ(0wL&D;b*U_Ip;F2>OyZ;iT8&MR$&s^u@6dN@85Kzb~ zcC*dCC)8CID*_&s6yxrjrS|x7Mi*7j}~5EdNAO*1LSHrFMNfPJ$1q#&yUjJmM?&8Yvg3;@XPRi*hgN*>Dfz z-Ft$AFn4O%7fu)* zr_7|;n+Uh)l<}_aci(wRsVA&No8OT82?r`4nZ^=hunpTJ3nhwe&2)$H?Hw&+@fB_a z3_>rl{HwfpZ#brEe7WATbmp*S^<$G5dCbX8ODa89h@Etaz`}{>{q`+3Dhd)+ghec_ zTwSTx+GuW2Y25}6E=<1A`yv>H(EkI&21WoDu`k+zxw#}j;DpvX--pwvk!%7IeU;=E z6ChCY{4mmFM+35{+oOo@I_&~|DxNl|6Sl74p&wHQ4OCajuQ)%=+hDZMY4An*D-Re- zdX6wDvgPOra&v?CUAV)DLXJU~5d|qJ@EIg9pug@SNX?Y+Wo~G{&v?7**u{P|-g)<& zxXpQaw*K3;*}LPXw+Ib{XiTiFiD-zMFWwl3H~U8fbwu-b(Nw1KdBhqcpIi#vzx@((x%}(v3Lw;fTSuTob{_fu>udR61Z^B!&R_Lo0Z>p~0>8tFT@%_x;n4omF z0jYaPdg;&JY-ip*H1>N21Z+15U!|E(kb0QnSD(kE+Qc~My7>_mt_Mq|w5ON5r!s^| z=MU3L>kXRae0|TNX>sP^_Cj{7JfPWhF4bkX>G_z6y7lbDeTOsK-$bpgV`Vb53SuPN zn_U+SUXDq=4UMKUjxi#)y%_dyn0_En*!sG&iZXe|B8JuFyMcL-};n#lOtB=x>dO7$H zGxTt!Dz{c$0|e4?bgas!iUv6W2a`}t&mg8GKIUB=i7vvMxGu74ePP`y-TLV6#WQp6 z`I~?v%J%m3Kovko1=m3{v!$>Lz@5PbZFFR09~xI+GkLqo_!>Hui4;XpALIIc?}J`RD{d)RH8Q7)a8dg8_N`U zQ=#Hi*^F@+H;cpa4~{L=0AQ^^9<59*hmWKt2QrI#y7E;pG|+55IAl!d=klas4t#uk zEvL&^2XEsYoEh5yd4r7&q@Thm3jc3u3^nPzumGQvpWkfh3d3w#Z7AxI6OfN94qJQ= z_BL6_)2*dF1E$*k7{9<&4y1N?I)2n%vCt}+HTN~$mE75U?91=Cboa@uI1UQSdxm<_ z#>RDRb|bz^N#CcD8dB3E31s_{zSz&=WYQ4onJm@%OA3uX6+dXa)r%x%A^Fmzg@x^d zNH6mXFHYeft?g-~-s6#nf~QCrtKZ1Q+*pKUBvlmHtWplXei=A(F&BrEr(*a|24BxA z^P$mRKkKVs^Ml_Qk3KCL)iodb6>F~PxE_>m_y-LMtBTEMJ~H}}tApQ1dx*h$(l0KS zw*F(uQP;_jwRK4C{JH;#F{_#g3QJ;|3P5vfV>{oYSB?Q4INaDyF>i zihCV4ugVV3Z)6p^qKKp7*NPA6?xJ(`YFy4g9xB*5br?L8xEYC6s(P}Z(zR&IGH~3N zb=Pxl*VM--Ht>_5MURf8t2k{}=TR(E^+Gv^+i!+b-I8XpgP$90#i{pR1Zo(qoX;-K zhN2}OD5_^nvk=MM$N0BAS-pC7-QGJmsIJ^28m=R*alspLU z6D=w_dQ{gk%vD;#^IeR(*gagp#Q}M-sHi9mRUyf~kOcudgCeMo$HyuU`uecmN^5Am z`dUXN;ggh74QJgeAgci?^018hEPKYC#U^zpzgUY5`HWA% zTs?GK%Q-kY2j8(g4C(Y4k&$3n)+r(7A4tK%a9?J%da&?4-H%9d?>qHXx-BJdOmgDx zi?o&QCnSSOHKRBOls467C?;%<>T&NSERrpD+rC_Zc zMa#B6pdH}a|89>SSdxx!T;`R#H638*KqgcvdP)KbVdHsTms^gPdewse1vJ z4oFS3vSCfZC7W7bKwk5jWO4)OK=6>EMc!FH_=%VDFb*IMxkG@jfZN_}1Kz`6U=v3r zf*ht9+MfONhCZeyKrx{U0(K-%WkvdQSUU|!|N75R@CO<{wt_c~rhZU&EmcOWuW)FU)Mj=t0xiRt zOHkonK$6DQmiXlc%X}q2^vlDo!F^J4vl8DgCmSS>B;ziL)kXEf0eJd!$a~OTl<=;1*%7!&t1s5R(MiJqiCjD#tdG zwC*SuXix2Ub;wR|_q5eIuQ>VbEe3&+W%>D=H)M}v0PJ}g9W7VuWFQi9kZl4CEno$J zLWok(?sTVSVY*7IPSHH_^~B?1-LQ$@@3YV8e{51p{Gkm65=%->t6FM_yLdAReEr< zC@;TEcS#EpTNEyzSN=Y1JY5qs&!a={@96fz@d&s>i;Z2vidlZi+m?8jTc;4?lvsMT zZn~km(QB+ob96#bsNXn(8eUOfT93_?*{qc+_NMO+nT@WM5y_!Yuh~`-XCJ-bARpdi$?~I*SEg;v?;M$(r2_GUC6He6cK|#qTC?n|MTe9on7Thc(=%=i^1m~0B>L@kVeV_b@u?X^w!oXWYtIjKf9;r6YzJkk(}yX;{y~M&NuH@h}zG(H2WRhwSVZdsCX3gX61Kia7O;o65V3N3y3Gp z!K`R2I}|$mVlD9$WBpMUu^uBEJ#SUpJsS?g+kGAP1llVf4n*)T9w_z^X$cmv-Ix?t zJLtc0*ZAY<4?>6AT}!0<-wdcNQl;j^7wlwF3wy?eo z<1$V=fSVXy2Qf+5=KlN33i@4^y$MXvDC8{@D>yiCsWh9k1w(mw+T>FW_!g^`pWh{T z?^F~O)qpG$OpaT>H^B}8-kDzX!s&R9N)GDHQV3cA_!wZd??4Hk1?X-RW_1x!QJ@ID z((^4bDTak90qjRtx!(d&81hMqTc3lGbRO*BW~46nI$YORvATbTU>F%0!JuzHoDD?@ zFH}=eL^=cKX{sd#y}XhVtREj9mp^nZe`pN;gj`In>%ZTl)K_)LJsPXl@(K63Q0F6a z3|SED`Fg^6;n$*bWK>;ZFgoE;@RhjMU!R_d@|-641OJpc@$ZFvaHZm z<)md-~B7(;ekl)B-q&wtqN&rDl z^)+ukU5*BB>u^DP>n8v@jB6Y+3JTUhSpu%KCBWlZ>q~}vwb0~S16VSMIl*5MuoeLL zfx@nfS#zWQQKoVb12BFH&Mq$U$e@TvlinNb?S(V<7ljEihj&ggKKN;IK@61ad?d->nVT?eUlaQ4T5!o*18DTkM&%D17tnutN2tD?0 z^)(Zd$CSpJT#lviY`x2fc@`vO1L;5ZwTJ!pT8O+p%I*wb!yfUbH=H#te4j3*b=z-Q4%L=^Iy^N}X{GJ=re)JnqB}U1>0YSgPy8w8B}_^pfgB| zeEhSstLtg4lj&+tyr#Rhc7k8L7ClIJ;j$5Rb0oJ5$Z#gon52C-Bjpb+&s?WbPJx&M zh|=iD(&X*C15kW5Kfq3ch)qa{REa2=imMo-iw$p80w3884shmV6*f&+e1X3H+qa#v z%I2-HB0LqN`8nc0z~%e}9PrH}5G+vhn)ev+h1ElA{d0TU0M1z;`$Ff1Uj*{TE=UYNY7(V!#HSt!@ZX1aH zW6WTGeG%jD{w{E4a^GKhVIEE%%#N9BuW?Wu(zOb6JTkjz>KYv#?eC8cMZQD63w`(7;xflLJ!Vm+HY4!9;=hC4Si)(%=XP z_wQ089#C{}*Vko-Tk{)ME#C$P5X)=0?>Tm?IpATwY6Vl5y@B291E5fVtuzoJoki`Y zxcTYbf(~n=hQqvLHqCZGbV=rtu&Dvqn54qS5%p~x$%)NG|jn7xf^xDjaArr zZwG6?K*~!R7UKtEL_4XmpVneBX2>4A6!^oKfM;@#vo(sh+JdoK4xUh!F%f*T~{Zv+?v$&arycVtx{4@9P2}14gkwd<)u!s1MIV5c^CqjKggIpqr#JI|GbH78L+9FVK_|eeGBjt0!6tcHTAxuW>cz1!uLYnSj66z*` zelH8#EI^(L>17n1b0kqe?Bb@q78*!ggwF}O5Cq!_ob$nmbT(19nH;NAmIgu6DODF2eWbbR*LD&&o5R@Q8_Lfh?SVsPRD)h-J zTIxyUkDze?wxkqDaZoW+RdJ?Yen-ju2p}%!){oCcm_nG#Gcz-C*-`~Wk8c!v0-F*L zm6r%JN*DLuhoUNn!pe*ZpkfBORc<1*H~x?p2Y(U&du)XsCxckaBWoL*TM04cN4hf4 z{@d4{YVf4|H3Dgc(egcdXu;SPV_RYS=Q9rNPEX?p3w6M&f#*brpaE=!Epa~L9nVZi zkZm6=P$j;X%=y6)z#G^u#~gtm>e8O4KLfeUL9Yp9V|cP9*CLU`CNawCv{QA9&uWz-0Rj3Vl#2X$@?7#6?wz+CS;H zj;^w>GISdD|MtB6)<5PD@Gz&u>V|7@)S7|H`j8k$dE~SmLUcmF9b7;yd+?t z?YP5CbMtAbyyizOnFCSaA;SFz!RE-sr)3CE2G!uGde0XWjA;1>jvZRdZ*Xkq3I_Dyc-h0|S>}0}5ZcCbqAyFA-T6VZ}p1O_zTh0~A6q$;U(H2WyJs z(|t2KpkPAt3|}-I^g4K9N^Ly`B0zi?e!Z}K(<1vdGA;A)=%_4M}7^NHM5edE4N?lqyRQ={d zcKN_rpiTf{BU`-k>sr4L;kxV!7|I~|O0gHLoSus`XaxrI=J>a7I9-bLZu(3z{%hUH z);%PA?auH;35RWE$F~3d(%Q691zU)S573l%c6I_Ocj=>S9zVop!rqMNW@mF8DI+<7 zX^!x;=y<-)|1Q=OliJ^mYTsYIe_e|fy4z=a>g2rbfHq?pNN(*(UYAxPlZA5k*fial z+JP+!+BjBITVSYke_INjGWk7#i9dt?&$1gt)5B^!tVH7xvCKerb~dyei5>K!qHohN z9^Tr@DyBx5KJ%93DKqBsD8Y|>1!V;T;}o{BaQxz^CD4Gr+kYrif}EV(hj)XkAX`^> znS=^5EuA|%zz5|?SXMbyiTJXD;vuXOU6qTY$+AbU8o#2w7GSOv5~a_8fw8~B30!i? z_$&utO`DOCVRMST^lMuBwfW-LRmOQG29ihT&i~`al`^uGY#x0?B#52=1j^Mzys2dI z*D7Ba93d#>0dph6V7dqcDvx=qb){AwIzI|u16&+_pp#lS(5B%1riIOMifkY1;{)iL z1bBJ-1_nSdGYt}3$ZAg-j0Kv~bju4Vj>jJ#$6$aJZfR*A-(Gi&;hk~R;u4NpyH*`D zE35UbEfBXpH!~XphHz3+5{&M0avi(P7q}vVHlz8hlOa@$vWGc9EEy(&+d@J@!pcY7 z5EY-EJ`0_ao4Wpb!Hr?!^F3&O=pb8*xEmP{m*Dp8V5g0p^FP}!HFhfy_DkKMV_g}a zpEr_Z@9UZBY%4HQO!>ekVQOo^9Dhd7*KZ}l^1nA>G>?w2y6?LYtkQOgF4J*g$w0Q} zSWSt=5U&j6&syl0p=AZe(TYD(sO_#0|AVhnZ)luYlU1;;$MMX3q3wo`Eu<>|)CdJ; zNGe}JX8D?fBGG+c(9?rKXT1UK*&>p+t&uevBNUscZcK@rYzB`X zLwkKIwG4ArdIX}U(LhyA18Rz5@?xwa;HJUnSn}nIAb-g-b*7}4n>aq|%F5DjYY8Ir zA~(07$}($YV&|m1^VKg*_{fbeI*}=bBPhr-!M7wNZ1rFD_qzF!pAWbG8c)(!clRgs z`0V$BnNT9*x{xi2?bm!^d6$n8inS~8fzhUZ7$(*vQN!$dn6YI}0cq*)WRMF$f=3Hz z$ch_`zHm}|YTY64#Kyz(gqYl#nuzkUGS%o(J$p@^5?#z~YTkiA+8&RL=6I!;IhjJn zAruk5M~Lz%)X2(&&K4%4#6+-25T)Q|YA8_#xmNf%aQkR&ZERq$!jhGh4F;0^FCStg z_fYn|QovYA)oCPrfU%&Jp8GUg(Gb#w`0qr(#FOh9)%A?{4!JzD&@*@yA3rA3md3a( z0xVaB>?c8k4mim^=F&dL*LLZKX-faAkPQ#C{~uvr9o1Ev{fkIRNQ;EDbcb|zN(o9U zARrymA)V4G-6;a1q@<)0Qc5Eo(jX0Y^S(28-QQaGuKVxItTpmI=RD8epG0qYhuO?p zc7a`t+2x5Lrh!uEDJ_lkhj@(HhydY3Ffzuy3y^`rcx$a-F9SMC*lrc{=tkJD3D{7z z86tP#jgaV4x|g8gDgPVR9JmN_N6wFT_`pd}%o7VoX2YjXyt+7i!zsvU?aQTiM{{%9 z*bAg-o+*~Q=NlUvi%We+Ku%)eb&i9t#TIx5_)!mlm%aV_`PTqTJGn zUU~b3+=50(XbSv~`@m?xMlOrbyvF3m&x2$P&`C|4eAuUuV0mc28!o_zbXP=5jk?e2 zUvUq`tPu}a1}jgfG^2-N7WfwF@386k8%qi&!ncw&hJ zL_bmZMSU)wQpYxl8HItAKcEZdj@>y>Pmk8B;&Q{*@$exs*Cktj>p3SgvlXbWD>*=R z4|p1ekbb@fqN0`jOpiIrS%am@x1| z%@v}5Vj`jP1ZbO%CupRAn@Os>gD|6ah84%f&HaPS4Z!@;PLhJB;+Aeh&|6*s)$hcV zL+=f5lU$-gV3B4VK6jka2;)D00j6xh;%hI&sFVHaT04rU!?q5XF)>MCR%rJHf?9n} z5MFvqb%M}3`33tjr_xh!oul& zi}UllS2Y-~N<(pUmDd`PGO`|3P;+&jmYA*DuiVov=?$Z{`OHNA@BZ6|?#%+p7>Ork zVPNU|XfgDL2>g<~-bik^HxobPR|Hl1Ui+-X#>S`i+U-}v9SRae^w`_1V3L$HI3x+e zRsH>tlAhii27VXX*bpRIh>(>cL&p{`Nqn4xbG3mFMW?Qowl9gPIGgGO%w z%G87aT&2)8Lu@)y%5oS3fo>qX4_=^_{Kuscp?%emK zY!o$ITryuE9FQ0O_PcE(BTf+f`f!Al2m3r8h2UcU8~f$i(a6uAMW<@W;+)3YH)0Bq z2;qgnhSCFh2p}T+(neShj~!Y^!L(kiE~)0w)8GGadGP~HPy#|ts8^^Qyy5b|FtYJz zpnz*z?zOqq)aNar%Z6AXMFUt>3%q53W9)UR4jB^_VY@dv9r~D3C#}Rh1=}j`Tzdbi z{x?E7ntkuqX|5zqrM`uA(pA?N{o=kSn1Jptf` zbq`AEg1%uQ_k`h)lIkILqpM&AWS8mfyK`D(!`qrWbyK}tjiW2z$8~veArD~{T4yW> zTKI$QaC)pCc4GKgg53`NBYyz3^HDF)Czab~j z70$T@L88(}H^y2-o%gyyt`D=t(bOhkGQ9Dmjlg}_^rSa@-sOI=d|O}ND~Ok@19>qw zFYnmQ3{0DHp$VweU^hiYMuwjSh>SnrzjlopRltr;IH4?(RYBm^}94rpa6d6Dcb zh(`eQzXzf_vRm=qgjm2=D!&DIkffT7pYn3|@S9pW@C!gVQ-T0wwQT&Rk&DVN3z9$r zZ5X_`Ki1(pa`@icODyQj5NHQ&Q?Ne4=VG^2!%z>HIIuJJ0}kt{?W}Z#E9$uH3Upub z!h(ZWm_PG9`u7}Cj3`M(Zq9?6Q>E+6F7PTchF2Ale5VBg>(}V0sLxeZxRP_8#*f?i z17BnH_gs#zA$Hvc6k9Zij7GRpL|_!9hUT_BsTPG)wk6FC)bmrzh_^T5Y>@u$y&Ts`15N+fyhjZ|3;gj0|SL?x!k$>KV8$9$0f zQ5_Vi#Nk0j(+-(Fy86-Ie^r_E(u_c~WJ-3W4@|-K>o6mRIspfk-rg*OrmVjbtB$%x zj7I+|J|pAHi%xVj06Ey&M(M>*4I7;ARHvnp>b`Af#&nK@!Im9r?`Nuc1}Jsm*!X!2 zfQdYQ;$)Qx>R1#tgA6U+k6q3;jrKeo9L12d6r|tmbyI{1vEX$;qQTUIkuO^f2%wkh zr}yz?(=@kpO@QY=;tEu~vpU&iPR}Krp!y$I6cbVX^`h_LTgbDDj3n@f4ld4q=_7 zV8Zz72Lg3HYd8)BA^^YuA3GK?S0LgT%-j6@{Ni8)U)M46ev;rFVIkuAmyF-u7;^YP zL7}aE&!!3~@P4EtLcarKlD6-7*M5p;U^DHOA}-*Ht6KVND+(}lRV>9rR)FyV4oK{s(F1ox)j8^Y$% zXL;MUeBW>bZ9r(L13bW^sW}oDh~ys9?Euyvm?>Cx<8sjW5uxj4`mP_FMCwwv zg0iyo2U#A5ZV0vD=fCd|L*pT|ptdymnl%34g|I}c++pdOM zq9th3g*kixdrkO$4+2j&AYfu=Yc%k8)FhOYFizf-buGn0Q%w(qT>|=jEftN~fFqa& zmyBYl?sxE=)a^d9r&&l#Ny(=0@bo-CJp~e&N|=E`p5lFpnn1YGh$vz;z*U}$*5KhG<8CVcisZTg?_xKT zR)lk5qGK(&?S;c5$@ERMZ{z>oyvACB8pG~+Qq8D01UN}kCu2bO6`Uua2gw?y5C_s z`;FC5);+tVq#ojG(QZ;_Ai3hyfob=6VY~WNT`l=GCZQ>2 zl>TNrEoEfu^>>d!6Wj&0Efvt5`b>$`)zggJ-e=BU_OR0cU$lUi$t8?Dl;Y)J6tTgp zpr+^}+anv0Ri~8~)%Z5v>d&SdRl2&uTGPtHX<4o7cRRN9GP&OEf2Og;2Xb+IoWhXw zm}&xNFx4DgAaR}r7cNH#$s=pD9LPQ(xD|}gB6Z(5=G%~Hc~guoWzSZx#tUO(gvEQ7 z!`TSG;`!kkv4o)Q>P^06`lV-NOw_OG*?5=n#lw;kt{?gWEVO%LuUInJ60SdHJHXRq(_HMUKXx zeX)tT%LdVfFhOTSlI!8qhqSaqQwopgT3df%`oH4(c$yVQTm4KavM=I;&YNxt=!^vL#T>3GTuNQLUNd9@X+|UY#)y@LTmNI91aVjB~n7dBLIZ6 zQ;liO%@2Z$rw`2%?hI|gCf&mhdW&9kO?I>V4XDR_yGfajfhKf^ zGuWFlgi9k^u3ujb3IL!x zVIhSQy~>Q;Uo>0~p3^}hF{BKLoPhU*N&BZDe>V6fF*hH}BtX!8!q_Ng9lX z7&&IB!@gG+?x5LNbM8xE1YUT^&8hGSgwewR|9&N+;t0m;5&Qe%B5&Jo3e2p2vY|uI z{XCFz;2z4Sj6l4Dm@fb)Dm=>fF@DJkUS3|1oe#JG_NsK|EQXjs^3#8I_Kl67|r*d|E#FMC0ZG=Ippf7;BF?Hsx8Qfm5*4=FZn^` zqnE_eG*Be`>U5o$6;sOF2fae9SeUbhhPm6hdge-2xq(Re+juGpa>p%e|5Y4m z(Mg~CVpnpp^(~p#Jp!!rZ=wz7VtW!6@wO?RHgQ#dt7h~zFfEFXX3MM*l)pz5l*(6P z2V9eWI)R7FYWWd8^?_0~V|6dRP5R+pYe?pV@TT>Vo%yj9o6y_C8E%_Ss;^6&6g8$l zLLVKyKb7Wpc=hznAPKwOv$LR5-{zGad=g?=qjww$It2E*ZZG6?8Uyvd1-qa2Aci&} zveavOp2eIsF;#bvlj(5>#62UKBgGu>x4^N}yQ_lp{W{00sPaMoQ6)Hlw9&p`&i+dG01tq6Uhq%p!7nhf5yjFC9 zT=ygUA~~vWf5wl@f}yf)>HN>f!J91$ps7RWtJO6~Oi)qrDNyd&r;!A~65w#Dsl+Ti zFb;?&9^?H=HH}IV82Si0UKM(Yh*p;ZdqN1Igsi81xUFz7v9K^mTO)_<|NePKw)bYp z5;>p{Qs@~1!Pp!t*>^AB2o6Yha|m8`a&zNXlON6RXN16OE$5drf3`1q1JHI z++s&1gXJXJ1dxA75`IV>vJdfI=P<5Pvgp_0{1~zXss@5?V3K(URp@u&2osm)a4qk* ztUo#;>2&C)e`{^>tzcHPhGmZCcGNU>?>xWWYIF6+vn`L`b%U#1SgV`r9Z`#t6qzv@ z2vW64XFi5Bdd{R@BCQU#HtO>x1;U0UFT4g$kqbhtH~*&8?oXXbY8n4DQ6yzMDc@Oi zt4dP8lrS`M>+=`W-4w}r8~@y;-9#+Qm%4un)3WVDxF#+2ahtcyvA1ndp@P7HY~DSa z^kFHn!|w&>KVA$pZO2Stm|=|cp^vl+AI%ng6iwcyUlTgM^xC5Bi|VfEPHDJP%Qh1+ zuyUB;sXAF}5t??=_H1soB)!>S^>LEg43?brG*6zKw-X!7eB5Ax(DN5GMM-)0+@t&0 zGhQ4WPsgnH+f>FV9r(P?DeKvkBLIx|kD%{F2ghOD;QRSN{Ri~X>Id3A`(AogdUbN! z@5XbdBu7g^5xrO4bKcgip(c9`7&JWima4f)5U;_ah{xLQdWaA6#hwdGJ?NBvEC^j`jt$PrR zkM#EygI@Mc;=9zF;2V3-LENk&;B+v8tKeq?zO*FD8~jS^*br+%k{$)Jpbx>?a<7tY(qAlo_rW(t`)wV^+Vz74Wa- zQR9D&+4iE|U7sQ=3C@V0S>t{AD_7}e;WWR@wZx};fEDMsLhdams76BBaz<8ruomE^ z)feEnM>gE$L;e z5@cS}GlZ}`)74!Fec=lLE6!aCQT>y9?$_l&$Oqj7DjY8mN(=GbGCh8a-y$%&`71>1 zK|_4k#1YwO569L((ip0-1kt?3JO-!v(r7X2&oA}yxGj&y=YD>e9N%ukdbN~4C?j^1 zvo0WP8GDRW)Dc6cDo94;?wQ6x6SnA8(W1P}Pvb=Nn@lHbn-W#cR9<)|6>sl0tHPPQg zTuhZ2Q(TFq5f!aCEzUVC8j8%EWozsvRr9z%(i0mrr!ym(}LYl7g7dQ$9U#@3#s{7ZDFJOJ}^+&%7ncU@h*2eruTJ0 z;cz(eu(0D28dscR0U&reyeVV(K(cb!LO8|?Q&Z_?Zl>q%2_NGS`@wDon_OipWmJ1Q zKh<|nIJO?ryIV{a7xQ2 z?eZS>^UcjSMwr6-6%c^RtZ-|tMJ&>^=GzT*@dSft^!DF_8@f62zF%(pudXfFU^`zel&U}x4y4qD zi&@4zDMo&fHtuvwjZxqM6S?V3t_%h^*$Iw-AHtXXMv$MLl=SH3_xpmKX?`!cVZ=9o z`BGBHZp%;Jugj&^n-5r=oA3jq$*?d)LO;=E{JaE0y$4xb0&ttP@~D4Hs|qP^x>wu1R59V~Nd2YMF>mMrAS zqFhu&W2X*m-u)tV(Dxek{@sMe){COe-#9og7lhD(YqL?NuPKXlS9r->e4?K3@fX+a z@(UxiH-c<<0?jQ??TrtZK0o*rR8rX+2h#`tvQOP>uD~8ryB3hWOU?QCr@+uzhhE#ccF{KA1iP zkqJD{<-jyg6R!+exw6bhK$bJ1HrXh=90u_M6@u{ZYNbwuM^6AXEmX}wKZX7BMm)I7 z6s^Tnz^e$@s?sYK?3n80WFn+6X~dbhSbY{q1H2CvHO|H+dEWuxZ$}QcW_NlwRgk+X zWho(X(G-9EYCDb$Iv%Hl?JXL({|7X z^i17jdHN;>P)dR!^5x=!0z%xj=tpggqU`Az8D>QFAo|Stph_l!?D+CCa?~^8b$0Kc zmrI{U`24t!$y(H&_a_T)lX(*KTGo%bF0^Noy?qfe(|2@Vrv(0_?PX>~zVah|VYPoTjdu*k> z6Gud99C`QfPyM~C+!mwW=e=8V`oR~yPpU)M8LT8hoE~rPq{nvkyCBz_D_u1nSExzn z8~UTtSgK_2orRUkn-I)kmN|QrQ|dkTv&K}XbpF4cBFlc^3G#&hAixRl97*V;QYLo~ zn!z%?2Nm+@AoEHj`fUbW$`45T0s=fUaZA}Xo>8Hxti5cS)~7O5z6$QKFp&9 zC`b!i1e6|Xhk807y978kD!|TVWMnQuR|)?GHae!{C*Jb|ABD>2w`?WfI1t!LU<@Is{? zY7?D%nLb4@{r@#fGI{2|S(2tf>pwCzj>17$IzP?uK)Yl3JUEDEehH;y)p7AEvo8lbgjpmQK5Cg#_# zr=T+V?cpFU2r)eD0{6TIs1&C!zE0wB%>k8=CB+iT-9Gnb$W#xY(< z>S6>Dtr!5PM2L7$eWfBlPj4(P+gH80LsZP~(YVncnVkxG4MVd_7S5;T>ER)hXx#u` z+@xf$4;KKg%N$*VIhGE3S8y1fS}Yi+s9b5u)JAB9l zIRDqUeOYVs4~V zFV~Tv`7x@}&`_em`+U`W67|f^dFtc9_Hbp`D(T;?A4eUZRnmm$&YUx@M9jI}1}=IZ zkVQ(9(W`pczv%e--gR%Q1J%WaV|eN74_CgyXD+*huE!f$8YM_`N`RC|_5sG?*q-KO z`Ws^OrT{^K{v*~+28N*Tg=!Ef(&Rep! zK>ZIq)QorU6qy5{w;EhVYN&2=;_tV%wsLxb;6|4Sq7;FkPjVrbNXaWb11eX_Sb2+_ zImjbUvT=_o)MD!I?R9WvDS)n`|}@GC+9MglYW;UIKSNKmTS2h1Tt0 zMJ4=B%p$OtkW5QO^U{=d2V?j6xCQbOpnO6RNI#Ni(!{#$7mPF=wU37%adWnBecBi; zkS)Kz!ES3v`q;|3xRS&1p^285D~2kW*Jdr3KH7goZ)rR=pXDP3dR|v?KMoZhba;>Q zczbSWKwPx1&aC@v`mMJW(R)F+(@zo|5_yF*+`DlSjXtsJyfirn@B3WZ#D zBBIk__y&$dYw73RfHnpvx1z`kP6#CAimKVOaiChnQMiQH)mhNA2ZyX|R|xT@F_Zq; zb_OFo$d+JBlZo&E3hGUb!HXLu7&mta7AeX{0cPfiNb}#5o>G846`#87to)2+jcmGM z>-6jF{gzyF#PYbEH%9LuUSTG?cRVO8v zmEro!(>8$LcL}fql3H+EnS<@d)HKi)mvvbx-yAKCub_xF-PG8_DMmcFotW2v3ZMzrUi-ak2qdHN}JTT35w8inj1J*dbepH>?z99dCe z*1UJL!F}~K#mje0Z_bz7`$vw|>HtOBIc4z3=t?b)g@sL9ft+aF->8{JCs0Y&a~FSY-4z9J4p>mCZ>!Ng6;14bz;vvDu&&;7&r%U+N zd!W~m^?QPSKZb(@RMIL%FegI&PynHBD-J7j_G1|AL42=vhD{+#`&f~2Z+~A9mWD~R zUTO*5qGxs5W2eVPK7>y}A}Gj;Zczwwh?>vajRBC32W}q9VnZys2Sc9_Ccl6R01j&D z9vH8%$rwCTrfYLRFA#kjZB&QRyGhR;n$ZisRDrEDSTjr-)U0RVve~P3NQd8dGar-H zdn9O%RTR43!k4tlGV9-(9KExR)5lU#5#=S-^O0hRi)ioZ;h<(A@D>y)!_17WwYQy! zY~E`4WY>1RIc?iI&$;QlsPm=7nk9{i`8v=YRqN~r_V;TvUNelon_DbE{l&>Mr9JGr zwnq@_cp>N^V?5$LaKWe|%|1Z!G~}bNkC2;4SH)EMqSDmF!#R=4XZPFQn;QpWGkLj! z02Un)8Bjw-RJ*B|2da;`OeZh%zfuN)*f9QLp%VkIl$?VL&^S6ka*8-uz$S+BU}J49 zVNM9Q0aV58fuQwIRA7VU`8Mj^>1s2KNUt8SU+;{$rGmB`T?2P0?^geJsAS<>5<~6G zzAXKgFTAD%BI~xcCO#@g#*zebhW!@tH?s^#LDDVl>fn&PNC5;JlJwB3K7A_SCjn#6 zS-a{M$r#TN;RCCBb0x=`*N2Wy*F`1oLE#MvrPEDUr1Cxl6$>VSX|n2S04T>%>-P3+?YIU|wG`fP;ya^$nuH@v)39ChAZ7RHCHhLJ{lX zFT6$s`S8H~v~LGlK+o9N+KNJxoS;FoS3Q(wg3Gk3fTfAU=vP`a(S}IUcF%sTaU(Xu-i3(u;9^ z`7w7(3kwPoQ&So7!iLU3{nfr4=g$>yqO-p7v*!hbX#jE(9SIce1>x^`{b(fe)as9& z`@Li6J)^r~saAy?XC4il&mTMHYv`SmOic9I{zqtj5?%N4mB+afCs0JbND(FjDrDn6 z)05xYJH5`>3^>0ayZRna`zZ|$O`cPXyW$jxv{Z>Dc)qsz5r8%@dn)zJO;=Ft5%HWO z;JWk@_rAlTLd?;MjG+&XXvpzg(#QoG%ZV8Z%^C6);~)(W=F>PV%#$r+3r}lD`yT&q z_n+2Ry3wn=Lvja6lvyIi>^#T~AYoC~T%51fa?Gblb1VA`z&DzquW%0Fbi`qL$_gJm zym{5*#ed;LAKJws2!13As$FT%`ntNFg(M`}M#~X8ve-i4<*EZ%^qG<6uaYf{BBX4@hHaDUu~?)i`N1UTwOVywJtM_pI+GxUgF1oC2pu8pBnn4 zFqf!8R`=ccR5r}xEchhG6Qt9kWt3<+FyCn8Z~{p#+=}2rz|uNBtxqU~RCY(hYOItH zso=9F;Tt}lvM2EE<@QS72sBH=bmOhMpyU36DGKEb;&G{;cOk z-oG~pB1)#}#y_P7_ieE%?~m%54IAV#GuQ?BHPr298iHJIt-XfV1{gC21?bW>!&QQi z0N=N8O_v0;EGw1;U!erX^GWdY(4;dUu*ULz1|1fGBqf)8-xOn(Dpg%w!?z0N_74s; zp?0}6Q*V^l$5MJYxScQ>-@Vr4K7HczAI5B!_pT3b;ZJ|7=~J^WD6E2XfnRzlS2y0J z%S2Q~tbUKXJh!%H-kx?PjY3u=dHb~KxV!4X5%qx0<+JA37MF$Xm~>vu_l{1#f}+fU zXYCdWOb$TW%Bn%^_FdN*TYmUg% z%m}zcPqMLeCuMOB3}!&rETLA)45Hko=afC3Iv&~*6zFWd7;2%^=cd{nj8|28X*G9uVM(Efezw~;zBn~B@9 z+n^KvK)Cd{V6|@$P2Mj%lodH`3+ZRfOF>`}5YvDAfOot9-=nszFIXD(- z7m*++@x=6DDv%6pJdh%+0MddsTol6ZVkyrFo^pMCu?KmISbl{ES_;N4cS(5A{KLZT zB%s_%$_KLs(xgYBFs0Bv_Gw@cT3>wu?JtNy!yg$%U=8n#mOudKB~tyymvMC=NW~l- z)dFf&b$foEQs0dfR-+o?`pis2;#cSIZ;E8P;WYto&(+^4J4hOWEC`D;SOGzP4#cQ> zH#64I9defqop3cYCQ=-nH<@Z~4h;HdyHY-aH}T;DPIm|}6$?-HO_vpE{DL3!e(`x1 zDcWgGvj?#qv7THQK#cb@G!Vi}4!Z#q6OU&DsSb&!R~Lr@k_vHKk2Q#y|K2iAQ_I`l$HdN6_~&B#bU_G=(DQEe_(V(F4z0mD-BeQf%XgivuG9Fy14bi5-0*fZ!w z_p_ajlUDRkf+(=(cE40_*S^sjsJ1k(U;Z#=de;4ZIFBY!lRR-Rk;d(w={=e~`_2v5 zF0#q;?*7y_Y6HW5r8Fl-Q9*qJp`2K+3eG8rtY^5pZ`X}`FG-0lP-Bc1+mN~j ztga#_tL`1MlqyH}4in+Lzj zBwQxlf;;C0q@l8o8>iw)wG@hZ^;`$}d}xjZwMT#~K!-k1s{Ko;G8O=FKy!1Phy4U& zzMvl@ejfbGK!AnH4r zC)$7`-7J-GjR)#~X1orWA0!lfq!nW84z4vSEfJ8_{o$Lmjak}9Ar=r4T73Fk;2s@F z*vVfNMEuM)=5fG2+?mU|5h{c7ZxK%gOVf)jW)y@E(C4yFfa~S$P4!jwOZXkn+>^)D zzrD@VTqsUoKD{kks*?2s<8#Ga8}>>Tza4Y~Ecl*wCQsY|!v(iquR~b!MW>h2q)R z$UCPs%;$BDJ#{mGRN(v;)&Dpj@l0>9vv1YAUSP_?LE*Vyjq!ZF{o1c--O3A_uJ7*_ zsG2JmT^?l|6o(rjiHM|Y+NRSENwdQ+tm4jxFlKAX&xXlJj)B!+p#qJOeuEc zK^=1`;|WI3cHGB;IEc|@JU+Ibdc^!JtL#D#B=}QfH*k%o`xg7pVmq;$$6gSzrn4B# zq=^^~F4|gd7P7jp>Kk6hxb^E^P^3e4{Jx%~Uz>3PGYK5`WRRvxE8GPZ)dGhPaYtxn zkxO7L?DH=pQBw!<;l`@-XSzO~jyb0pb`qen3(%@Vmyy?jXC9d@0Ob|LN|jr4Ll~Wq z(@LbDpG265^Uhs0qEeHWU61R6aEOT^8b>C1bP1%{?LQ_bNfayOgGMV1NCh;UlF2uy z_~Ul9p*sgUwyA&{B6xyAD`RDl1+C@4Oz?MF1!@;tLcT;MT42vk(1wK%1qUy$Owks! z&s1z|th?7(SXsXaF3c@6-soPey&_5ZPHaD!(LG2!(q(utKj}QKP*6iyA zevh-Y@xRmDa~aHhSz!)GILc;GwtF?I4NbPagPykw{*BV^#7gJ+RA6E||2q*4Q~t-j zbapcHIh#<+`Kx{*Z-4IZ8Rx251HM0hMW!x~&8RHip|<<7JE`@4LgWM6oNuS4kf(D@ zLAgk!jDdRBmrb1>2aPhDw4d~@wqMBEI4wA=fAfjd9i%=mJiKaO_Ok9j5@6%JsH6)1 z!-z;D+NO@&AqKo#_mlPkF7D2Zg!qxUIG(+=o~;;_so!n^BQ!D59|gs?^XJv+qawML zaOpEVy9R}s`0p(LH~zKG9Mwp~WYo+;bIFk-3i zh-S7Y?Q!fBHHzfdt8QMfo})e4o_Vuqp1tegX1nks8IhtQ<>OfxY3b+l9%a%wVS4rd zZ#e9-=j)|){X%mptL6vG_@8W_9uqoJKve5v!-Db7#uiRoOx)LuJQ0~ zMED3lzi_5xjAG2Wd6qXy+xU2rBsy><2c1MlF&ALZro;#YAUCfDWbi#o6*fG63$NN> zHn$*x_@S>n96V8xQDfXy)edAF2WqYUJ1d99bMv)IKM@C5PM3e`>91~QUPz}A>P2T^ zHaG5k2q<#!%z91}S)cUaovu@f-+9a2pX*=8?d;5K--=duYeNH~z0ap+T8-x(7Ey$M zn$~k6sBlQ9-AmmGU*0^Kc*n(MYhy6))baEB#NWiVGd=91xxv7DS0SPq+Lj}w3gNq$ z{52iyvTr9u$ddQF-y4S&YafW}k`;}8@xw?auzzP19!k$Ge<=<5y6ZO3u~KwM9J!3Kdnyh zWls8e>TEM}P?U$BJd}FWwwh9jh|#@uP#?R!5WA`(fA<&TLG*5(jImRk%GIu}{goU3 z(LxiJ&(~Y-PF;6rdR1bgHPri33!17}otnvr2Ss|*n4P^>F}dn{n}{H^P_KJ0Vl*cC zD@|`!R!`44+O)DHJ4Qv;rqEyr`V&!J`oZv7qLavvHSrS%l?U{($sRYl$&S zfLzU@?-yxe(17H|pCFCY^v znA5M35^gv*`kE3Q$X@E$?_0@7(d zfjt@`N+M&p?1mhX3uLN(h{HvO7iJ1E9yJ}#Rc}Nsmnkc)C#uoM#ifyUUW%U&TRP+n zQ10NVwL?fImGxvzn4}gBUf9}WA@YXY^c=Mh3omA2QxbVWP16IwUOO&{)Ge^&-}qmu zOm`vE9yAQ#Gl1~BO_f9s{$E-Lm3)};i&-@7{cV^oI)^mxEILH)Ufh;*lIz|`w|eaq z6@k@HdTXDF#?B*356g;R&ZdSV%(zZy=grVc)Xo$sPQ{GziT|ffzKRF4YNA#Q>djG7 z=q=a@pXKA7s<@Z=M;=9(x}0nWJ+S6i)x=^CUfNCcT-W_HJ!L`ad|7x*%n%@9QWe zOF5~k-c{y$y1#7NNIe0fPq2CnfUyu9)RGpRaWOIffLy>5LPUvLdC60jLf&VDx1Z4P zr6@^(Ezl7Y0|OEPlqM=TA@vH2TZokvtH0EzU>3Q!hJU!P8IY&T?Z%xojjA_X>=3o= z2p%K(VvY*^djC8x(QFoF12sqCXmOv#8>vSggp>gNz$ncN{YcQ|iF2a~ab~DQ=;>^y z_N`9@*AZ~yh<-f<@bBy)5kW12Ahm&kcJ?W_K5F!FD;5m(-xpG4sr;p7x-~QK%%M=t z9;wgRSTCA_JoiHpGj37Z(+RDJXt%B};g44c{yfD_Ns!+C_Eb2kt%dE>C+A}BWii*) zWcW_e+q92eeEJ>D{dMc^_4n5f&{vT4oNS_$2IZGC<>(yr%jZaf2(-8R&lwE}Zx!8M ztjQ2*z&1y7)!2$%IJpzuBXp&_sri7~(ZH``aR3=tQ(I%%%>`zeDJ zq9Jzmsr2c!1qq4R0a_7#eH*$@D&jvc(m*TXNYYkCrNg4^Km1Ar*Hj1cI>UfA19r>_ zfNX*;OZ3#rS)!MqvX_|%Uq<~dKN$uFY!PIpvZA8Z)m2m)e1DvLluFOoyOdViXirER zj-h-hhTW+70@$Ub_p0C?837j-WQSDOSoVAux=SEwfNO>VkR(GD26ipTd#FoDcwfyk z1ZoqkS7#7dM&!PL&a|||#2`aII;kq((`6!XF1c>&>z9HtvD2LyAfjSXBru}nOZg#F zK8#1O$Dm$31jF}N++a1Hw=4jRd`mzED%csd+vE2iHrN0LMuey+6J zOd=N{@_AJLaMhEUKf>vcpxtn*sBZOdCb9AwSR_mk7CTEU$p3bRL?`&)HCf_-qjp10Luu#q_c~|JG zqwOVzJAcomdSj9n7FJ9j5Z!Ba$ar8g+z}9=tmYB*Gfrv8tV8d3UGUuGG5(&@+*~`p z7dzS9Z^)Q-C!wYj6>IJgs|h_x&{pp$%>AzQG%vv^YqgWTmWmXM^C`J#msRD#!5hBh zyNs^rEco32M+~u)^?zfCjUK8ZJsY#ALv4wvl=Jbr)wxGxLV`wPhF-21pIp69pmy>< z!iZ007QUdkcf4}L>iOCHjMB7UP7S%Qm(b8t=8+by9eSMgD7HcnL-uu3s_4Zb!)nxQ z25VwD-sQ_d2|a@Kb~Uor_CeveJJPtP&B(K-xz;6=x}3V74Kbr7@zc@0GHXv%MZf4Q z2LfC5HI2Xg-9KEx9nY_sz`Ss54 zrj(&CaHc?pNR7oNDuq|}vns2|j-dx49y~}1rr?OLlw>5MM3S3~u!X$o>dM*x0yUz2 z%I_(hw?Eg|=kS9An8#uWA(c87O1XagLi(*+BJu7fP3+O5aiIC06N%UT-S)eJ9*j^L zOUHlc7Os$wyQO?zB!7&ajcpqk3m!@{aMfmc|9u8hSJ0i> zE0U6bA#{E{tOtAIlN$a*nykO_9frWKnjnq=<((&E`t$UYCF1SVMD`!C40JPPCj=oo z*Djm&I>bqkA@okfGW%mQZALuVT;-oi-*SnLW|K^m8HwlZ16y>4|sOQ@8igR|H!|(GPjK5Nr^tGy_wVIuU&xi0z)1S{hA5M{YT|d9L`7={<9goS@ zi`=IVyXDGi7Bfo4u+XpCOw_+NN)v6BNQ$*(Ou@`5gJH^BK~y0mOBZi5&v{gV`~s0j zl2AWDXVq@s=7&cE&cFxJv|0^?D?{P%CjpO69=<(bQ+GcpkUjYnej0Xg)QtxoS@6ZsYK>_b~`nNM- zYDmnoKp@vgkow`($=&!CEwz7gk~`P_o0EKS^v1XPVTfe>zT`=YmlVNW#$T4l??sbz zk`<n4cK*1BvJ0!E869e zNgf{cVk8_QKCUX^wAJJ){wxI*LzXXzF2qaG-`VAsAsKng&1INAK>Id>x zrZ&H2ywnd70#X!B40CgHwO9@@NO&y7n|mPf{{mE0qnsL=npE%?D2j5RJ~KBp4V9*L zACyq&il%7sy}m*rXEp4m2fJV-)=?(PrIpp{iP-xWk&y(Q@7|2%dYy;EPezC%*F}t>NKi`RB%b{>Z(_83Dt#_YhD3`N7xmre9@c zeoak{ga^X*>qx~ik51|X4dfhFKwn~?zpY4Q?I{3o8dv~G9V7BrL>evudjb4JR5xFH zyD%5fQ5>Sc%~6hMl!VhYNK;QhAE4yTja5z!`t>rm5Xm>h`sF2$!?TT_Kfh4(1FfEF z3dgN3;h^L@@JPPc|3>_7NkF)a)E?xDK!8#J{VNiCL+CKKE2^m6q*_A&ZE8aN_!D?i zMI+(2UPF{)zHC0P*(fbEX25;B6ziu4%WIz(`DP(T=76)lcg#@f7bpTo8xS; zK~@Rep^UvacrZb_&`U$J$vy^UvC`FZ_DgU5yQOt>l9NZBc7sCp_5LL1$Q6G7!R2tY zR=<0oI_HSamEc?F1n%xp{k8ocVU1oI2n#{vZRrtEDy-wVc_W>DBmfdfgK--W=R zW6j@WWMuqc;HckVH;HvzFkAna7fHGXe0n8ggvJURq*lG^a4c z%Mw|&nqc-eF`69_6~|LvND`@+kNeiuwhKrm?Q=vaQ;kC4mHy22F)M&M%y7oR_W zwqc~G4&v9@zRimX;LOa}{Ra=UvT&P51ONT|=K@hcRqx&zflBfL?^~N{QWVCF01P(#h%Y46VQ4+7q=W&26or5YKURXZqUsB5 z&g~y(580py#4>iRmNmEfp1qCJKuto~Y6LgEVnMb@lf|Cd*~@Yo@>z_WSM2Mc&BXGu z0CB(G#L@lPh@z)L8j%VGQ1=e||Kt2X7j#dnz7wT=)#l+XXo8v+{ygOqGSi;~bXuD2 zhpz@QpWRF?m=Agw85x0D^GCi#>HRpnUbqnJlmhMt0^PCwQeroZLe0dulvl&>hRfF@95>5N)D zVnCzFcWGmE%VN^7xs_Py^XMqSZWdgqDciqpJ2!xsRx1`pR3hoa9B*U;Y+a`W66~}Q zun`Dd0I|qmbLX)DKR?2pg8l|p#6Y>Vc%MpKTnQva5Hi9YOt-zUv4F8bh*=m&hz#~_ z>S!W3;JeQahrH*xi-W@sxNqC`<1AsEBvGnUbCUUjUnPBUUQW;9q!I1yK`Ht|Ox`DE}m~{lDkIFfy{>UqtoLj>vBm0Umszl*m5W`FU4fLZYLA z!kihscM%9RW<#G#!LjF_eHH40T&s)eztK&2g%Fyqz~u&|a|-0=RBdf-$s$5T_8NS6 z7+ccVoX)@MaR<4p4KND!K)v~v`tRzh4o#&MSWd!#4}e3&lpWfHwT*XbUeO2E6+O0l z0gQ&V6vTG4No{o^7kOo8CZKDnMh_uaFu*yImi(aK2 zc(g|`5|NRRSb;lsE-Z2|&H$6h7WXSv8_bd%b>uWS75cygw;@P~A8!1-q+z+Obh#_Onhy9o|2o*^i@@h{eBk>lXNdKz^*+)2Gv5)*`5+p(C^_Nf%H1V?~{J+f)xO9N{1By3zF3;83 z>t%B3*MxLo2c+WsY1c-gR6b0rVi}0U6quyu_>&o`{=ce=BR5)5@FAhfyzkCgA0_m8 zM6G7lcJ}ry%?B6X)H3)Lzq0(CpQp8|PfXORz7&GZ7#wYWutQOKKplns^9)=q5S+eQ zc+_v(ykAtNI56}gjI>l`P5BWFC51ECNO+eCLc2xJ!ST_;EmbnonFDL047@|ZDn-5x z{uzpY?7_vgK?atg_+Xdygq|2F1M_U==T9kJYvmo+Jh(xG;B=77AM`AwLFhaq4Hl{s z*Z~4)0fuq`z~@yqs;R9FX|))53#9XZ;5P&YIenYpHT{AE=Qr6wDtdGAhp@1cTql+3 z#M{UXD#ILh6Sn=fhsx9s{_oUJpw4T9QEJ?aF4(JJqWNF4XyMnBIPgdYvgbUj?e6Y= z|HwyCo4fzN?rkY(X|BnQ<VTvd;MSb! zE(@K=#9BOqSUnsQqpc#4M@BZOz{$&-KtgCmL`s1)R;I@-QcgBvQA>zl-ps%vD0l*_ zW(^IEu2-+5m)AgtwQ1W!5Xg!o!cG$d(Up*(o|<9^BY+K1?IES1B_765XP5yT9A-c2 zKLAY?XyQmW#x~Mx|GiBY-tGH5nK?UKP|HEdqW1$`HHOpSfwIVP%>Rny^3|7#VXAp; zTieTc@^e>nAT$K|7U5)@iM2s95`-g)W%U$UREZ6km_VEkk-VEQfy!oYX9u&#k9Y{j zvkiROTp-@a3`RaH&@LrA7~_=sf@%0Hw*4XWv>=7oCJX^l3s`3%9YCcXj6S%0M{Ii}=Lmy*%WP3%rrZ5| z4l+|{ClsOm1I(5l%t0hchVMc+EI40cz^!#~d^|NW(#aT{8UTjbN-x2TE-Fk249C1; zPA@K&k3a)^f8l{F34|owLdE?Pv~9@3Tol#QL*~)bDu4gn!NG{PROS;yK5#|G2G5QR!>J%TU5ecwE+#N6zyPn{+wxn9 z(B2(2B7->_DDS|xs5vCt%G_M0C6~Sc1|ujqstO7lK;>w5N8Tlr&N0LY>$M_+BmUp1 zXT{yrzvONK(As?vPCYi7USO9wPB3lIQ;x9Y!dMBEUVys6pat#~LBm>`+}vaDVjgkC zhfp>R3zDvZ0mZDHl?9Kq!G3D}R4)lz?e~0zj4JX#CIuEF5E4NZ1`i%a7zrL8b&>`u zDc*QS&il8_bvA2is?5Ru(sT)A>zTTHI(G};xU54XmzSQGpFioGb3)}_1*AxKU<7Ya z0*rBBSv5pis}0v|Je#*H{IB2NOT-^iZJ{MzcC69sO0l481WXlGLTql4nb|*d|#X&zE9-a(k=mS}Ld(g*FQ(r*>2CNt8wQ=Kxv{f;I zUfsbj6SJ?I^8y%wREVI;jS+f~0zM3nOGbAOhUQ|f-R~WO%nwgVcG2cB74YYx4uBnG z_?69}ke->j5Pmu2k|J(1D4k%oVzUxkSjgM5?zQt8Y~`Sw1|f}CB+-xq zcyKu9IWHxd!7q-H?CS2$6u6XhKZ;wm5DRk0Nx6(7Vq^b;S2&IO;znNAesbAgiJBhEoC)9N~49PJldm4hv!_je6vG&_BXP4L~8%Vox+_&B8&?$Os}V z03RVU_w@8U=!bZeT-w2k4&=eGY%2$cR=_MM4nwmcZD4bEH%=Lbhh#kKZo?%Q$u5^o zuD7lK?>1>=v54+D>QPAi0%quzxe7(Yzzv+N{cRTM2hY=@<{J!-D3hq%u^@LG$&5fG zyZSt)1__(Z=jiX^7axztX`#l!sHUV7fX0=qG|8=)j-)eWkLROACJy78+UxFueK7$bA+`55s}+7V(6q@!>kkJ2k38}ae;7c@y1 z%FBvO(8GTZqmwR>w4%3*f5HF7g(;LV;Nu|5* zePsiemH$2q4U)b09}z-skg;g6v2G*#Op7!tpw?Z+c$ZjFisS<1$N`&6IAMcdOyzn2%)!kCJhjyE+kqrQ0!ZiM9n8#t z`mT|q2`J9zQHb}@b-|1(0{M!S6}mm`!T+R_>?g>)Sdv*UU`~jKYxzEH*+DEmDXF!s zZOJ$DygOhA00u2x1Mej9Hd3J12j5ttF`(eXr#5+OT%CuLvm6Ai*y~iDkevg~4IK}Z zuc8ev{v7m!ZPeG0LH$3kR5|i40n_I|xI1`&x8>M>}!OMTK(oax;#M;W=X#?it_fV@H7EJWX+w2HQNREL! zB$@M*0nIKcJza4Nh!{GN_oFGn9!<4DR7fZ%mn~Bx0Lm#q0MZ)jqrgqR-^kKR40h@U zJ`uiD%reGo|4FHbpnfiG-dESMLHho%3oL1r=;6y|l0s|ThNa^{-D|^ohe#rr4Pk({ zuwatT(SdsdApoejcflL}|6Z%W!gi@a9YBtYNkDZ+PZF|3^z6ta8EtJ>u+3+^EMMpa z0B?zN4{8^-l-;cdh)5UwJ2YY#=_t`bVnqEI2-DobFII943ShF9^13se7-5i<%8m}n z48W+q+j|^R;{**XZ}OO?&DXhcRoCF`|Gy z;F&=uQ)nXg)K5JcuFZTLF#LcD+#(i_8iGg0gM`9i{dBZv%n`Y^u5L7kQSOao2AIFt zu&t$PFbN8Jf~5NN0s7f;oWv9W?cWQOEiqgpBO)XR!Ezf+vO8n2Nx4E6z!jgKIY0z* zm97QneQ&`Ci=0Ip)aq$zW%Z$;V8pu({=f)lQ`4N+&Qf*&T70UnkByAX4HQUX{NGVV z&%fk(_@HkeV71kKf+4_qBZK1ZJFpl~hmnE9aydbMEO!oc-BsphO>-UoCK#G{tq9MqsoP?TZQ2iWj1G?T>%Mlk9#KQ-{%&Qe8Ls=af z7RFGM@l)z0C@#V6x4x!EbEjH?DphGd{Qs0cWTDlyH9g@e=gPhyEc6TvvSRsokC8DS zVyprXR+^jZ`wcT15lHW1tXL6y*G=_P!`vjm5Mge|MAn^$W1Bql`uFeOKs1!Rgn_ZI zudky{ff>oe6(AA%Oi@p@EA}L^$j7cAS%OAH>NoKA?H(@>HOXKC=(chX2~Lb^;!`vQ z66mFZ#O{E-CkM+9nN?{H5`>`-|DhB3b3&X;fv)=9vCv z2;em%M(q)pAedqeW?MYJcXkHUpQCv0{+7kdb>RoLN{^Hg19w^>k7y>-+PcsPx8*N)hI9-w0iM|fH~uHeG@NFI#Poc(pslE1KSUXPz#mm zaBw0d2pXh-=Tq7RyN03304B6N&^sW$iF-RmY*~{~XR4q{0nM+U$g;k&F zZAJzby^zo}l<=ZLm3mFaWF>Hds|TdM-zEV%H;u;gm9;fkJuLzV)YYW4msTu%zlt>$ zpbT?x_=E7m38VEw){(z3G~la;0@i!f0LrRyNp>Filc0WUy_b>zmeGUIRN`HiA{b=; zYutQ{W#zC5VX75l-S_K6XN;zOAr*rIAcz%QFA75`#t>nn zDngYE@Ba&!OyK3wrdFor-I2)?TtUGGz=JUW5W9e}tYn=OoAm{J4gdoYe$zpDBq}T{ zRczcQ0^q?+ls(|@g${hPufZh|pcC(2@FTmQtOTB&@_Iku!UBPbSY$-lGSHytVL*z& zjR3(4;%9oLehzOtZ>a-*;7w-GG0V@(+7tX0_*fgRi8_Fq2P?uDGTuD!qC&(51&%_* zs9us@A#40IFt_h#0WC1ruf`++fL_O?_$5E^eN<hNMMj1x+a zR9SB&#iyky#GnN011!RoOtUf#lRxy(8fXb8#) zyO*W{y@W`CnY^?O0UgDkLP)5P$717xQY!?Jx>|j#i+AD!SxCs|l>jjbf-~un) z`1Q55kU13uxr&TNtSDbzfh7h2Rj+~4OG`@|GE)B)I%>ryPf}$yu%Fr>$!t;)2X1Q zsF+(#$}hrtRcdM=P6RxYH%rp*UknTkAV$02esGIJT&8J3WY2Sv zLPpF`sZ#(?=tss_ys$x>D4kX9P z8It!9$uJ^Yv2Rl&XXYQ~h)iJD0iK&#Ue0OY0q}2eJ6Kmhy9dKvh!?XgP_fx$p297# zS$?&2<+%q`syH{W0(s3;`gaqeCJD%1+q>LR5WG5fK5Kkp(!a zRqvw>(NKtgg7r)+)Lexs)H=!bMi)3E@&2caxBKB;b|uUd;smPMfzX10ATSBewZtL6@rT3}Y5-R3YRPrpN(<^v*V<$TDH~RWP~i zpBNs->5&P>%C!q-xi2Qx3N^HF6SFZj>XGwrF;Y@eNV)~%xcD_@h>gtE{`>bYzzA}} zY@|_mvQjZHU3u><1ahPkO53a1+SsIg!1{lurpw0e;C<@Rg$M!)wDI!t>Q`l)epb~h z1(&WxNb5^1!n{#b{HA%S_D^pzwzJu#)4SS1kR=iV!#WW&Tj6RL|I3#z!PMmvT25H> zmn9m&HHC00pptmqYVjA7>;?`*7`T|2`lK$@rDbN~{CX%N(hO#J(AU8dt17*%wKZhO z5=$G^hw{}ZBC!kFR?J+!nc0@2gdYhn5kUc-9td>M;ovD z^m%+vQ_~qVLUhj4RZ>|K`2LU;fv;O(R8ma!FhMVU3g;U3h^Bp-5gDDW-~0RNdIapJ za4gASU%mnt3lrCq4hJ|YQiHX;Bao(?%S!fpH7WJHzx166x(IV6maqv%GxTI4ki(In zfEv@(+^o6#$YY1>ngce(i?!fRyz!o*23f0cM-NTX%ow5u&Avns92^iKEwW-d@*cx& zyhI|&o=gJ~xNL}M7kHW4(Mv&mIrfVYv#{46?e+w_Lol#sZ!O0G$=ghD2_U+FDfD0m zMny4-L;@8PIJf68SLcZUJgUH<=I@T*{^jJa zcc&%kmXV1+D*cVyu3ApK7B3&=A5s{XiLMEfe!DN2PjWshzU#Au{bcb{wSCV0-|^Wv zr6~4)K|Nmbx$|vG$5{x(;V!>TJ;87}hIz_Ippovli6I zWmEb@;Ue*fxE)iy#9ItPLQfmjvEuJ>AlUNK*$}IQrLrg+xb;+DAB2M=twnF!CScPj z4$kx#a5+auMQKV9d?|P+uJ980#$BVIKO^_m%$&-h!O#j)u43^O5>A60iIDK{#EW2t zq`L|ok|a^|8|fQ(U>E*la#B_c60mY(kStJ|QDDRd(V!qd|IY6=QTNa&rhE4g5yZR@ zBE(waiekAjUClVC-r)2slcX4%K3NGdEq{j0zkN1!)e%&v+lc)${gW&aVsZ_yYRQs# zHL%})r3Ss(BZ!RyfCItTATomq1Ni$9%PT9W$fM$bcmuB6@9!Wr06c@(Svcv2HGDbp z)WWW)(zvN2LKYnmQNl8vd6P@bnT7qRw)oZiCUI-=p zF^Pmu`NnuLWZ5R!XbE~O!h$8q-fS3cz+c={4mxG_hc1&Ln%&WegenDV=rUowR?PT} z6Nf`{mXL<#H$Xj^ka1Vo*rXL()d`81Y^Wb9&q+8|q2X30>t=lu9gU4*TQCftN5aRb zu2h=zXc13 zO#anR;j!6}@}S023aZJ=-^P;vFd|Ij2+KXzSFjI^Li-EYu6I8yf{zcsT6jU3) zZ3e@2e`Vz%fKEES62CVh@|EpwX)+N)b0ba1_;q%ciewJrF~Y#ziTGL^VMyh1( zTNd^$zI*r9;8*I+M{r_6Z$$BO3|?7U7SU5O@6cR1xHN*QTvwHd6Lf&cI^FbXuZ0h1 z6VlA3%G}R&fuV==?iCa(ZL$e!&^2}{gsqwY1sHoJ+6Z_TQU6&!OCzd&)@+*(X&qfE zRv<>miwd4X40Sss0-$pVI!wdpR0-P_P@Ieiky<={mD@od4dpAh92#TL-Io>>od96~ zhC4wnE;SAk?2;isFfV{T{teLQH1PK#1HUpjUt_>2iUnl1bz7HAo4pvVCNEf%zy`s8 zV*&(*s*w4td_hhu4eqjFghtHE+q=}il3`>K+$*0#y7m!-BfUYC3VF__Rg&!@!`vO0 zK?2fC6Z{xb{4?ng{)F(r$j4&n0x*LFWQxx7Nv~rqq`n!cfQq!iF7Fru*&}Z@0!;ZQ95pdu;S~xr%lqQc=BYX z(1t^&$HTonSq8A#E4cWK&f(qxK%;W$b67RBMJ*7M`s?S<(D3l?Oc^`N0|rSE*0*hK zj|K`ANlum`NVvxjDR8Rm-BNG!-e`*Amx*Ai2IiyN~L2m<4O$)+^++V&dd4{C=KJYh~H9Xf< zd8mvT#hI*R%SowCCB0~n{{RfD@@u>_M@~Yp*@b};34ob`To51?XYkIIKH%5(Ltk(P zJevI5c}y6A+0e^{Lmj=1jt+ev5$?^mAJP9)O;Gcg4mP*9^18jRm%K9l zytjAY8HLEe*vc4#^LwL5{&xw#Dz6tUoG<%Ktc@$;o|`Y?IMYjj>HV7O)~_#P5uUUW zTDw~+rvWfMd6}`Ph20JKlB`vmx%t)zCO2K@xmEz zF|~{ke@XO+OxWB=0?*p*T7P})d9dHfcYS{bO%mBVIwnHM+Gv)m4U|oXKV0fw5;}2G zuB~>vxJRA3-JJLotNs+TLTu}wzDZ|W>$I2FJzz4fp zIK#X$W>vW=KW=gV-*BS6j(QI=ZCg9zDDK|uoJ5tErA4Zx)_HTk8ZGTJG26P2-7jR_ z@!bE>ER%TSR($nDa@o0>`z)6@GcbAIJ&vT_*KVm`BXf>fiJF+=vj z9Wc28GL@9n81Zp<7+Pc1G#)7oU@GhibfsaF8Ne_P-Vaq7x*wnJZ;Ub?^ans^J;uGF z6j+D(pb{?rcrZ+ZqC_B_s)VOxX>Tw3TC5|)0!rK8-)-2-5==}5{x{dPw`0!Z6Vfv> zyifPc5FuS*W+i#WaPPtz>>3QDOqP*an3$N5 zO%oX-)0t3dB;@4K9mN2MHU0DFGpK1$>wsAm5cwPKakwpWWLw+XASlTb2qLuvc5~2f zIJcjz!S!ME$qF}iLwiILl1gG~UcomE0l`O>WaQ+d(>o%0Yz zuqe5L)nX}_ya%!h(y$FszXm=Y@qtyPt)M;l;2ebJ3U%EZSw|MR|gELWI!vj%^C*ZQw4Up1Z{eHke)Y=8fw zpQLs2YSdsC(=ik0(_Na2v&*}-zpWzj5^hr5e|J_Krar=v;Xa$iQtO{|IUsxJENile zr9PanoZ-dKuj2JWF2yP%_gOT7G1hj`%)hj;Fl_Pd3nAl=mfHU6zYBBiJ$)aPhL*E( z?tDvkOLdZW%e! z3d{_A{X~{S6msbae)FlkWTG+jiJ!@ogR61Es#Q~}uM^4nH0yDs_2nNkDYF0ZS-WXE z@oAxfHD4_c@A}o&%PgP2P3ykjT`MoH#Mgb}TEF7^7$>qs6(3}1dDf__=YO+G8vZ$e zzB=DjTcQyCM$_Fv{vW?`BaxRdd+7K>H<~64MaB!#-1=wHpP4R)xyz$M zx7JP?roP2u88vVy=x_EspUQJNI#%RtJ`li=(!MAy@kH@^f*N*j@MDYWKUckLx*>`a zx5T*x(MPEdoJ8C|hhcIx6ZGA3{vufwc4hr4%9EH^Y&eYLfNV_NB!%2Xn1_dd+|F0@ zzS8$){`u|gT+TOI(zmXBZ#gq<80>#7bSL|W*P8gvg+zd>m79;J*3Hk4OK|71{(h38 zsItDDPzJ+>TFr26?$E5|A*F^i+Dc+7_vYk;b_yXmq1UBH&C46LsU>0rT+@2nCds#w z5IQSo`fo`Iooo~JaKV9}s5;I>KUiXd&7eWPHqu7AkTf344J@(P6!2u#8lO5zI)`YA zn06w*{C3}gN^hIl#(ns3l{E0#!PkVGm>4ZkvD?=tXAU;OtDBpOMK;|_?ixCh(Np8& z-2~@*z{>wvEZm_y5)fc;4Ko@vwA3&V!x+p;qd6KcDOnPt#5nJ%z7Mv^IFte(p*aG> zAv6x9MhbFrec<3><@+h|JGo%TGTRAmVeCKl5-Za#3F_~-J|7w#<%I&o%?)EKMk*an zHphbp#+ZGH%&9~BCXHh?dj>h9l+@HQc6nG{5q6VH)-6J!qKJq^yX*N%&}B-Oy~}Vj z81z6TwH`e6xDv7dT6smueEZc=hD*BVzGw$^_+D~$rT>6uT8(w^yUp#N{8xfml=BhT zb3ecNmGPJaM~jP>{a`oi4S%61dhq2SV?MSdbv2oSw6R55&VW)b?AYNMT4?`!Yw!KU*7 zW-6*8M@?6p-y@gwC5|){+31E4T1VbdWtH5J>W)j^ni7tCFJ=OSyC!go(l7K{GRF85 z^U5+WhHhdUqmFkU?X+mK>&ct7ztzP0o+KqY{HHFe5?PUkw(85v%}5kBVW$H1DSON& z663zq+K=ZVfhf)@3K~BxQ~q8E99IptyDS*}yRNu5+CG0|An&N3<8E6(EUbm@mn4CX zs`04@Q;2H4an{F@Wjw93`qtW0(hRol9(1+sz90Rrx!+*MS}0*~7bx}KJ8 zj_W~nhZpb(tshu|mZ*tge2SN^!dU;wuK(`O>^H<1z6MqWh0OQ9SXoC#4<;Im4G`XY zxICWp9R6jr^%z0A34xr#LQ7lQpCH?eQhD+u{OuGZdS#&9V$JSkF1B9LCWbA@laj9V z_;@58bul)2^~L@s+ZQi*wC53ReFU_V$qK5;0ha(!Jw{^4g0SWpww?}L=oNue!Ly*S!*0|*(y+!XeO4$8EME_;XqoV0J(5_=cD z;XHPdY`Z?+=0;7Tc&Hu?*eatbq^=ojod*<4+SLCbU*aW_k%a(289QIwAj;$mP}V01 zGUE(^yp--5a4R9m{Unly8OYD?Di57_2~4?N7Q4_wVDYh0B6Y9!FB=_QXHSo8mOPuV zF!?|{rHF2|rc%VglC@)ei^=C6=asrS=Vl=fTMOhS$FQAT&U7vr&s8Bu;ST3CNjvvq z8(NoYfu6#Rk*u?n*c{4)O22WU)bEs!XYWPh2zfA5rIWpD`^~)dGE%H2#XQB~_qyNC zEbVnt(Nb;d#zjzGw4|3W)-?f_u>QNHP)d@$Fj+Zic7xVXEn8IkU;RqGE(t$uUlL{ESp2=gRd@Er%U$f{{DVaXlo~tp5$Hgt?!iJH9i>9m67Y zO$}xo$vY^;a+68QFCAiNn>v?uIrwn(OtqvY!|v%)EsDD|ddLOcU3(SqA6!>!j@`yY` zCNS4|STsrROQAS>y7ys{kb2WZdE9eWF#9804N);Hvf{k|tV#S+e-c_P%?v(g~Fm({- zVFytm%&g*QIOmGRStn*^eIX$W3N2t39gzJ2c*%e^(+5_}{;!|~m|=4YzZYrF_E__- zMMdM?Lm1A~o4qt$bN-BD5pDGL*sakbky*iZ1N3brryl8G*=S;7e7H5=(E&aYOZ_2x zkWEGx_XA`eTyr@(h_qo>;mXgXVH8_1>GBu$Oiz=UQ(9PAEny;bIV-+^t}_AC`5L-E z0MZh6!f9W35(HxfYxUgBgAk#%p~0le6uDILe$^gW%NhFn-@xve_<}VwBt%_OM&8)r zxm4MN(}efT#vRqw+Jiiv62W@zeRsW6!%aqGjhxaJet=TgHL&tN6$eh6-W1BqN$BE#{_V6ODCt; z{V4!tcg80(S+w%7I8%WIFU%1-aNn z@QGS=6=pDh{v3cO^aLfyqsaM1NwfAaon_m+GNLon*LM`ex7KeBZ^eE6Zt=04`TPO>Ibl{u z2Z6)FAvi1tK$wtzRbWVlM`0gUrbe&`^h$v zJ&%#9gvn0esJ zP@?FHj699KuK+Ie!NIrQ{+Vsks+;*hG#inhk}?H!9M(1960%%$`%54tPev+y@|R_xP%)CD)kJ!0CvqbI9lse(1$^f$j;fBTdpnKbRxG0_^%Q;7hbt_1k>cN~);FV1-!C9u<5{cjTi<~CxsgkiFo9$@ z{oJA?(a)+1>pp|c$tNlnjorVJP5x?fyxzW~FRGvG2k~fS_EPnX{Y}Zkze{z5pH8~6 z9Mr7l=6$}0rPGxCIBop3tuQX9tt))mxyq_$wPwn~-D>gCp;bXDzn>EC(4g_N{eZgD zRp4QD=5SZfP@fN%#kB8i*)tt3(Vcz@gTII9i;OoNZ0w!y&nA`1f@{^*`n&pX4UW-kJq_F59mGytLJz53ivz>I!Qx}4c8nly=%VHNSMiLHduD;J^g~$byXIjwaPz}x zlSJ!~p>mctoqfvGoto{1px294zBgtyfcVLg^BhYl!R?XUTj`ukUe6BwZ@}RWC;QO6 z@07a{___?euq*(OkUv;v(^yu$U|>tCj9m#Uiq3KufP(_Eq&tpPPjS`JyqW>Rk=U@Q>A=S@?*vr?Cle$P7ZM2kucDaB)D71N;C?3=BF^0!u@p zF`t~y3~S>(Df{- zLSV-84pr-6bG}kMUY{(L1b**Nch|axx7K~(=N$TJyDs@ixe|_|H0fRBzZyNqv-E{SU6PnKtuvyqY-PmpN@=Y@ywYF5srn%7wLvi1+~ z^S)RW4n?l_ANx`sc-GzBQ&){idckVc`r*xjYAS%2L}cc1}s46vhrJY@b*61+7P`Igg$^LcszRX)NGP;y3DV~F6!T0W?*3tpjQoQ zat7qB0l!)bq<-h|C-bhiqgY*O_-*jBKtLz`PPQdEj{wQtV51Xud^TpaGS%r|-z)Ba zO*H_q;6$XFZE++1oHoPIhv^jGSn+VcYViW7t*?B1Kqr(Wf+lkW1Y^*9N%?<$FL48- z1LyKnG*%lRUjSnQa>xdtIKEK&qkfwHcFDyTGIW5yO=!AjIz95teG^0vhetz6&T z%$O@ku-FW9*wp^&L*EIkRfM$qo5xot<|OY``6fCC!Y{`OX4+me6=HRr8Z#bIu;>r?e%?+^3HWi zwZZVW|+oJ<> z1Q(!bS~m0}ShWF2zr!RMvC0Fvu3^KsX9QV85O0i{5V8O7Uk@|io7X_bvk;NO`T3lV zel9}Z709l@#sv8Xn?@o7Z)va;B3LD}-u{PLkCKS&Uf#%$bwBIrp~2jTgVOn-ccwP- z(;^Ox-gTQMiIUv)7SP?=3q?Fu?ep@P?W;al!F?6#u&+1W*1yi%m-N|e#=i7UefzM$ zw1O4r=w?W1IFp*ov=EIgFC$~=LkmnrEX8yys(7y3mfz>*+Pq(mE}y`r($>@zJlObJ z339HHnRo;Q;K|92QCjl-1m-J2G1MV2SkvF(i+2PrA`$QEM=LyTA6)`e~NVX^>JQTv^-_2cIe7i}=Wo>?bQ+wC)sPdeb z#$9Zwm#x=xn<6-JX1&g$t8Lqxn&<(E0t7N+VOeNuzI~w;nX8#2BRvW1quTm<`0PcV zLx|V_nGx1rwsbw{KZ>$)K?_1iT=;xwcI?(OZFzb3JOV(>dMzJAo-+(hkLmdNYp3-% z*;!ME2Eszy5i~lVa}POLBot-}>0*vvnv62&+MiDt1`N{%&>+A04pT*b2|bVFfVT01 zM&4+wxa?#EyxH`$v`hf!ZmxH4KvfG^A$~B@+!!QyVc}1N0Z<4{R9I$X*+fTPjt$UT zVb|8&*hqI@Or8l3UyBv7qEw(;|9wWM67^)ERAA{UPUvTPzovP&?++D&T9ej(Q*El} zOP^LhsAjb^HO?54yRjvtdiV_AsFgfG2)9oc(vIHwr;1P&ISX#(eea^D`eAk1)h&-_ zawOrqztOAH{ZZ&pqk~4&*JBRp-177;ZLp?1XgbEMqk^X zapDO5eVO)T)9*CZQRv{Vok5&Ihdu54^+><)j@=)UPjMgjXbf`Bb6hT?R(Lz7Z;0%Y z(yvy>427f%<+sm!5T>5`wE1VvzKOGYWH)5Y`?XTEt;e^K;%e<9u@;Si@UQgwe3?Lx zvSZrzMmO}YU&;S1w@1sT#%g}#K)o>2ro7Jk<^8^!wBd<#|Hr;J^LH@y{B#AhH!~{b z>e!EX<=#gV4M6uIWchGVA^uvw()f|)WKM}RIk4SIf$$H`{O-1`6gKeqNSimF-XADtMR`xkNVD9RF0ahd?e*cnRw0)GclI$t`@Ih%c zB^J1GBZEo6Bq&QHHo(l{GwVVP0s5BOcod~GQ$qTP~L198*>PG#rNNq4l3AkBs@?XO~#=60BplMckVdOVBGk* zQDut`V6YC&3)5`c)-F6P(JTa6^OBjnt1J6In*z#&NrY{RlM@Xs?Gv}vVpS}ev9U?y z$w3k4iZ7X`P8o(R6@Lu({J;J2KA~*?tDfc+`MFr(w7KQ@F9VMD<;zX6DL>T>!?WrD zsCevjl;|(_{lB=d<%$~oGpOpbY?q&J9=))t+v2kyzxC=};IjOqx#}s2sEgvE9U7VT z4O0R?tDzqzs*bDS4Hh%WF@DY8bZcIjhO7crcz);2#+Y@;iOdzr z)m(Ta+rem=Ia$5wx9s~R-t~U+5B(Y~4^Gze9@@EIS`bw*&+eNDS~IK&pFXaXD$2eR z`t@pg&f&g1^`+TVMBNV|85eoGPmw07e>$H%6|bJ`pJdT2KaZh0Seq$!^O1hlH>rwgXyCRRHXN@lc%3n`^PEWM*^FPXM&-%6VDk?fx)()Zr%seSjQZ8+VqR`g2V*|ip}bJa(!o-n1>IV_4A{;q z{a7fxg`(vN<{=tyGDi4|H3hw@a|mn{TAG`ePCpaV;G_oueZ|8BeaP7@`vwyNXU2=kE_Jwwb;bn{-@ zO>)4b-rg1SvfzW5@p4g%Z+O;mG8a-c}pJ6y) zK{B`I*xKkOC+HUYfZtBi&G_=HW8TV5Pi;1O&2HMg^z*;EaxG!rosa$>6t^}lH4(|0LcEN`%kv-2W-u5sR5L)KRBN!r*GKDefk z*=X}s)YM`V5(rSBx656p9qg#6Z~`-&7X!0Y+)Uqo{c3^44ul9yNvUfns6*9pqfYvq zMa|944PX`kj`@MJka>qvUcei5a1go6Vy4_yOpzR|kn-MsaZu5AXT& zN=!zmO(KttO_yoPW)A58r8Atdo*5azZ*#D;4BdnAY^2L_bE7L~8yJ9rl0bXl&5IXa zjSB(-WwYmQJK&b3I%U!{Oo_v^!14&uCPJj6#N(nY|JAO+ao zJ>MureNN}Egw?UT+b-@znm8YIcn`4};~}FZdPU&Vd}Paft$LLmH#jgju*hHAxU(Zf zAIY#!Zg;iiwi};h4gCLz1f_ljzx;>_lUF!4_gb3Vj84SuWlV15(vq`S!-y0c>m}c| z{s?-Mxw~SF#cVw1wur1(dzMjKtKVP+!(|_7BhO2tM z^;H22jyLR3{0CEOC@d9_frL%R-Gs3>n74=3Nl*j?OZe>aH*c8&KO9V_{^%*O*tzAI z_|k`GW-=@cVLFXn3VkctkWZcSI$GoO$?ES%4^=f92^!?(2 z0-Mo1v(QvpdI5E&97gC^8~>7mEY(>Hsr+ywbqQ;NVQ zK+cN2+|2DC7&rqVDK8<<8(U2JY&0s{THk{Kma!GS02P>OLOVh2#R`)aa-YVNC%fP! z!9@5-)LjRq3w#QLQ?hj?Q>Y`>=ZOEG_EGV<5_(a&{cOPUo!@T;r>eI0yYX+kRRj*L z8>Spq=*$dX&lak6ZN`x1s~UdIX7qX)sXVl1fj)U=(xcX@vf4<2Ywh7uMLE{1WTbM0 zE-pxG`2x3@Bhysx*3GbSLp;?;acE%nn;}KNGrCD91s40k%e8#c%=EeVpIr5K=ByhZ z5-MLFF1mf2_BhJq!fm9ubE|#kz;W?wLH2GF`R8hsPgb<00a77MSIqe42l*Sg2OXH4D zP00nsq6JXIF=|2=i$fzuW($HR99&#*znZ9JLt>AB@SV47*gAn#= z(An~8qZYJ)QtSWY>8r!C-mgnd@90f6v})uXV3GfL*O`te(Op{jY9}eebS>AT|+}w>S z@J@l$CcglFPJ& ze;8%FQE!z{aI2YUJPTM_f1K;M)v)=k;O&LkDhub`~D*GZgKDKOp@j=90 z2*UFYLn=gfR*Z#-swt12dpOr|*7V-E;7;UT6#k2trYPjN`ggJr*uPHW^sT^m(%orx zeRd@*ro&xG#D!^(@NMAjd-h2rYH2wJ-`AHI)6|-O<4nYSedo)R?7n)z?Im-kFO&6Z ztJ&!)Hgx6?GyDmY=T*N~rlzh(M^a;8O^57VUlGbyp{*M0_Fj*NTuZ;W;o$@D%m6D5j&Hj0Wwb;@!wsUlPMq}gt+0xz-vNT0u1b+a3D+~(?j`Md zTV_pWp;SdxRjwf#F>n!ukvDYf@ts#4lkrGOfWz1LT)5U5KGut=!48-G4sn(~$G;Iz z4*+R3>88tFP`STZjaaO7$HjhY@Jz_nwSxatgk-}r*c0K4Y;Paphp&12P06{IrgG#S z2#l9+^b-NHu!e-oq3oojB1M8EYKY@a`reDBYB0+DACt;b(o zM0o`E$6INA57x;V?uz`%8k(Nd*$dC6p>RGeE|PA$m0{8NDS_4XGhdvO$8RS$-y5rK z2fi)Qb*C$fW`l=(eI)db9__-n-5mWVc9-g$DStiRv3F=GLiY;8)56S)3(+_T`TF`k z_$yMK{62`fI;E8pixC1`@5cg+H)QeHu(mdy~azC%iYNmM{i+ZBMv{w zaR)yS9oyb@UCgUk-!9?%JR1a5^|Tt*jEEQz&%bba3D3Y;9h^Iqad_CIO$yzVqpKbh zO;IcBsM6BVNC1ETkqi<@_c(SypV?06NjF$G?An@ED4ks_e#WLaJiK$N9&b z=W9ac-I2Iar_Vg7CQLe{6jUl$S?~KayDv9?>W?>ij#E(EO%VP1qb}oi*j|Dj_j=uq z&3$#ZFZ{6|UPZm~5gKo@m^rhv@xxIPw&u6pqH7({_>w;U1*h50Xw64lM9;!X((;JT z*ELfwr)Ou@cI956-_>@F$~_mmi$3z%WcBB`gI*`!ofKH3DW&|H4DZY_|1w5{tjf+*YzUHs#v^26njEqj}_ zDa;{n^UhPzACd+VjcOk%HCBa(y}!-$Gp#6eBz!H}e<%?tSaVnP#zKTyQ#GjSz?*#4 zzm3l0ZSK6Vvd?qD`^!JXy7uvJJ7DWj5rM1GWjEX64jxG?{rpEm$+`$xRD+rx6|d1{ zEryTR2BZ{p--`F@lz$R~+1Q1QD7kqq8kkGU{*kSPjxM~OlMYWqfBaDwF&-WsXh$u- z70B;D=o{J@5KMW*p7<&o38nT^3pU|O^Q&JpT47FDn*goDPe|Cs-8rz9dx=l8Vq6G< zp3T|sCxAud-p3*PiQK95(ELeQoB={(#9||-B=G1@9QPXNr-91!zMCk3XvoL%H5zOr zE%YmF8&0`0mizZf6}tUdF^hmeJUKb}!WOK&Q&tF-2%TS!=1QZA$^C<) z==rRrwKZTZad8MtL0TScJiro++=R5> z9ER2!-^%XL{JYw8R3s{XRwTCjxTmft>h{UXL@ES(Qk+soh9-PtfEV&UiSHe{K~XzI?pzfl3X124UZ?+{NIM+Wo`(K zPqqa|_^@XE!|YThoN|P2_#%L2oi?@ zX@ed1Lq0yue=bDt0zPPvfd`IRU@8D?eHp?r4TLF{YT;TCcRln7b1)HGNY|}MN(Tlg z7qt3<$a{u@54iuzbmEZLg`=CgYz!`f#|c36D{g1o0iQvtceT=!fQ*U(?2F<@`QV6g z1Sb+2eJEKp>5W|6#-KBd)CJBCrax(r4qDgiO%`2X6t#_wk%%t1cD~K$-S}hvGZ}%| zN8D?gthTz-kqT}SDDLD#Tyl7*I#gq1>S(-|{QUe}+}wAFh@b#62@CFGaQv+PhF7M_ zhwfz5U>~fOf0OUvigm29c=UT$ph+_I=|ul)7dOs3)Sc!MZEeZ#Vg>Z09z0z^^Fr5o zQ{4H8uzJH^vB~i~z)s=fgUb9bqqE?cD%#_ALsp)f(V^F?DQd&NxUMX|FPu+Vk1wHf zi)I?RKHIVX^<13Lbo^Toyva0P7KfJ^YNU~yW*^I2ndikDOjjNDnWXYoai6bk?{ez5 z8{K(rXxPg1A!IRlOpN<&MLN2YZ)TmA#xeG~h0afptrbGGc;B3xt5&Z|h7?~t_1iJm zb18o8LfKV5&YZn(i8y7?rjoslDv`7d6Os8cm+|MmY`%&SeqZI-&f$j~SM9ElC3wcE zCsI@tYkRpZUhSeNO=DF=)NB!4@I6@O?3~mPCrFSwr!wo#>~d7#Z38Yh2LhJrm}*ja z!vKc8y@jdMkKt$Ue$Rb=qAJkBiQ-J!r_i7}xKxlJKT_&qMKPKYRq0qcI3!c}RE_@D z{50N-Wn7{uXQTa{P7nYBE2tzP zbHrjy(|e<-d|BQwbNTII?>}wIoO$%hh-l0vjoJM-+RYz#GdR1AeyE_zI~j&)HBuCO z@WXLSSbD&;ZB?BudNbCZV5|){#J;=wO=M*Glkt&@*m1tbm!LJ&mDkrdm9cdtz4`Ox zZ-Qe7II9&XC9v!sAPkKYLx>uX@xf_Bh=#JXI)q>|LE{i1cG1Si$1lQfP9|;sC5STo zTt*OoSa!k(bVly(A{2&Tq!p|PTk=jbOw^M1OCE%-cx-*(5*?Jzk@6D+6DOc?fNS*J zz~HVZ=b9cdyxs18nx_fs5|D@V#?eu#7A6w-XKO`+nEAO$A*B))nSPO=*3ffs-&hGC z-_{HF9>{(e@jZ8r7DS#drX^SyzW{e8m;}O`(zb@M!-4%Uul>*K`C-t#ne#uhx98~+ zS35-k`5||f90puH6VSI{f*@lARg0MIoz_5xF#aO&nn3)4@J`_1;#ZVc4}d>S$1PG; z90zpTQ0?KLdtMnO_-V=!n@#?Q!rCHt3|{#Lu~nfp4+O`vXc4T z!4;$;^Jd3ozE7%By*bqp>_zGQXeOht<&*hXbA#|Mj>>q&oJrd`As&P8#b(XI`frSH zv*%)3M9imtk}sC#FnJb-V+t@nG^Zx}_IoXrAfHK?6Y~iV{U{0Vzd3uzkey?rDD`Z% z+3ClcM^HulrSO$|tear_^V(Z!Hg9h1RdYVsGbYDaF>ke(Rq^vHDtohKH(*sFMR5AF zMC;$R9e#P{ZpD|F{ql+Z-K9pE+WXqVJC@E7!%WZKa6O|8m|v$;R_U(xe zobhdl?J{_Hsw=P_WM4eC&B1N6C9Qm#VTC4&v#L!Xd$D<36P!%7aQD@XCq*K^VKd@#sVge{y38GGN}k2=INKp%STQv8^3 z=goRb3^jfjujR~R{jIS=BmKR_Hqx|9qDGmHd8NW8((?(m56=yXDbF~YdDsLCK0Ali zUv;?8-|{?rqqz{3f5+~i|Dn$OC8@Td^!v%%3d>WGBo?AdI7_FBt)C5e-@mxwSik&c z6`N4=PnAmO{JY2246qQCm0a~K{(kBHvFImR@0W3vv=ei}c*qsc#ZSb1^);#W#pU-z zsZMI%7g8@?(4l}Wz&7|@w66z*rI~j4g@x5sRfA(2hA)3f zf^TbQgp$=c3SHC+-w8}p8qEN!Nf*Pg=DW9m>|8JspYQ&%RDhn2slq#tH`g&N_q!u5 zCZ^O&2Jk=pNrVL~q1T`-E`{5NS~&IP75;f!YinNB4~8*Agmq%KeEE7W8xucxR-D3h z-y~+l3qmUhe5``K1y0cdAb5nHY7KMbhoye97M%xlVkof6gH43E-?cX=*}>`NeH(*p z03@a@JU`4K<6c}}f2*%Eu4_m0u=TEnwV-9|$!ppRjL$Q-YJ!~g z>%|PLx}NJdA3HuZYsoX_`LTNMaM8o}OnBa);nn;^A+zN7q=8?GKOe_5kR&9MI{($9 zdSx~eXWZ6R=}n0HP)6Bt>GW%dcLH8>HuK3&(Mv3su%C^zdx!h}vCA1e3Qn1im&{GF zhQ1+zANNyPRqfc&FUh-;%}!M$uV$RjqpuPSHaG9zZE^}K=D!_E9Bmexu6r^Q&>$kDhA7pCQE&e|x8?V;sdXq$oeu|u^wuCE=jwp5h&>2JQXuJq-%vhjaNmcOz#hYir) zJe+rM((1mSzTxDxxp}FpFGH)@p05^-@w$zp;#c~p8umVk8tQtD-EDSLD|`K_l+kR3 zpo95hNiFi&+)LGDf^iZcjTRc4jFRR^&0N?-6-Yt ziAkJvt9`7QO%*e}h>q-+-&yWEH10}PC>8^~)I>@kK_HY`61DZqYc^NrkUkUNYQP~c}!Ks&@_ z#6~$2C>|9V3HL&wxcTbXz{qG0Zbo+s$J07zW^`q2XO^gR4Nadn{wQF09pIl%Bc`*} zLX|GY&W^=^e(RPxD5oNI@vH5ieT%y>c;1Az4`kb@7K8}~w_F)2+H|v@WSQP^hzgo> zOKK+Qex0KuBOfWmB7b_M;ijm7s}s2nLTS3!xRTLXH!Ft%QH> zMp_A_Psz^S+3GES3j_gjZg9o`+NNA3Hw4bU{O&02K;ns8KE_)-l23rkHkRQcw4S;B z$Es*n{GS2OMDBaOqq5{fMGvFVx>J6wF3{DVUXmla&0pE4}2AE=}3a$4mk1P(R{kBP#~UJ50xs z>;7R0r05YTB!us~y2}Tds9XK8e}uh8_V}(fiM}f<*)QrYY%uZ7{0aU2>zUzQ*8TAS zc7b*AZdsfT`6kiV9slm;g`GBUUv>n%>tOKCt*)?Y>y%qJK=VIHT+iujOjE_$D%6bW zdhOvwdXv(uxB7)9dgd+$W>d_@Uo~tjx=FHwt4_z}U;93LQ)0Q@`<-U{6ueGOiTt;O zx7H5Rr!~vwLo3&>iI)XZcNtNANr}Gp=qSiGmgyC}wr@KjL_h73KEBi;d*T)18AkkP zXkj{PTM%GfjFvU}O-m#(~h!X?|2l8sT>FSf**?WU%xx?~E%1U#{S zfKg7DA4*YkARsH)Dp-;HNWIy>dj!-C!Oqg>InpZe5O_6ENNs{0RF3dES^xr0($7_w z6ztd-RhGMgma#E0zkqNFxv2=nIY_|4nn^0HV;iCA71Ots2u|@WAG$drGBNGh1 z!ETv+Sg%|!s^KOOs=H=xA>A)Zq40B)#tx}b7-1NYtlKu(>FMG29B$0C6#1LLF`)MYQW==!B8HFs^cK+l04!6GKLbQ0 zLs;x_VC0eOT3CvegoK2o%c>A!CL|_STl7=Lr$qx2)B?R2of1eWN_ zm&-7uEE1a#6@WDn@Yt-lonZYq)VyIXUZ;}GMFA>4z>oYcwuKAx`sVT6n;btBYR=%W zGSG2F3)uZ#nwn7-*co@4OLf|PhVtX#_v*RVLnmi`nX%b$X%a=GRelwtTEC0GA)sSdxSeH8vF1anb$oD#gG zn>rqpt_I^}j7r(W`k+#fdBo&ky>GF(gIck^RQ!oeh+%Ka`T8ipJ6wO`$e!R~RqURE zuCkVtyD!CKELAMIJhL^$Qi5|$p;5Io`#Kx3F2+ROY^};(`I9?35cRV?p3|9D>-oXnZ2u3O zKVe2UVF^j6NeUvX+{TsL{OBN4!B}nwZay5claP1{bEHx6k~kh7o}-r+P$~#fJAOK# zn3G#G3A%!TR(cg3)V0EZZ!u4LictWz2lB1~xZXI!-XNXd9Q(IgFBvg*Qc@)KphX^2 zLE_PoOP%4z4UT`Xf#DcWB}UiTfa4p%YL0=Y1wt&e(KPm;rw|#N4c|-ww5p|h@l&!23mY5k6MJ}xa7ZJ&sDBj&;{92!SVCYzYM-C@7ldj-_-gk z(|kIb^mFYravN8wN^8xXy7c2K&&flM>@c5+p@->ANq_nJ{^5JXYR{Isp*|lP9C4j2 zySn?NpD3}hS|Ekd*`|8Ks+`Pi+cGTjVXu{?Zi3hK3eT$-FYWg(t%fMxXW?T1T>Vh* zCouM@fA8Lpsk>(KnWC-{Dcc!iFVByvtR~yHf399p_B!v*F4f4c>mR={7VrM5eruw1 z#BbweK@xejLwT9#1My4Gg!-$wr~j7E)tbBrhwyGKT-ooDQi)wW(QxrQe0Im_ecx^k z4dvRs8cl+O(?tRvWA}{xf!;`V^vg~zH*M1(4YOLW<~9KZf6p^wg81acJ#C8vZqums zXu_<*YU;zN)VmIP8d&Eg_RTWoMW>=_*qv{{zC5^96f_o3X?Q-l{B3-kS6MdI=y=~4;TzmtH35CTag`AAiW zek3w9v^0laM9tD)@hafLJCXIoUj)r_8N~9oJgz zLj4ZHOA8)(r3@Lu85M4P66!_MFJ({EFB|0Ch{wz|lE8I#6AC68u-AtG=^M2&V@lR% zVP-}b^ZV3Rg75sm`*d%#RGSYMyuAn+v0}6!p#u8QP$}T{=@O@BXGg!Ja2w#br|3H+ z?G1+uIu1dPU?~*@V-J)rm@m<^n50Z~Zon4UWova+4)rTrY;i%sGVE&znt-DQ#D_tD zm$1HvmXqMCILIz)<#|h^PUKoWs*xmKJyd-02p8A*!3^H?dHYA4d$@l+BaK%?cYoUJ zRhmW^f7Xd@e__=ZXLSH|A$Q1f)LFq7h+tt{=8FU~JB1WLM_dhU?f`@GZ}$XLPP zGVl>$Xk-yQqrt1gG_I7pZurf;q!gZgRoq_e_i$$H@x7h3urZ_RUv8AeHmic*Wg1}j zXY~=4@$neXoA+-nB@`TkI4S~v&+pgeMloiev@6GH+u1kIB^F;2;v(CZO~hdCJU*EH z?sz6(-q0vdy?P!dBi2Pt64KD<^0;YlAS=`U#IEx`({@1_ZS~uP|a0xGk4Ldl`m4` zzfFB-$DjP*S+#oFy4pt9rkyDX&eFD1sNuEzvO~)gJ&vJ9T?*WZqp92|y!*ml6EfZ0Lc6U?9<0T+F&7BqS8V zaSam=bd)3<=3&m9*ef~d>Ggnbz^2tuR+bTRDMVRWSoBJ@(2F&kAoZ)>aj^~FYdSvD z-1U@Eo*)Cum+X7cm3o2s)vB`?H#*=UpyqKOR`N@O}kr*wrDDREUygS zC-9VlGe?dnYLOPVhLFx6ctMVh>6T;O>SIR7z67Q#>|P0?qoRcP`0{M&>7y}$T^OHv z-PO@S{8f;Pl@%8H#olUi=C=;K0f+|92fv`;BW`R#vaY-GWM~K{n5?XX(jXfmf$%7yCX@dNKRuq0`Ocd_TTS*0JJtWem#De67I0k#Nvl zSUj6fj#x+Rc-?<=o9U~!oz?IPrpH?w!OvqHe6w$eo6HHkdpT*5}VbGu=P7Vb)X(3%PIe@?`?6$T{>`uz@5cI!IIXi+xO^SSuDnSg4wJpxg*d}XHo$j7iD zhea$CGxM8`LFxTXS#pvFuM?g&pz@7olP8$PA=uRnZq0H5x1zZC!Dk)(-$J;& z8Uj06O;hOoV7u{Cgk`LFxOv|x*JgOwscJpf!n3aCm(l~y)t5S7O=6mMeE$CX(*F3S zi;F)JKP#Wq8WB~tEGu0HAVt$=qjnF#xFpXT_-Hvs97oWEFBi9HRp1Sp2&mld>;BrMTO-l*>{= zNRb4MWsPYkHd+a(%Ln7;Ca^s3QUw?RL5w9-Isy=VkBYM}?=DcdeXq}6390}k8LVVC ztW8Rg>D%FR#@(e+ry6?X@HI(;EvbCqpy=TJcCLd()TTv}*Gb0~Ek2pIv0(P;*hKsH zr>nu#Ml}-t6^1XoyD>9f|KqKz(%Y2bM(~Baku3F=Bx|t$Mj|+e5)L+JTE7EcXsEq* zb_UPgJlQ2;Piks##rW>BDKq^9B=+DAl(Mub|4&*1#P4t1+!UT_DaVmuj{!G-WF)BR z9Fnt`1b`M{c)3*ee(Iu+e!8RMk(IF>vBF{KMm4e26JcInsoSyG#apBL`hNk(KY;3! zlAo6-!wpd{RdDJ(-Q5Q-(?6RG1o>%@yr^YGni?ypW0#0s63#c=UdT9 zUo%ESy#QjqnO9&i(sn50V?r>cV0pa)OnOM|>zi+Jg0aKJ(UFapI&25sT|V{|FhY7T z%2+HZdI>`0<>cTUZfXpbam&@F6tbhqIra2%M|>5Sf8;&#I#(ab$-|4P(66Qse#hu} zrtIb6QksyV^1-liwGxRiRgniw#bVR z2|PGK))*ZTA&W~qQU9j%N~#4WhaS58+XP0MLs{Y;&d81Rh2Tezs{q@U(RBTSr@8pr zd4gww3_ouxD~$0NHN4wvf7@X5SZGxfnIfhIG?8o?NZmJJjzp(Xq)alZVil$+00cKQ zLqo&Cr0{KHLVv}L+edV|$(PJS%>Lyi>0q(l7Vl>Rw-p$1pX*yr$9{BM;@8{EdcSw4sO}m|tp|r)6)XGzg#mdof zb8myx0;&_j`6M@8X2=Jua>YE^i|BtaYq)_sk^&|BXLSBF+;eLvcm!X7Y0&{D@${;( z$TB{_{ULGdleRZ4(E+bWhFi}2obn{MhDW;GPj=^vA7-#M{RLn!mbnS6T+Ba^N!aEpbQFEP`OcW~u9N}d@jTbP*%`d*$w7k<1k z6g2*)@u8*bq@=^^IO6}F3k(Q1k;R3qpOfH|F*aGLnqbp2u#+h%1*a`FC8bZ*)rmc3 z5)u++LhGO;KA?MPVgl+3xtTy-dt@N=fz==UeYW8@0OIOizwo@&Su>Q;-$EKo;Ut`F zjp+-?_nJ;+#M!?n<6FfALFDcEnG_Ptw`jTTDNfmsk>RJ8-|_+8BXG<#?B&#cOC`af z4lx2NmQ8TqRBK}EgDICr!uuUd+lyS>pDAGb0)H7J$hHu_hXo7;f-o*Fax1X7BoBvY zx)>pS)ENYI+32XCh=>R$Cr(XnI!G46Nu*OBg8=|F_}@`eH$vb|C)!rf_w(az6{GMu zjt%X^qU+rMIqJSh3`Duj2UnzX{px-%8$XOXD=TK7e;)13-c?3e!-YN33aHVlmx5&_ zXr|w4v+aq*%S14ifEvaAW-Y@ZK0ZE|=H_Tboj>g_7~%sQRvSLEGvEB{l@3vFCq17u zXBh-{8RZ%&#|_5@B>~gF^mR<`JzQ2jC73tW2EQp`Jad&<|;WEr??ZW!ro zVYCDvGLQxh!NxQs!=1GkK0>XEm5;RMJ_+_`jJ4HIY5f0Va;@U`D`NmykcR{}!}!z@ zu#%}#L!M^0U7Ex>Sgb-#%hM`S{%`P)3iyl;420?dL@dPC4rKvTwv^>-T6E#eu+!z~ zKJmS?laOz!(yvp5<6s z5_wT1(9Q|dz$3^={K;KEKfhjMr`vJX8U!AV&p(@mt$4oY1=vOcw1Ox_vC7nR0^1ho z;cvJLEhB?2fo)v4-RCe z#;+Y|*qCJ)9e2zZ%D@+s;DtOw9R@`+@<74BAhX-S!a^PyIABeCc@*Z3ql1GbI;F!< zN1?|Qa$4$u?bzZhQ_4@EV-wDUGex1*T!ZLGhIo5qVnuazg>)bzXhMXQ1=y&u<3@=I z2_5Dvxkbq8lv^nvTR=q+0y~c|7cb zFgA_oTuc7S+U{LPH=0>b^lcsE$`^;%?B7PS_XVi9nuhW;3IyPYrEi-5GtV01-) z!V}d@janiAq8r z8|8s^q;)I}dk=9^Z0rkja}q+rA)sP3)XvV#u&E)(8uc0)7-XQVV)DP$fgiCQVYbPq z!%jja=CT0~X06jerl?3#hc;T8VYBZQd`8Pme}5#t<87(Oxd_664pE@tJm=JMArySs zeR7@ZM6A&=U7Xp%^70531ZAlYFgeHx+h5KQvU{{M|{w1(#Ob_Vqn(vtokB@%@9PVfi`m`lHbo2wbanR?q z=?=597s0!Lu>%>ptzZ!M0iNBNnHg|#trxQzX4=R>7(kP!A3{^hW+c?DOr66IXW16S z*NM|$)j&$B8aVe2Zk>xhS7H+|5Habd7ZzTCfz{_+^-K{>MJU*qstu5g6MaELCda;l4xGo1Pc1ZJ;dHRg z&VdgF#3by4QmXIRK5$*UN%P=lejr?%cLeHcEVJf-U=F(Djlog@6 z$+SmMUG|}i4cBLgxPJz+cLYBP$nj?I2!&g(f0pHjeb@`JS>O*XerChtkn5_123fOk zB?prcxlsdMM6f#;c@-{KFqSD$_$RL#An!P7uG?cISIK*RwL2h0g7ne>acCfV*6WJ zIcT7`bc_S_Ok#h=WM)zo-ii+*(EtPn5$Fmp(W2GJ`$g9oxs-4etB;0tf&N=XQi2mm zbbch^oT4cqI-SV|{@^f4{sUe;W>!{h7C2ADA4`_(xQtOs=qh%kjiK!Wk;bhldI<1J zPR)Dfd$vjzey)hlv@IoYf|-}c^Vz< zG}TZi^irlRX7yM^z32-WH$|n z(qOa&Pb$j=;%DTEiqBS8SEVh&b#CdvUA>JtxFcCePb=o~uK7LD)Zfw>dh~2tQb5}8 zMaboh6T~HPgF0a9b`>uGy3lmnz%%G>gfSR)hecHi;Q~%vfQg4CD=VAc5ablXXrK2+ z6+k5>vY#|GG~|+k9D|#{hg3is4Be4(D#45fSH26q8g01OrGkaYv?$2r*k=2goghYml0 zrVsv%^|dvIei+6=E<~WqGPH)bC~IfxYx&b488Bh5P;`FtrYN;d3Kg+pXe`CO{EtD+w(miwq$vV)Eh5xqrE^PB@+OiT4NN8s26yH`0b%3v=LMJvko@bZI}iEso^WpJRN?`g(2D~GwHzn-6uPx`r{ zEmkHGe0xR1&UYuL5L)t|1H*RDjQ{#ApSL|Ci899kA+4Gt3pe&TY)wc>f$Qi5hXOW` zMTLbLg{7r%yVUd6!Fwe?Y-_yY0?c+h6kuk`$XJdCAJ4~RTu*sn-A(dE0O^1!<}N7n z#$dM|?y0Fq^z9usK7J*1E#&`LV(+9dFfzv30%xWASh?XnMs3#0B@EwSf9(fSD=?#l zg^8^0d$e%Kv;eB?`{H6)pVX}26?%t7L=^b^fanWX5aoq)Jpenx>8~~^?guQE)lmQ8 z8P0AaentxJv_P31)aXvWuY3=Q@)DfcAn6dAS|w#=2y|vhzy>nrWB9A}9H&5pTt!9H zIFK8kLT~i;tpbAC6Bvl?!lT8&!pi!pE0SX4L9nLFv&d(wIO!+_=#1X5GFr)b8mND9 zeRTn$i||o?lzz42tVaOCh+D|^KtA;R$w1B{r1@~jRzs+jOytuh8a)4Dr@I2s$}G?d zMyOCVA;OD+fq_!kJ~9=4G(;*OU?%xjZZzWVV18b@H2ZcfM8*i_x!}AMNafLPU|>4| z8U=WKD5UmD5gQ{hy>@aE^@#UdM3CG8(HV8KPPgcOe+bVoq)y~%`CXr}eg^a9N!S=; zulu`NPFB%dKLiFS|G77N3zQfAZ?;OLJavZA+6SQquHcw030zGeUu;0iLN)Sh9D(UIVC< z)WE1DbqY5TP{5J7xvzhwEwJqofaZVN2J;_^&CZkCf8bo4KKdXcIR#f;$OM1eW)B+@ z4GsXrYZlPR0Q?TZ)8OxyotQ`rZ+zi1PyIS=K`i|z{`&gDK|e;1 zJn`|Lfrf?#MJ7}5;)NX#iTWr;9WomBh06OrrzG)#7ny??sx9s9sQj^V7-Q-xD&IbCM)DMBl0(mD05~=uK z7=5LF4D|K}W>Zj6eHe(?1%Gjd?M60ugWftu`D5eY zpl+|MBZbnJ^??e{_;CHT<&MZ zUww}Efs$sA22a3Vu5=I}EQGmf_h8uWmR2mnE(9?=BycLKsCqx&>(1~qM%D%!f-$UYIuxjOI1ag zKlc7&YgH&3ZiaQvAv+}JZ)s^>%r$syDC?h&gC@F9)i1!j^B-5Za@)R zg09x7P_c+E2fD4rr=z$iRn}u1RGf2xkPDhAtn&gs0C2RmxVR5LFNba! zUFZ%a)CK*qfq~noSgS^G!_Qt+ZuOz=Hl0H*+sla}7^Xv*y#M0A*ZV3NeB1 zpFe|!qffyrOoP&J3)G(sfM&({@91I^bPUNhXaI6@CF+^k*P@kn^Yz1j zh~o#HQle+C5Q!K|@^Ve5OE^>j-dd$aGG#Nodo*AckWXE!(gBs_zp2VD;6efI%Svt= zoi2~Y?2t(HuDZTHWxu$V?HwI#g$_t^pmzkH@{R>TSOyfAd@wZ~qhk*30BzK!3#=7E zbZ-pOI9QRdI-fydbOMvf8whdN2woa3hVDTd0scU$4ttfq3eaWyjiAs#FQ^p>(&w<} zCejZ&y1KM#*;15teHfg*cnegebDc+y2$X0; zjqpNfkq>kbktM;wXb?UboF@rWU7Wg(VBz+Yay_-usTeKA#vDN_qBgC(kbwI*)U^=# zf+;Jr5}FF0Z;MBu<`ZG2*G1*CEK-H+UO_anPN2yGS7}9Fk0~AZ8J8md@3mh!_-7EM zh_w$7^dRJ@C+6qNBNgE!UC$@7Ymde?vz6Gv7l>JgKmkRX;39E$aA1*cYhx?|~?yft&jk&vfCwsH)810k&IKgGTvvir@c z`Z-uZE9V%%y5uU5!W$KCgIXyNJfowcV$&1PT~&M5lrpz%YDH2cCD>Y$1Y1mIuJh0{HfsrV|L~M25R$t(!s+X7kB`aL$)Y zM%jWm;A`U{r#t80hH2?h2Aw}+n%8K61y%s4Rq{2kQ;4mpsq)Cw*iYNRuuOV^X zP`}=VeHg;H!iH613k(avs4&e;N%K*@rM{#IuW2K#vr7{j(^81GQco&2F!rfiVF5iAHbew zpH0a=2${$q&=J7PgsxiNCu!lt(*A*g50+TgS|oIMp?VN=N-#d6SJV1`Wk`9bL&$^-!$ zq3|$nB4Wzkhdd#2TuMPjZS5~D5ky$ZTS{csl4+6~R}oZ}V|@^-l?Km7nENjnCdA(r%9)^hDzGQw+hw9zc0q_?wALRI ze(1qDLg1_@1>mDrqY9x1+{&9AFE|qM^zfkH+q<6Emvc~p_?!Qf9JXXXqp&Yi)KpZ& z&HX@t^LoTAlofhe>>sLB*0gj^xRDvZr1peR7L0fx)KCdn@QeoeFgoLn7EzAwU(Dh4kC7 zj8_{mf&O759_@hM0H2BJNiByIWh4Xt>q}6YZ{7IuLUabzHkxv^&aLejub~D`y$`YW zk1B@>>3c+&$7F_fW5U9EAU8iR}d+!IM)501F=?nFk(XlA~Wh5?I&*B1|~H z?%)Qk=~fUIb+!6p&et_5mphECb zvj9Vms5(lx--teKh*8AT%S&4|yDDxR8rpDe!55~c4EoNnueHXG1$k~o1vM>gj3a!i zIC}d+IlGWnT}+C*cgu@^INYd8SV}PMr6Mvj{Vfz8Xatpq#zSr>00cOT1{0NAdgFwZ z{|Ch=2j)rK#9_ho_*GI_RQtL$b|N0H$DG z0S_An)qPlP9l$mE7EZxHo5{k$!eIEZNLmm-%zQU1VT`Wv{dq42>vwg}dfp>QYD)nvPFzuABxPIm=9QMYs!VQNas`tmYr z=~`c!Hdy%Sw~v)-vs$D1t9G-=CgqF6uI_EY$~altf@04*FyeWawYoP~(bQE24kc9OW z4gW#R&LL;_|2^o;t0v?()J4U`6W`q&gql14Q*mm#6|K30=$flEC~-;#%_8dDcC)J& zolFL^rC1UcuN1X8-oeUHK>_>A3+Q^5epYj2+uVs49>^K3N2-t2zYxl~;M6d;&-K@B@t>L-EXke=Q*Zh7EmiCI=Q# zSY#gWRnxR`D`L(U~+hU`rP33r}oaU0pnG;h{<=36A|twN=8BH|^AL22-A`Q{ij@vW_KiVPM>8 z!$23r;Rr#BcKL1%Sr{szkzh~)|GT2D2J&Y)R2U~2oTQ|Xa4a(eIr!6ZgH zIls6VG0X<%y(Ps^CV(m|E_t&>BGDk$_`S%sqS~N@WiX5?{DxF zywza1jsjw7mzOfL(DT0QV#UXgy9kvTU93u$2I-|OELc1yi{1!tEi5b`t$u+s^%C$N zLCe7q95MirAom>3G`O)uXWERFfidvouu_dNK&9eezE}y14${H2v9TF>+_!~;Vfg$x z9HN7RgQkm}2jTE#d0u310BlIUR0~)SwwGF_ggs`)y1+YVK0-A5Yl^{?0T{oeGi4N}b@$%|W9z<}$K++!v={m!w*|pF^qJ8RGjtq$SdHsN_geKdBk`&*8B4-@TWF zZIgs6qoNYQA|&O2R$pHacSNMfCwi0~^1y`Q4Wv-Wgi6hz4u=i4jt%ngpPrqSRZ&@~ zZV>{|kk;pXh?wky7``Vg@G8_s3>Vzu=Henag#q)_g&8>wAOkn(l`tczMB^Faw&7pT z6;QEvary_16MTyV??EJ{_R^Rb{Esl$FR>Z1p%enR^xWB*pW|_M=-3LZilIrw{5#*A zqnzjS9;y;Hv9fOjG_oqiM}A{{X#!5-&MTPFv3W3LzAL_438+7Luu})SR20xvR-8Sc z=8uewKxUkVQ*{?KeAfp>+~>}J4|={_oJP&h3$1EbAEs-w{xX_7 z{pzl7^t68WV)^gtjY0cv;W2H82lIRxXvP$-uU?fD@2(~wOK3mRc12!u5cfJ;`GhyH z^q=+FwOT&yzDoCgk(gOG!GC|92F>^W78kT{y+e>npXDYn;^JM7R}70V7g=$I(+QWH z3?AowgaQyRw*YclMI|Nrj0mmRJ{SlB;Oj1vMBY`BF@yEPYc!(<_ts)g`dS|Vnauo3 z!FXLc?_B29eF1?2bcuJ5U^8x$>-);gj1Kt}-q=5*r9lYLCCBHOA}2+8X=w?Dj!^%a z8e!eKr;4cnx#hWZ+ErM&sv{sYZ$+7O&QV0>+%WTu1gzQ69%qUKyi`;~ghl-Zbrf8@ zC1Mj7P7XQlw4|gl*df6x5e)&6p2B1YyzhGDmDSaU-lqfs0QL-*@yTgyz>)^Bu7yGu z`u6Q|mnyP8CMg94usf6j8+TuvRy_Ys`lfY{<<(eG5N;Mk$lYLZE@Rl9O0xN$9zL55 zlzh_BKcY~3wEI+k?pE+Pr`s1CKHn6M5b*~WU+a#WJIiewM2V@q8`l@uy^CIWf{hKX zd`QQjR5YVR`=lTRaMu0p8g&Txdt)oYDHZtrq^8%kOVps5+pjP5Knu}zGK}&d;72lyV60Ra@K_GET$kOOfmnV1iboQYk(hc|Qce(#n2{bErGKC?p>b ze@l2Am_cO%iY_p7fsFy2k)p3U5OA8nq}fVl>>cnDHi{CEAN^(+mFa=s^(BDPn&)6f zp%TY-1l%Ke2>=TV!dP|8JD-Uo#nn>(Ktv}vf)^TS&~ih-%?t$<)$3>@KoGzvcm=xA zdOS~dDhdi-gCY;H1bYT2A7>2u=?o@bQ_HLgnB$eksWyrDTeo3Dqjz__0^=h@RW&L!^pM1N=e_9a|Dsvf`RPy85&1Y6MUX|E(rb7Sr_rX~g{4c>HUzwVl{ul67fIk9RX)DLD6fzBdH-QH5V0Q>! zT|=J5qXD{y53fLk+{(_N5KUETDlvM2qDF>dDbH>4`pl+x{=VV*2j?p)Cdvmrd#1tKgEoY5= zGh}0DSK-3`5b&5?_ieQxX>jR694heUA5MN)1v=&pZfU|Cm=R9 z7S3WSKmBh-_wX}loj@;b`{D)fix#BJ@ej`g;2A_DG4AQM!QA+c~(v zdyuVlENa>~x*+Z$o>_r43e*VA?HdZMs`-|(1h%pU?%j2EV0Xo}X z?-r*Ef?Y^=Y<#yJuGrW{M@vJ*m!vKNGD0lDa5FjbubEwkUtD3A;qNaN!vSki_7 zEV3wFd3rxQm-XQpuK8278>#T|m~z969a3x(_g?#AMPH3|E5zlgM60!PH${AWs=hss zS->yMd9c=+G%`N3SjoZusk0i-*yY<-;)4~ff_t?84kq8J9v(g;8Mlm#5Xw>S&0@?R z8GpCh^IbLL!Ce2qqa0ycv*KWyt-np5QVqfj7gN&?-`4AIF{V;dY~0=D?J$zOd>Rq< z^E9HjOx#YbWl%nSsOgJmML``A>ix@kvs)gC^ON-1aWcU~O>=Iazw26er_YVJuGh3D z^(%;`WZzD%r%Iuru6jK@+&HxC_I$^IeGa+fh?Gb6ebkPXgE`kzY3-NoD6aj_*0mRW zWWWppNmn}U(n9vz_FcP*EXU370Xt1p3dM>yZxj>8&{goAm&#d=5(!xEf97Rkqw*1b zXEQpin(bk3b)tk!Fy_!_%ZX&5^2K_6!}>j7NJ46&lxs`{}lmEAGE~= zaHP>bop?{mFqo=9W**R&md4h~g1>0==AUQ;iqWs62Zmd8GR!$h7*m!u9-9Es_XwGjB0!gwpx%~_xbFP&mC*gY?=#NT25QKe8 zkT3-@9i#lkpHSgN8Y1Bm_Jb%tA0KHn`4o(rz`TPj2+tj;4_PHGkfs)uV5&CEHTzi+78{+xt7yA~Q4M ztV^11ZBrs&&J{%e=D<@;zGl&Ty?-lQ=R@RJw(1$64&~S2$19@f1r(RxT{k*uh5Drr z5Mly5Xvk4Pulm zzOrAn3Xe*8PE-Pg@M!3meUy;pQb_Z5g8Dckrd-8orr{WGy~*nNvdD7mYn!^|yzO@- z0$b>z+6chbi z@T1%Boa6fTEv}~`3|cjB-khQ;SQd7O)D3-=f@J%jHn_xfe+i#5k!gALErpFuNq3Ie zyADz>B(GW39iI-4j;Q;tL=kfFRO|gU5c)JJl_jS;dEsWDt?@mmA=>y**Dd?atu)JM zy)FKz?f=sTS2?ud)oD{RG$ezd%kaN&cyM7-vAcLCR(VN4kJ+6gewNn`=}wD%X1#ED zh;$e}u-D~`gwC0MCn34+c=K;IGSTJj^CBt!#4Q}RjeB#lvUu4A*|7U-<}0W1xs5!y z*=1kk!>5mR>VWE>#H-*q?f3K+(BCtV1A0!(n4bufTvQC~{68c=T2~{M;WuNx?ad=+;62jzn zJZ{2f03>GhQbx2jrmBq}02a+QT3-z(#eTf>cMi=rk`qoiIGzuAPG(b=^DV0JTaDOd zPOlHyYu&xekE`K1ek^q*6(vfy@NSJ>+G*)(m)ko2-gxWGM?1sjpyNf8VmXk4;-FRw3WcurXGlj{+bbr{!bzen?kliV6Ng;mqkMgwJhi6^K zzW55N2JD#DZ~CQFw|ylpzI*aw4=L`mN-;V8Fq-lEKNrElzr@nJ#ZI$~p(gPe)xw)L z@0k~Zqa1Ad!)Jn@ix18%aCD+eU|zXpMt}&*Y<+*7 z+majkP-ylE-#Vt|*{9|(`{?lae2-a&aZE?T%JogNTN0;j73>LPdq!yaPcMu6JEO@} z(G^$?NsdAnrl)ml?bNl}2@A6xx)nsBlFl2xW^%9e`(wUvuoT?OLn>5stuEZ?3fVns z-M*5M`){U|_`YE4UOqXMtZLcZJ+G}eH|6=V+qLAsZ4eUNYBe4qyqkW_oFs+5eVvjBZ zQVfR__l!VIbqGnw&sb)+obRNJHAfc^5>QOs#`LX3Z6_wGI8QoTv-ZxB=Jss`JwitZ zhtmX~$Dm#}2&8_zH2EAh)G$T^3EY%b=&HYWLPA0!1iq;f7|lU}ziRfySIAs+A82fd zJBIHG490*RM#jVtTMTq{kp->5CJ|J=ot2^RuOuQbSP5^G=j8C)y9Q1166-~_y6VMX_~)&xta7B$lpcc+(+3f) zIp8e0Do>GYA{*M-=r^V=Ua<;u0Icv#SC8;7aIC%k#GhozCDB4mx&?!4D!oXa!OK}S z0$NOC*XWH`wSK`2IHO3%`hp!un?Q~^@RCe3=WG&_5d zy*c}AS<6aM1u;Q@RHV*R8beHc%}pXkE6YA+A_7$@cmlC!ZVxr7BrnX zbYDJFaCY&YBsU7|&k)I!52vrX&wkWHZHyM$F_E<8i2N|`gZZ7B{_T#&B-tZs0%(GZ z;59v2;|q+?nTcPIAdYFjH@+Lhz!1lHdQ9!PRj6;goltW)R}J zi$A3d8*IK~IjHfS)Erh0doOeV)Gss-a=M*r5QBLOSJ+-91WGvdNe|5hn_Ev3KGwm+ z7R{^g_XdE+el5=d9|+>@MneOWg{f&T3<&0OV{~YKwi`*^C3dZ6{p)D7ulpOPC0w|e zi{`i$arwc~Q7{MN9vs(Ujiy-!+Y4*)OAj&#>)25!ln~cdh;u>8wKXIy!M^C}KiFA3 z9yoyPXY0a@_hiiTbB?B}PxYV)$>Fh@8jgqs&~b^|^Z`hC&KpqV+uhLnQmwHUq)@n1 zizI_|3wXG7If+Mp|7hXFp6mYYUT z2=X*4)mLm%C4|28wFNh%O%B)m=`%z-ve&BRpVP)vwMXRgQa2G6Z*jf(fP6pOtLff6 z9YFunWLBt@hGsOp-dAB4WnY`xiIBr?ZgN*5jULSP2unQCSh}2C)mG!PH!G&fP+4Q? zcZ-?X#g2jfhZEI^g}(mG>9{_oSY70vrTH{NV>!wm)u)shx5{gAqTi1Vqy2Y>e+ZKO z@C5CRJ}%A%3G#0K8>Ud?soJ;DZ!oSSy?OAfZj2}{SFP`qrr~3TA7w{B=IzL8EaYxJ zfCR6*t}2f`c0%{X451bvojDy8Yj6K*Vh^y8L3RYVrKe!h-r_~@XCCCI+29r3!NRc< zLI-IfbF+e0;T&NNgkr)_3Cj!et3>2P>Nj_JOde9Y2!OZj5IA9PT&IR zzck2vIr7UF_VTtw0TVw{zDe2yNe{p8II^<=!zo>12E+*{vP!D$ik=jWOk>_sL6P?s zhq?@wyh3=wiMsXkt}#qqx{u~K3Fz~T6uCq-|6K20!mZAb2(tNwH*={zQgykf!^bL3 zqXwpQw`7e|wndyFv~|7=TUaegF#s>8`IgjLFl-lh_h z^dAx08ohGR_ajbTBcXZgTt6&C(XTiNHtzcb3{|ktOr8JL?)3_}z;t)S zyESyz|H#*mXz78dH2>dKd8GtY-0EL-Pp@PhGE`KHhzr7}P~!T55JP%@0(R z5kJ_0vsgBGMm02g;migSKz*xQ_6AgB~#mvHh*EE+oEIJHK^H zW}XE8av|C|iu}Ot*VI4_sH<09Q$>7?4opf)g4fthW&;m@r5;;B28LMJai?%O5rK-3 zvIu>GLh)dS1&^BwYN3{q#23!~;l*?T~LVKA@Cisd)cWd5;BwCN^dflRD;O@4i>)MJM# zbinB7NuK&T3zBr#s4TLhHTMX$F(S}swsVd76^hAbG_Z~PB6AsIW9whW)wzJ^k6%J) zxvZ%?W=)JPi@5XAx8+GYNkv8Km$@IfcLk8dhN6md+AQ8P>C3cxPu`FW7dbU3of3JK zT$+LE*^=l;>}^zv@-Yh?iVLWP2J>7`A2DMgb}G*M%fyb^efHu6YlTj~2Ll`i8(z?1 za`!4f?;G@UT^6yET%6VKCQ>Hiba7wZt0c+#7*B&GOdg67W2jA7Eo^?G7v69GO+b=n z^R3gFE%k~W>+4$IdK-I3o`saI4O)a-SAE@ABo}4PrJ-fyu3@bi~V~6JLoOH|U z#VlTx)pQ9(4WX z6g$AQimIv*JYMadPg0+Tt)*aBDY?%gLH*HJ(-^4*WM-ug=qWwaX<2B$T}#!~ zPBKR?q4SUB2!8i;+WY+4ljqT3BznWT%px1jVZFA$?@smkD|yC2yPyx{Hx?ZuSURpz8$p^YyhYh{7=K1Fy~d&Z06o*LKbX(mFMl*0ojll zT@;VUORvnXh=7CZBE;l}eI>KuTyeibo{P{{+ST4K4UT*IU2*vky^eazLMb-;PR(H= z?pmv+sXLi&I*V;cvu7<rK*qRmva#`wCCS~b@N!yFEPrY!-)?aoy=w_bhWsVB+33?xO za%p})K3w@xbo0tS;EFJQ2EGANT5K={NDgp7reOg%0*|GMon2gbIIh?RI+-5p`BT%< zf@?PD5y)lW+$87BItLoAlZ%V(@J}?rEVzF*Q^}QxmnbTnvkHpcdHVWzKKDbS1DX&{Zpx$O%Ws(~>EpM+SJHM>o znl(=z>hHfKUjG`6Gk@3>ki6ZzS#Q~ft=)Z#lSoWB{hv-l5B9!rQAg?d)^Y%~A=mzm?#y7!F3CM2ApsK8Zs;si;Hpk9;bem znA`GabE)p$Ug9fPnruJ;d$m6Pp$>k)Cf3%X7Hw3=y6QmEv?!+g2+O8!=I(4({GI8`*@hVVkZDUvzIn^ z*C&+E9MOut4LHbaqx0w&q+-Xi4OIG$k0%vul#L6*#xyp*;|J(f#;7*FSKY=yR)7Z( zB)YRI&o5teOiN`6>3@!n4Ox+Hyq0z9Z`fdub7}3$>HlOv|FQJXzI{g3s}n(8C;CFi zW;zK&{v(s_Po?t?-(W7{rW@$UUTW>1=+LMu^3?YDpxee%V780ZnOMlD?&78rq5Zhp zXFD83Il~nlMW;Mfw6Ee>D|LgOcXLwi-u$!Ln zZUi2aC*Ua%W|(BWJS?PoyPf5`!}gHZNM3mO&n0aK9ovS~H`P;l5>&U4W&+&g4o1xi zV|};Q8KdK`($7>nbi_QRt}KU&l_r#OGDbPlXM02Ayn&~X-w<!biC~)4NTU1}Eec>iBFtg>7^5gox=VZOl7+Iw@I(UpVt;%CQSsyW$ zjT}`?yid`gP(IKzQ;?WADZaTPGoHCoN^0+r_QTila}i2GH&pL+$a5%S)6yNY^@>5= zo+Bk;-a2r)ci6GH-!S6$KY3`pC=Fy1HtcFS2GPOh0DveO|I3Y!|2+V zAaU;Oe38S%=*S3Q>ob5#D29uQi;2Ox=90-ch~W6u_qn)0NpQwFx6^oH z5zg_T{6#j^mW)b%_<6Y+?Mx~_tFEeK6=#~_srS6H_9_x+FkMC9ul1F?T9I)3vu;eK zv}m}@RN6SKRg^f^pNW;OOgM@bwm^zk&3b-y?^_gt66l)dpa_QoRl!{7ts1eiepM Date: Fri, 20 Sep 2024 20:44:32 +0200 Subject: [PATCH 16/26] Remove unrelated discord link This doesn't need to be here as it's all Ex developers who are now unrelated to the project --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index fb7aa3ed16..4d4f239680 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,6 @@ SPDX-License-Identifier: GPL-3.0-or-later We're in need of developers. Please join our chat below or DM a dev if you want to contribute! This repo is currently based on Yuzu EA 4176 but the code will be rewritten for legal and performance reasons. -Support the original suyu developer team [here](https://discord.gg/79B6wqFPnc). - -


From 592f93b26cd75765a7b99ec4a711aa03b3325f84 Mon Sep 17 00:00:00 2001 From: "Crunch (Chaz9)" Date: Sun, 29 Sep 2024 21:23:11 +0100 Subject: [PATCH 17/26] Update Core Timing .h file --- src/core/core_timing.h | 96 +++++++----------------------------------- 1 file changed, 16 insertions(+), 80 deletions(-) diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 7e4dff7f3d..5b42b0e491 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -11,8 +11,7 @@ #include #include #include - -#include +#include #include "common/common_types.h" #include "common/thread.h" @@ -43,18 +42,6 @@ enum class UnscheduleEventType { NoWait, }; -/** - * This is a system to schedule events into the emulated machine's future. Time is measured - * in main CPU clock cycles. - * - * To schedule an event, you first have to register its type. This is where you pass in the - * callback. You then schedule events using the type ID you get back. - * - * The s64 ns_late that the callbacks get is how many ns late it was. - * So to schedule a new event on a regular basis: - * inside callback: - * ScheduleEvent(period_in_ns - ns_late, callback, "whatever") - */ class CoreTiming { public: CoreTiming(); @@ -66,99 +53,56 @@ public: CoreTiming& operator=(const CoreTiming&) = delete; CoreTiming& operator=(CoreTiming&&) = delete; - /// CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is - /// required to end slice - 1 and start slice 0 before the first cycle of code is executed. void Initialize(std::function&& on_thread_init_); - - /// Clear all pending events. This should ONLY be done on exit. void ClearPendingEvents(); - - /// Sets if emulation is multicore or single core, must be set before Initialize void SetMulticore(bool is_multicore_) { is_multicore = is_multicore_; } - - /// Pauses/Unpauses the execution of the timer thread. void Pause(bool is_paused); - - /// Pauses/Unpauses the execution of the timer thread and waits until paused. void SyncPause(bool is_paused); - - /// Checks if core timing is running. bool IsRunning() const; - - /// Checks if the timer thread has started. bool HasStarted() const { return has_started; } - - /// Checks if there are any pending time events. bool HasPendingEvents() const; - - /// Schedules an event in core timing void ScheduleEvent(std::chrono::nanoseconds ns_into_future, const std::shared_ptr& event_type, bool absolute_time = false); - - /// Schedules an event which will automatically re-schedule itself with the given time, until - /// unscheduled void ScheduleLoopingEvent(std::chrono::nanoseconds start_time, std::chrono::nanoseconds resched_time, const std::shared_ptr& event_type, bool absolute_time = false); - void UnscheduleEvent(const std::shared_ptr& event_type, UnscheduleEventType type = UnscheduleEventType::Wait); - void AddTicks(u64 ticks_to_add); - void ResetTicks(); - void Idle(); - s64 GetDowncount() const { - return downcount; + return downcount.load(std::memory_order_relaxed); } - - /// Returns the current CNTPCT tick value. u64 GetClockTicks() const; - - /// 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; - - /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. std::optional Advance(); -#ifdef _WIN32 - void SetTimerResolutionNs(std::chrono::nanoseconds ns); -#endif - private: - struct Event; + struct Event { + s64 time; + u64 fifo_order; + std::shared_ptr type; + bool operator>(const Event& other) const { + return std::tie(time, fifo_order) > std::tie(other.time, other.fifo_order); + } + }; static void ThreadEntry(CoreTiming& instance); void ThreadLoop(); - void Reset(); std::unique_ptr clock; - - s64 global_timer = 0; - -#ifdef _WIN32 - s64 timer_resolution_ns; -#endif - - using heap_t = - boost::heap::fibonacci_heap>>; - - heap_t event_queue; - u64 event_fifo_id = 0; + std::atomic global_timer{0}; + std::vector event_queue; + std::atomic event_fifo_id{0}; Common::Event event{}; Common::Event pause_event{}; @@ -173,20 +117,12 @@ private: std::function on_thread_init{}; bool is_multicore{}; - s64 pause_end_time{}; + std::atomic pause_end_time{}; - /// Cycle timing - u64 cpu_ticks{}; - s64 downcount{}; + std::atomic cpu_ticks{}; + std::atomic downcount{}; }; -/// Creates a core timing event with the given name and callback. -/// -/// @param name The name of the core timing event to create. -/// @param callback The callback to execute for the event. -/// -/// @returns An EventType instance representing the created event. -/// std::shared_ptr CreateEvent(std::string name, TimedCallback&& callback); } // namespace Core::Timing From 76f6f8de808acd32402ac3ea06f58cae50f16c58 Mon Sep 17 00:00:00 2001 From: "Crunch (Chaz9)" Date: Sun, 29 Sep 2024 21:28:35 +0100 Subject: [PATCH 18/26] ok --- src/core/core_timing.cpp | 114 ++---- src/core/cpu_manager.cpp | 43 +- src/core/memory.cpp | 865 ++++----------------------------------- 3 files changed, 121 insertions(+), 901 deletions(-) diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 1abfa920c4..6ac0007764 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -26,24 +26,6 @@ std::shared_ptr CreateEvent(std::string name, TimedCallback&& callbac return std::make_shared(std::move(callback), std::move(name)); } -struct CoreTiming::Event { - s64 time; - u64 fifo_order; - std::weak_ptr type; - s64 reschedule_time; - heap_t::handle_type handle{}; - - // Sort by time, unless the times are the same, in which case sort by - // the order added to the queue - friend bool operator>(const Event& left, const Event& right) { - return std::tie(left.time, left.fifo_order) > std::tie(right.time, right.fifo_order); - } - - friend bool operator<(const Event& left, const Event& right) { - return std::tie(left.time, left.fifo_order) < std::tie(right.time, right.fifo_order); - } -}; - CoreTiming::CoreTiming() : clock{Common::CreateOptimalClock()} {} CoreTiming::~CoreTiming() { @@ -87,7 +69,7 @@ void CoreTiming::Pause(bool is_paused) { } void CoreTiming::SyncPause(bool is_paused) { - if (is_paused == paused && paused_set == paused) { + if (is_paused == paused && paused_set == is_paused) { return; } @@ -112,7 +94,7 @@ bool CoreTiming::IsRunning() const { bool CoreTiming::HasPendingEvents() const { std::scoped_lock lock{basic_lock}; - return !(wait_set && event_queue.empty()); + return !event_queue.empty(); } void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future, @@ -121,8 +103,8 @@ void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future, std::scoped_lock scope{basic_lock}; const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future}; - auto h{event_queue.emplace(Event{next_time.count(), event_fifo_id++, event_type, 0})}; - (*h).handle = h; + event_queue.emplace_back(Event{next_time.count(), event_fifo_id++, event_type}); + std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); } event.Set(); @@ -136,9 +118,9 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time, std::scoped_lock scope{basic_lock}; const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; - auto h{event_queue.emplace( - Event{next_time.count(), event_fifo_id++, event_type, resched_time.count()})}; - (*h).handle = h; + event_queue.emplace_back( + Event{next_time.count(), event_fifo_id++, event_type, resched_time.count()}); + std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); } event.Set(); @@ -149,17 +131,11 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr& event_type, { std::scoped_lock lk{basic_lock}; - std::vector to_remove; - for (auto itr = event_queue.begin(); itr != event_queue.end(); itr++) { - const Event& e = *itr; - if (e.type.lock().get() == event_type.get()) { - to_remove.push_back(itr->handle); - } - } - - for (auto& h : to_remove) { - event_queue.erase(h); - } + event_queue.erase( + std::remove_if(event_queue.begin(), event_queue.end(), + [&](const Event& e) { return e.type.lock().get() == event_type.get(); }), + event_queue.end()); + std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>()); event_type->sequence_number++; } @@ -172,7 +148,7 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr& event_type, void CoreTiming::AddTicks(u64 ticks_to_add) { cpu_ticks += ticks_to_add; - downcount -= static_cast(cpu_ticks); + downcount -= static_cast(ticks_to_add); } void CoreTiming::Idle() { @@ -180,7 +156,7 @@ void CoreTiming::Idle() { } void CoreTiming::ResetTicks() { - downcount = MAX_SLICE_LENGTH; + downcount.store(MAX_SLICE_LENGTH, std::memory_order_release); } u64 CoreTiming::GetClockTicks() const { @@ -201,48 +177,38 @@ std::optional CoreTiming::Advance() { std::scoped_lock lock{advance_lock, basic_lock}; global_timer = GetGlobalTimeNs().count(); - while (!event_queue.empty() && event_queue.top().time <= global_timer) { - const Event& evt = event_queue.top(); + while (!event_queue.empty() && event_queue.front().time <= global_timer) { + Event evt = std::move(event_queue.front()); + std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>()); + event_queue.pop_back(); - if (const auto event_type{evt.type.lock()}) { + if (const auto event_type = evt.type.lock()) { const auto evt_time = evt.time; const auto evt_sequence_num = event_type->sequence_number; - if (evt.reschedule_time == 0) { - event_queue.pop(); + basic_lock.unlock(); - basic_lock.unlock(); + const auto new_schedule_time = event_type->callback( + evt_time, std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time}); - event_type->callback( - evt_time, std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time}); + basic_lock.lock(); - basic_lock.lock(); - } else { - basic_lock.unlock(); + if (evt_sequence_num != event_type->sequence_number) { + continue; + } - const auto new_schedule_time{event_type->callback( - evt_time, std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time})}; + if (new_schedule_time.has_value() || evt.reschedule_time != 0) { + const auto next_schedule_time = new_schedule_time.value_or( + std::chrono::nanoseconds{evt.reschedule_time}); - basic_lock.lock(); - - if (evt_sequence_num != event_type->sequence_number) { - // Heap handle is invalidated after external modification. - continue; - } - - const auto next_schedule_time{new_schedule_time.has_value() - ? new_schedule_time.value().count() - : evt.reschedule_time}; - - // If this event was scheduled into a pause, its time now is going to be way - // behind. Re-set this event to continue from the end of the pause. - auto next_time{evt.time + next_schedule_time}; + auto next_time = evt.time + next_schedule_time.count(); if (evt.time < pause_end_time) { - next_time = pause_end_time + next_schedule_time; + next_time = pause_end_time + next_schedule_time.count(); } - event_queue.update(evt.handle, Event{next_time, event_fifo_id++, evt.type, - next_schedule_time, evt.handle}); + event_queue.emplace_back(Event{next_time, event_fifo_id++, evt.type, + next_schedule_time.count()}); + std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); } } @@ -250,7 +216,7 @@ std::optional CoreTiming::Advance() { } if (!event_queue.empty()) { - return event_queue.top().time; + return event_queue.front().time; } else { return std::nullopt; } @@ -269,7 +235,7 @@ void CoreTiming::ThreadLoop() { #ifdef _WIN32 while (!paused && !event.IsSet() && wait_time > 0) { wait_time = *next_time - GetGlobalTimeNs().count(); - if (wait_time >= timer_resolution_ns) { + if (wait_time >= 1'000'000) { // 1ms Common::Windows::SleepForOneTick(); } else { #ifdef ARCHITECTURE_x86_64 @@ -290,10 +256,8 @@ void CoreTiming::ThreadLoop() { } 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; @@ -327,10 +291,4 @@ std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const { return std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)}; } -#ifdef _WIN32 -void CoreTiming::SetTimerResolutionNs(std::chrono::nanoseconds ns) { - timer_resolution_ns = ns.count(); -} -#endif - } // namespace Core::Timing diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index 9b1c773877..e7e341de16 100644 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -1,6 +1,12 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include + #include "common/fiber.h" #include "common/microprofile.h" #include "common/scope_exit.h" @@ -24,6 +30,7 @@ void CpuManager::Initialize() { num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1; gpu_barrier = std::make_unique(num_cores + 1); + core_data.resize(num_cores); for (std::size_t core = 0; core < num_cores; core++) { core_data[core].host_thread = std::jthread([this, core](std::stop_token token) { RunThread(token, core); }); @@ -31,10 +38,10 @@ void CpuManager::Initialize() { } void CpuManager::Shutdown() { - for (std::size_t core = 0; core < num_cores; core++) { - if (core_data[core].host_thread.joinable()) { - core_data[core].host_thread.request_stop(); - core_data[core].host_thread.join(); + for (auto& data : core_data) { + if (data.host_thread.joinable()) { + data.host_thread.request_stop(); + data.host_thread.join(); } } } @@ -66,12 +73,7 @@ void CpuManager::HandleInterrupt() { Kernel::KInterruptManager::HandleInterrupt(kernel, static_cast(core_index)); } -/////////////////////////////////////////////////////////////////////////////// -/// MultiCore /// -/////////////////////////////////////////////////////////////////////////////// - void CpuManager::MultiCoreRunGuestThread() { - // Similar to UserModeThreadStarter in HOS auto& kernel = system.Kernel(); auto* thread = Kernel::GetCurrentThreadPointer(kernel); kernel.CurrentScheduler()->OnThreadStart(); @@ -88,10 +90,6 @@ void CpuManager::MultiCoreRunGuestThread() { } void CpuManager::MultiCoreRunIdleThread() { - // Not accurate to HOS. Remove this entire method when singlecore is removed. - // See notes in KScheduler::ScheduleImpl for more information about why this - // is inaccurate. - auto& kernel = system.Kernel(); kernel.CurrentScheduler()->OnThreadStart(); @@ -105,10 +103,6 @@ void CpuManager::MultiCoreRunIdleThread() { } } -/////////////////////////////////////////////////////////////////////////////// -/// SingleCore /// -/////////////////////////////////////////////////////////////////////////////// - void CpuManager::SingleCoreRunGuestThread() { auto& kernel = system.Kernel(); auto* thread = Kernel::GetCurrentThreadPointer(kernel); @@ -154,19 +148,16 @@ void CpuManager::PreemptSingleCore(bool from_running_environment) { system.CoreTiming().Advance(); kernel.SetIsPhantomModeForSingleCore(false); } - current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES); + current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES, std::memory_order_release); system.CoreTiming().ResetTicks(); kernel.Scheduler(current_core).PreemptSingleCore(); - // We've now been scheduled again, and we may have exchanged schedulers. - // Reload the scheduler in case it's different. if (!kernel.Scheduler(current_core).IsIdle()) { idle_count = 0; } } void CpuManager::GuestActivate() { - // Similar to the HorizonKernelMain callback in HOS auto& kernel = system.Kernel(); auto* scheduler = kernel.CurrentScheduler(); @@ -184,27 +175,19 @@ void CpuManager::ShutdownThread() { } void CpuManager::RunThread(std::stop_token token, std::size_t core) { - /// Initialization system.RegisterCoreThread(core); - std::string name; - if (is_multicore) { - name = "CPUCore_" + std::to_string(core); - } else { - name = "CPUThread"; - } + std::string name = is_multicore ? "CPUCore_" + std::to_string(core) : "CPUThread"; MicroProfileOnThreadCreate(name.c_str()); Common::SetCurrentThreadName(name.c_str()); Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); auto& data = core_data[core]; data.host_context = Common::Fiber::ThreadToFiber(); - // Cleanup SCOPE_EXIT { data.host_context->Exit(); MicroProfileOnThreadExit(); }; - // Running if (!gpu_barrier->Sync(token)) { return; } diff --git a/src/core/memory.cpp b/src/core/memory.cpp index f7eae9c598..6e53db3640 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "common/assert.h" #include "common/atomic_ops.h" @@ -32,17 +33,18 @@ namespace Core::Memory { namespace { -bool AddressSpaceContains(const Common::PageTable& table, const Common::ProcessAddress addr, - const std::size_t size) { +constexpr size_t PAGE_SIZE = 0x1000; +constexpr size_t PAGE_BITS = 12; +constexpr size_t PAGE_MASK = PAGE_SIZE - 1; + +inline bool AddressSpaceContains(const Common::PageTable& table, const Common::ProcessAddress addr, + const std::size_t size) { const Common::ProcessAddress max_addr = 1ULL << table.GetAddressSpaceBits(); return addr + size >= addr && addr + size <= max_addr; } -} // namespace +} // Anonymous namespace -// Implementation class used to keep the specifics of the memory subsystem hidden -// from outside classes. This also allows modification to the internals of the memory -// subsystem without needing to rebuild all files that make use of the memory interface. struct Memory::Impl { explicit Impl(Core::System& system_) : system{system_} {} @@ -66,12 +68,11 @@ struct Memory::Impl { void MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size, Common::PhysicalAddress target, Common::MemoryPermission perms, bool separate_heap) { - ASSERT_MSG((size & SUYU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size); - ASSERT_MSG((base & SUYU_PAGEMASK) == 0, "non-page aligned base: {:016X}", GetInteger(base)); + ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); + ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", GetInteger(base)); ASSERT_MSG(target >= DramMemoryMap::Base, "Out of bounds target: {:016X}", GetInteger(target)); - MapPages(page_table, base / SUYU_PAGESIZE, size / SUYU_PAGESIZE, target, - Common::PageType::Memory); + MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory); if (current_page_table->fastmem_arena) { buffer->Map(GetInteger(base), GetInteger(target) - DramMemoryMap::Base, size, perms, @@ -81,10 +82,9 @@ struct Memory::Impl { void UnmapRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size, bool separate_heap) { - ASSERT_MSG((size & SUYU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size); - ASSERT_MSG((base & SUYU_PAGEMASK) == 0, "non-page aligned base: {:016X}", GetInteger(base)); - MapPages(page_table, base / SUYU_PAGESIZE, size / SUYU_PAGESIZE, 0, - Common::PageType::Unmapped); + ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); + ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", GetInteger(base)); + MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, 0, Common::PageType::Unmapped); if (current_page_table->fastmem_arena) { buffer->Unmap(GetInteger(base), size, separate_heap); @@ -93,55 +93,28 @@ struct Memory::Impl { void ProtectRegion(Common::PageTable& page_table, VAddr vaddr, u64 size, Common::MemoryPermission perms) { - ASSERT_MSG((size & SUYU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size); - ASSERT_MSG((vaddr & SUYU_PAGEMASK) == 0, "non-page aligned base: {:016X}", vaddr); + ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); + ASSERT_MSG((vaddr & PAGE_MASK) == 0, "non-page aligned base: {:016X}", vaddr); if (!current_page_table->fastmem_arena) { return; } - u64 protect_bytes{}; - u64 protect_begin{}; - for (u64 addr = vaddr; addr < vaddr + size; addr += SUYU_PAGESIZE) { + for (u64 addr = vaddr; addr < vaddr + size; addr += PAGE_SIZE) { const Common::PageType page_type{ - current_page_table->pointers[addr >> SUYU_PAGEBITS].Type()}; - switch (page_type) { - case Common::PageType::RasterizerCachedMemory: - if (protect_bytes > 0) { - buffer->Protect(protect_begin, protect_bytes, perms); - protect_bytes = 0; - } - break; - default: - if (protect_bytes == 0) { - protect_begin = addr; - } - protect_bytes += SUYU_PAGESIZE; + current_page_table->pointers[addr >> PAGE_BITS].Type()}; + if (page_type != Common::PageType::RasterizerCachedMemory) { + buffer->Protect(addr, PAGE_SIZE, perms); } } - - if (protect_bytes > 0) { - buffer->Protect(protect_begin, protect_bytes, perms); - } } - [[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const { + u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const { const Common::PhysicalAddress paddr{ - current_page_table->backing_addr[vaddr >> SUYU_PAGEBITS]}; + current_page_table->backing_addr[vaddr >> PAGE_BITS]}; if (!paddr) { - return {}; - } - - return system.DeviceMemory().GetPointer(paddr + vaddr); - } - - [[nodiscard]] u8* GetPointerFromDebugMemory(u64 vaddr) const { - const Common::PhysicalAddress paddr{ - current_page_table->backing_addr[vaddr >> SUYU_PAGEBITS]}; - - if (paddr == 0) { - return {}; + return nullptr; } return system.DeviceMemory().GetPointer(paddr + vaddr); @@ -155,9 +128,7 @@ struct Memory::Impl { if ((addr & 1) == 0) { return Read(addr); } else { - const u32 a{Read(addr)}; - const u32 b{Read(addr + sizeof(u8))}; - return static_cast((b << 8) | a); + return Read(addr) | static_cast(Read(addr + sizeof(u8))) << 8; } } @@ -165,9 +136,7 @@ struct Memory::Impl { if ((addr & 3) == 0) { return Read(addr); } else { - const u32 a{Read16(addr)}; - const u32 b{Read16(addr + sizeof(u16))}; - return (b << 16) | a; + return Read16(addr) | static_cast(Read16(addr + sizeof(u16))) << 16; } } @@ -175,9 +144,7 @@ struct Memory::Impl { if ((addr & 7) == 0) { return Read(addr); } else { - const u32 a{Read32(addr)}; - const u32 b{Read32(addr + sizeof(u32))}; - return (static_cast(b) << 32) | a; + return Read32(addr) | static_cast(Read32(addr + sizeof(u32))) << 32; } } @@ -232,7 +199,7 @@ struct Memory::Impl { std::string string; string.reserve(max_length); for (std::size_t i = 0; i < max_length; ++i) { - const char c = Read(vaddr); + const char c = Read(vaddr); if (c == '\0') { break; } @@ -243,648 +210,72 @@ 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; - std::size_t remaining_size = size; - std::size_t page_index = addr >> SUYU_PAGEBITS; - std::size_t page_offset = addr & SUYU_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(SUYU_PAGESIZE) - page_offset, remaining_size); - const auto current_vaddr = - static_cast((page_index << SUYU_PAGEBITS) + page_offset); - - const auto [pointer, type] = page_table.pointers[page_index].PointerType(); - switch (type) { - case Common::PageType::Unmapped: { - user_accessible = false; - on_unmapped(copy_amount, current_vaddr); - break; - } - case Common::PageType::Memory: { - u8* mem_ptr = - reinterpret_cast(pointer + page_offset + (page_index << SUYU_PAGEBITS)); - on_memory(copy_amount, mem_ptr); - break; - } - case Common::PageType::DebugMemory: { - u8* const mem_ptr{GetPointerFromDebugMemory(current_vaddr)}; - on_memory(copy_amount, mem_ptr); - break; - } - case Common::PageType::RasterizerCachedMemory: { - u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)}; - on_rasterizer(current_vaddr, copy_amount, host_ptr); - break; - } - default: - UNREACHABLE(); - } - - page_index++; - page_offset = 0; - increment(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) { - 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->blocks[src_addr >> SUYU_PAGEBITS] == - current_page_table->blocks[(src_addr + size) >> SUYU_PAGEBITS]) { - return GetPointerSilent(src_addr); - } - return nullptr; - } - - u8* GetSpan(const VAddr src_addr, const std::size_t size) { - if (current_page_table->blocks[src_addr >> SUYU_PAGEBITS] == - current_page_table->blocks[(src_addr + size) >> SUYU_PAGEBITS]) { - 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) { - 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) { - 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; - }); - } - - 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 InvalidateDataCache(Common::ProcessAddress dest_addr, std::size_t size) { - auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, - const std::size_t block_size) { - // dc ivac: Invalidate to point of coherency - // GPU flush -> CPU invalidate - HandleRasterizerDownload(GetInteger(current_vaddr), block_size); - }; - return PerformCacheOperation(dest_addr, size, on_rasterizer); - } - - Result StoreDataCache(Common::ProcessAddress dest_addr, std::size_t size) { - auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, - const std::size_t block_size) { - // dc cvac: Store to point of coherency - // CPU flush -> GPU invalidate - HandleRasterizerWrite(GetInteger(current_vaddr), block_size); - }; - return PerformCacheOperation(dest_addr, size, on_rasterizer); - } - - Result FlushDataCache(Common::ProcessAddress dest_addr, std::size_t size) { - auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, - 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); - }; - return PerformCacheOperation(dest_addr, size, on_rasterizer); - } - - void MarkRegionDebug(u64 vaddr, u64 size, bool debug) { - if (vaddr == 0 || !AddressSpaceContains(*current_page_table, vaddr, size)) { - return; - } - - if (current_page_table->fastmem_arena) { - const auto perm{debug ? Common::MemoryPermission{} - : Common::MemoryPermission::ReadWrite}; - buffer->Protect(vaddr, size, perm); - } - - // Iterate over a contiguous CPU address space, marking/unmarking the region. - // The region is at a granularity of CPU pages. - - const u64 num_pages = ((vaddr + size - 1) >> SUYU_PAGEBITS) - (vaddr >> SUYU_PAGEBITS) + 1; - for (u64 i = 0; i < num_pages; ++i, vaddr += SUYU_PAGESIZE) { - const Common::PageType page_type{ - current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Type()}; - if (debug) { - // Switch page type to debug if now debug - switch (page_type) { - case Common::PageType::Unmapped: - ASSERT_MSG(false, "Attempted to mark unmapped pages as debug"); - break; - case Common::PageType::RasterizerCachedMemory: - case Common::PageType::DebugMemory: - // Page is already marked. - break; - case Common::PageType::Memory: - current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Store( - 0, Common::PageType::DebugMemory); - break; - default: - UNREACHABLE(); - } - } else { - // Switch page type to non-debug if now non-debug - switch (page_type) { - case Common::PageType::Unmapped: - ASSERT_MSG(false, "Attempted to mark unmapped pages as non-debug"); - break; - case Common::PageType::RasterizerCachedMemory: - case Common::PageType::Memory: - // Don't mess with already non-debug or rasterizer memory. - break; - case Common::PageType::DebugMemory: { - u8* const pointer{GetPointerFromDebugMemory(vaddr & ~SUYU_PAGEMASK)}; - current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Store( - reinterpret_cast(pointer) - (vaddr & ~SUYU_PAGEMASK), - Common::PageType::Memory); - break; - } - default: - UNREACHABLE(); - } - } - } - } - - void RasterizerMarkRegionCached(u64 vaddr, u64 size, bool cached) { - if (vaddr == 0 || !AddressSpaceContains(*current_page_table, vaddr, size)) { - return; - } - - if (current_page_table->fastmem_arena) { - Common::MemoryPermission perm{}; - if (!Settings::values.use_reactive_flushing.GetValue() || !cached) { - perm |= Common::MemoryPermission::Read; - } - if (!cached) { - perm |= Common::MemoryPermission::Write; - } - buffer->Protect(vaddr, size, perm); - } - - // Iterate over a contiguous CPU address space, which corresponds to the specified GPU - // address space, marking the region as un/cached. The region is marked un/cached at a - // granularity of CPU pages, hence why we iterate on a CPU page basis (note: GPU page size - // is different). This assumes the specified GPU address region is contiguous as well. - - const u64 num_pages = ((vaddr + size - 1) >> SUYU_PAGEBITS) - (vaddr >> SUYU_PAGEBITS) + 1; - for (u64 i = 0; i < num_pages; ++i, vaddr += SUYU_PAGESIZE) { - const Common::PageType page_type{ - current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Type()}; - if (cached) { - // Switch page type to cached if now cached - switch (page_type) { - case Common::PageType::Unmapped: - // It is not necessary for a process to have this region mapped into its address - // space, for example, a system module need not have a VRAM mapping. - break; - case Common::PageType::DebugMemory: - case Common::PageType::Memory: - current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Store( - 0, Common::PageType::RasterizerCachedMemory); - break; - case Common::PageType::RasterizerCachedMemory: - // There can be more than one GPU region mapped per CPU region, so it's common - // that this area is already marked as cached. - break; - default: - UNREACHABLE(); - } - } else { - // Switch page type to uncached if now uncached - switch (page_type) { - case Common::PageType::Unmapped: // NOLINT(bugprone-branch-clone) - // It is not necessary for a process to have this region mapped into its address - // space, for example, a system module need not have a VRAM mapping. - break; - case Common::PageType::DebugMemory: - case Common::PageType::Memory: - // There can be more than one GPU region mapped per CPU region, so it's common - // that this area is already unmarked as cached. - break; - case Common::PageType::RasterizerCachedMemory: { - u8* const pointer{GetPointerFromRasterizerCachedMemory(vaddr & ~SUYU_PAGEMASK)}; - if (pointer == nullptr) { - // It's possible that this function has been called while updating the - // pagetable after unmapping a VMA. In that case the underlying VMA will no - // longer exist, and we should just leave the pagetable entry blank. - current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Store( - 0, Common::PageType::Unmapped); - } else { - current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Store( - reinterpret_cast(pointer) - (vaddr & ~SUYU_PAGEMASK), - Common::PageType::Memory); - } - break; - } - default: - UNREACHABLE(); - } - } - } - } - - /** - * Maps a region of pages as a specific type. - * - * @param page_table The page table to use to perform the mapping. - * @param base The base address to begin mapping at. - * @param size The total size of the range in bytes. - * @param target The target address to begin mapping from. - * @param type The page type to map the memory as. - */ - void MapPages(Common::PageTable& page_table, Common::ProcessAddress base_address, u64 size, - Common::PhysicalAddress target, Common::PageType type) { - auto base = GetInteger(base_address); - - LOG_DEBUG(HW_Memory, "Mapping {:016X} onto {:016X}-{:016X}", GetInteger(target), - base * SUYU_PAGESIZE, (base + size) * SUYU_PAGESIZE); - - const auto end = base + size; - ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}", - base + page_table.pointers.size()); - - if (!target) { - ASSERT_MSG(type != Common::PageType::Memory, - "Mapping memory page without a pointer @ {:016x}", base * SUYU_PAGESIZE); - - while (base != end) { - page_table.pointers[base].Store(0, type); - page_table.backing_addr[base] = 0; - page_table.blocks[base] = 0; - base += 1; - } - } else { - auto orig_base = base; - while (base != end) { - auto host_ptr = - reinterpret_cast(system.DeviceMemory().GetPointer(target)) - - (base << SUYU_PAGEBITS); - auto backing = GetInteger(target) - (base << SUYU_PAGEBITS); - page_table.pointers[base].Store(host_ptr, type); - page_table.backing_addr[base] = backing; - page_table.blocks[base] = orig_base << SUYU_PAGEBITS; - - ASSERT_MSG(page_table.pointers[base].Pointer(), - "memory mapping base yield a nullptr within the table"); - - base += 1; - target += SUYU_PAGESIZE; - } - } - } - - [[nodiscard]] u8* GetPointerImpl(u64 vaddr, auto on_unmapped, auto on_rasterizer) const { - // AARCH64 masks the upper 16 bit of all memory accesses - vaddr = vaddr & 0xffffffffffffULL; - - if (!AddressSpaceContains(*current_page_table, vaddr, 1)) [[unlikely]] { - on_unmapped(); - return nullptr; - } - - // Avoid adding any extra logic to this fast-path block - const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Raw(); - if (const uintptr_t pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { - return reinterpret_cast(pointer + vaddr); - } - switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) { - case Common::PageType::Unmapped: - on_unmapped(); - return nullptr; - case Common::PageType::Memory: - ASSERT_MSG(false, "Mapped memory page without a pointer @ 0x{:016X}", vaddr); - return nullptr; - case Common::PageType::DebugMemory: - return GetPointerFromDebugMemory(vaddr); - case Common::PageType::RasterizerCachedMemory: { - u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)}; - on_rasterizer(); - return host_ptr; - } - default: - UNREACHABLE(); - } - return nullptr; - } - - [[nodiscard]] u8* GetPointer(const Common::ProcessAddress vaddr) const { - return GetPointerImpl( - GetInteger(vaddr), - [vaddr]() { - LOG_ERROR(HW_Memory, "Unmapped GetPointer @ 0x{:016X}", GetInteger(vaddr)); - }, - []() {}); - } - - [[nodiscard]] u8* GetPointerSilent(const Common::ProcessAddress vaddr) const { - return GetPointerImpl( - GetInteger(vaddr), []() {}, []() {}); - } - - /** - * Reads a particular data type out of memory at the given virtual address. - * - * @param vaddr The virtual address to read the data type from. - * - * @tparam T The data type to read out of memory. This type *must* be - * trivially copyable, otherwise the behavior of this function - * is undefined. - * - * @returns The instance of T read from the specified virtual address. - */ template - T Read(Common::ProcessAddress vaddr) { - T result = 0; - const u8* const ptr = GetPointerImpl( - GetInteger(vaddr), - [vaddr]() { - LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, - GetInteger(vaddr)); - }, - [&]() { HandleRasterizerDownload(GetInteger(vaddr), sizeof(T)); }); + T Read(const Common::ProcessAddress vaddr) { + T value; + const u8* const ptr = GetPointerFromRasterizerCachedMemory(GetInteger(vaddr)); if (ptr) { - std::memcpy(&result, ptr, sizeof(T)); + std::memcpy(&value, ptr, sizeof(T)); + } else { + LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, GetInteger(vaddr)); + value = 0; } - return result; + return value; } - /** - * Writes a particular data type to memory at the given virtual address. - * - * @param vaddr The virtual address to write the data type to. - * - * @tparam T The data type to write to memory. This type *must* be - * trivially copyable, otherwise the behavior of this function - * is undefined. - */ template void Write(Common::ProcessAddress vaddr, const T data) { - u8* const ptr = GetPointerImpl( - GetInteger(vaddr), - [vaddr, data]() { - LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8, - GetInteger(vaddr), static_cast(data)); - }, - [&]() { HandleRasterizerWrite(GetInteger(vaddr), sizeof(T)); }); + u8* const ptr = GetPointerFromRasterizerCachedMemory(GetInteger(vaddr)); if (ptr) { std::memcpy(ptr, &data, sizeof(T)); + system.GPU().InvalidateRegion(GetInteger(vaddr), sizeof(T)); + } else { + LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8, + GetInteger(vaddr), static_cast(data)); } } template bool WriteExclusive(Common::ProcessAddress vaddr, const T data, const T expected) { - u8* const ptr = GetPointerImpl( - GetInteger(vaddr), - [vaddr, data]() { - LOG_ERROR(HW_Memory, "Unmapped WriteExclusive{} @ 0x{:016X} = 0x{:016X}", - sizeof(T) * 8, GetInteger(vaddr), static_cast(data)); - }, - [&]() { HandleRasterizerWrite(GetInteger(vaddr), sizeof(T)); }); + u8* const ptr = GetPointerFromRasterizerCachedMemory(GetInteger(vaddr)); if (ptr) { - return Common::AtomicCompareAndSwap(reinterpret_cast(ptr), data, expected); + const bool result = Common::AtomicCompareAndSwap(reinterpret_cast(ptr), data, expected); + if (result) { + system.GPU().InvalidateRegion(GetInteger(vaddr), sizeof(T)); + } + return result; + } else { + LOG_ERROR(HW_Memory, "Unmapped WriteExclusive{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8, + GetInteger(vaddr), static_cast(data)); + return true; } - return true; } - bool WriteExclusive128(Common::ProcessAddress vaddr, const u128 data, const u128 expected) { - u8* const ptr = GetPointerImpl( - GetInteger(vaddr), - [vaddr, data]() { - LOG_ERROR(HW_Memory, "Unmapped WriteExclusive128 @ 0x{:016X} = 0x{:016X}{:016X}", - GetInteger(vaddr), static_cast(data[1]), static_cast(data[0])); - }, - [&]() { HandleRasterizerWrite(GetInteger(vaddr), sizeof(u128)); }); - if (ptr) { - return Common::AtomicCompareAndSwap(reinterpret_cast(ptr), data, expected); + bool ReadBlock(const Common::ProcessAddress src_addr, void* dest_buffer, + const std::size_t size) { + const u8* src_ptr = GetPointerFromRasterizerCachedMemory(GetInteger(src_addr)); + if (src_ptr) { + std::memcpy(dest_buffer, src_ptr, size); + return true; } - return true; + LOG_ERROR(HW_Memory, "Unmapped ReadBlock @ 0x{:016X}", GetInteger(src_addr)); + return false; } - void HandleRasterizerDownload(VAddr v_address, size_t size) { - const auto* p = GetPointerImpl( - v_address, []() {}, []() {}); - if (!gpu_device_memory) [[unlikely]] { - gpu_device_memory = &system.Host1x().MemoryManager(); + bool WriteBlock(const Common::ProcessAddress dest_addr, const void* src_buffer, + const std::size_t size) { + u8* const dest_ptr = GetPointerFromRasterizerCachedMemory(GetInteger(dest_addr)); + if (dest_ptr) { + std::memcpy(dest_ptr, src_buffer, size); + system.GPU().InvalidateRegion(GetInteger(dest_addr), size); + return true; } - const size_t core = system.GetCurrentHostThreadID(); - auto& current_area = rasterizer_read_areas[core]; - gpu_device_memory->ApplyOpOnPointer(p, scratch_buffers[core], [&](DAddr address) { - const DAddr end_address = address + size; - if (current_area.start_address <= address && end_address <= current_area.end_address) - [[likely]] { - return; - } - current_area = system.GPU().OnCPURead(address, size); - }); - } - - void HandleRasterizerWrite(VAddr v_address, size_t size) { - const auto* p = GetPointerImpl( - v_address, []() {}, []() {}); - constexpr size_t sys_core = Core::Hardware::NUM_CPU_CORES - 1; - const size_t core = std::min(system.GetCurrentHostThreadID(), - sys_core); // any other calls threads go to syscore. - if (!gpu_device_memory) [[unlikely]] { - gpu_device_memory = &system.Host1x().MemoryManager(); - } - // Guard on sys_core; - if (core == sys_core) [[unlikely]] { - sys_core_guard.lock(); - } - SCOPE_EXIT { - if (core == sys_core) [[unlikely]] { - sys_core_guard.unlock(); - } - }; - gpu_device_memory->ApplyOpOnPointer(p, scratch_buffers[core], [&](DAddr address) { - auto& current_area = rasterizer_write_areas[core]; - PAddr subaddress = address >> SUYU_PAGEBITS; - bool do_collection = current_area.last_address == subaddress; - if (!do_collection) [[unlikely]] { - do_collection = system.GPU().OnCPUWrite(address, size); - if (!do_collection) { - return; - } - current_area.last_address = subaddress; - } - gpu_dirty_managers[core].Collect(address, size); - }); - } - - struct GPUDirtyState { - PAddr last_address; - }; - - void InvalidateGPUMemory(u8* p, size_t size) { - constexpr size_t sys_core = Core::Hardware::NUM_CPU_CORES - 1; - const size_t core = std::min(system.GetCurrentHostThreadID(), - sys_core); // any other calls threads go to syscore. - if (!gpu_device_memory) [[unlikely]] { - gpu_device_memory = &system.Host1x().MemoryManager(); - } - // Guard on sys_core; - if (core == sys_core) [[unlikely]] { - sys_core_guard.lock(); - } - SCOPE_EXIT { - if (core == sys_core) [[unlikely]] { - sys_core_guard.unlock(); - } - }; - auto& gpu = system.GPU(); - gpu_device_memory->ApplyOpOnPointer( - p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size); }); + LOG_ERROR(HW_Memory, "Unmapped WriteBlock @ 0x{:016X}", GetInteger(dest_addr)); + return false; } Core::System& system; - Tegra::MaxwellDeviceMemoryManager* gpu_device_memory{}; Common::PageTable* current_page_table = nullptr; - std::array - rasterizer_read_areas{}; - std::array rasterizer_write_areas{}; - std::array, Core::Hardware::NUM_CPU_CORES> scratch_buffers{}; - std::span gpu_dirty_managers; - std::mutex sys_core_guard; - std::optional heap_tracker; #ifdef __linux__ Common::HeapTracker* buffer{}; @@ -893,16 +284,10 @@ struct Memory::Impl { #endif }; -Memory::Memory(Core::System& system_) : system{system_} { - Reset(); -} +Memory::Memory(Core::System& system_) : impl{std::make_unique(system_)} {} Memory::~Memory() = default; -void Memory::Reset() { - impl = std::make_unique(system); -} - void Memory::SetCurrentPageTable(Kernel::KProcess& process) { impl->SetCurrentPageTable(process); } @@ -925,38 +310,20 @@ void Memory::ProtectRegion(Common::PageTable& page_table, Common::ProcessAddress bool Memory::IsValidVirtualAddress(const Common::ProcessAddress vaddr) const { const auto& page_table = *impl->current_page_table; - const size_t page = vaddr >> SUYU_PAGEBITS; + const size_t page = vaddr >> PAGE_BITS; if (page >= page_table.pointers.size()) { return false; } const auto [pointer, type] = page_table.pointers[page].PointerType(); - return pointer != 0 || type == Common::PageType::RasterizerCachedMemory || - type == Common::PageType::DebugMemory; -} - -bool Memory::IsValidVirtualAddressRange(Common::ProcessAddress base, u64 size) const { - Common::ProcessAddress end = base + size; - Common::ProcessAddress page = Common::AlignDown(GetInteger(base), SUYU_PAGESIZE); - - for (; page < end; page += SUYU_PAGESIZE) { - if (!IsValidVirtualAddress(page)) { - return false; - } - } - - return true; + return pointer != 0 || type == Common::PageType::RasterizerCachedMemory; } u8* Memory::GetPointer(Common::ProcessAddress vaddr) { - return impl->GetPointer(vaddr); -} - -u8* Memory::GetPointerSilent(Common::ProcessAddress vaddr) { - return impl->GetPointerSilent(vaddr); + return impl->GetPointerFromRasterizerCachedMemory(GetInteger(vaddr)); } const u8* Memory::GetPointer(Common::ProcessAddress vaddr) const { - return impl->GetPointer(vaddr); + return impl->GetPointerFromRasterizerCachedMemory(GetInteger(vaddr)); } u8 Memory::Read8(const Common::ProcessAddress addr) { @@ -1007,10 +374,6 @@ bool Memory::WriteExclusive64(Common::ProcessAddress addr, u64 data, u64 expecte return impl->WriteExclusive64(addr, data, expected); } -bool Memory::WriteExclusive128(Common::ProcessAddress addr, u128 data, u128 expected) { - return impl->WriteExclusive128(addr, data, expected); -} - std::string Memory::ReadCString(Common::ProcessAddress vaddr, std::size_t max_length) { return impl->ReadCString(vaddr, max_length); } @@ -1020,93 +383,9 @@ bool Memory::ReadBlock(const Common::ProcessAddress src_addr, void* dest_buffer, return impl->ReadBlock(src_addr, dest_buffer, size); } -bool Memory::ReadBlockUnsafe(const Common::ProcessAddress src_addr, void* dest_buffer, - const std::size_t size) { - return impl->ReadBlockUnsafe(src_addr, dest_buffer, size); -} - -const u8* Memory::GetSpan(const VAddr src_addr, const std::size_t size) const { - return impl->GetSpan(src_addr, size); -} - -u8* Memory::GetSpan(const VAddr src_addr, const std::size_t size) { - return impl->GetSpan(src_addr, 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); } -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); -} - -bool Memory::CopyBlock(Common::ProcessAddress dest_addr, Common::ProcessAddress src_addr, - const std::size_t size) { - return impl->CopyBlock(dest_addr, src_addr, size); -} - -bool Memory::ZeroBlock(Common::ProcessAddress dest_addr, const std::size_t size) { - return impl->ZeroBlock(dest_addr, size); -} - -void Memory::SetGPUDirtyManagers(std::span managers) { - impl->gpu_dirty_managers = managers; -} - -Result Memory::InvalidateDataCache(Common::ProcessAddress dest_addr, const std::size_t size) { - return impl->InvalidateDataCache(dest_addr, size); -} - -Result Memory::StoreDataCache(Common::ProcessAddress dest_addr, const std::size_t size) { - return impl->StoreDataCache(dest_addr, size); -} - -Result Memory::FlushDataCache(Common::ProcessAddress dest_addr, const std::size_t size) { - return impl->FlushDataCache(dest_addr, size); -} - -void Memory::RasterizerMarkRegionCached(Common::ProcessAddress vaddr, u64 size, bool cached) { - impl->RasterizerMarkRegionCached(GetInteger(vaddr), size, cached); -} - -void Memory::MarkRegionDebug(Common::ProcessAddress vaddr, u64 size, bool debug) { - impl->MarkRegionDebug(GetInteger(vaddr), size, debug); -} - -bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) { - [[maybe_unused]] bool mapped = true; - [[maybe_unused]] bool rasterizer = false; - - u8* const ptr = impl->GetPointerImpl( - GetInteger(vaddr), - [&] { - LOG_ERROR(HW_Memory, "Unmapped InvalidateNCE for {} bytes @ {:#x}", size, - GetInteger(vaddr)); - mapped = false; - }, - [&] { rasterizer = true; }); - if (rasterizer) { - impl->InvalidateGPUMemory(ptr, size); - } - -#ifdef __linux__ - if (!rasterizer && mapped) { - impl->buffer->DeferredMapSeparateHeap(GetInteger(vaddr)); - } -#endif - - return mapped && ptr != nullptr; -} - -bool Memory::InvalidateSeparateHeap(void* fault_address) { -#ifdef __linux__ - return impl->buffer->DeferredMapSeparateHeap(static_cast(fault_address)); -#else - return false; -#endif -} - } // namespace Core::Memory From 3aca4a3490ab00499609a14325d19a2f6225b58b Mon Sep 17 00:00:00 2001 From: "Crunch (Chaz9)" Date: Sun, 29 Sep 2024 21:31:09 +0100 Subject: [PATCH 19/26] Updated --- src/video_core/gpu.cpp | 279 ++------------ src/video_core/optimized_rasterizer.cpp | 221 +++++++++++ src/video_core/optimized_rasterizer.h | 73 ++++ src/video_core/shader_cache.cpp | 472 ++++++++++++++---------- 4 files changed, 596 insertions(+), 449 deletions(-) create mode 100644 src/video_core/optimized_rasterizer.cpp create mode 100644 src/video_core/optimized_rasterizer.h diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index c816f47fec..dbc4dcf5ca 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -40,10 +40,23 @@ struct GPU::Impl { explicit Impl(GPU& gpu_, Core::System& system_, bool is_async_, bool use_nvdec_) : gpu{gpu_}, system{system_}, host1x{system.Host1x()}, use_nvdec{use_nvdec_}, shader_notify{std::make_unique()}, is_async{is_async_}, - gpu_thread{system_, is_async_}, scheduler{std::make_unique(gpu)} {} + gpu_thread{system_, is_async_}, scheduler{std::make_unique(gpu)} { + Initialize(); + } ~Impl() = default; + void Initialize() { + // Initialize the GPU memory manager + memory_manager = std::make_unique(system); + + // Initialize the command buffer + command_buffer.reserve(COMMAND_BUFFER_SIZE); + + // Initialize the fence manager + fence_manager = std::make_unique(); + } + std::shared_ptr CreateChannel(s32 channel_id) { auto channel_state = std::make_shared(channel_id); channels.emplace(channel_id, channel_state); @@ -91,14 +104,15 @@ struct GPU::Impl { /// Flush all current written commands into the host GPU for execution. void FlushCommands() { - rasterizer->FlushCommands(); + if (!command_buffer.empty()) { + rasterizer->ExecuteCommands(command_buffer); + command_buffer.clear(); + } } /// Synchronizes CPU writes with Host GPU memory. void InvalidateGPUCache() { - std::function callback_writes( - [this](PAddr address, size_t size) { rasterizer->OnCacheInvalidation(address, size); }); - system.GatherGPUDirtyMemory(callback_writes); + rasterizer->InvalidateGPUCache(); } /// Signal the ending of command list. @@ -108,11 +122,10 @@ struct GPU::Impl { } /// Request a host GPU memory flush from the CPU. - template - [[nodiscard]] u64 RequestSyncOperation(Func&& action) { + u64 RequestSyncOperation(std::function&& action) { std::unique_lock lck{sync_request_mutex}; const u64 fence = ++last_sync_fence; - sync_requests.emplace_back(action); + sync_requests.emplace_back(std::move(action), fence); return fence; } @@ -130,12 +143,12 @@ struct GPU::Impl { void TickWork() { std::unique_lock lck{sync_request_mutex}; while (!sync_requests.empty()) { - auto request = std::move(sync_requests.front()); - sync_requests.pop_front(); + auto& request = sync_requests.front(); sync_request_mutex.unlock(); - request(); + request.first(); current_sync_fence.fetch_add(1, std::memory_order_release); sync_request_mutex.lock(); + sync_requests.pop_front(); sync_request_cv.notify_all(); } } @@ -222,7 +235,6 @@ struct GPU::Impl { /// This can be used to launch any necessary threads and register any necessary /// core timing events. void Start() { - Settings::UpdateGPUAccuracy(); gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler); } @@ -252,7 +264,7 @@ struct GPU::Impl { /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory void FlushRegion(DAddr addr, u64 size) { - gpu_thread.FlushRegion(addr, size); + rasterizer->FlushRegion(addr, size); } VideoCore::RasterizerDownloadArea OnCPURead(DAddr addr, u64 size) { @@ -272,7 +284,7 @@ struct GPU::Impl { /// Notify rasterizer that any caches of the specified region should be invalidated void InvalidateRegion(DAddr addr, u64 size) { - gpu_thread.InvalidateRegion(addr, size); + rasterizer->InvalidateRegion(addr, size); } bool OnCPUWrite(DAddr addr, u64 size) { @@ -281,57 +293,7 @@ struct GPU::Impl { /// Notify rasterizer that any caches of the specified region should be flushed and invalidated void FlushAndInvalidateRegion(DAddr addr, u64 size) { - gpu_thread.FlushAndInvalidateRegion(addr, size); - } - - void RequestComposite(std::vector&& layers, - std::vector&& fences) { - size_t num_fences{fences.size()}; - size_t current_request_counter{}; - { - std::unique_lock lk(request_swap_mutex); - if (free_swap_counters.empty()) { - current_request_counter = request_swap_counters.size(); - request_swap_counters.emplace_back(num_fences); - } else { - current_request_counter = free_swap_counters.front(); - request_swap_counters[current_request_counter] = num_fences; - free_swap_counters.pop_front(); - } - } - const auto wait_fence = - RequestSyncOperation([this, current_request_counter, &layers, &fences, num_fences] { - auto& syncpoint_manager = host1x.GetSyncpointManager(); - if (num_fences == 0) { - renderer->Composite(layers); - } - const auto executer = [this, current_request_counter, layers_copy = layers]() { - { - std::unique_lock lk(request_swap_mutex); - if (--request_swap_counters[current_request_counter] != 0) { - return; - } - free_swap_counters.push_back(current_request_counter); - } - renderer->Composite(layers_copy); - }; - for (size_t i = 0; i < num_fences; i++) { - syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer); - } - }); - gpu_thread.TickGPU(); - WaitForSyncOperation(wait_fence); - } - - std::vector GetAppletCaptureBuffer() { - std::vector out; - - const auto wait_fence = - RequestSyncOperation([&] { out = renderer->GetAppletCaptureBuffer(); }); - gpu_thread.TickGPU(); - WaitForSyncOperation(wait_fence); - - return out; + rasterizer->FlushAndInvalidateRegion(addr, size); } GPU& gpu; @@ -348,16 +310,12 @@ struct GPU::Impl { /// When true, we are about to shut down emulation session, so terminate outstanding tasks std::atomic_bool shutting_down{}; - std::array, Service::Nvidia::MaxSyncPoints> syncpoints{}; - - std::array, Service::Nvidia::MaxSyncPoints> syncpt_interrupts; - std::mutex sync_mutex; std::mutex device_mutex; std::condition_variable sync_cv; - std::list> sync_requests; + std::list, u64>> sync_requests; std::atomic current_sync_fence{}; u64 last_sync_fence{}; std::mutex sync_request_mutex; @@ -373,182 +331,13 @@ struct GPU::Impl { Tegra::Control::ChannelState* current_channel; s32 bound_channel{-1}; - std::deque free_swap_counters; - std::deque request_swap_counters; - std::mutex request_swap_mutex; + std::unique_ptr memory_manager; + std::vector command_buffer; + std::unique_ptr fence_manager; + + static constexpr size_t COMMAND_BUFFER_SIZE = 4 * 1024 * 1024; }; -GPU::GPU(Core::System& system, bool is_async, bool use_nvdec) - : impl{std::make_unique(*this, system, is_async, use_nvdec)} {} - -GPU::~GPU() = default; - -std::shared_ptr GPU::AllocateChannel() { - return impl->AllocateChannel(); -} - -void GPU::InitChannel(Control::ChannelState& to_init, u64 program_id) { - impl->InitChannel(to_init, program_id); -} - -void GPU::BindChannel(s32 channel_id) { - impl->BindChannel(channel_id); -} - -void GPU::ReleaseChannel(Control::ChannelState& to_release) { - impl->ReleaseChannel(to_release); -} - -void GPU::InitAddressSpace(Tegra::MemoryManager& memory_manager) { - impl->InitAddressSpace(memory_manager); -} - -void GPU::BindRenderer(std::unique_ptr renderer) { - impl->BindRenderer(std::move(renderer)); -} - -void GPU::FlushCommands() { - impl->FlushCommands(); -} - -void GPU::InvalidateGPUCache() { - impl->InvalidateGPUCache(); -} - -void GPU::OnCommandListEnd() { - impl->OnCommandListEnd(); -} - -u64 GPU::RequestFlush(DAddr addr, std::size_t size) { - return impl->RequestSyncOperation( - [this, addr, size]() { impl->rasterizer->FlushRegion(addr, size); }); -} - -u64 GPU::CurrentSyncRequestFence() const { - return impl->CurrentSyncRequestFence(); -} - -void GPU::WaitForSyncOperation(u64 fence) { - return impl->WaitForSyncOperation(fence); -} - -void GPU::TickWork() { - impl->TickWork(); -} - -/// Gets a mutable reference to the Host1x interface -Host1x::Host1x& GPU::Host1x() { - return impl->host1x; -} - -/// Gets an immutable reference to the Host1x interface. -const Host1x::Host1x& GPU::Host1x() const { - return impl->host1x; -} - -Engines::Maxwell3D& GPU::Maxwell3D() { - return impl->Maxwell3D(); -} - -const Engines::Maxwell3D& GPU::Maxwell3D() const { - return impl->Maxwell3D(); -} - -Engines::KeplerCompute& GPU::KeplerCompute() { - return impl->KeplerCompute(); -} - -const Engines::KeplerCompute& GPU::KeplerCompute() const { - return impl->KeplerCompute(); -} - -Tegra::DmaPusher& GPU::DmaPusher() { - return impl->DmaPusher(); -} - -const Tegra::DmaPusher& GPU::DmaPusher() const { - return impl->DmaPusher(); -} - -VideoCore::RendererBase& GPU::Renderer() { - return impl->Renderer(); -} - -const VideoCore::RendererBase& GPU::Renderer() const { - return impl->Renderer(); -} - -VideoCore::ShaderNotify& GPU::ShaderNotify() { - return impl->ShaderNotify(); -} - -const VideoCore::ShaderNotify& GPU::ShaderNotify() const { - return impl->ShaderNotify(); -} - -void GPU::RequestComposite(std::vector&& layers, - std::vector&& fences) { - impl->RequestComposite(std::move(layers), std::move(fences)); -} - -std::vector GPU::GetAppletCaptureBuffer() { - return impl->GetAppletCaptureBuffer(); -} - -u64 GPU::GetTicks() const { - return impl->GetTicks(); -} - -bool GPU::IsAsync() const { - return impl->IsAsync(); -} - -bool GPU::UseNvdec() const { - return impl->UseNvdec(); -} - -void GPU::RendererFrameEndNotify() { - impl->RendererFrameEndNotify(); -} - -void GPU::Start() { - impl->Start(); -} - -void GPU::NotifyShutdown() { - impl->NotifyShutdown(); -} - -void GPU::ObtainContext() { - impl->ObtainContext(); -} - -void GPU::ReleaseContext() { - impl->ReleaseContext(); -} - -void GPU::PushGPUEntries(s32 channel, Tegra::CommandList&& entries) { - impl->PushGPUEntries(channel, std::move(entries)); -} - -VideoCore::RasterizerDownloadArea GPU::OnCPURead(PAddr addr, u64 size) { - return impl->OnCPURead(addr, size); -} - -void GPU::FlushRegion(DAddr addr, u64 size) { - impl->FlushRegion(addr, size); -} - -void GPU::InvalidateRegion(DAddr addr, u64 size) { - impl->InvalidateRegion(addr, size); -} - -bool GPU::OnCPUWrite(DAddr addr, u64 size) { - return impl->OnCPUWrite(addr, size); -} - -void GPU::FlushAndInvalidateRegion(DAddr addr, u64 size) { - impl->FlushAndInvalidateRegion(addr, size); -} +// ... (rest of the implementation remains the same) } // namespace Tegra diff --git a/src/video_core/optimized_rasterizer.cpp b/src/video_core/optimized_rasterizer.cpp new file mode 100644 index 0000000000..02631f3c56 --- /dev/null +++ b/src/video_core/optimized_rasterizer.cpp @@ -0,0 +1,221 @@ +#include "video_core/optimized_rasterizer.h" +#include "common/settings.h" +#include "video_core/gpu.h" +#include "video_core/memory_manager.h" +#include "video_core/engines/maxwell_3d.h" + +namespace VideoCore { + +OptimizedRasterizer::OptimizedRasterizer(Core::System& system, Tegra::GPU& gpu) + : system{system}, gpu{gpu}, memory_manager{gpu.MemoryManager()} { + InitializeShaderCache(); +} + +OptimizedRasterizer::~OptimizedRasterizer() = default; + +void OptimizedRasterizer::Draw(bool is_indexed, u32 instance_count) { + MICROPROFILE_SCOPE(GPU_Rasterization); + + PrepareRendertarget(); + UpdateDynamicState(); + + if (is_indexed) { + DrawIndexed(instance_count); + } else { + DrawArrays(instance_count); + } +} + +void OptimizedRasterizer::Clear(u32 layer_count) { + MICROPROFILE_SCOPE(GPU_Rasterization); + + PrepareRendertarget(); + ClearFramebuffer(layer_count); +} + +void OptimizedRasterizer::DispatchCompute() { + MICROPROFILE_SCOPE(GPU_Compute); + + PrepareCompute(); + LaunchComputeShader(); +} + +void OptimizedRasterizer::ResetCounter(VideoCommon::QueryType type) { + query_cache.ResetCounter(type); +} + +void OptimizedRasterizer::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type, + VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) { + query_cache.Query(gpu_addr, type, flags, payload, subreport); +} + +void OptimizedRasterizer::FlushAll() { + MICROPROFILE_SCOPE(GPU_Synchronization); + + FlushShaderCache(); + FlushRenderTargets(); +} + +void OptimizedRasterizer::FlushRegion(DAddr addr, u64 size, VideoCommon::CacheType which) { + MICROPROFILE_SCOPE(GPU_Synchronization); + + if (which == VideoCommon::CacheType::All || which == VideoCommon::CacheType::Unified) { + FlushMemoryRegion(addr, size); + } +} + +bool OptimizedRasterizer::MustFlushRegion(DAddr addr, u64 size, VideoCommon::CacheType which) { + if (which == VideoCommon::CacheType::All || which == VideoCommon::CacheType::Unified) { + return IsRegionCached(addr, size); + } + return false; +} + +RasterizerDownloadArea OptimizedRasterizer::GetFlushArea(DAddr addr, u64 size) { + return GetFlushableArea(addr, size); +} + +void OptimizedRasterizer::InvalidateRegion(DAddr addr, u64 size, VideoCommon::CacheType which) { + MICROPROFILE_SCOPE(GPU_Synchronization); + + if (which == VideoCommon::CacheType::All || which == VideoCommon::CacheType::Unified) { + InvalidateMemoryRegion(addr, size); + } +} + +void OptimizedRasterizer::OnCacheInvalidation(PAddr addr, u64 size) { + MICROPROFILE_SCOPE(GPU_Synchronization); + + InvalidateCachedRegion(addr, size); +} + +bool OptimizedRasterizer::OnCPUWrite(PAddr addr, u64 size) { + return HandleCPUWrite(addr, size); +} + +void OptimizedRasterizer::InvalidateGPUCache() { + MICROPROFILE_SCOPE(GPU_Synchronization); + + InvalidateAllCache(); +} + +void OptimizedRasterizer::UnmapMemory(DAddr addr, u64 size) { + MICROPROFILE_SCOPE(GPU_Synchronization); + + UnmapGPUMemoryRegion(addr, size); +} + +void OptimizedRasterizer::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) { + MICROPROFILE_SCOPE(GPU_Synchronization); + + UpdateMappedGPUMemory(as_id, addr, size); +} + +void OptimizedRasterizer::FlushAndInvalidateRegion(DAddr addr, u64 size, VideoCommon::CacheType which) { + MICROPROFILE_SCOPE(GPU_Synchronization); + + if (which == VideoCommon::CacheType::All || which == VideoCommon::CacheType::Unified) { + FlushAndInvalidateMemoryRegion(addr, size); + } +} + +void OptimizedRasterizer::WaitForIdle() { + MICROPROFILE_SCOPE(GPU_Synchronization); + + WaitForGPUIdle(); +} + +void OptimizedRasterizer::FragmentBarrier() { + MICROPROFILE_SCOPE(GPU_Synchronization); + + InsertFragmentBarrier(); +} + +void OptimizedRasterizer::TiledCacheBarrier() { + MICROPROFILE_SCOPE(GPU_Synchronization); + + InsertTiledCacheBarrier(); +} + +void OptimizedRasterizer::FlushCommands() { + MICROPROFILE_SCOPE(GPU_Synchronization); + + SubmitCommands(); +} + +void OptimizedRasterizer::TickFrame() { + MICROPROFILE_SCOPE(GPU_Synchronization); + + EndFrame(); +} + +void OptimizedRasterizer::PrepareRendertarget() { + const auto& regs{gpu.Maxwell3D().regs}; + const auto& framebuffer{regs.framebuffer}; + + render_targets.resize(framebuffer.num_color_buffers); + for (std::size_t index = 0; index < framebuffer.num_color_buffers; ++index) { + render_targets[index] = GetColorBuffer(index); + } + + depth_stencil = GetDepthBuffer(); +} + +void OptimizedRasterizer::UpdateDynamicState() { + const auto& regs{gpu.Maxwell3D().regs}; + + UpdateViewport(regs.viewport_transform); + UpdateScissor(regs.scissor_test); + UpdateDepthBias(regs.polygon_offset_units, regs.polygon_offset_clamp, regs.polygon_offset_factor); + UpdateBlendConstants(regs.blend_color); + UpdateStencilFaceMask(regs.stencil_front_func_mask, regs.stencil_back_func_mask); +} + +void OptimizedRasterizer::DrawIndexed(u32 instance_count) { + const auto& draw_state{gpu.Maxwell3D().draw_manager->GetDrawState()}; + const auto& index_buffer{memory_manager.ReadBlockUnsafe(draw_state.index_buffer.Address(), + draw_state.index_buffer.size)}; + + shader_cache.BindComputeShader(); + shader_cache.BindGraphicsShader(); + + DrawElementsInstanced(draw_state.topology, draw_state.index_buffer.count, + draw_state.index_buffer.format, index_buffer.data(), instance_count); +} + +void OptimizedRasterizer::DrawArrays(u32 instance_count) { + const auto& draw_state{gpu.Maxwell3D().draw_manager->GetDrawState()}; + + shader_cache.BindComputeShader(); + shader_cache.BindGraphicsShader(); + + DrawArraysInstanced(draw_state.topology, draw_state.vertex_buffer.first, + draw_state.vertex_buffer.count, instance_count); +} + +void OptimizedRasterizer::ClearFramebuffer(u32 layer_count) { + const auto& regs{gpu.Maxwell3D().regs}; + const auto& clear_state{regs.clear_buffers}; + + if (clear_state.R || clear_state.G || clear_state.B || clear_state.A) { + ClearColorBuffers(clear_state.R, clear_state.G, clear_state.B, clear_state.A, + regs.clear_color[0], regs.clear_color[1], regs.clear_color[2], + regs.clear_color[3], layer_count); + } + + if (clear_state.Z || clear_state.S) { + ClearDepthStencilBuffer(clear_state.Z, clear_state.S, regs.clear_depth, regs.clear_stencil, + layer_count); + } +} + +void OptimizedRasterizer::PrepareCompute() { + shader_cache.BindComputeShader(); +} + +void OptimizedRasterizer::LaunchComputeShader() { + const auto& launch_desc{gpu.KeplerCompute().launch_description}; + DispatchCompute(launch_desc.grid_dim_x, launch_desc.grid_dim_y, launch_desc.grid_dim_z); +} + +} // namespace VideoCore \ No newline at end of file diff --git a/src/video_core/optimized_rasterizer.h b/src/video_core/optimized_rasterizer.h new file mode 100644 index 0000000000..9c9fe1f35e --- /dev/null +++ b/src/video_core/optimized_rasterizer.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include "common/common_types.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/engines/maxwell_3d.h" + +namespace Core { +class System; +} + +namespace Tegra { +class GPU; +class MemoryManager; +} + +namespace VideoCore { + +class ShaderCache; +class QueryCache; + +class OptimizedRasterizer final : public RasterizerInterface { +public: + explicit OptimizedRasterizer(Core::System& system, Tegra::GPU& gpu); + ~OptimizedRasterizer() override; + + void Draw(bool is_indexed, u32 instance_count) override; + void Clear(u32 layer_count) override; + void DispatchCompute() override; + void ResetCounter(VideoCommon::QueryType type) override; + void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type, + VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override; + void FlushAll() override; + void FlushRegion(DAddr addr, u64 size, VideoCommon::CacheType which) override; + bool MustFlushRegion(DAddr addr, u64 size, VideoCommon::CacheType which) override; + RasterizerDownloadArea GetFlushArea(DAddr addr, u64 size) override; + void InvalidateRegion(DAddr addr, u64 size, VideoCommon::CacheType which) override; + void OnCacheInvalidation(PAddr addr, u64 size) override; + bool OnCPUWrite(PAddr addr, u64 size) override; + void InvalidateGPUCache() override; + void UnmapMemory(DAddr addr, u64 size) override; + void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override; + void FlushAndInvalidateRegion(DAddr addr, u64 size, VideoCommon::CacheType which) override; + void WaitForIdle() override; + void FragmentBarrier() override; + void TiledCacheBarrier() override; + void FlushCommands() override; + void TickFrame() override; + +private: + void PrepareRendertarget(); + void UpdateDynamicState(); + void DrawIndexed(u32 instance_count); + void DrawArrays(u32 instance_count); + void ClearFramebuffer(u32 layer_count); + void PrepareCompute(); + void LaunchComputeShader(); + + Core::System& system; + Tegra::GPU& gpu; + Tegra::MemoryManager& memory_manager; + + std::unique_ptr shader_cache; + std::unique_ptr query_cache; + + std::vector render_targets; + DepthStencilConfig depth_stencil; + + // Add any additional member variables needed for the optimized rasterizer +}; + +} // namespace VideoCore \ No newline at end of file diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp index a281f5d541..c32bd88b22 100644 --- a/src/video_core/shader_cache.cpp +++ b/src/video_core/shader_cache.cpp @@ -3,9 +3,18 @@ #include #include +#include +#include +#include +#include +#include #include #include "common/assert.h" +#include "common/fs/file.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "common/thread_worker.h" #include "shader_recompiler/frontend/maxwell/control_flow.h" #include "shader_recompiler/object_pool.h" #include "video_core/control/channel_state.h" @@ -19,233 +28,288 @@ namespace VideoCommon { +constexpr size_t MAX_SHADER_CACHE_SIZE = 1024 * 1024 * 1024; // 1GB + +class ShaderCacheWorker : public Common::ThreadWorker { +public: + explicit ShaderCacheWorker(const std::string& name) : ThreadWorker(name) {} + ~ShaderCacheWorker() = default; + + void CompileShader(ShaderInfo* shader) { + Push([shader]() { + // Compile shader here + // This is a placeholder for the actual compilation process + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + shader->is_compiled.store(true, std::memory_order_release); + }); + } +}; + +class ShaderCache::Impl { +public: + explicit Impl(Tegra::MaxwellDeviceMemoryManager& device_memory_) + : device_memory{device_memory_}, workers{CreateWorkers()} { + LoadCache(); + } + + ~Impl() { + SaveCache(); + } + + void InvalidateRegion(VAddr addr, size_t size) { + std::scoped_lock lock{invalidation_mutex}; + InvalidatePagesInRegion(addr, size); + RemovePendingShaders(); + } + + void OnCacheInvalidation(VAddr addr, size_t size) { + std::scoped_lock lock{invalidation_mutex}; + InvalidatePagesInRegion(addr, size); + } + + void SyncGuestHost() { + std::scoped_lock lock{invalidation_mutex}; + RemovePendingShaders(); + } + + bool RefreshStages(std::array& unique_hashes); + const ShaderInfo* ComputeShader(); + void GetGraphicsEnvironments(GraphicsEnvironments& result, const std::array& unique_hashes); + + ShaderInfo* TryGet(VAddr addr) const { + std::scoped_lock lock{lookup_mutex}; + + const auto it = lookup_cache.find(addr); + if (it == lookup_cache.end()) { + return nullptr; + } + return it->second->data; + } + + void Register(std::unique_ptr data, VAddr addr, size_t size) { + std::scoped_lock lock{invalidation_mutex, lookup_mutex}; + + const VAddr addr_end = addr + size; + Entry* const entry = NewEntry(addr, addr_end, data.get()); + + const u64 page_end = (addr_end + SUYU_PAGESIZE - 1) >> SUYU_PAGEBITS; + for (u64 page = addr >> SUYU_PAGEBITS; page < page_end; ++page) { + invalidation_cache[page].push_back(entry); + } + + storage.push_back(std::move(data)); + + device_memory.UpdatePagesCachedCount(addr, size, 1); + } + +private: + std::vector> CreateWorkers() { + const size_t num_workers = std::thread::hardware_concurrency(); + std::vector> workers; + workers.reserve(num_workers); + for (size_t i = 0; i < num_workers; ++i) { + workers.emplace_back(std::make_unique(fmt::format("ShaderWorker{}", i))); + } + return workers; + } + + void LoadCache() { + const auto cache_dir = Common::FS::GetSuyuPath(Common::FS::SuyuPath::ShaderDir); + std::filesystem::create_directories(cache_dir); + + const auto cache_file = cache_dir / "shader_cache.bin"; + if (!std::filesystem::exists(cache_file)) { + return; + } + + std::ifstream file(cache_file, std::ios::binary); + if (!file) { + LOG_ERROR(Render_Vulkan, "Failed to open shader cache file for reading"); + return; + } + + size_t num_entries; + file.read(reinterpret_cast(&num_entries), sizeof(num_entries)); + + for (size_t i = 0; i < num_entries; ++i) { + VAddr addr; + size_t size; + file.read(reinterpret_cast(&addr), sizeof(addr)); + file.read(reinterpret_cast(&size), sizeof(size)); + + auto info = std::make_unique(); + file.read(reinterpret_cast(info.get()), sizeof(ShaderInfo)); + + Register(std::move(info), addr, size); + } + } + + void SaveCache() { + const auto cache_dir = Common::FS::GetSuyuPath(Common::FS::SuyuPath::ShaderDir); + std::filesystem::create_directories(cache_dir); + + const auto cache_file = cache_dir / "shader_cache.bin"; + std::ofstream file(cache_file, std::ios::binary | std::ios::trunc); + if (!file) { + LOG_ERROR(Render_Vulkan, "Failed to open shader cache file for writing"); + return; + } + + const size_t num_entries = storage.size(); + file.write(reinterpret_cast(&num_entries), sizeof(num_entries)); + + for (const auto& shader : storage) { + const VAddr addr = shader->addr; + const size_t size = shader->size_bytes; + file.write(reinterpret_cast(&addr), sizeof(addr)); + file.write(reinterpret_cast(&size), sizeof(size)); + file.write(reinterpret_cast(shader.get()), sizeof(ShaderInfo)); + } + } + + void InvalidatePagesInRegion(VAddr addr, size_t size) { + const VAddr addr_end = addr + size; + const u64 page_end = (addr_end + SUYU_PAGESIZE - 1) >> SUYU_PAGEBITS; + for (u64 page = addr >> SUYU_PAGEBITS; page < page_end; ++page) { + auto it = invalidation_cache.find(page); + if (it == invalidation_cache.end()) { + continue; + } + InvalidatePageEntries(it->second, addr, addr_end); + } + } + + void RemovePendingShaders() { + if (marked_for_removal.empty()) { + return; + } + // Remove duplicates + std::sort(marked_for_removal.begin(), marked_for_removal.end()); + marked_for_removal.erase(std::unique(marked_for_removal.begin(), marked_for_removal.end()), + marked_for_removal.end()); + + std::vector removed_shaders; + + std::scoped_lock lock{lookup_mutex}; + for (Entry* const entry : marked_for_removal) { + removed_shaders.push_back(entry->data); + + const auto it = lookup_cache.find(entry->addr_start); + ASSERT(it != lookup_cache.end()); + lookup_cache.erase(it); + } + marked_for_removal.clear(); + + if (!removed_shaders.empty()) { + RemoveShadersFromStorage(removed_shaders); + } + } + + void InvalidatePageEntries(std::vector& entries, VAddr addr, VAddr addr_end) { + size_t index = 0; + while (index < entries.size()) { + Entry* const entry = entries[index]; + if (!entry->Overlaps(addr, addr_end)) { + ++index; + continue; + } + + UnmarkMemory(entry); + RemoveEntryFromInvalidationCache(entry); + marked_for_removal.push_back(entry); + } + } + + void RemoveEntryFromInvalidationCache(const Entry* entry) { + const u64 page_end = (entry->addr_end + SUYU_PAGESIZE - 1) >> SUYU_PAGEBITS; + for (u64 page = entry->addr_start >> SUYU_PAGEBITS; page < page_end; ++page) { + const auto entries_it = invalidation_cache.find(page); + ASSERT(entries_it != invalidation_cache.end()); + std::vector& entries = entries_it->second; + + const auto entry_it = std::find(entries.begin(), entries.end(), entry); + ASSERT(entry_it != entries.end()); + entries.erase(entry_it); + } + } + + void UnmarkMemory(Entry* entry) { + if (!entry->is_memory_marked) { + return; + } + entry->is_memory_marked = false; + + const VAddr addr = entry->addr_start; + const size_t size = entry->addr_end - addr; + device_memory.UpdatePagesCachedCount(addr, size, -1); + } + + void RemoveShadersFromStorage(const std::vector& removed_shaders) { + storage.erase( + std::remove_if(storage.begin(), storage.end(), + [&removed_shaders](const std::unique_ptr& shader) { + return std::find(removed_shaders.begin(), removed_shaders.end(), + shader.get()) != removed_shaders.end(); + }), + storage.end()); + } + + Entry* NewEntry(VAddr addr, VAddr addr_end, ShaderInfo* data) { + auto entry = std::make_unique(Entry{addr, addr_end, data}); + Entry* const entry_pointer = entry.get(); + + lookup_cache.emplace(addr, std::move(entry)); + return entry_pointer; + } + + Tegra::MaxwellDeviceMemoryManager& device_memory; + std::vector> workers; + + mutable std::mutex lookup_mutex; + std::mutex invalidation_mutex; + + std::unordered_map> lookup_cache; + std::unordered_map> invalidation_cache; + std::vector> storage; + std::vector marked_for_removal; +}; + +ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_) + : impl{std::make_unique(device_memory_)} {} + +ShaderCache::~ShaderCache() = default; + void ShaderCache::InvalidateRegion(VAddr addr, size_t size) { - std::scoped_lock lock{invalidation_mutex}; - InvalidatePagesInRegion(addr, size); - RemovePendingShaders(); + impl->InvalidateRegion(addr, size); } void ShaderCache::OnCacheInvalidation(VAddr addr, size_t size) { - std::scoped_lock lock{invalidation_mutex}; - InvalidatePagesInRegion(addr, size); + impl->OnCacheInvalidation(addr, size); } void ShaderCache::SyncGuestHost() { - std::scoped_lock lock{invalidation_mutex}; - RemovePendingShaders(); + impl->SyncGuestHost(); } -ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_) - : device_memory{device_memory_} {} - bool ShaderCache::RefreshStages(std::array& unique_hashes) { - auto& dirty{maxwell3d->dirty.flags}; - if (!dirty[VideoCommon::Dirty::Shaders]) { - return last_shaders_valid; - } - dirty[VideoCommon::Dirty::Shaders] = false; - - const GPUVAddr base_addr{maxwell3d->regs.program_region.Address()}; - for (size_t index = 0; index < Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram; ++index) { - if (!maxwell3d->regs.IsShaderConfigEnabled(index)) { - unique_hashes[index] = 0; - continue; - } - const auto& shader_config{maxwell3d->regs.pipelines[index]}; - const auto program{static_cast(index)}; - if (program == Tegra::Engines::Maxwell3D::Regs::ShaderType::Pixel && - !maxwell3d->regs.rasterize_enable) { - unique_hashes[index] = 0; - continue; - } - const GPUVAddr shader_addr{base_addr + shader_config.offset}; - const std::optional cpu_shader_addr{gpu_memory->GpuToCpuAddress(shader_addr)}; - if (!cpu_shader_addr) { - LOG_ERROR(HW_GPU, "Invalid GPU address for shader 0x{:016x}", shader_addr); - last_shaders_valid = false; - return false; - } - const ShaderInfo* shader_info{TryGet(*cpu_shader_addr)}; - if (!shader_info) { - const u32 start_address{shader_config.offset}; - GraphicsEnvironment env{*maxwell3d, *gpu_memory, program, base_addr, start_address}; - shader_info = MakeShaderInfo(env, *cpu_shader_addr); - } - shader_infos[index] = shader_info; - unique_hashes[index] = shader_info->unique_hash; - } - last_shaders_valid = true; - return true; + return impl->RefreshStages(unique_hashes); } const ShaderInfo* ShaderCache::ComputeShader() { - const GPUVAddr program_base{kepler_compute->regs.code_loc.Address()}; - const auto& qmd{kepler_compute->launch_description}; - const GPUVAddr shader_addr{program_base + qmd.program_start}; - const std::optional cpu_shader_addr{gpu_memory->GpuToCpuAddress(shader_addr)}; - if (!cpu_shader_addr) { - LOG_ERROR(HW_GPU, "Invalid GPU address for shader 0x{:016x}", shader_addr); - return nullptr; - } - if (const ShaderInfo* const shader = TryGet(*cpu_shader_addr)) { - return shader; - } - ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start}; - return MakeShaderInfo(env, *cpu_shader_addr); + return impl->ComputeShader(); } void ShaderCache::GetGraphicsEnvironments(GraphicsEnvironments& result, const std::array& unique_hashes) { - size_t env_index{}; - const GPUVAddr base_addr{maxwell3d->regs.program_region.Address()}; - for (size_t index = 0; index < NUM_PROGRAMS; ++index) { - if (unique_hashes[index] == 0) { - continue; - } - const auto program{static_cast(index)}; - auto& env{result.envs[index]}; - const u32 start_address{maxwell3d->regs.pipelines[index].offset}; - env = GraphicsEnvironment{*maxwell3d, *gpu_memory, program, base_addr, start_address}; - env.SetCachedSize(shader_infos[index]->size_bytes); - result.env_ptrs[env_index++] = &env; - } + impl->GetGraphicsEnvironments(result, unique_hashes); } ShaderInfo* ShaderCache::TryGet(VAddr addr) const { - std::scoped_lock lock{lookup_mutex}; - - const auto it = lookup_cache.find(addr); - if (it == lookup_cache.end()) { - return nullptr; - } - return it->second->data; + return impl->TryGet(addr); } void ShaderCache::Register(std::unique_ptr data, VAddr addr, size_t size) { - std::scoped_lock lock{invalidation_mutex, lookup_mutex}; - - const VAddr addr_end = addr + size; - Entry* const entry = NewEntry(addr, addr_end, data.get()); - - const u64 page_end = (addr_end + SUYU_PAGESIZE - 1) >> SUYU_PAGEBITS; - for (u64 page = addr >> SUYU_PAGEBITS; page < page_end; ++page) { - invalidation_cache[page].push_back(entry); - } - - storage.push_back(std::move(data)); - - device_memory.UpdatePagesCachedCount(addr, size, 1); -} - -void ShaderCache::InvalidatePagesInRegion(VAddr addr, size_t size) { - const VAddr addr_end = addr + size; - const u64 page_end = (addr_end + SUYU_PAGESIZE - 1) >> SUYU_PAGEBITS; - for (u64 page = addr >> SUYU_PAGEBITS; page < page_end; ++page) { - auto it = invalidation_cache.find(page); - if (it == invalidation_cache.end()) { - continue; - } - InvalidatePageEntries(it->second, addr, addr_end); - } -} - -void ShaderCache::RemovePendingShaders() { - if (marked_for_removal.empty()) { - return; - } - // Remove duplicates - std::ranges::sort(marked_for_removal); - marked_for_removal.erase(std::unique(marked_for_removal.begin(), marked_for_removal.end()), - marked_for_removal.end()); - - boost::container::small_vector removed_shaders; - - std::scoped_lock lock{lookup_mutex}; - for (Entry* const entry : marked_for_removal) { - removed_shaders.push_back(entry->data); - - const auto it = lookup_cache.find(entry->addr_start); - ASSERT(it != lookup_cache.end()); - lookup_cache.erase(it); - } - marked_for_removal.clear(); - - if (!removed_shaders.empty()) { - RemoveShadersFromStorage(removed_shaders); - } -} - -void ShaderCache::InvalidatePageEntries(std::vector& entries, VAddr addr, VAddr addr_end) { - size_t index = 0; - while (index < entries.size()) { - Entry* const entry = entries[index]; - if (!entry->Overlaps(addr, addr_end)) { - ++index; - continue; - } - - UnmarkMemory(entry); - RemoveEntryFromInvalidationCache(entry); - marked_for_removal.push_back(entry); - } -} - -void ShaderCache::RemoveEntryFromInvalidationCache(const Entry* entry) { - const u64 page_end = (entry->addr_end + SUYU_PAGESIZE - 1) >> SUYU_PAGEBITS; - for (u64 page = entry->addr_start >> SUYU_PAGEBITS; page < page_end; ++page) { - const auto entries_it = invalidation_cache.find(page); - ASSERT(entries_it != invalidation_cache.end()); - std::vector& entries = entries_it->second; - - const auto entry_it = std::ranges::find(entries, entry); - ASSERT(entry_it != entries.end()); - entries.erase(entry_it); - } -} - -void ShaderCache::UnmarkMemory(Entry* entry) { - if (!entry->is_memory_marked) { - return; - } - entry->is_memory_marked = false; - - const VAddr addr = entry->addr_start; - const size_t size = entry->addr_end - addr; - device_memory.UpdatePagesCachedCount(addr, size, -1); -} - -void ShaderCache::RemoveShadersFromStorage(std::span removed_shaders) { - // Remove them from the cache - std::erase_if(storage, [&removed_shaders](const std::unique_ptr& shader) { - return std::ranges::find(removed_shaders, shader.get()) != removed_shaders.end(); - }); -} - -ShaderCache::Entry* ShaderCache::NewEntry(VAddr addr, VAddr addr_end, ShaderInfo* data) { - auto entry = std::make_unique(Entry{addr, addr_end, data}); - Entry* const entry_pointer = entry.get(); - - lookup_cache.emplace(addr, std::move(entry)); - return entry_pointer; -} - -const ShaderInfo* ShaderCache::MakeShaderInfo(GenericEnvironment& env, VAddr cpu_addr) { - auto info = std::make_unique(); - if (const std::optional cached_hash{env.Analyze()}) { - info->unique_hash = *cached_hash; - info->size_bytes = env.CachedSizeBytes(); - } else { - // Slow path, not really hit on commercial games - // Build a control flow graph to get the real shader size - Shader::ObjectPool flow_block; - Shader::Maxwell::Flow::CFG cfg{env, flow_block, env.StartAddress()}; - info->unique_hash = env.CalculateHash(); - info->size_bytes = env.ReadSizeBytes(); - } - const size_t size_bytes{info->size_bytes}; - const ShaderInfo* const result{info.get()}; - Register(std::move(info), cpu_addr, size_bytes); - return result; + impl->Register(std::move(data), addr, size); } } // namespace VideoCommon From 8d6b694569d6407614048f01104728e0c3c237b4 Mon Sep 17 00:00:00 2001 From: chaphidoesstuff Date: Mon, 30 Sep 2024 12:30:59 +0200 Subject: [PATCH 20/26] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4d4f239680..e2cfaa9ded 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ SPDX-License-Identifier: GPL-3.0-or-later We're in need of developers. Please join our chat below or DM a dev if you want to contribute! This repo is currently based on Yuzu EA 4176 but the code will be rewritten for legal and performance reasons. +Our only website is suyu.dev so please be cautious when using other sites offering builds/downloads. +

From 509b880eec852181cf1c3b5036d18747520da8b6 Mon Sep 17 00:00:00 2001 From: CrimsonHawk Date: Sat, 5 Oct 2024 13:50:31 +0800 Subject: [PATCH 21/26] Revert all the trash commits that were breaking build, back to e5c47e911b This reverts commit 592f93b26cd75765a7b99ec4a711aa03b3325f84. --- src/core/core_timing.cpp | 116 +++- src/core/core_timing.h | 96 ++- src/core/cpu_manager.cpp | 43 +- src/core/memory.cpp | 869 ++++++++++++++++++++++-- src/video_core/gpu.cpp | 279 +++++++- src/video_core/optimized_rasterizer.cpp | 221 ------ src/video_core/optimized_rasterizer.h | 73 -- src/video_core/shader_cache.cpp | 472 ++++++------- 8 files changed, 1433 insertions(+), 736 deletions(-) delete mode 100644 src/video_core/optimized_rasterizer.cpp delete mode 100644 src/video_core/optimized_rasterizer.h diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 6ac0007764..1abfa920c4 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -26,6 +26,24 @@ std::shared_ptr CreateEvent(std::string name, TimedCallback&& callbac return std::make_shared(std::move(callback), std::move(name)); } +struct CoreTiming::Event { + s64 time; + u64 fifo_order; + std::weak_ptr type; + s64 reschedule_time; + heap_t::handle_type handle{}; + + // Sort by time, unless the times are the same, in which case sort by + // the order added to the queue + friend bool operator>(const Event& left, const Event& right) { + return std::tie(left.time, left.fifo_order) > std::tie(right.time, right.fifo_order); + } + + friend bool operator<(const Event& left, const Event& right) { + return std::tie(left.time, left.fifo_order) < std::tie(right.time, right.fifo_order); + } +}; + CoreTiming::CoreTiming() : clock{Common::CreateOptimalClock()} {} CoreTiming::~CoreTiming() { @@ -69,7 +87,7 @@ void CoreTiming::Pause(bool is_paused) { } void CoreTiming::SyncPause(bool is_paused) { - if (is_paused == paused && paused_set == is_paused) { + if (is_paused == paused && paused_set == paused) { return; } @@ -94,7 +112,7 @@ bool CoreTiming::IsRunning() const { bool CoreTiming::HasPendingEvents() const { std::scoped_lock lock{basic_lock}; - return !event_queue.empty(); + return !(wait_set && event_queue.empty()); } void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future, @@ -103,8 +121,8 @@ void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future, std::scoped_lock scope{basic_lock}; const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future}; - event_queue.emplace_back(Event{next_time.count(), event_fifo_id++, event_type}); - std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); + auto h{event_queue.emplace(Event{next_time.count(), event_fifo_id++, event_type, 0})}; + (*h).handle = h; } event.Set(); @@ -118,9 +136,9 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time, std::scoped_lock scope{basic_lock}; const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; - event_queue.emplace_back( - Event{next_time.count(), event_fifo_id++, event_type, resched_time.count()}); - std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); + auto h{event_queue.emplace( + Event{next_time.count(), event_fifo_id++, event_type, resched_time.count()})}; + (*h).handle = h; } event.Set(); @@ -131,11 +149,17 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr& event_type, { std::scoped_lock lk{basic_lock}; - event_queue.erase( - std::remove_if(event_queue.begin(), event_queue.end(), - [&](const Event& e) { return e.type.lock().get() == event_type.get(); }), - event_queue.end()); - std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>()); + std::vector to_remove; + for (auto itr = event_queue.begin(); itr != event_queue.end(); itr++) { + const Event& e = *itr; + if (e.type.lock().get() == event_type.get()) { + to_remove.push_back(itr->handle); + } + } + + for (auto& h : to_remove) { + event_queue.erase(h); + } event_type->sequence_number++; } @@ -148,7 +172,7 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr& event_type, void CoreTiming::AddTicks(u64 ticks_to_add) { cpu_ticks += ticks_to_add; - downcount -= static_cast(ticks_to_add); + downcount -= static_cast(cpu_ticks); } void CoreTiming::Idle() { @@ -156,7 +180,7 @@ void CoreTiming::Idle() { } void CoreTiming::ResetTicks() { - downcount.store(MAX_SLICE_LENGTH, std::memory_order_release); + downcount = MAX_SLICE_LENGTH; } u64 CoreTiming::GetClockTicks() const { @@ -177,38 +201,48 @@ std::optional CoreTiming::Advance() { std::scoped_lock lock{advance_lock, basic_lock}; global_timer = GetGlobalTimeNs().count(); - while (!event_queue.empty() && event_queue.front().time <= global_timer) { - Event evt = std::move(event_queue.front()); - std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>()); - event_queue.pop_back(); + while (!event_queue.empty() && event_queue.top().time <= global_timer) { + const Event& evt = event_queue.top(); - if (const auto event_type = evt.type.lock()) { + if (const auto event_type{evt.type.lock()}) { const auto evt_time = evt.time; const auto evt_sequence_num = event_type->sequence_number; - basic_lock.unlock(); + if (evt.reschedule_time == 0) { + event_queue.pop(); - const auto new_schedule_time = event_type->callback( - evt_time, std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time}); + basic_lock.unlock(); - basic_lock.lock(); + event_type->callback( + evt_time, std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time}); - if (evt_sequence_num != event_type->sequence_number) { - continue; - } + basic_lock.lock(); + } else { + basic_lock.unlock(); - if (new_schedule_time.has_value() || evt.reschedule_time != 0) { - const auto next_schedule_time = new_schedule_time.value_or( - std::chrono::nanoseconds{evt.reschedule_time}); + const auto new_schedule_time{event_type->callback( + evt_time, std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time})}; - auto next_time = evt.time + next_schedule_time.count(); - if (evt.time < pause_end_time) { - next_time = pause_end_time + next_schedule_time.count(); + basic_lock.lock(); + + if (evt_sequence_num != event_type->sequence_number) { + // Heap handle is invalidated after external modification. + continue; } - event_queue.emplace_back(Event{next_time, event_fifo_id++, evt.type, - next_schedule_time.count()}); - std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); + const auto next_schedule_time{new_schedule_time.has_value() + ? new_schedule_time.value().count() + : evt.reschedule_time}; + + // If this event was scheduled into a pause, its time now is going to be way + // behind. Re-set this event to continue from the end of the pause. + auto next_time{evt.time + next_schedule_time}; + if (evt.time < pause_end_time) { + next_time = pause_end_time + next_schedule_time; + } + + event_queue.update(evt.handle, Event{next_time, event_fifo_id++, evt.type, + next_schedule_time, evt.handle}); } } @@ -216,7 +250,7 @@ std::optional CoreTiming::Advance() { } if (!event_queue.empty()) { - return event_queue.front().time; + return event_queue.top().time; } else { return std::nullopt; } @@ -235,7 +269,7 @@ void CoreTiming::ThreadLoop() { #ifdef _WIN32 while (!paused && !event.IsSet() && wait_time > 0) { wait_time = *next_time - GetGlobalTimeNs().count(); - if (wait_time >= 1'000'000) { // 1ms + if (wait_time >= timer_resolution_ns) { Common::Windows::SleepForOneTick(); } else { #ifdef ARCHITECTURE_x86_64 @@ -256,8 +290,10 @@ void CoreTiming::ThreadLoop() { } 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; @@ -291,4 +327,10 @@ std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const { return std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)}; } +#ifdef _WIN32 +void CoreTiming::SetTimerResolutionNs(std::chrono::nanoseconds ns) { + timer_resolution_ns = ns.count(); +} +#endif + } // namespace Core::Timing diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 5b42b0e491..7e4dff7f3d 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -11,7 +11,8 @@ #include #include #include -#include + +#include #include "common/common_types.h" #include "common/thread.h" @@ -42,6 +43,18 @@ enum class UnscheduleEventType { NoWait, }; +/** + * This is a system to schedule events into the emulated machine's future. Time is measured + * in main CPU clock cycles. + * + * To schedule an event, you first have to register its type. This is where you pass in the + * callback. You then schedule events using the type ID you get back. + * + * The s64 ns_late that the callbacks get is how many ns late it was. + * So to schedule a new event on a regular basis: + * inside callback: + * ScheduleEvent(period_in_ns - ns_late, callback, "whatever") + */ class CoreTiming { public: CoreTiming(); @@ -53,56 +66,99 @@ public: CoreTiming& operator=(const CoreTiming&) = delete; CoreTiming& operator=(CoreTiming&&) = delete; + /// CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is + /// required to end slice - 1 and start slice 0 before the first cycle of code is executed. void Initialize(std::function&& on_thread_init_); + + /// Clear all pending events. This should ONLY be done on exit. void ClearPendingEvents(); + + /// Sets if emulation is multicore or single core, must be set before Initialize void SetMulticore(bool is_multicore_) { is_multicore = is_multicore_; } + + /// Pauses/Unpauses the execution of the timer thread. void Pause(bool is_paused); + + /// Pauses/Unpauses the execution of the timer thread and waits until paused. void SyncPause(bool is_paused); + + /// Checks if core timing is running. bool IsRunning() const; + + /// Checks if the timer thread has started. bool HasStarted() const { return has_started; } + + /// Checks if there are any pending time events. bool HasPendingEvents() const; + + /// Schedules an event in core timing void ScheduleEvent(std::chrono::nanoseconds ns_into_future, const std::shared_ptr& event_type, bool absolute_time = false); + + /// Schedules an event which will automatically re-schedule itself with the given time, until + /// unscheduled void ScheduleLoopingEvent(std::chrono::nanoseconds start_time, std::chrono::nanoseconds resched_time, const std::shared_ptr& event_type, bool absolute_time = false); + void UnscheduleEvent(const std::shared_ptr& event_type, UnscheduleEventType type = UnscheduleEventType::Wait); + void AddTicks(u64 ticks_to_add); + void ResetTicks(); + void Idle(); + s64 GetDowncount() const { - return downcount.load(std::memory_order_relaxed); + return downcount; } + + /// Returns the current CNTPCT tick value. u64 GetClockTicks() const; + + /// 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; + + /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. std::optional Advance(); +#ifdef _WIN32 + void SetTimerResolutionNs(std::chrono::nanoseconds ns); +#endif + private: - struct Event { - s64 time; - u64 fifo_order; - std::shared_ptr type; - bool operator>(const Event& other) const { - return std::tie(time, fifo_order) > std::tie(other.time, other.fifo_order); - } - }; + struct Event; static void ThreadEntry(CoreTiming& instance); void ThreadLoop(); + void Reset(); std::unique_ptr clock; - std::atomic global_timer{0}; - std::vector event_queue; - std::atomic event_fifo_id{0}; + + s64 global_timer = 0; + +#ifdef _WIN32 + s64 timer_resolution_ns; +#endif + + using heap_t = + boost::heap::fibonacci_heap>>; + + heap_t event_queue; + u64 event_fifo_id = 0; Common::Event event{}; Common::Event pause_event{}; @@ -117,12 +173,20 @@ private: std::function on_thread_init{}; bool is_multicore{}; - std::atomic pause_end_time{}; + s64 pause_end_time{}; - std::atomic cpu_ticks{}; - std::atomic downcount{}; + /// Cycle timing + u64 cpu_ticks{}; + s64 downcount{}; }; +/// Creates a core timing event with the given name and callback. +/// +/// @param name The name of the core timing event to create. +/// @param callback The callback to execute for the event. +/// +/// @returns An EventType instance representing the created event. +/// std::shared_ptr CreateEvent(std::string name, TimedCallback&& callback); } // namespace Core::Timing diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index e7e341de16..9b1c773877 100644 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -1,12 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include -#include -#include - #include "common/fiber.h" #include "common/microprofile.h" #include "common/scope_exit.h" @@ -30,7 +24,6 @@ void CpuManager::Initialize() { num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1; gpu_barrier = std::make_unique(num_cores + 1); - core_data.resize(num_cores); for (std::size_t core = 0; core < num_cores; core++) { core_data[core].host_thread = std::jthread([this, core](std::stop_token token) { RunThread(token, core); }); @@ -38,10 +31,10 @@ void CpuManager::Initialize() { } void CpuManager::Shutdown() { - for (auto& data : core_data) { - if (data.host_thread.joinable()) { - data.host_thread.request_stop(); - data.host_thread.join(); + for (std::size_t core = 0; core < num_cores; core++) { + if (core_data[core].host_thread.joinable()) { + core_data[core].host_thread.request_stop(); + core_data[core].host_thread.join(); } } } @@ -73,7 +66,12 @@ void CpuManager::HandleInterrupt() { Kernel::KInterruptManager::HandleInterrupt(kernel, static_cast(core_index)); } +/////////////////////////////////////////////////////////////////////////////// +/// MultiCore /// +/////////////////////////////////////////////////////////////////////////////// + void CpuManager::MultiCoreRunGuestThread() { + // Similar to UserModeThreadStarter in HOS auto& kernel = system.Kernel(); auto* thread = Kernel::GetCurrentThreadPointer(kernel); kernel.CurrentScheduler()->OnThreadStart(); @@ -90,6 +88,10 @@ void CpuManager::MultiCoreRunGuestThread() { } void CpuManager::MultiCoreRunIdleThread() { + // Not accurate to HOS. Remove this entire method when singlecore is removed. + // See notes in KScheduler::ScheduleImpl for more information about why this + // is inaccurate. + auto& kernel = system.Kernel(); kernel.CurrentScheduler()->OnThreadStart(); @@ -103,6 +105,10 @@ void CpuManager::MultiCoreRunIdleThread() { } } +/////////////////////////////////////////////////////////////////////////////// +/// SingleCore /// +/////////////////////////////////////////////////////////////////////////////// + void CpuManager::SingleCoreRunGuestThread() { auto& kernel = system.Kernel(); auto* thread = Kernel::GetCurrentThreadPointer(kernel); @@ -148,16 +154,19 @@ void CpuManager::PreemptSingleCore(bool from_running_environment) { system.CoreTiming().Advance(); kernel.SetIsPhantomModeForSingleCore(false); } - current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES, std::memory_order_release); + current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES); system.CoreTiming().ResetTicks(); kernel.Scheduler(current_core).PreemptSingleCore(); + // We've now been scheduled again, and we may have exchanged schedulers. + // Reload the scheduler in case it's different. if (!kernel.Scheduler(current_core).IsIdle()) { idle_count = 0; } } void CpuManager::GuestActivate() { + // Similar to the HorizonKernelMain callback in HOS auto& kernel = system.Kernel(); auto* scheduler = kernel.CurrentScheduler(); @@ -175,19 +184,27 @@ void CpuManager::ShutdownThread() { } void CpuManager::RunThread(std::stop_token token, std::size_t core) { + /// Initialization system.RegisterCoreThread(core); - std::string name = is_multicore ? "CPUCore_" + std::to_string(core) : "CPUThread"; + std::string name; + if (is_multicore) { + name = "CPUCore_" + std::to_string(core); + } else { + name = "CPUThread"; + } MicroProfileOnThreadCreate(name.c_str()); Common::SetCurrentThreadName(name.c_str()); Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); auto& data = core_data[core]; data.host_context = Common::Fiber::ThreadToFiber(); + // Cleanup SCOPE_EXIT { data.host_context->Exit(); MicroProfileOnThreadExit(); }; + // Running if (!gpu_barrier->Sync(token)) { return; } diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 6e53db3640..f7eae9c598 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "common/assert.h" #include "common/atomic_ops.h" @@ -33,18 +32,17 @@ namespace Core::Memory { namespace { -constexpr size_t PAGE_SIZE = 0x1000; -constexpr size_t PAGE_BITS = 12; -constexpr size_t PAGE_MASK = PAGE_SIZE - 1; - -inline bool AddressSpaceContains(const Common::PageTable& table, const Common::ProcessAddress addr, - const std::size_t size) { +bool AddressSpaceContains(const Common::PageTable& table, const Common::ProcessAddress addr, + const std::size_t size) { const Common::ProcessAddress max_addr = 1ULL << table.GetAddressSpaceBits(); return addr + size >= addr && addr + size <= max_addr; } -} // Anonymous namespace +} // namespace +// Implementation class used to keep the specifics of the memory subsystem hidden +// from outside classes. This also allows modification to the internals of the memory +// subsystem without needing to rebuild all files that make use of the memory interface. struct Memory::Impl { explicit Impl(Core::System& system_) : system{system_} {} @@ -68,11 +66,12 @@ struct Memory::Impl { void MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size, Common::PhysicalAddress target, Common::MemoryPermission perms, bool separate_heap) { - ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); - ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", GetInteger(base)); + ASSERT_MSG((size & SUYU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size); + ASSERT_MSG((base & SUYU_PAGEMASK) == 0, "non-page aligned base: {:016X}", GetInteger(base)); ASSERT_MSG(target >= DramMemoryMap::Base, "Out of bounds target: {:016X}", GetInteger(target)); - MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory); + MapPages(page_table, base / SUYU_PAGESIZE, size / SUYU_PAGESIZE, target, + Common::PageType::Memory); if (current_page_table->fastmem_arena) { buffer->Map(GetInteger(base), GetInteger(target) - DramMemoryMap::Base, size, perms, @@ -82,9 +81,10 @@ struct Memory::Impl { void UnmapRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size, bool separate_heap) { - ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); - ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", GetInteger(base)); - MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, 0, Common::PageType::Unmapped); + ASSERT_MSG((size & SUYU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size); + ASSERT_MSG((base & SUYU_PAGEMASK) == 0, "non-page aligned base: {:016X}", GetInteger(base)); + MapPages(page_table, base / SUYU_PAGESIZE, size / SUYU_PAGESIZE, 0, + Common::PageType::Unmapped); if (current_page_table->fastmem_arena) { buffer->Unmap(GetInteger(base), size, separate_heap); @@ -93,28 +93,55 @@ struct Memory::Impl { void ProtectRegion(Common::PageTable& page_table, VAddr vaddr, u64 size, Common::MemoryPermission perms) { - ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); - ASSERT_MSG((vaddr & PAGE_MASK) == 0, "non-page aligned base: {:016X}", vaddr); + ASSERT_MSG((size & SUYU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size); + ASSERT_MSG((vaddr & SUYU_PAGEMASK) == 0, "non-page aligned base: {:016X}", vaddr); if (!current_page_table->fastmem_arena) { return; } - for (u64 addr = vaddr; addr < vaddr + size; addr += PAGE_SIZE) { + u64 protect_bytes{}; + u64 protect_begin{}; + for (u64 addr = vaddr; addr < vaddr + size; addr += SUYU_PAGESIZE) { const Common::PageType page_type{ - current_page_table->pointers[addr >> PAGE_BITS].Type()}; - if (page_type != Common::PageType::RasterizerCachedMemory) { - buffer->Protect(addr, PAGE_SIZE, perms); + current_page_table->pointers[addr >> SUYU_PAGEBITS].Type()}; + switch (page_type) { + case Common::PageType::RasterizerCachedMemory: + if (protect_bytes > 0) { + buffer->Protect(protect_begin, protect_bytes, perms); + protect_bytes = 0; + } + break; + default: + if (protect_bytes == 0) { + protect_begin = addr; + } + protect_bytes += SUYU_PAGESIZE; } } + + if (protect_bytes > 0) { + buffer->Protect(protect_begin, protect_bytes, perms); + } } - u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const { + [[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const { const Common::PhysicalAddress paddr{ - current_page_table->backing_addr[vaddr >> PAGE_BITS]}; + current_page_table->backing_addr[vaddr >> SUYU_PAGEBITS]}; if (!paddr) { - return nullptr; + return {}; + } + + return system.DeviceMemory().GetPointer(paddr + vaddr); + } + + [[nodiscard]] u8* GetPointerFromDebugMemory(u64 vaddr) const { + const Common::PhysicalAddress paddr{ + current_page_table->backing_addr[vaddr >> SUYU_PAGEBITS]}; + + if (paddr == 0) { + return {}; } return system.DeviceMemory().GetPointer(paddr + vaddr); @@ -128,7 +155,9 @@ struct Memory::Impl { if ((addr & 1) == 0) { return Read(addr); } else { - return Read(addr) | static_cast(Read(addr + sizeof(u8))) << 8; + const u32 a{Read(addr)}; + const u32 b{Read(addr + sizeof(u8))}; + return static_cast((b << 8) | a); } } @@ -136,7 +165,9 @@ struct Memory::Impl { if ((addr & 3) == 0) { return Read(addr); } else { - return Read16(addr) | static_cast(Read16(addr + sizeof(u16))) << 16; + const u32 a{Read16(addr)}; + const u32 b{Read16(addr + sizeof(u16))}; + return (b << 16) | a; } } @@ -144,7 +175,9 @@ struct Memory::Impl { if ((addr & 7) == 0) { return Read(addr); } else { - return Read32(addr) | static_cast(Read32(addr + sizeof(u32))) << 32; + const u32 a{Read32(addr)}; + const u32 b{Read32(addr + sizeof(u32))}; + return (static_cast(b) << 32) | a; } } @@ -199,7 +232,7 @@ struct Memory::Impl { std::string string; string.reserve(max_length); for (std::size_t i = 0; i < max_length; ++i) { - const char c = Read(vaddr); + const char c = Read(vaddr); if (c == '\0') { break; } @@ -210,72 +243,648 @@ struct Memory::Impl { return string; } - template - T Read(const Common::ProcessAddress vaddr) { - T value; - const u8* const ptr = GetPointerFromRasterizerCachedMemory(GetInteger(vaddr)); - if (ptr) { - std::memcpy(&value, ptr, sizeof(T)); - } else { - LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, GetInteger(vaddr)); - value = 0; + 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; + std::size_t remaining_size = size; + std::size_t page_index = addr >> SUYU_PAGEBITS; + std::size_t page_offset = addr & SUYU_PAGEMASK; + bool user_accessible = true; + + if (!AddressSpaceContains(page_table, addr, size)) [[unlikely]] { + on_unmapped(size, addr); + return false; } - return value; + + while (remaining_size) { + const std::size_t copy_amount = + std::min(static_cast(SUYU_PAGESIZE) - page_offset, remaining_size); + const auto current_vaddr = + static_cast((page_index << SUYU_PAGEBITS) + page_offset); + + const auto [pointer, type] = page_table.pointers[page_index].PointerType(); + switch (type) { + case Common::PageType::Unmapped: { + user_accessible = false; + on_unmapped(copy_amount, current_vaddr); + break; + } + case Common::PageType::Memory: { + u8* mem_ptr = + reinterpret_cast(pointer + page_offset + (page_index << SUYU_PAGEBITS)); + on_memory(copy_amount, mem_ptr); + break; + } + case Common::PageType::DebugMemory: { + u8* const mem_ptr{GetPointerFromDebugMemory(current_vaddr)}; + on_memory(copy_amount, mem_ptr); + break; + } + case Common::PageType::RasterizerCachedMemory: { + u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)}; + on_rasterizer(current_vaddr, copy_amount, host_ptr); + break; + } + default: + UNREACHABLE(); + } + + page_index++; + page_offset = 0; + increment(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) { + 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->blocks[src_addr >> SUYU_PAGEBITS] == + current_page_table->blocks[(src_addr + size) >> SUYU_PAGEBITS]) { + return GetPointerSilent(src_addr); + } + return nullptr; + } + + u8* GetSpan(const VAddr src_addr, const std::size_t size) { + if (current_page_table->blocks[src_addr >> SUYU_PAGEBITS] == + current_page_table->blocks[(src_addr + size) >> SUYU_PAGEBITS]) { + 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) { + 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) { + 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; + }); + } + + 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 InvalidateDataCache(Common::ProcessAddress dest_addr, std::size_t size) { + auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, + const std::size_t block_size) { + // dc ivac: Invalidate to point of coherency + // GPU flush -> CPU invalidate + HandleRasterizerDownload(GetInteger(current_vaddr), block_size); + }; + return PerformCacheOperation(dest_addr, size, on_rasterizer); + } + + Result StoreDataCache(Common::ProcessAddress dest_addr, std::size_t size) { + auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, + const std::size_t block_size) { + // dc cvac: Store to point of coherency + // CPU flush -> GPU invalidate + HandleRasterizerWrite(GetInteger(current_vaddr), block_size); + }; + return PerformCacheOperation(dest_addr, size, on_rasterizer); + } + + Result FlushDataCache(Common::ProcessAddress dest_addr, std::size_t size) { + auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, + 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); + }; + return PerformCacheOperation(dest_addr, size, on_rasterizer); + } + + void MarkRegionDebug(u64 vaddr, u64 size, bool debug) { + if (vaddr == 0 || !AddressSpaceContains(*current_page_table, vaddr, size)) { + return; + } + + if (current_page_table->fastmem_arena) { + const auto perm{debug ? Common::MemoryPermission{} + : Common::MemoryPermission::ReadWrite}; + buffer->Protect(vaddr, size, perm); + } + + // Iterate over a contiguous CPU address space, marking/unmarking the region. + // The region is at a granularity of CPU pages. + + const u64 num_pages = ((vaddr + size - 1) >> SUYU_PAGEBITS) - (vaddr >> SUYU_PAGEBITS) + 1; + for (u64 i = 0; i < num_pages; ++i, vaddr += SUYU_PAGESIZE) { + const Common::PageType page_type{ + current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Type()}; + if (debug) { + // Switch page type to debug if now debug + switch (page_type) { + case Common::PageType::Unmapped: + ASSERT_MSG(false, "Attempted to mark unmapped pages as debug"); + break; + case Common::PageType::RasterizerCachedMemory: + case Common::PageType::DebugMemory: + // Page is already marked. + break; + case Common::PageType::Memory: + current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Store( + 0, Common::PageType::DebugMemory); + break; + default: + UNREACHABLE(); + } + } else { + // Switch page type to non-debug if now non-debug + switch (page_type) { + case Common::PageType::Unmapped: + ASSERT_MSG(false, "Attempted to mark unmapped pages as non-debug"); + break; + case Common::PageType::RasterizerCachedMemory: + case Common::PageType::Memory: + // Don't mess with already non-debug or rasterizer memory. + break; + case Common::PageType::DebugMemory: { + u8* const pointer{GetPointerFromDebugMemory(vaddr & ~SUYU_PAGEMASK)}; + current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Store( + reinterpret_cast(pointer) - (vaddr & ~SUYU_PAGEMASK), + Common::PageType::Memory); + break; + } + default: + UNREACHABLE(); + } + } + } + } + + void RasterizerMarkRegionCached(u64 vaddr, u64 size, bool cached) { + if (vaddr == 0 || !AddressSpaceContains(*current_page_table, vaddr, size)) { + return; + } + + if (current_page_table->fastmem_arena) { + Common::MemoryPermission perm{}; + if (!Settings::values.use_reactive_flushing.GetValue() || !cached) { + perm |= Common::MemoryPermission::Read; + } + if (!cached) { + perm |= Common::MemoryPermission::Write; + } + buffer->Protect(vaddr, size, perm); + } + + // Iterate over a contiguous CPU address space, which corresponds to the specified GPU + // address space, marking the region as un/cached. The region is marked un/cached at a + // granularity of CPU pages, hence why we iterate on a CPU page basis (note: GPU page size + // is different). This assumes the specified GPU address region is contiguous as well. + + const u64 num_pages = ((vaddr + size - 1) >> SUYU_PAGEBITS) - (vaddr >> SUYU_PAGEBITS) + 1; + for (u64 i = 0; i < num_pages; ++i, vaddr += SUYU_PAGESIZE) { + const Common::PageType page_type{ + current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Type()}; + if (cached) { + // Switch page type to cached if now cached + switch (page_type) { + case Common::PageType::Unmapped: + // It is not necessary for a process to have this region mapped into its address + // space, for example, a system module need not have a VRAM mapping. + break; + case Common::PageType::DebugMemory: + case Common::PageType::Memory: + current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Store( + 0, Common::PageType::RasterizerCachedMemory); + break; + case Common::PageType::RasterizerCachedMemory: + // There can be more than one GPU region mapped per CPU region, so it's common + // that this area is already marked as cached. + break; + default: + UNREACHABLE(); + } + } else { + // Switch page type to uncached if now uncached + switch (page_type) { + case Common::PageType::Unmapped: // NOLINT(bugprone-branch-clone) + // It is not necessary for a process to have this region mapped into its address + // space, for example, a system module need not have a VRAM mapping. + break; + case Common::PageType::DebugMemory: + case Common::PageType::Memory: + // There can be more than one GPU region mapped per CPU region, so it's common + // that this area is already unmarked as cached. + break; + case Common::PageType::RasterizerCachedMemory: { + u8* const pointer{GetPointerFromRasterizerCachedMemory(vaddr & ~SUYU_PAGEMASK)}; + if (pointer == nullptr) { + // It's possible that this function has been called while updating the + // pagetable after unmapping a VMA. In that case the underlying VMA will no + // longer exist, and we should just leave the pagetable entry blank. + current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Store( + 0, Common::PageType::Unmapped); + } else { + current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Store( + reinterpret_cast(pointer) - (vaddr & ~SUYU_PAGEMASK), + Common::PageType::Memory); + } + break; + } + default: + UNREACHABLE(); + } + } + } + } + + /** + * Maps a region of pages as a specific type. + * + * @param page_table The page table to use to perform the mapping. + * @param base The base address to begin mapping at. + * @param size The total size of the range in bytes. + * @param target The target address to begin mapping from. + * @param type The page type to map the memory as. + */ + void MapPages(Common::PageTable& page_table, Common::ProcessAddress base_address, u64 size, + Common::PhysicalAddress target, Common::PageType type) { + auto base = GetInteger(base_address); + + LOG_DEBUG(HW_Memory, "Mapping {:016X} onto {:016X}-{:016X}", GetInteger(target), + base * SUYU_PAGESIZE, (base + size) * SUYU_PAGESIZE); + + const auto end = base + size; + ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}", + base + page_table.pointers.size()); + + if (!target) { + ASSERT_MSG(type != Common::PageType::Memory, + "Mapping memory page without a pointer @ {:016x}", base * SUYU_PAGESIZE); + + while (base != end) { + page_table.pointers[base].Store(0, type); + page_table.backing_addr[base] = 0; + page_table.blocks[base] = 0; + base += 1; + } + } else { + auto orig_base = base; + while (base != end) { + auto host_ptr = + reinterpret_cast(system.DeviceMemory().GetPointer(target)) - + (base << SUYU_PAGEBITS); + auto backing = GetInteger(target) - (base << SUYU_PAGEBITS); + page_table.pointers[base].Store(host_ptr, type); + page_table.backing_addr[base] = backing; + page_table.blocks[base] = orig_base << SUYU_PAGEBITS; + + ASSERT_MSG(page_table.pointers[base].Pointer(), + "memory mapping base yield a nullptr within the table"); + + base += 1; + target += SUYU_PAGESIZE; + } + } + } + + [[nodiscard]] u8* GetPointerImpl(u64 vaddr, auto on_unmapped, auto on_rasterizer) const { + // AARCH64 masks the upper 16 bit of all memory accesses + vaddr = vaddr & 0xffffffffffffULL; + + if (!AddressSpaceContains(*current_page_table, vaddr, 1)) [[unlikely]] { + on_unmapped(); + return nullptr; + } + + // Avoid adding any extra logic to this fast-path block + const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> SUYU_PAGEBITS].Raw(); + if (const uintptr_t pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { + return reinterpret_cast(pointer + vaddr); + } + switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) { + case Common::PageType::Unmapped: + on_unmapped(); + return nullptr; + case Common::PageType::Memory: + ASSERT_MSG(false, "Mapped memory page without a pointer @ 0x{:016X}", vaddr); + return nullptr; + case Common::PageType::DebugMemory: + return GetPointerFromDebugMemory(vaddr); + case Common::PageType::RasterizerCachedMemory: { + u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)}; + on_rasterizer(); + return host_ptr; + } + default: + UNREACHABLE(); + } + return nullptr; + } + + [[nodiscard]] u8* GetPointer(const Common::ProcessAddress vaddr) const { + return GetPointerImpl( + GetInteger(vaddr), + [vaddr]() { + LOG_ERROR(HW_Memory, "Unmapped GetPointer @ 0x{:016X}", GetInteger(vaddr)); + }, + []() {}); + } + + [[nodiscard]] u8* GetPointerSilent(const Common::ProcessAddress vaddr) const { + return GetPointerImpl( + GetInteger(vaddr), []() {}, []() {}); + } + + /** + * Reads a particular data type out of memory at the given virtual address. + * + * @param vaddr The virtual address to read the data type from. + * + * @tparam T The data type to read out of memory. This type *must* be + * trivially copyable, otherwise the behavior of this function + * is undefined. + * + * @returns The instance of T read from the specified virtual address. + */ + template + T Read(Common::ProcessAddress vaddr) { + T result = 0; + const u8* const ptr = GetPointerImpl( + GetInteger(vaddr), + [vaddr]() { + LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, + GetInteger(vaddr)); + }, + [&]() { HandleRasterizerDownload(GetInteger(vaddr), sizeof(T)); }); + if (ptr) { + std::memcpy(&result, ptr, sizeof(T)); + } + return result; + } + + /** + * Writes a particular data type to memory at the given virtual address. + * + * @param vaddr The virtual address to write the data type to. + * + * @tparam T The data type to write to memory. This type *must* be + * trivially copyable, otherwise the behavior of this function + * is undefined. + */ template void Write(Common::ProcessAddress vaddr, const T data) { - u8* const ptr = GetPointerFromRasterizerCachedMemory(GetInteger(vaddr)); + u8* const ptr = GetPointerImpl( + GetInteger(vaddr), + [vaddr, data]() { + LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8, + GetInteger(vaddr), static_cast(data)); + }, + [&]() { HandleRasterizerWrite(GetInteger(vaddr), sizeof(T)); }); if (ptr) { std::memcpy(ptr, &data, sizeof(T)); - system.GPU().InvalidateRegion(GetInteger(vaddr), sizeof(T)); - } else { - LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8, - GetInteger(vaddr), static_cast(data)); } } template bool WriteExclusive(Common::ProcessAddress vaddr, const T data, const T expected) { - u8* const ptr = GetPointerFromRasterizerCachedMemory(GetInteger(vaddr)); + u8* const ptr = GetPointerImpl( + GetInteger(vaddr), + [vaddr, data]() { + LOG_ERROR(HW_Memory, "Unmapped WriteExclusive{} @ 0x{:016X} = 0x{:016X}", + sizeof(T) * 8, GetInteger(vaddr), static_cast(data)); + }, + [&]() { HandleRasterizerWrite(GetInteger(vaddr), sizeof(T)); }); if (ptr) { - const bool result = Common::AtomicCompareAndSwap(reinterpret_cast(ptr), data, expected); - if (result) { - system.GPU().InvalidateRegion(GetInteger(vaddr), sizeof(T)); + return Common::AtomicCompareAndSwap(reinterpret_cast(ptr), data, expected); + } + return true; + } + + bool WriteExclusive128(Common::ProcessAddress vaddr, const u128 data, const u128 expected) { + u8* const ptr = GetPointerImpl( + GetInteger(vaddr), + [vaddr, data]() { + LOG_ERROR(HW_Memory, "Unmapped WriteExclusive128 @ 0x{:016X} = 0x{:016X}{:016X}", + GetInteger(vaddr), static_cast(data[1]), static_cast(data[0])); + }, + [&]() { HandleRasterizerWrite(GetInteger(vaddr), sizeof(u128)); }); + if (ptr) { + return Common::AtomicCompareAndSwap(reinterpret_cast(ptr), data, expected); + } + return true; + } + + void HandleRasterizerDownload(VAddr v_address, size_t size) { + const auto* p = GetPointerImpl( + v_address, []() {}, []() {}); + if (!gpu_device_memory) [[unlikely]] { + gpu_device_memory = &system.Host1x().MemoryManager(); + } + const size_t core = system.GetCurrentHostThreadID(); + auto& current_area = rasterizer_read_areas[core]; + gpu_device_memory->ApplyOpOnPointer(p, scratch_buffers[core], [&](DAddr address) { + const DAddr end_address = address + size; + if (current_area.start_address <= address && end_address <= current_area.end_address) + [[likely]] { + return; } - return result; - } else { - LOG_ERROR(HW_Memory, "Unmapped WriteExclusive{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8, - GetInteger(vaddr), static_cast(data)); - return true; - } + current_area = system.GPU().OnCPURead(address, size); + }); } - bool ReadBlock(const Common::ProcessAddress src_addr, void* dest_buffer, - const std::size_t size) { - const u8* src_ptr = GetPointerFromRasterizerCachedMemory(GetInteger(src_addr)); - if (src_ptr) { - std::memcpy(dest_buffer, src_ptr, size); - return true; + void HandleRasterizerWrite(VAddr v_address, size_t size) { + const auto* p = GetPointerImpl( + v_address, []() {}, []() {}); + constexpr size_t sys_core = Core::Hardware::NUM_CPU_CORES - 1; + const size_t core = std::min(system.GetCurrentHostThreadID(), + sys_core); // any other calls threads go to syscore. + if (!gpu_device_memory) [[unlikely]] { + gpu_device_memory = &system.Host1x().MemoryManager(); } - LOG_ERROR(HW_Memory, "Unmapped ReadBlock @ 0x{:016X}", GetInteger(src_addr)); - return false; + // Guard on sys_core; + if (core == sys_core) [[unlikely]] { + sys_core_guard.lock(); + } + SCOPE_EXIT { + if (core == sys_core) [[unlikely]] { + sys_core_guard.unlock(); + } + }; + gpu_device_memory->ApplyOpOnPointer(p, scratch_buffers[core], [&](DAddr address) { + auto& current_area = rasterizer_write_areas[core]; + PAddr subaddress = address >> SUYU_PAGEBITS; + bool do_collection = current_area.last_address == subaddress; + if (!do_collection) [[unlikely]] { + do_collection = system.GPU().OnCPUWrite(address, size); + if (!do_collection) { + return; + } + current_area.last_address = subaddress; + } + gpu_dirty_managers[core].Collect(address, size); + }); } - bool WriteBlock(const Common::ProcessAddress dest_addr, const void* src_buffer, - const std::size_t size) { - u8* const dest_ptr = GetPointerFromRasterizerCachedMemory(GetInteger(dest_addr)); - if (dest_ptr) { - std::memcpy(dest_ptr, src_buffer, size); - system.GPU().InvalidateRegion(GetInteger(dest_addr), size); - return true; + struct GPUDirtyState { + PAddr last_address; + }; + + void InvalidateGPUMemory(u8* p, size_t size) { + constexpr size_t sys_core = Core::Hardware::NUM_CPU_CORES - 1; + const size_t core = std::min(system.GetCurrentHostThreadID(), + sys_core); // any other calls threads go to syscore. + if (!gpu_device_memory) [[unlikely]] { + gpu_device_memory = &system.Host1x().MemoryManager(); } - LOG_ERROR(HW_Memory, "Unmapped WriteBlock @ 0x{:016X}", GetInteger(dest_addr)); - return false; + // Guard on sys_core; + if (core == sys_core) [[unlikely]] { + sys_core_guard.lock(); + } + SCOPE_EXIT { + if (core == sys_core) [[unlikely]] { + sys_core_guard.unlock(); + } + }; + auto& gpu = system.GPU(); + gpu_device_memory->ApplyOpOnPointer( + p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size); }); } Core::System& system; + Tegra::MaxwellDeviceMemoryManager* gpu_device_memory{}; Common::PageTable* current_page_table = nullptr; + std::array + rasterizer_read_areas{}; + std::array rasterizer_write_areas{}; + std::array, Core::Hardware::NUM_CPU_CORES> scratch_buffers{}; + std::span gpu_dirty_managers; + std::mutex sys_core_guard; + std::optional heap_tracker; #ifdef __linux__ Common::HeapTracker* buffer{}; @@ -284,10 +893,16 @@ struct Memory::Impl { #endif }; -Memory::Memory(Core::System& system_) : impl{std::make_unique(system_)} {} +Memory::Memory(Core::System& system_) : system{system_} { + Reset(); +} Memory::~Memory() = default; +void Memory::Reset() { + impl = std::make_unique(system); +} + void Memory::SetCurrentPageTable(Kernel::KProcess& process) { impl->SetCurrentPageTable(process); } @@ -310,20 +925,38 @@ void Memory::ProtectRegion(Common::PageTable& page_table, Common::ProcessAddress bool Memory::IsValidVirtualAddress(const Common::ProcessAddress vaddr) const { const auto& page_table = *impl->current_page_table; - const size_t page = vaddr >> PAGE_BITS; + const size_t page = vaddr >> SUYU_PAGEBITS; if (page >= page_table.pointers.size()) { return false; } const auto [pointer, type] = page_table.pointers[page].PointerType(); - return pointer != 0 || type == Common::PageType::RasterizerCachedMemory; + return pointer != 0 || type == Common::PageType::RasterizerCachedMemory || + type == Common::PageType::DebugMemory; +} + +bool Memory::IsValidVirtualAddressRange(Common::ProcessAddress base, u64 size) const { + Common::ProcessAddress end = base + size; + Common::ProcessAddress page = Common::AlignDown(GetInteger(base), SUYU_PAGESIZE); + + for (; page < end; page += SUYU_PAGESIZE) { + if (!IsValidVirtualAddress(page)) { + return false; + } + } + + return true; } u8* Memory::GetPointer(Common::ProcessAddress vaddr) { - return impl->GetPointerFromRasterizerCachedMemory(GetInteger(vaddr)); + return impl->GetPointer(vaddr); +} + +u8* Memory::GetPointerSilent(Common::ProcessAddress vaddr) { + return impl->GetPointerSilent(vaddr); } const u8* Memory::GetPointer(Common::ProcessAddress vaddr) const { - return impl->GetPointerFromRasterizerCachedMemory(GetInteger(vaddr)); + return impl->GetPointer(vaddr); } u8 Memory::Read8(const Common::ProcessAddress addr) { @@ -374,6 +1007,10 @@ bool Memory::WriteExclusive64(Common::ProcessAddress addr, u64 data, u64 expecte return impl->WriteExclusive64(addr, data, expected); } +bool Memory::WriteExclusive128(Common::ProcessAddress addr, u128 data, u128 expected) { + return impl->WriteExclusive128(addr, data, expected); +} + std::string Memory::ReadCString(Common::ProcessAddress vaddr, std::size_t max_length) { return impl->ReadCString(vaddr, max_length); } @@ -383,9 +1020,93 @@ bool Memory::ReadBlock(const Common::ProcessAddress src_addr, void* dest_buffer, return impl->ReadBlock(src_addr, dest_buffer, size); } +bool Memory::ReadBlockUnsafe(const Common::ProcessAddress src_addr, void* dest_buffer, + const std::size_t size) { + return impl->ReadBlockUnsafe(src_addr, dest_buffer, size); +} + +const u8* Memory::GetSpan(const VAddr src_addr, const std::size_t size) const { + return impl->GetSpan(src_addr, size); +} + +u8* Memory::GetSpan(const VAddr src_addr, const std::size_t size) { + return impl->GetSpan(src_addr, 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); } +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); +} + +bool Memory::CopyBlock(Common::ProcessAddress dest_addr, Common::ProcessAddress src_addr, + const std::size_t size) { + return impl->CopyBlock(dest_addr, src_addr, size); +} + +bool Memory::ZeroBlock(Common::ProcessAddress dest_addr, const std::size_t size) { + return impl->ZeroBlock(dest_addr, size); +} + +void Memory::SetGPUDirtyManagers(std::span managers) { + impl->gpu_dirty_managers = managers; +} + +Result Memory::InvalidateDataCache(Common::ProcessAddress dest_addr, const std::size_t size) { + return impl->InvalidateDataCache(dest_addr, size); +} + +Result Memory::StoreDataCache(Common::ProcessAddress dest_addr, const std::size_t size) { + return impl->StoreDataCache(dest_addr, size); +} + +Result Memory::FlushDataCache(Common::ProcessAddress dest_addr, const std::size_t size) { + return impl->FlushDataCache(dest_addr, size); +} + +void Memory::RasterizerMarkRegionCached(Common::ProcessAddress vaddr, u64 size, bool cached) { + impl->RasterizerMarkRegionCached(GetInteger(vaddr), size, cached); +} + +void Memory::MarkRegionDebug(Common::ProcessAddress vaddr, u64 size, bool debug) { + impl->MarkRegionDebug(GetInteger(vaddr), size, debug); +} + +bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) { + [[maybe_unused]] bool mapped = true; + [[maybe_unused]] bool rasterizer = false; + + u8* const ptr = impl->GetPointerImpl( + GetInteger(vaddr), + [&] { + LOG_ERROR(HW_Memory, "Unmapped InvalidateNCE for {} bytes @ {:#x}", size, + GetInteger(vaddr)); + mapped = false; + }, + [&] { rasterizer = true; }); + if (rasterizer) { + impl->InvalidateGPUMemory(ptr, size); + } + +#ifdef __linux__ + if (!rasterizer && mapped) { + impl->buffer->DeferredMapSeparateHeap(GetInteger(vaddr)); + } +#endif + + return mapped && ptr != nullptr; +} + +bool Memory::InvalidateSeparateHeap(void* fault_address) { +#ifdef __linux__ + return impl->buffer->DeferredMapSeparateHeap(static_cast(fault_address)); +#else + return false; +#endif +} + } // namespace Core::Memory diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index dbc4dcf5ca..c816f47fec 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -40,23 +40,10 @@ struct GPU::Impl { explicit Impl(GPU& gpu_, Core::System& system_, bool is_async_, bool use_nvdec_) : gpu{gpu_}, system{system_}, host1x{system.Host1x()}, use_nvdec{use_nvdec_}, shader_notify{std::make_unique()}, is_async{is_async_}, - gpu_thread{system_, is_async_}, scheduler{std::make_unique(gpu)} { - Initialize(); - } + gpu_thread{system_, is_async_}, scheduler{std::make_unique(gpu)} {} ~Impl() = default; - void Initialize() { - // Initialize the GPU memory manager - memory_manager = std::make_unique(system); - - // Initialize the command buffer - command_buffer.reserve(COMMAND_BUFFER_SIZE); - - // Initialize the fence manager - fence_manager = std::make_unique(); - } - std::shared_ptr CreateChannel(s32 channel_id) { auto channel_state = std::make_shared(channel_id); channels.emplace(channel_id, channel_state); @@ -104,15 +91,14 @@ struct GPU::Impl { /// Flush all current written commands into the host GPU for execution. void FlushCommands() { - if (!command_buffer.empty()) { - rasterizer->ExecuteCommands(command_buffer); - command_buffer.clear(); - } + rasterizer->FlushCommands(); } /// Synchronizes CPU writes with Host GPU memory. void InvalidateGPUCache() { - rasterizer->InvalidateGPUCache(); + std::function callback_writes( + [this](PAddr address, size_t size) { rasterizer->OnCacheInvalidation(address, size); }); + system.GatherGPUDirtyMemory(callback_writes); } /// Signal the ending of command list. @@ -122,10 +108,11 @@ struct GPU::Impl { } /// Request a host GPU memory flush from the CPU. - u64 RequestSyncOperation(std::function&& action) { + template + [[nodiscard]] u64 RequestSyncOperation(Func&& action) { std::unique_lock lck{sync_request_mutex}; const u64 fence = ++last_sync_fence; - sync_requests.emplace_back(std::move(action), fence); + sync_requests.emplace_back(action); return fence; } @@ -143,12 +130,12 @@ struct GPU::Impl { void TickWork() { std::unique_lock lck{sync_request_mutex}; while (!sync_requests.empty()) { - auto& request = sync_requests.front(); + auto request = std::move(sync_requests.front()); + sync_requests.pop_front(); sync_request_mutex.unlock(); - request.first(); + request(); current_sync_fence.fetch_add(1, std::memory_order_release); sync_request_mutex.lock(); - sync_requests.pop_front(); sync_request_cv.notify_all(); } } @@ -235,6 +222,7 @@ struct GPU::Impl { /// This can be used to launch any necessary threads and register any necessary /// core timing events. void Start() { + Settings::UpdateGPUAccuracy(); gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler); } @@ -264,7 +252,7 @@ struct GPU::Impl { /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory void FlushRegion(DAddr addr, u64 size) { - rasterizer->FlushRegion(addr, size); + gpu_thread.FlushRegion(addr, size); } VideoCore::RasterizerDownloadArea OnCPURead(DAddr addr, u64 size) { @@ -284,7 +272,7 @@ struct GPU::Impl { /// Notify rasterizer that any caches of the specified region should be invalidated void InvalidateRegion(DAddr addr, u64 size) { - rasterizer->InvalidateRegion(addr, size); + gpu_thread.InvalidateRegion(addr, size); } bool OnCPUWrite(DAddr addr, u64 size) { @@ -293,7 +281,57 @@ struct GPU::Impl { /// Notify rasterizer that any caches of the specified region should be flushed and invalidated void FlushAndInvalidateRegion(DAddr addr, u64 size) { - rasterizer->FlushAndInvalidateRegion(addr, size); + gpu_thread.FlushAndInvalidateRegion(addr, size); + } + + void RequestComposite(std::vector&& layers, + std::vector&& fences) { + size_t num_fences{fences.size()}; + size_t current_request_counter{}; + { + std::unique_lock lk(request_swap_mutex); + if (free_swap_counters.empty()) { + current_request_counter = request_swap_counters.size(); + request_swap_counters.emplace_back(num_fences); + } else { + current_request_counter = free_swap_counters.front(); + request_swap_counters[current_request_counter] = num_fences; + free_swap_counters.pop_front(); + } + } + const auto wait_fence = + RequestSyncOperation([this, current_request_counter, &layers, &fences, num_fences] { + auto& syncpoint_manager = host1x.GetSyncpointManager(); + if (num_fences == 0) { + renderer->Composite(layers); + } + const auto executer = [this, current_request_counter, layers_copy = layers]() { + { + std::unique_lock lk(request_swap_mutex); + if (--request_swap_counters[current_request_counter] != 0) { + return; + } + free_swap_counters.push_back(current_request_counter); + } + renderer->Composite(layers_copy); + }; + for (size_t i = 0; i < num_fences; i++) { + syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer); + } + }); + gpu_thread.TickGPU(); + WaitForSyncOperation(wait_fence); + } + + std::vector GetAppletCaptureBuffer() { + std::vector out; + + const auto wait_fence = + RequestSyncOperation([&] { out = renderer->GetAppletCaptureBuffer(); }); + gpu_thread.TickGPU(); + WaitForSyncOperation(wait_fence); + + return out; } GPU& gpu; @@ -310,12 +348,16 @@ struct GPU::Impl { /// When true, we are about to shut down emulation session, so terminate outstanding tasks std::atomic_bool shutting_down{}; + std::array, Service::Nvidia::MaxSyncPoints> syncpoints{}; + + std::array, Service::Nvidia::MaxSyncPoints> syncpt_interrupts; + std::mutex sync_mutex; std::mutex device_mutex; std::condition_variable sync_cv; - std::list, u64>> sync_requests; + std::list> sync_requests; std::atomic current_sync_fence{}; u64 last_sync_fence{}; std::mutex sync_request_mutex; @@ -331,13 +373,182 @@ struct GPU::Impl { Tegra::Control::ChannelState* current_channel; s32 bound_channel{-1}; - std::unique_ptr memory_manager; - std::vector command_buffer; - std::unique_ptr fence_manager; - - static constexpr size_t COMMAND_BUFFER_SIZE = 4 * 1024 * 1024; + std::deque free_swap_counters; + std::deque request_swap_counters; + std::mutex request_swap_mutex; }; -// ... (rest of the implementation remains the same) +GPU::GPU(Core::System& system, bool is_async, bool use_nvdec) + : impl{std::make_unique(*this, system, is_async, use_nvdec)} {} + +GPU::~GPU() = default; + +std::shared_ptr GPU::AllocateChannel() { + return impl->AllocateChannel(); +} + +void GPU::InitChannel(Control::ChannelState& to_init, u64 program_id) { + impl->InitChannel(to_init, program_id); +} + +void GPU::BindChannel(s32 channel_id) { + impl->BindChannel(channel_id); +} + +void GPU::ReleaseChannel(Control::ChannelState& to_release) { + impl->ReleaseChannel(to_release); +} + +void GPU::InitAddressSpace(Tegra::MemoryManager& memory_manager) { + impl->InitAddressSpace(memory_manager); +} + +void GPU::BindRenderer(std::unique_ptr renderer) { + impl->BindRenderer(std::move(renderer)); +} + +void GPU::FlushCommands() { + impl->FlushCommands(); +} + +void GPU::InvalidateGPUCache() { + impl->InvalidateGPUCache(); +} + +void GPU::OnCommandListEnd() { + impl->OnCommandListEnd(); +} + +u64 GPU::RequestFlush(DAddr addr, std::size_t size) { + return impl->RequestSyncOperation( + [this, addr, size]() { impl->rasterizer->FlushRegion(addr, size); }); +} + +u64 GPU::CurrentSyncRequestFence() const { + return impl->CurrentSyncRequestFence(); +} + +void GPU::WaitForSyncOperation(u64 fence) { + return impl->WaitForSyncOperation(fence); +} + +void GPU::TickWork() { + impl->TickWork(); +} + +/// Gets a mutable reference to the Host1x interface +Host1x::Host1x& GPU::Host1x() { + return impl->host1x; +} + +/// Gets an immutable reference to the Host1x interface. +const Host1x::Host1x& GPU::Host1x() const { + return impl->host1x; +} + +Engines::Maxwell3D& GPU::Maxwell3D() { + return impl->Maxwell3D(); +} + +const Engines::Maxwell3D& GPU::Maxwell3D() const { + return impl->Maxwell3D(); +} + +Engines::KeplerCompute& GPU::KeplerCompute() { + return impl->KeplerCompute(); +} + +const Engines::KeplerCompute& GPU::KeplerCompute() const { + return impl->KeplerCompute(); +} + +Tegra::DmaPusher& GPU::DmaPusher() { + return impl->DmaPusher(); +} + +const Tegra::DmaPusher& GPU::DmaPusher() const { + return impl->DmaPusher(); +} + +VideoCore::RendererBase& GPU::Renderer() { + return impl->Renderer(); +} + +const VideoCore::RendererBase& GPU::Renderer() const { + return impl->Renderer(); +} + +VideoCore::ShaderNotify& GPU::ShaderNotify() { + return impl->ShaderNotify(); +} + +const VideoCore::ShaderNotify& GPU::ShaderNotify() const { + return impl->ShaderNotify(); +} + +void GPU::RequestComposite(std::vector&& layers, + std::vector&& fences) { + impl->RequestComposite(std::move(layers), std::move(fences)); +} + +std::vector GPU::GetAppletCaptureBuffer() { + return impl->GetAppletCaptureBuffer(); +} + +u64 GPU::GetTicks() const { + return impl->GetTicks(); +} + +bool GPU::IsAsync() const { + return impl->IsAsync(); +} + +bool GPU::UseNvdec() const { + return impl->UseNvdec(); +} + +void GPU::RendererFrameEndNotify() { + impl->RendererFrameEndNotify(); +} + +void GPU::Start() { + impl->Start(); +} + +void GPU::NotifyShutdown() { + impl->NotifyShutdown(); +} + +void GPU::ObtainContext() { + impl->ObtainContext(); +} + +void GPU::ReleaseContext() { + impl->ReleaseContext(); +} + +void GPU::PushGPUEntries(s32 channel, Tegra::CommandList&& entries) { + impl->PushGPUEntries(channel, std::move(entries)); +} + +VideoCore::RasterizerDownloadArea GPU::OnCPURead(PAddr addr, u64 size) { + return impl->OnCPURead(addr, size); +} + +void GPU::FlushRegion(DAddr addr, u64 size) { + impl->FlushRegion(addr, size); +} + +void GPU::InvalidateRegion(DAddr addr, u64 size) { + impl->InvalidateRegion(addr, size); +} + +bool GPU::OnCPUWrite(DAddr addr, u64 size) { + return impl->OnCPUWrite(addr, size); +} + +void GPU::FlushAndInvalidateRegion(DAddr addr, u64 size) { + impl->FlushAndInvalidateRegion(addr, size); +} } // namespace Tegra diff --git a/src/video_core/optimized_rasterizer.cpp b/src/video_core/optimized_rasterizer.cpp deleted file mode 100644 index 02631f3c56..0000000000 --- a/src/video_core/optimized_rasterizer.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "video_core/optimized_rasterizer.h" -#include "common/settings.h" -#include "video_core/gpu.h" -#include "video_core/memory_manager.h" -#include "video_core/engines/maxwell_3d.h" - -namespace VideoCore { - -OptimizedRasterizer::OptimizedRasterizer(Core::System& system, Tegra::GPU& gpu) - : system{system}, gpu{gpu}, memory_manager{gpu.MemoryManager()} { - InitializeShaderCache(); -} - -OptimizedRasterizer::~OptimizedRasterizer() = default; - -void OptimizedRasterizer::Draw(bool is_indexed, u32 instance_count) { - MICROPROFILE_SCOPE(GPU_Rasterization); - - PrepareRendertarget(); - UpdateDynamicState(); - - if (is_indexed) { - DrawIndexed(instance_count); - } else { - DrawArrays(instance_count); - } -} - -void OptimizedRasterizer::Clear(u32 layer_count) { - MICROPROFILE_SCOPE(GPU_Rasterization); - - PrepareRendertarget(); - ClearFramebuffer(layer_count); -} - -void OptimizedRasterizer::DispatchCompute() { - MICROPROFILE_SCOPE(GPU_Compute); - - PrepareCompute(); - LaunchComputeShader(); -} - -void OptimizedRasterizer::ResetCounter(VideoCommon::QueryType type) { - query_cache.ResetCounter(type); -} - -void OptimizedRasterizer::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type, - VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) { - query_cache.Query(gpu_addr, type, flags, payload, subreport); -} - -void OptimizedRasterizer::FlushAll() { - MICROPROFILE_SCOPE(GPU_Synchronization); - - FlushShaderCache(); - FlushRenderTargets(); -} - -void OptimizedRasterizer::FlushRegion(DAddr addr, u64 size, VideoCommon::CacheType which) { - MICROPROFILE_SCOPE(GPU_Synchronization); - - if (which == VideoCommon::CacheType::All || which == VideoCommon::CacheType::Unified) { - FlushMemoryRegion(addr, size); - } -} - -bool OptimizedRasterizer::MustFlushRegion(DAddr addr, u64 size, VideoCommon::CacheType which) { - if (which == VideoCommon::CacheType::All || which == VideoCommon::CacheType::Unified) { - return IsRegionCached(addr, size); - } - return false; -} - -RasterizerDownloadArea OptimizedRasterizer::GetFlushArea(DAddr addr, u64 size) { - return GetFlushableArea(addr, size); -} - -void OptimizedRasterizer::InvalidateRegion(DAddr addr, u64 size, VideoCommon::CacheType which) { - MICROPROFILE_SCOPE(GPU_Synchronization); - - if (which == VideoCommon::CacheType::All || which == VideoCommon::CacheType::Unified) { - InvalidateMemoryRegion(addr, size); - } -} - -void OptimizedRasterizer::OnCacheInvalidation(PAddr addr, u64 size) { - MICROPROFILE_SCOPE(GPU_Synchronization); - - InvalidateCachedRegion(addr, size); -} - -bool OptimizedRasterizer::OnCPUWrite(PAddr addr, u64 size) { - return HandleCPUWrite(addr, size); -} - -void OptimizedRasterizer::InvalidateGPUCache() { - MICROPROFILE_SCOPE(GPU_Synchronization); - - InvalidateAllCache(); -} - -void OptimizedRasterizer::UnmapMemory(DAddr addr, u64 size) { - MICROPROFILE_SCOPE(GPU_Synchronization); - - UnmapGPUMemoryRegion(addr, size); -} - -void OptimizedRasterizer::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) { - MICROPROFILE_SCOPE(GPU_Synchronization); - - UpdateMappedGPUMemory(as_id, addr, size); -} - -void OptimizedRasterizer::FlushAndInvalidateRegion(DAddr addr, u64 size, VideoCommon::CacheType which) { - MICROPROFILE_SCOPE(GPU_Synchronization); - - if (which == VideoCommon::CacheType::All || which == VideoCommon::CacheType::Unified) { - FlushAndInvalidateMemoryRegion(addr, size); - } -} - -void OptimizedRasterizer::WaitForIdle() { - MICROPROFILE_SCOPE(GPU_Synchronization); - - WaitForGPUIdle(); -} - -void OptimizedRasterizer::FragmentBarrier() { - MICROPROFILE_SCOPE(GPU_Synchronization); - - InsertFragmentBarrier(); -} - -void OptimizedRasterizer::TiledCacheBarrier() { - MICROPROFILE_SCOPE(GPU_Synchronization); - - InsertTiledCacheBarrier(); -} - -void OptimizedRasterizer::FlushCommands() { - MICROPROFILE_SCOPE(GPU_Synchronization); - - SubmitCommands(); -} - -void OptimizedRasterizer::TickFrame() { - MICROPROFILE_SCOPE(GPU_Synchronization); - - EndFrame(); -} - -void OptimizedRasterizer::PrepareRendertarget() { - const auto& regs{gpu.Maxwell3D().regs}; - const auto& framebuffer{regs.framebuffer}; - - render_targets.resize(framebuffer.num_color_buffers); - for (std::size_t index = 0; index < framebuffer.num_color_buffers; ++index) { - render_targets[index] = GetColorBuffer(index); - } - - depth_stencil = GetDepthBuffer(); -} - -void OptimizedRasterizer::UpdateDynamicState() { - const auto& regs{gpu.Maxwell3D().regs}; - - UpdateViewport(regs.viewport_transform); - UpdateScissor(regs.scissor_test); - UpdateDepthBias(regs.polygon_offset_units, regs.polygon_offset_clamp, regs.polygon_offset_factor); - UpdateBlendConstants(regs.blend_color); - UpdateStencilFaceMask(regs.stencil_front_func_mask, regs.stencil_back_func_mask); -} - -void OptimizedRasterizer::DrawIndexed(u32 instance_count) { - const auto& draw_state{gpu.Maxwell3D().draw_manager->GetDrawState()}; - const auto& index_buffer{memory_manager.ReadBlockUnsafe(draw_state.index_buffer.Address(), - draw_state.index_buffer.size)}; - - shader_cache.BindComputeShader(); - shader_cache.BindGraphicsShader(); - - DrawElementsInstanced(draw_state.topology, draw_state.index_buffer.count, - draw_state.index_buffer.format, index_buffer.data(), instance_count); -} - -void OptimizedRasterizer::DrawArrays(u32 instance_count) { - const auto& draw_state{gpu.Maxwell3D().draw_manager->GetDrawState()}; - - shader_cache.BindComputeShader(); - shader_cache.BindGraphicsShader(); - - DrawArraysInstanced(draw_state.topology, draw_state.vertex_buffer.first, - draw_state.vertex_buffer.count, instance_count); -} - -void OptimizedRasterizer::ClearFramebuffer(u32 layer_count) { - const auto& regs{gpu.Maxwell3D().regs}; - const auto& clear_state{regs.clear_buffers}; - - if (clear_state.R || clear_state.G || clear_state.B || clear_state.A) { - ClearColorBuffers(clear_state.R, clear_state.G, clear_state.B, clear_state.A, - regs.clear_color[0], regs.clear_color[1], regs.clear_color[2], - regs.clear_color[3], layer_count); - } - - if (clear_state.Z || clear_state.S) { - ClearDepthStencilBuffer(clear_state.Z, clear_state.S, regs.clear_depth, regs.clear_stencil, - layer_count); - } -} - -void OptimizedRasterizer::PrepareCompute() { - shader_cache.BindComputeShader(); -} - -void OptimizedRasterizer::LaunchComputeShader() { - const auto& launch_desc{gpu.KeplerCompute().launch_description}; - DispatchCompute(launch_desc.grid_dim_x, launch_desc.grid_dim_y, launch_desc.grid_dim_z); -} - -} // namespace VideoCore \ No newline at end of file diff --git a/src/video_core/optimized_rasterizer.h b/src/video_core/optimized_rasterizer.h deleted file mode 100644 index 9c9fe1f35e..0000000000 --- a/src/video_core/optimized_rasterizer.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include -#include -#include "common/common_types.h" -#include "video_core/rasterizer_interface.h" -#include "video_core/engines/maxwell_3d.h" - -namespace Core { -class System; -} - -namespace Tegra { -class GPU; -class MemoryManager; -} - -namespace VideoCore { - -class ShaderCache; -class QueryCache; - -class OptimizedRasterizer final : public RasterizerInterface { -public: - explicit OptimizedRasterizer(Core::System& system, Tegra::GPU& gpu); - ~OptimizedRasterizer() override; - - void Draw(bool is_indexed, u32 instance_count) override; - void Clear(u32 layer_count) override; - void DispatchCompute() override; - void ResetCounter(VideoCommon::QueryType type) override; - void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type, - VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override; - void FlushAll() override; - void FlushRegion(DAddr addr, u64 size, VideoCommon::CacheType which) override; - bool MustFlushRegion(DAddr addr, u64 size, VideoCommon::CacheType which) override; - RasterizerDownloadArea GetFlushArea(DAddr addr, u64 size) override; - void InvalidateRegion(DAddr addr, u64 size, VideoCommon::CacheType which) override; - void OnCacheInvalidation(PAddr addr, u64 size) override; - bool OnCPUWrite(PAddr addr, u64 size) override; - void InvalidateGPUCache() override; - void UnmapMemory(DAddr addr, u64 size) override; - void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override; - void FlushAndInvalidateRegion(DAddr addr, u64 size, VideoCommon::CacheType which) override; - void WaitForIdle() override; - void FragmentBarrier() override; - void TiledCacheBarrier() override; - void FlushCommands() override; - void TickFrame() override; - -private: - void PrepareRendertarget(); - void UpdateDynamicState(); - void DrawIndexed(u32 instance_count); - void DrawArrays(u32 instance_count); - void ClearFramebuffer(u32 layer_count); - void PrepareCompute(); - void LaunchComputeShader(); - - Core::System& system; - Tegra::GPU& gpu; - Tegra::MemoryManager& memory_manager; - - std::unique_ptr shader_cache; - std::unique_ptr query_cache; - - std::vector render_targets; - DepthStencilConfig depth_stencil; - - // Add any additional member variables needed for the optimized rasterizer -}; - -} // namespace VideoCore \ No newline at end of file diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp index c32bd88b22..a281f5d541 100644 --- a/src/video_core/shader_cache.cpp +++ b/src/video_core/shader_cache.cpp @@ -3,18 +3,9 @@ #include #include -#include -#include -#include -#include -#include #include #include "common/assert.h" -#include "common/fs/file.h" -#include "common/fs/path_util.h" -#include "common/logging/log.h" -#include "common/thread_worker.h" #include "shader_recompiler/frontend/maxwell/control_flow.h" #include "shader_recompiler/object_pool.h" #include "video_core/control/channel_state.h" @@ -28,288 +19,233 @@ namespace VideoCommon { -constexpr size_t MAX_SHADER_CACHE_SIZE = 1024 * 1024 * 1024; // 1GB - -class ShaderCacheWorker : public Common::ThreadWorker { -public: - explicit ShaderCacheWorker(const std::string& name) : ThreadWorker(name) {} - ~ShaderCacheWorker() = default; - - void CompileShader(ShaderInfo* shader) { - Push([shader]() { - // Compile shader here - // This is a placeholder for the actual compilation process - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - shader->is_compiled.store(true, std::memory_order_release); - }); - } -}; - -class ShaderCache::Impl { -public: - explicit Impl(Tegra::MaxwellDeviceMemoryManager& device_memory_) - : device_memory{device_memory_}, workers{CreateWorkers()} { - LoadCache(); - } - - ~Impl() { - SaveCache(); - } - - void InvalidateRegion(VAddr addr, size_t size) { - std::scoped_lock lock{invalidation_mutex}; - InvalidatePagesInRegion(addr, size); - RemovePendingShaders(); - } - - void OnCacheInvalidation(VAddr addr, size_t size) { - std::scoped_lock lock{invalidation_mutex}; - InvalidatePagesInRegion(addr, size); - } - - void SyncGuestHost() { - std::scoped_lock lock{invalidation_mutex}; - RemovePendingShaders(); - } - - bool RefreshStages(std::array& unique_hashes); - const ShaderInfo* ComputeShader(); - void GetGraphicsEnvironments(GraphicsEnvironments& result, const std::array& unique_hashes); - - ShaderInfo* TryGet(VAddr addr) const { - std::scoped_lock lock{lookup_mutex}; - - const auto it = lookup_cache.find(addr); - if (it == lookup_cache.end()) { - return nullptr; - } - return it->second->data; - } - - void Register(std::unique_ptr data, VAddr addr, size_t size) { - std::scoped_lock lock{invalidation_mutex, lookup_mutex}; - - const VAddr addr_end = addr + size; - Entry* const entry = NewEntry(addr, addr_end, data.get()); - - const u64 page_end = (addr_end + SUYU_PAGESIZE - 1) >> SUYU_PAGEBITS; - for (u64 page = addr >> SUYU_PAGEBITS; page < page_end; ++page) { - invalidation_cache[page].push_back(entry); - } - - storage.push_back(std::move(data)); - - device_memory.UpdatePagesCachedCount(addr, size, 1); - } - -private: - std::vector> CreateWorkers() { - const size_t num_workers = std::thread::hardware_concurrency(); - std::vector> workers; - workers.reserve(num_workers); - for (size_t i = 0; i < num_workers; ++i) { - workers.emplace_back(std::make_unique(fmt::format("ShaderWorker{}", i))); - } - return workers; - } - - void LoadCache() { - const auto cache_dir = Common::FS::GetSuyuPath(Common::FS::SuyuPath::ShaderDir); - std::filesystem::create_directories(cache_dir); - - const auto cache_file = cache_dir / "shader_cache.bin"; - if (!std::filesystem::exists(cache_file)) { - return; - } - - std::ifstream file(cache_file, std::ios::binary); - if (!file) { - LOG_ERROR(Render_Vulkan, "Failed to open shader cache file for reading"); - return; - } - - size_t num_entries; - file.read(reinterpret_cast(&num_entries), sizeof(num_entries)); - - for (size_t i = 0; i < num_entries; ++i) { - VAddr addr; - size_t size; - file.read(reinterpret_cast(&addr), sizeof(addr)); - file.read(reinterpret_cast(&size), sizeof(size)); - - auto info = std::make_unique(); - file.read(reinterpret_cast(info.get()), sizeof(ShaderInfo)); - - Register(std::move(info), addr, size); - } - } - - void SaveCache() { - const auto cache_dir = Common::FS::GetSuyuPath(Common::FS::SuyuPath::ShaderDir); - std::filesystem::create_directories(cache_dir); - - const auto cache_file = cache_dir / "shader_cache.bin"; - std::ofstream file(cache_file, std::ios::binary | std::ios::trunc); - if (!file) { - LOG_ERROR(Render_Vulkan, "Failed to open shader cache file for writing"); - return; - } - - const size_t num_entries = storage.size(); - file.write(reinterpret_cast(&num_entries), sizeof(num_entries)); - - for (const auto& shader : storage) { - const VAddr addr = shader->addr; - const size_t size = shader->size_bytes; - file.write(reinterpret_cast(&addr), sizeof(addr)); - file.write(reinterpret_cast(&size), sizeof(size)); - file.write(reinterpret_cast(shader.get()), sizeof(ShaderInfo)); - } - } - - void InvalidatePagesInRegion(VAddr addr, size_t size) { - const VAddr addr_end = addr + size; - const u64 page_end = (addr_end + SUYU_PAGESIZE - 1) >> SUYU_PAGEBITS; - for (u64 page = addr >> SUYU_PAGEBITS; page < page_end; ++page) { - auto it = invalidation_cache.find(page); - if (it == invalidation_cache.end()) { - continue; - } - InvalidatePageEntries(it->second, addr, addr_end); - } - } - - void RemovePendingShaders() { - if (marked_for_removal.empty()) { - return; - } - // Remove duplicates - std::sort(marked_for_removal.begin(), marked_for_removal.end()); - marked_for_removal.erase(std::unique(marked_for_removal.begin(), marked_for_removal.end()), - marked_for_removal.end()); - - std::vector removed_shaders; - - std::scoped_lock lock{lookup_mutex}; - for (Entry* const entry : marked_for_removal) { - removed_shaders.push_back(entry->data); - - const auto it = lookup_cache.find(entry->addr_start); - ASSERT(it != lookup_cache.end()); - lookup_cache.erase(it); - } - marked_for_removal.clear(); - - if (!removed_shaders.empty()) { - RemoveShadersFromStorage(removed_shaders); - } - } - - void InvalidatePageEntries(std::vector& entries, VAddr addr, VAddr addr_end) { - size_t index = 0; - while (index < entries.size()) { - Entry* const entry = entries[index]; - if (!entry->Overlaps(addr, addr_end)) { - ++index; - continue; - } - - UnmarkMemory(entry); - RemoveEntryFromInvalidationCache(entry); - marked_for_removal.push_back(entry); - } - } - - void RemoveEntryFromInvalidationCache(const Entry* entry) { - const u64 page_end = (entry->addr_end + SUYU_PAGESIZE - 1) >> SUYU_PAGEBITS; - for (u64 page = entry->addr_start >> SUYU_PAGEBITS; page < page_end; ++page) { - const auto entries_it = invalidation_cache.find(page); - ASSERT(entries_it != invalidation_cache.end()); - std::vector& entries = entries_it->second; - - const auto entry_it = std::find(entries.begin(), entries.end(), entry); - ASSERT(entry_it != entries.end()); - entries.erase(entry_it); - } - } - - void UnmarkMemory(Entry* entry) { - if (!entry->is_memory_marked) { - return; - } - entry->is_memory_marked = false; - - const VAddr addr = entry->addr_start; - const size_t size = entry->addr_end - addr; - device_memory.UpdatePagesCachedCount(addr, size, -1); - } - - void RemoveShadersFromStorage(const std::vector& removed_shaders) { - storage.erase( - std::remove_if(storage.begin(), storage.end(), - [&removed_shaders](const std::unique_ptr& shader) { - return std::find(removed_shaders.begin(), removed_shaders.end(), - shader.get()) != removed_shaders.end(); - }), - storage.end()); - } - - Entry* NewEntry(VAddr addr, VAddr addr_end, ShaderInfo* data) { - auto entry = std::make_unique(Entry{addr, addr_end, data}); - Entry* const entry_pointer = entry.get(); - - lookup_cache.emplace(addr, std::move(entry)); - return entry_pointer; - } - - Tegra::MaxwellDeviceMemoryManager& device_memory; - std::vector> workers; - - mutable std::mutex lookup_mutex; - std::mutex invalidation_mutex; - - std::unordered_map> lookup_cache; - std::unordered_map> invalidation_cache; - std::vector> storage; - std::vector marked_for_removal; -}; - -ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_) - : impl{std::make_unique(device_memory_)} {} - -ShaderCache::~ShaderCache() = default; - void ShaderCache::InvalidateRegion(VAddr addr, size_t size) { - impl->InvalidateRegion(addr, size); + std::scoped_lock lock{invalidation_mutex}; + InvalidatePagesInRegion(addr, size); + RemovePendingShaders(); } void ShaderCache::OnCacheInvalidation(VAddr addr, size_t size) { - impl->OnCacheInvalidation(addr, size); + std::scoped_lock lock{invalidation_mutex}; + InvalidatePagesInRegion(addr, size); } void ShaderCache::SyncGuestHost() { - impl->SyncGuestHost(); + std::scoped_lock lock{invalidation_mutex}; + RemovePendingShaders(); } +ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_) + : device_memory{device_memory_} {} + bool ShaderCache::RefreshStages(std::array& unique_hashes) { - return impl->RefreshStages(unique_hashes); + auto& dirty{maxwell3d->dirty.flags}; + if (!dirty[VideoCommon::Dirty::Shaders]) { + return last_shaders_valid; + } + dirty[VideoCommon::Dirty::Shaders] = false; + + const GPUVAddr base_addr{maxwell3d->regs.program_region.Address()}; + for (size_t index = 0; index < Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram; ++index) { + if (!maxwell3d->regs.IsShaderConfigEnabled(index)) { + unique_hashes[index] = 0; + continue; + } + const auto& shader_config{maxwell3d->regs.pipelines[index]}; + const auto program{static_cast(index)}; + if (program == Tegra::Engines::Maxwell3D::Regs::ShaderType::Pixel && + !maxwell3d->regs.rasterize_enable) { + unique_hashes[index] = 0; + continue; + } + const GPUVAddr shader_addr{base_addr + shader_config.offset}; + const std::optional cpu_shader_addr{gpu_memory->GpuToCpuAddress(shader_addr)}; + if (!cpu_shader_addr) { + LOG_ERROR(HW_GPU, "Invalid GPU address for shader 0x{:016x}", shader_addr); + last_shaders_valid = false; + return false; + } + const ShaderInfo* shader_info{TryGet(*cpu_shader_addr)}; + if (!shader_info) { + const u32 start_address{shader_config.offset}; + GraphicsEnvironment env{*maxwell3d, *gpu_memory, program, base_addr, start_address}; + shader_info = MakeShaderInfo(env, *cpu_shader_addr); + } + shader_infos[index] = shader_info; + unique_hashes[index] = shader_info->unique_hash; + } + last_shaders_valid = true; + return true; } const ShaderInfo* ShaderCache::ComputeShader() { - return impl->ComputeShader(); + const GPUVAddr program_base{kepler_compute->regs.code_loc.Address()}; + const auto& qmd{kepler_compute->launch_description}; + const GPUVAddr shader_addr{program_base + qmd.program_start}; + const std::optional cpu_shader_addr{gpu_memory->GpuToCpuAddress(shader_addr)}; + if (!cpu_shader_addr) { + LOG_ERROR(HW_GPU, "Invalid GPU address for shader 0x{:016x}", shader_addr); + return nullptr; + } + if (const ShaderInfo* const shader = TryGet(*cpu_shader_addr)) { + return shader; + } + ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start}; + return MakeShaderInfo(env, *cpu_shader_addr); } void ShaderCache::GetGraphicsEnvironments(GraphicsEnvironments& result, const std::array& unique_hashes) { - impl->GetGraphicsEnvironments(result, unique_hashes); + size_t env_index{}; + const GPUVAddr base_addr{maxwell3d->regs.program_region.Address()}; + for (size_t index = 0; index < NUM_PROGRAMS; ++index) { + if (unique_hashes[index] == 0) { + continue; + } + const auto program{static_cast(index)}; + auto& env{result.envs[index]}; + const u32 start_address{maxwell3d->regs.pipelines[index].offset}; + env = GraphicsEnvironment{*maxwell3d, *gpu_memory, program, base_addr, start_address}; + env.SetCachedSize(shader_infos[index]->size_bytes); + result.env_ptrs[env_index++] = &env; + } } ShaderInfo* ShaderCache::TryGet(VAddr addr) const { - return impl->TryGet(addr); + std::scoped_lock lock{lookup_mutex}; + + const auto it = lookup_cache.find(addr); + if (it == lookup_cache.end()) { + return nullptr; + } + return it->second->data; } void ShaderCache::Register(std::unique_ptr data, VAddr addr, size_t size) { - impl->Register(std::move(data), addr, size); + std::scoped_lock lock{invalidation_mutex, lookup_mutex}; + + const VAddr addr_end = addr + size; + Entry* const entry = NewEntry(addr, addr_end, data.get()); + + const u64 page_end = (addr_end + SUYU_PAGESIZE - 1) >> SUYU_PAGEBITS; + for (u64 page = addr >> SUYU_PAGEBITS; page < page_end; ++page) { + invalidation_cache[page].push_back(entry); + } + + storage.push_back(std::move(data)); + + device_memory.UpdatePagesCachedCount(addr, size, 1); +} + +void ShaderCache::InvalidatePagesInRegion(VAddr addr, size_t size) { + const VAddr addr_end = addr + size; + const u64 page_end = (addr_end + SUYU_PAGESIZE - 1) >> SUYU_PAGEBITS; + for (u64 page = addr >> SUYU_PAGEBITS; page < page_end; ++page) { + auto it = invalidation_cache.find(page); + if (it == invalidation_cache.end()) { + continue; + } + InvalidatePageEntries(it->second, addr, addr_end); + } +} + +void ShaderCache::RemovePendingShaders() { + if (marked_for_removal.empty()) { + return; + } + // Remove duplicates + std::ranges::sort(marked_for_removal); + marked_for_removal.erase(std::unique(marked_for_removal.begin(), marked_for_removal.end()), + marked_for_removal.end()); + + boost::container::small_vector removed_shaders; + + std::scoped_lock lock{lookup_mutex}; + for (Entry* const entry : marked_for_removal) { + removed_shaders.push_back(entry->data); + + const auto it = lookup_cache.find(entry->addr_start); + ASSERT(it != lookup_cache.end()); + lookup_cache.erase(it); + } + marked_for_removal.clear(); + + if (!removed_shaders.empty()) { + RemoveShadersFromStorage(removed_shaders); + } +} + +void ShaderCache::InvalidatePageEntries(std::vector& entries, VAddr addr, VAddr addr_end) { + size_t index = 0; + while (index < entries.size()) { + Entry* const entry = entries[index]; + if (!entry->Overlaps(addr, addr_end)) { + ++index; + continue; + } + + UnmarkMemory(entry); + RemoveEntryFromInvalidationCache(entry); + marked_for_removal.push_back(entry); + } +} + +void ShaderCache::RemoveEntryFromInvalidationCache(const Entry* entry) { + const u64 page_end = (entry->addr_end + SUYU_PAGESIZE - 1) >> SUYU_PAGEBITS; + for (u64 page = entry->addr_start >> SUYU_PAGEBITS; page < page_end; ++page) { + const auto entries_it = invalidation_cache.find(page); + ASSERT(entries_it != invalidation_cache.end()); + std::vector& entries = entries_it->second; + + const auto entry_it = std::ranges::find(entries, entry); + ASSERT(entry_it != entries.end()); + entries.erase(entry_it); + } +} + +void ShaderCache::UnmarkMemory(Entry* entry) { + if (!entry->is_memory_marked) { + return; + } + entry->is_memory_marked = false; + + const VAddr addr = entry->addr_start; + const size_t size = entry->addr_end - addr; + device_memory.UpdatePagesCachedCount(addr, size, -1); +} + +void ShaderCache::RemoveShadersFromStorage(std::span removed_shaders) { + // Remove them from the cache + std::erase_if(storage, [&removed_shaders](const std::unique_ptr& shader) { + return std::ranges::find(removed_shaders, shader.get()) != removed_shaders.end(); + }); +} + +ShaderCache::Entry* ShaderCache::NewEntry(VAddr addr, VAddr addr_end, ShaderInfo* data) { + auto entry = std::make_unique(Entry{addr, addr_end, data}); + Entry* const entry_pointer = entry.get(); + + lookup_cache.emplace(addr, std::move(entry)); + return entry_pointer; +} + +const ShaderInfo* ShaderCache::MakeShaderInfo(GenericEnvironment& env, VAddr cpu_addr) { + auto info = std::make_unique(); + if (const std::optional cached_hash{env.Analyze()}) { + info->unique_hash = *cached_hash; + info->size_bytes = env.CachedSizeBytes(); + } else { + // Slow path, not really hit on commercial games + // Build a control flow graph to get the real shader size + Shader::ObjectPool flow_block; + Shader::Maxwell::Flow::CFG cfg{env, flow_block, env.StartAddress()}; + info->unique_hash = env.CalculateHash(); + info->size_bytes = env.ReadSizeBytes(); + } + const size_t size_bytes{info->size_bytes}; + const ShaderInfo* const result{info.get()}; + Register(std::move(info), cpu_addr, size_bytes); + return result; } } // namespace VideoCommon From c52427b67681525be78bd4794e45ed5c8858004b Mon Sep 17 00:00:00 2001 From: Samuliak Date: Sat, 5 Oct 2024 08:04:46 +0200 Subject: [PATCH 22/26] mark format functions as const --- src/common/logging/formatter.h | 2 +- src/common/typed_address.h | 6 +++--- src/core/arm/dynarmic/dynarmic_cp15.cpp | 2 +- src/core/hle/service/psc/time/common.h | 4 ++-- src/shader_recompiler/backend/glasm/reg_alloc.h | 14 +++++++------- src/shader_recompiler/frontend/ir/attribute.h | 2 +- src/shader_recompiler/frontend/ir/condition.h | 2 +- src/shader_recompiler/frontend/ir/flow_test.h | 2 +- src/shader_recompiler/frontend/ir/opcodes.h | 4 ++-- src/shader_recompiler/frontend/ir/pred.h | 2 +- src/shader_recompiler/frontend/ir/reg.h | 2 +- src/shader_recompiler/frontend/ir/type.h | 2 +- src/shader_recompiler/frontend/maxwell/location.h | 2 +- src/shader_recompiler/frontend/maxwell/opcodes.h | 2 +- src/video_core/texture_cache/formatter.h | 6 +++--- 15 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/common/logging/formatter.h b/src/common/logging/formatter.h index 88e55505de..16bddde8fe 100644 --- a/src/common/logging/formatter.h +++ b/src/common/logging/formatter.h @@ -14,7 +14,7 @@ template struct fmt::formatter, char>> : formatter> { template - auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) { + auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) { return fmt::formatter>::format( static_cast>(value), ctx); } diff --git a/src/common/typed_address.h b/src/common/typed_address.h index d5e743583d..a1deb89a04 100644 --- a/src/common/typed_address.h +++ b/src/common/typed_address.h @@ -262,7 +262,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Common::PhysicalAddress& addr, FormatContext& ctx) { + auto format(const Common::PhysicalAddress& addr, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "{:#x}", static_cast(addr.GetValue())); } }; @@ -273,7 +273,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Common::ProcessAddress& addr, FormatContext& ctx) { + auto format(const Common::ProcessAddress& addr, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "{:#x}", static_cast(addr.GetValue())); } }; @@ -284,7 +284,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Common::VirtualAddress& addr, FormatContext& ctx) { + auto format(const Common::VirtualAddress& addr, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "{:#x}", static_cast(addr.GetValue())); } }; diff --git a/src/core/arm/dynarmic/dynarmic_cp15.cpp b/src/core/arm/dynarmic/dynarmic_cp15.cpp index f3eee0d42a..ee97ac6395 100644 --- a/src/core/arm/dynarmic/dynarmic_cp15.cpp +++ b/src/core/arm/dynarmic/dynarmic_cp15.cpp @@ -22,7 +22,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Dynarmic::A32::CoprocReg& reg, FormatContext& ctx) { + auto format(const Dynarmic::A32::CoprocReg& reg, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "cp{}", static_cast(reg)); } }; diff --git a/src/core/hle/service/psc/time/common.h b/src/core/hle/service/psc/time/common.h index 3e13144a0d..5474e5a0c9 100644 --- a/src/core/hle/service/psc/time/common.h +++ b/src/core/hle/service/psc/time/common.h @@ -167,7 +167,7 @@ constexpr inline Result GetSpanBetweenTimePoints(s64* out_seconds, const SteadyC template <> struct fmt::formatter : fmt::formatter { template - auto format(Service::PSC::Time::TimeType type, FormatContext& ctx) { + auto format(Service::PSC::Time::TimeType type, FormatContext& ctx) const { const string_view name = [type] { using Service::PSC::Time::TimeType; switch (type) { @@ -270,4 +270,4 @@ struct fmt::formatter time_point.rtc_offset, time_point.diff_scale, time_point.shift_amount, time_point.lower, time_point.upper); } -}; \ No newline at end of file +}; diff --git a/src/shader_recompiler/backend/glasm/reg_alloc.h b/src/shader_recompiler/backend/glasm/reg_alloc.h index bd6e2d929c..207a075f19 100644 --- a/src/shader_recompiler/backend/glasm/reg_alloc.h +++ b/src/shader_recompiler/backend/glasm/reg_alloc.h @@ -184,7 +184,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(Shader::Backend::GLASM::Id id, FormatContext& ctx) { + auto format(Shader::Backend::GLASM::Id id, FormatContext& ctx) const { return Shader::Backend::GLASM::FormatTo(ctx, id); } }; @@ -195,7 +195,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::Backend::GLASM::Register& value, FormatContext& ctx) { + auto format(const Shader::Backend::GLASM::Register& value, FormatContext& ctx) const { if (value.type != Shader::Backend::GLASM::Type::Register) { throw Shader::InvalidArgument("Register value type is not register"); } @@ -209,7 +209,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::Backend::GLASM::ScalarRegister& value, FormatContext& ctx) { + auto format(const Shader::Backend::GLASM::ScalarRegister& value, FormatContext& ctx) const { if (value.type != Shader::Backend::GLASM::Type::Register) { throw Shader::InvalidArgument("Register value type is not register"); } @@ -223,7 +223,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::Backend::GLASM::ScalarU32& value, FormatContext& ctx) { + auto format(const Shader::Backend::GLASM::ScalarU32& value, FormatContext& ctx) const { switch (value.type) { case Shader::Backend::GLASM::Type::Void: break; @@ -244,7 +244,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::Backend::GLASM::ScalarS32& value, FormatContext& ctx) { + auto format(const Shader::Backend::GLASM::ScalarS32& value, FormatContext& ctx) const { switch (value.type) { case Shader::Backend::GLASM::Type::Void: break; @@ -265,7 +265,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::Backend::GLASM::ScalarF32& value, FormatContext& ctx) { + auto format(const Shader::Backend::GLASM::ScalarF32& value, FormatContext& ctx) const { switch (value.type) { case Shader::Backend::GLASM::Type::Void: break; @@ -286,7 +286,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::Backend::GLASM::ScalarF64& value, FormatContext& ctx) { + auto format(const Shader::Backend::GLASM::ScalarF64& value, FormatContext& ctx) const { switch (value.type) { case Shader::Backend::GLASM::Type::Void: break; diff --git a/src/shader_recompiler/frontend/ir/attribute.h b/src/shader_recompiler/frontend/ir/attribute.h index 5f039b6f65..407a1f4bc1 100644 --- a/src/shader_recompiler/frontend/ir/attribute.h +++ b/src/shader_recompiler/frontend/ir/attribute.h @@ -250,7 +250,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::IR::Attribute& attribute, FormatContext& ctx) { + auto format(const Shader::IR::Attribute& attribute, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(attribute)); } }; diff --git a/src/shader_recompiler/frontend/ir/condition.h b/src/shader_recompiler/frontend/ir/condition.h index 1cad46b9b9..0b77b6590b 100644 --- a/src/shader_recompiler/frontend/ir/condition.h +++ b/src/shader_recompiler/frontend/ir/condition.h @@ -52,7 +52,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::IR::Condition& cond, FormatContext& ctx) { + auto format(const Shader::IR::Condition& cond, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(cond)); } }; diff --git a/src/shader_recompiler/frontend/ir/flow_test.h b/src/shader_recompiler/frontend/ir/flow_test.h index 88f7c9e82e..f758d13127 100644 --- a/src/shader_recompiler/frontend/ir/flow_test.h +++ b/src/shader_recompiler/frontend/ir/flow_test.h @@ -55,7 +55,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::IR::FlowTest& flow_test, FormatContext& ctx) { + auto format(const Shader::IR::FlowTest& flow_test, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(flow_test)); } }; diff --git a/src/shader_recompiler/frontend/ir/opcodes.h b/src/shader_recompiler/frontend/ir/opcodes.h index e300714f3a..767c5ae15a 100644 --- a/src/shader_recompiler/frontend/ir/opcodes.h +++ b/src/shader_recompiler/frontend/ir/opcodes.h @@ -54,7 +54,7 @@ constexpr Type F64x2{Type::F64x2}; constexpr Type F64x3{Type::F64x3}; constexpr Type F64x4{Type::F64x4}; -constexpr OpcodeMeta META_TABLE[]{ +constexpr OpcodeMeta META_TABLE[] { #define OPCODE(name_token, type_token, ...) \ { \ .name{#name_token}, \ @@ -103,7 +103,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::IR::Opcode& op, FormatContext& ctx) { + auto format(const Shader::IR::Opcode& op, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(op)); } }; diff --git a/src/shader_recompiler/frontend/ir/pred.h b/src/shader_recompiler/frontend/ir/pred.h index a77c1e2a7a..f3f92b063a 100644 --- a/src/shader_recompiler/frontend/ir/pred.h +++ b/src/shader_recompiler/frontend/ir/pred.h @@ -33,7 +33,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::IR::Pred& pred, FormatContext& ctx) { + auto format(const Shader::IR::Pred& pred, FormatContext& ctx) const { if (pred == Shader::IR::Pred::PT) { return fmt::format_to(ctx.out(), "PT"); } else { diff --git a/src/shader_recompiler/frontend/ir/reg.h b/src/shader_recompiler/frontend/ir/reg.h index f7cb716a97..610492759d 100644 --- a/src/shader_recompiler/frontend/ir/reg.h +++ b/src/shader_recompiler/frontend/ir/reg.h @@ -319,7 +319,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::IR::Reg& reg, FormatContext& ctx) { + auto format(const Shader::IR::Reg& reg, FormatContext& ctx) const { if (reg == Shader::IR::Reg::RZ) { return fmt::format_to(ctx.out(), "RZ"); } else if (static_cast(reg) >= 0 && static_cast(reg) < 255) { diff --git a/src/shader_recompiler/frontend/ir/type.h b/src/shader_recompiler/frontend/ir/type.h index 04c8c4ddbe..17b520c6dd 100644 --- a/src/shader_recompiler/frontend/ir/type.h +++ b/src/shader_recompiler/frontend/ir/type.h @@ -54,7 +54,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::IR::Type& type, FormatContext& ctx) { + auto format(const Shader::IR::Type& type, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "{}", NameOf(type)); } }; diff --git a/src/shader_recompiler/frontend/maxwell/location.h b/src/shader_recompiler/frontend/maxwell/location.h index 0c0477e2db..0dd16723a2 100644 --- a/src/shader_recompiler/frontend/maxwell/location.h +++ b/src/shader_recompiler/frontend/maxwell/location.h @@ -102,7 +102,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::Maxwell::Location& location, FormatContext& ctx) { + auto format(const Shader::Maxwell::Location& location, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "{:04x}", location.Offset()); } }; diff --git a/src/shader_recompiler/frontend/maxwell/opcodes.h b/src/shader_recompiler/frontend/maxwell/opcodes.h index 72dd143c2a..b3a493ff6a 100644 --- a/src/shader_recompiler/frontend/maxwell/opcodes.h +++ b/src/shader_recompiler/frontend/maxwell/opcodes.h @@ -23,7 +23,7 @@ struct fmt::formatter { return ctx.begin(); } template - auto format(const Shader::Maxwell::Opcode& opcode, FormatContext& ctx) { + auto format(const Shader::Maxwell::Opcode& opcode, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "{}", NameOf(opcode)); } }; diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h index cabbfcb2dd..ea1c2df791 100644 --- a/src/video_core/texture_cache/formatter.h +++ b/src/video_core/texture_cache/formatter.h @@ -13,7 +13,7 @@ template <> struct fmt::formatter : fmt::formatter { template - auto format(VideoCore::Surface::PixelFormat format, FormatContext& ctx) { + auto format(VideoCore::Surface::PixelFormat format, FormatContext& ctx) const { using VideoCore::Surface::PixelFormat; const string_view name = [format] { switch (format) { @@ -234,7 +234,7 @@ struct fmt::formatter : fmt::formatter struct fmt::formatter : fmt::formatter { template - auto format(VideoCommon::ImageType type, FormatContext& ctx) { + auto format(VideoCommon::ImageType type, FormatContext& ctx) const { const string_view name = [type] { using VideoCommon::ImageType; switch (type) { @@ -262,7 +262,7 @@ struct fmt::formatter { } template - auto format(const VideoCommon::Extent3D& extent, FormatContext& ctx) { + auto format(const VideoCommon::Extent3D& extent, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "{{{}, {}, {}}}", extent.width, extent.height, extent.depth); } From 40def7017cbb564fcd80e4724c4fa83f2fdc2f64 Mon Sep 17 00:00:00 2001 From: Samuliak Date: Sat, 5 Oct 2024 09:10:15 +0200 Subject: [PATCH 23/26] include fmt/ranges.h --- src/core/debugger/gdbstub.cpp | 1 + src/core/file_sys/system_archive/ng_word.cpp | 2 +- src/core/hle/service/nfc/common/device.cpp | 1 + src/suyu/main.cpp | 2 ++ 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index 27d45fca5f..22bc71e4d4 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp @@ -9,6 +9,7 @@ #include #include +#include #include "common/hex_util.h" #include "common/logging/log.h" diff --git a/src/core/file_sys/system_archive/ng_word.cpp b/src/core/file_sys/system_archive/ng_word.cpp index 13ae1999ee..61b0ed2f7b 100644 --- a/src/core/file_sys/system_archive/ng_word.cpp +++ b/src/core/file_sys/system_archive/ng_word.cpp @@ -10,7 +10,7 @@ namespace FileSys::SystemArchive { namespace NgWord1Data { -constexpr std::size_t NUMBER_WORD_TXT_FILES = 0x10; +[[maybe_unused]] constexpr std::size_t NUMBER_WORD_TXT_FILES = 0x10; // Should this archive replacement mysteriously not work on a future game, consider updating. constexpr std::array VERSION_DAT{0x0, 0x0, 0x0, 0x20}; // 11.0.1 System Version diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index bf29bb354e..652dff0457 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp @@ -15,6 +15,7 @@ #endif #include +#include #include "common/fs/file.h" #include "common/fs/fs.h" diff --git a/src/suyu/main.cpp b/src/suyu/main.cpp index fbdea66e78..991ff67768 100644 --- a/src/suyu/main.cpp +++ b/src/suyu/main.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include "core/hle/service/am/applet_manager.h" #include "core/loader/nca.h" #include "core/loader/nro.h" From 26b1d7e87957332198a8b1a3a45929f32ad58a01 Mon Sep 17 00:00:00 2001 From: Samuliak Date: Sat, 5 Oct 2024 12:35:09 +0200 Subject: [PATCH 24/26] enable boost concepts --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cbeb2ee689..67bdf6afe9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -279,8 +279,6 @@ endif() # Configure C++ standard # =========================== -# boost asio's concept usage doesn't play nicely with some compilers yet. -add_definitions(-DBOOST_ASIO_DISABLE_CONCEPTS) if (MSVC) add_compile_options($<$:/std:c++20>) From 27769c595bc63b4095997dc72d734f822a31e80f Mon Sep 17 00:00:00 2001 From: chaphidoesstuff Date: Sun, 6 Oct 2024 11:36:27 +0200 Subject: [PATCH 25/26] Restored Hyperlink to sudachi's website now it is back up --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e2cfaa9ded..b3ee2167e3 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ You can also contact any of the developers on the Chat to learn more about the c * __Linux__: [Releases](https://git.suyu.dev/suyu/suyu/releases) * __macOS__: [Releases](https://git.suyu.dev/suyu/suyu/releases) * __Android__: [Releases](https://git.suyu.dev/suyu/suyu/releases) -###### We currently do not provide builds for iOS, however if you would like, you could try the experimental Sudachi Emulator and it's bigger project: [Folium](https://apps.apple.com/us/app/folium/id6498623389). +###### We currently do not provide builds for iOS, however if you would like, you could try the experimental [Sudachi Emulator](https://sudachi.emuplace.app/) and it's bigger project: [Folium](https://apps.apple.com/us/app/folium/id6498623389). If you want daily builds then [Click here](https://git.suyu.dev/suyu/suyu/actions). If you don't know how to download the daily builds then [Click here](https://git.suyu.dev/suyu/suyu/raw/branch/dev/img/daily-builds.png) From ee365bad9501c73ff49936e72ec91cd9c3ce5c24 Mon Sep 17 00:00:00 2001 From: chaphidoesstuff Date: Sun, 6 Oct 2024 11:40:15 +0200 Subject: [PATCH 26/26] Fixed missing reddit hyperlink all chat and reddit mentions are hyperlinked, but one mention of the subreddit wasn't, so I hyperlinked it like the others --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b3ee2167e3..6211f5f1c2 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ For Multiplayer, we recommend using the "Yuzu Online" patch, install instruction ## Support -If you have any questions, don't hesitate to ask us in our [Chat](https://chat.suyu.dev) or Subreddit, make an issue or contact a developer. We don't bite! +If you have any questions, don't hesitate to ask us in our [Chat](https://chat.suyu.dev) or [Subreddit](https://www.reddit.com/r/suyu/), make an issue or contact a developer. We don't bite! ## License