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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Π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ń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 Ib4q9e0PvaX-T(jq literal 0 HcmV?d00001 From e5c47e911b2e94bc2864db915ed259af1ee9a765 Mon Sep 17 00:00:00 2001 From: chad_mcguffin 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