diff options
126 files changed, 3597 insertions, 1382 deletions
diff --git a/dist/license.md b/dist/license.md new file mode 100644 index 000000000..b777ebb20 --- /dev/null +++ b/dist/license.md @@ -0,0 +1,31 @@ +The icons in this folder and its subfolders have the following licenses: + +Icon Name | License | Origin/Author +--- | --- | --- +qt_themes/default/icons/16x16/checked.png | Free for non-commercial use +qt_themes/default/icons/16x16/failed.png | Free for non-commercial use +qt_themes/default/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/default/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/default/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/default/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/default/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team +qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/qdarkstyle/icons/16x16/checked.png | Free for non-commercial use +qt_themes/qdarkstyle/icons/16x16/failed.png | Free for non-commercial use +qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/qdarkstyle/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/qdarkstyle/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/qdarkstyle/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/qdarkstyle/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team +qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/colorful/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/colorful/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/colorful/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/colorful/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/colorful/icons/48x48/plus.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/colorful/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com + +<!-- TODO: Add the license of the yuzu icon -->
\ No newline at end of file diff --git a/dist/qt_themes/colorful/icons/16x16/lock.png b/dist/qt_themes/colorful/icons/16x16/lock.png Binary files differnew file mode 100644 index 000000000..fd27069d8 --- /dev/null +++ b/dist/qt_themes/colorful/icons/16x16/lock.png diff --git a/dist/qt_themes/colorful/icons/256x256/plus_folder.png b/dist/qt_themes/colorful/icons/256x256/plus_folder.png Binary files differnew file mode 100644 index 000000000..760fe6245 --- /dev/null +++ b/dist/qt_themes/colorful/icons/256x256/plus_folder.png diff --git a/dist/qt_themes/colorful/icons/48x48/bad_folder.png b/dist/qt_themes/colorful/icons/48x48/bad_folder.png Binary files differnew file mode 100644 index 000000000..a7ab7a1f6 --- /dev/null +++ b/dist/qt_themes/colorful/icons/48x48/bad_folder.png diff --git a/dist/qt_themes/colorful/icons/48x48/chip.png b/dist/qt_themes/colorful/icons/48x48/chip.png Binary files differnew file mode 100644 index 000000000..6fa158999 --- /dev/null +++ b/dist/qt_themes/colorful/icons/48x48/chip.png diff --git a/dist/qt_themes/colorful/icons/48x48/folder.png b/dist/qt_themes/colorful/icons/48x48/folder.png Binary files differnew file mode 100644 index 000000000..498de4c62 --- /dev/null +++ b/dist/qt_themes/colorful/icons/48x48/folder.png diff --git a/dist/qt_themes/colorful/icons/48x48/plus.png b/dist/qt_themes/colorful/icons/48x48/plus.png Binary files differnew file mode 100644 index 000000000..bc2c47c91 --- /dev/null +++ b/dist/qt_themes/colorful/icons/48x48/plus.png diff --git a/dist/qt_themes/colorful/icons/48x48/sd_card.png b/dist/qt_themes/colorful/icons/48x48/sd_card.png Binary files differnew file mode 100644 index 000000000..29be71a0d --- /dev/null +++ b/dist/qt_themes/colorful/icons/48x48/sd_card.png diff --git a/dist/qt_themes/colorful/icons/index.theme b/dist/qt_themes/colorful/icons/index.theme new file mode 100644 index 000000000..b452aca16 --- /dev/null +++ b/dist/qt_themes/colorful/icons/index.theme @@ -0,0 +1,14 @@ +[Icon Theme] +Name=colorful +Comment=Colorful theme +Inherits=default +Directories=16x16,48x48,256x256 + +[16x16] +Size=16 + +[48x48] +Size=48 + +[256x256] +Size=256 diff --git a/dist/qt_themes/colorful/style.qrc b/dist/qt_themes/colorful/style.qrc new file mode 100644 index 000000000..af2f3fd56 --- /dev/null +++ b/dist/qt_themes/colorful/style.qrc @@ -0,0 +1,15 @@ +<RCC> + <qresource prefix="icons/colorful"> + <file alias="index.theme">icons/index.theme</file> + <file alias="16x16/lock.png">icons/16x16/lock.png</file> + <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file> + <file alias="48x48/chip.png">icons/48x48/chip.png</file> + <file alias="48x48/folder.png">icons/48x48/folder.png</file> + <file alias="48x48/plus.png">icons/48x48/plus.png</file> + <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> + <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> + </qresource> + <qresource prefix="colorful"> + <file>style.qss</file> + </qresource> +</RCC> diff --git a/dist/qt_themes/colorful/style.qss b/dist/qt_themes/colorful/style.qss new file mode 100644 index 000000000..413fc81da --- /dev/null +++ b/dist/qt_themes/colorful/style.qss @@ -0,0 +1,4 @@ +/* + This file is intentionally left blank. + We do not want to apply any stylesheet for colorful, only icons. +*/ diff --git a/dist/qt_themes/colorful_dark/icons/16x16/lock.png b/dist/qt_themes/colorful_dark/icons/16x16/lock.png Binary files differnew file mode 100644 index 000000000..32c505848 --- /dev/null +++ b/dist/qt_themes/colorful_dark/icons/16x16/lock.png diff --git a/dist/qt_themes/colorful_dark/icons/index.theme b/dist/qt_themes/colorful_dark/icons/index.theme new file mode 100644 index 000000000..94d5ae8aa --- /dev/null +++ b/dist/qt_themes/colorful_dark/icons/index.theme @@ -0,0 +1,8 @@ +[Icon Theme] +Name=colorful_dark +Comment=Colorful theme (Dark style) +Inherits=default +Directories=16x16 + +[16x16] +Size=16 diff --git a/dist/qt_themes/colorful_dark/style.qrc b/dist/qt_themes/colorful_dark/style.qrc new file mode 100644 index 000000000..27a6cc87d --- /dev/null +++ b/dist/qt_themes/colorful_dark/style.qrc @@ -0,0 +1,57 @@ +<RCC> + <qresource prefix="icons/colorful_dark"> + <file alias="index.theme">icons/index.theme</file> + <file alias="16x16/lock.png">icons/16x16/lock.png</file> + <file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file> + <file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file> + <file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file> + <file alias="48x48/plus.png">../colorful/icons/48x48/plus.png</file> + <file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file> + <file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file> + </qresource> + + <qresource prefix="qss_icons"> + <file alias="rc/up_arrow_disabled.png">../qdarkstyle/rc/up_arrow_disabled.png</file> + <file alias="rc/Hmovetoolbar.png">../qdarkstyle/rc/Hmovetoolbar.png</file> + <file alias="rc/stylesheet-branch-end.png">../qdarkstyle/rc/stylesheet-branch-end.png</file> + <file alias="rc/branch_closed-on.png">../qdarkstyle/rc/branch_closed-on.png</file> + <file alias="rc/stylesheet-vline.png">../qdarkstyle/rc/stylesheet-vline.png</file> + <file alias="rc/branch_closed.png">../qdarkstyle/rc/branch_closed.png</file> + <file alias="rc/branch_open-on.png">../qdarkstyle/rc/branch_open-on.png</file> + <file alias="rc/transparent.png">../qdarkstyle/rc/transparent.png</file> + <file alias="rc/right_arrow_disabled.png">../qdarkstyle/rc/right_arrow_disabled.png</file> + <file alias="rc/sizegrip.png">../qdarkstyle/rc/sizegrip.png</file> + <file alias="rc/close.png">../qdarkstyle/rc/close.png</file> + <file alias="rc/close-hover.png">../qdarkstyle/rc/close-hover.png</file> + <file alias="rc/close-pressed.png">../qdarkstyle/rc/close-pressed.png</file> + <file alias="rc/down_arrow.png">../qdarkstyle/rc/down_arrow.png</file> + <file alias="rc/Vmovetoolbar.png">../qdarkstyle/rc/Vmovetoolbar.png</file> + <file alias="rc/left_arrow.png">../qdarkstyle/rc/left_arrow.png</file> + <file alias="rc/stylesheet-branch-more.png">../qdarkstyle/rc/stylesheet-branch-more.png</file> + <file alias="rc/up_arrow.png">../qdarkstyle/rc/up_arrow.png</file> + <file alias="rc/right_arrow.png">../qdarkstyle/rc/right_arrow.png</file> + <file alias="rc/left_arrow_disabled.png">../qdarkstyle/rc/left_arrow_disabled.png</file> + <file alias="rc/Hsepartoolbar.png">../qdarkstyle/rc/Hsepartoolbar.png</file> + <file alias="rc/branch_open.png">../qdarkstyle/rc/branch_open.png</file> + <file alias="rc/Vsepartoolbar.png">../qdarkstyle/rc/Vsepartoolbar.png</file> + <file alias="rc/down_arrow_disabled.png">../qdarkstyle/rc/down_arrow_disabled.png</file> + <file alias="rc/undock.png">../qdarkstyle/rc/undock.png</file> + <file alias="rc/checkbox_checked_disabled.png">../qdarkstyle/rc/checkbox_checked_disabled.png</file> + <file alias="rc/checkbox_checked_focus.png">../qdarkstyle/rc/checkbox_checked_focus.png</file> + <file alias="rc/checkbox_checked.png">../qdarkstyle/rc/checkbox_checked.png</file> + <file alias="rc/checkbox_indeterminate.png">../qdarkstyle/rc/checkbox_indeterminate.png</file> + <file alias="rc/checkbox_indeterminate_focus.png">../qdarkstyle/rc/checkbox_indeterminate_focus.png</file> + <file alias="rc/checkbox_unchecked_disabled.png">../qdarkstyle/rc/checkbox_unchecked_disabled.png</file> + <file alias="rc/checkbox_unchecked_focus.png">../qdarkstyle/rc/checkbox_unchecked_focus.png</file> + <file alias="rc/checkbox_unchecked.png">../qdarkstyle/rc/checkbox_unchecked.png</file> + <file alias="rc/radio_checked_disabled.png">../qdarkstyle/rc/radio_checked_disabled.png</file> + <file alias="rc/radio_checked_focus.png">../qdarkstyle/rc/radio_checked_focus.png</file> + <file alias="rc/radio_checked.png">../qdarkstyle/rc/radio_checked.png</file> + <file alias="rc/radio_unchecked_disabled.png">../qdarkstyle/rc/radio_unchecked_disabled.png</file> + <file alias="rc/radio_unchecked_focus.png">../qdarkstyle/rc/radio_unchecked_focus.png</file> + <file alias="rc/radio_unchecked.png">../qdarkstyle/rc/radio_unchecked.png</file> + </qresource> + <qresource prefix="colorful_dark"> + <file alias="style.qss">../qdarkstyle/style.qss</file> + </qresource> +</RCC> diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc index 14a0cf6f9..d1a0ee1be 100644 --- a/dist/qt_themes/default/default.qrc +++ b/dist/qt_themes/default/default.qrc @@ -5,7 +5,21 @@ <file alias="16x16/checked.png">icons/16x16/checked.png</file> <file alias="16x16/failed.png">icons/16x16/failed.png</file> + + <file alias="16x16/lock.png">icons/16x16/lock.png</file> + + <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file> + + <file alias="48x48/chip.png">icons/48x48/chip.png</file> + + <file alias="48x48/folder.png">icons/48x48/folder.png</file> + + <file alias="48x48/plus.png">icons/48x48/plus.png</file> + + <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file> + + <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> </qresource> </RCC> diff --git a/dist/qt_themes/default/icons/16x16/lock.png b/dist/qt_themes/default/icons/16x16/lock.png Binary files differnew file mode 100644 index 000000000..496b58078 --- /dev/null +++ b/dist/qt_themes/default/icons/16x16/lock.png diff --git a/dist/qt_themes/default/icons/256x256/plus_folder.png b/dist/qt_themes/default/icons/256x256/plus_folder.png Binary files differnew file mode 100644 index 000000000..ae4afccc7 --- /dev/null +++ b/dist/qt_themes/default/icons/256x256/plus_folder.png diff --git a/dist/qt_themes/default/icons/48x48/bad_folder.png b/dist/qt_themes/default/icons/48x48/bad_folder.png Binary files differnew file mode 100644 index 000000000..2527c1318 --- /dev/null +++ b/dist/qt_themes/default/icons/48x48/bad_folder.png diff --git a/dist/qt_themes/default/icons/48x48/chip.png b/dist/qt_themes/default/icons/48x48/chip.png Binary files differnew file mode 100644 index 000000000..3efdf301e --- /dev/null +++ b/dist/qt_themes/default/icons/48x48/chip.png diff --git a/dist/qt_themes/default/icons/48x48/folder.png b/dist/qt_themes/default/icons/48x48/folder.png Binary files differnew file mode 100644 index 000000000..2e67d8b38 --- /dev/null +++ b/dist/qt_themes/default/icons/48x48/folder.png diff --git a/dist/qt_themes/default/icons/48x48/plus.png b/dist/qt_themes/default/icons/48x48/plus.png Binary files differnew file mode 100644 index 000000000..dbc74687b --- /dev/null +++ b/dist/qt_themes/default/icons/48x48/plus.png diff --git a/dist/qt_themes/default/icons/48x48/sd_card.png b/dist/qt_themes/default/icons/48x48/sd_card.png Binary files differnew file mode 100644 index 000000000..edacaeeb5 --- /dev/null +++ b/dist/qt_themes/default/icons/48x48/sd_card.png diff --git a/dist/qt_themes/default/icons/index.theme b/dist/qt_themes/default/icons/index.theme index ac67cb236..1edbe6408 100644 --- a/dist/qt_themes/default/icons/index.theme +++ b/dist/qt_themes/default/icons/index.theme @@ -1,10 +1,13 @@ [Icon Theme] Name=default Comment=default theme -Directories=16x16,256x256 +Directories=16x16,48x48,256x256 [16x16] Size=16 + +[48x48] +Size=48 [256x256] Size=256
\ No newline at end of file diff --git a/dist/qt_themes/qdarkstyle/icons/16x16/lock.png b/dist/qt_themes/qdarkstyle/icons/16x16/lock.png Binary files differnew file mode 100644 index 000000000..c750a39e8 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/icons/16x16/lock.png diff --git a/dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.png b/dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.png Binary files differnew file mode 100644 index 000000000..303f9a321 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.png diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/bad_folder.png b/dist/qt_themes/qdarkstyle/icons/48x48/bad_folder.png Binary files differnew file mode 100644 index 000000000..4a9709623 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/icons/48x48/bad_folder.png diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/chip.png b/dist/qt_themes/qdarkstyle/icons/48x48/chip.png Binary files differnew file mode 100644 index 000000000..973fabd05 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/icons/48x48/chip.png diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/folder.png b/dist/qt_themes/qdarkstyle/icons/48x48/folder.png Binary files differnew file mode 100644 index 000000000..0f1e987d6 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/icons/48x48/folder.png diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/plus.png b/dist/qt_themes/qdarkstyle/icons/48x48/plus.png Binary files differnew file mode 100644 index 000000000..16cc8b4f4 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/icons/48x48/plus.png diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png b/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png Binary files differnew file mode 100644 index 000000000..0291c6542 --- /dev/null +++ b/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png diff --git a/dist/qt_themes/qdarkstyle/icons/index.theme b/dist/qt_themes/qdarkstyle/icons/index.theme index 558ece40b..d1e12f3ef 100644 --- a/dist/qt_themes/qdarkstyle/icons/index.theme +++ b/dist/qt_themes/qdarkstyle/icons/index.theme @@ -2,10 +2,13 @@ Name=qdarkstyle Comment=dark theme Inherits=default -Directories=16x16,256x256 +Directories=16x16,48x48,256x256 [16x16] Size=16 - + +[48x48] +Size=48 + [256x256] Size=256
\ No newline at end of file diff --git a/dist/qt_themes/qdarkstyle/style.qrc b/dist/qt_themes/qdarkstyle/style.qrc index efbd0b9dc..c2c14c28a 100644 --- a/dist/qt_themes/qdarkstyle/style.qrc +++ b/dist/qt_themes/qdarkstyle/style.qrc @@ -1,6 +1,13 @@ <RCC> <qresource prefix="icons/qdarkstyle"> <file alias="index.theme">icons/index.theme</file> + <file alias="16x16/lock.png">icons/16x16/lock.png</file> + <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file> + <file alias="48x48/chip.png">icons/48x48/chip.png</file> + <file alias="48x48/folder.png">icons/48x48/folder.png</file> + <file alias="48x48/plus.png">icons/48x48/plus.png</file> + <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> + <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> </qresource> <qresource prefix="qss_icons"> <file>rc/up_arrow_disabled.png</file> diff --git a/externals/Vulkan-Headers b/externals/Vulkan-Headers -Subproject d05c8df88da98ec1ab3bc600d7f5783b4060895 +Subproject fd568d51ed3d9bc6132e1639d7492453a08fe1b diff --git a/externals/fmt b/externals/fmt -Subproject 9e554999ce02cf86fcdfe74fe740c4fe3f5a56d +Subproject 7512a55aa3ae309587ca89668ef9ec4074a51a1 diff --git a/license.txt b/license.txt index d511905c1..2b858f9a7 100644 --- a/license.txt +++ b/license.txt @@ -337,3 +337,19 @@ proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. + + +The icons used in this project have the following licenses: + +Icon Name | License | Origin/Author +--- | --- | --- +checked.png | Free for non-commercial use +failed.png | Free for non-commercial use +lock.png | CC BY-ND 3.0 | https://icons8.com +plus_folder.png | CC BY-ND 3.0 | https://icons8.com +bad_folder.png | CC BY-ND 3.0 | https://icons8.com +chip.png | CC BY-ND 3.0 | https://icons8.com +folder.png | CC BY-ND 3.0 | https://icons8.com +plus.png (Default, Dark) | CC0 1.0 | Designed by BreadFish64 from the Citra team +plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com +sd_card.png | CC BY-ND 3.0 | https://icons8.com diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 5462decee..877a9e353 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -70,6 +70,8 @@ add_library(core STATIC file_sys/sdmc_factory.h file_sys/submission_package.cpp file_sys/submission_package.h + file_sys/system_archive/mii_model.cpp + file_sys/system_archive/mii_model.h file_sys/system_archive/ng_word.cpp file_sys/system_archive/ng_word.h file_sys/system_archive/system_archive.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index 20d64f3b0..3d0978cbf 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -104,7 +104,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, return vfs->OpenFile(path, FileSys::Mode::Read); } struct System::Impl { - explicit Impl(System& system) : kernel{system}, cpu_core_manager{system}, reporter{system} {} + explicit Impl(System& system) + : kernel{system}, cpu_core_manager{system}, applet_manager{system}, reporter{system} {} Cpu& CurrentCpuCore() { return cpu_core_manager.GetCurrentCore(); diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 6dd633363..46aceec3d 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -37,6 +37,7 @@ namespace Core::Crypto { constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; +constexpr u64 FULL_TICKET_SIZE = 0x400; using namespace Common; @@ -55,6 +56,99 @@ const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{ {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, }; +namespace { +template <std::size_t Size> +bool IsAllZeroArray(const std::array<u8, Size>& array) { + return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; }); +} +} // namespace + +u64 GetSignatureTypeDataSize(SignatureType type) { + switch (type) { + case SignatureType::RSA_4096_SHA1: + case SignatureType::RSA_4096_SHA256: + return 0x200; + case SignatureType::RSA_2048_SHA1: + case SignatureType::RSA_2048_SHA256: + return 0x100; + case SignatureType::ECDSA_SHA1: + case SignatureType::ECDSA_SHA256: + return 0x3C; + } + UNREACHABLE(); +} + +u64 GetSignatureTypePaddingSize(SignatureType type) { + switch (type) { + case SignatureType::RSA_4096_SHA1: + case SignatureType::RSA_4096_SHA256: + case SignatureType::RSA_2048_SHA1: + case SignatureType::RSA_2048_SHA256: + return 0x3C; + case SignatureType::ECDSA_SHA1: + case SignatureType::ECDSA_SHA256: + return 0x40; + } + UNREACHABLE(); +} + +SignatureType Ticket::GetSignatureType() const { + if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { + return ticket->sig_type; + } + if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { + return ticket->sig_type; + } + if (auto ticket = std::get_if<ECDSATicket>(&data)) { + return ticket->sig_type; + } + + UNREACHABLE(); +} + +TicketData& Ticket::GetData() { + if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { + return ticket->data; + } + if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { + return ticket->data; + } + if (auto ticket = std::get_if<ECDSATicket>(&data)) { + return ticket->data; + } + + UNREACHABLE(); +} + +const TicketData& Ticket::GetData() const { + if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { + return ticket->data; + } + if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { + return ticket->data; + } + if (auto ticket = std::get_if<ECDSATicket>(&data)) { + return ticket->data; + } + + UNREACHABLE(); +} + +u64 Ticket::GetSize() const { + const auto sig_type = GetSignatureType(); + + return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) + + GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData); +} + +Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& rights_id) { + RSA2048Ticket out{}; + out.sig_type = SignatureType::RSA_2048_SHA256; + out.data.rights_id = rights_id; + out.data.title_key_common = title_key; + return Ticket{out}; +} + Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { Key128 out{}; @@ -135,6 +229,27 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) { } } +RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { + if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) + return {}; + + const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); + + std::vector<u8> extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10); + std::array<u8, 0x230> extended_dec{}; + AESCipher<Key128> rsa_1(eticket_final, Mode::CTR); + rsa_1.SetIV(extended_iv); + rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, + extended_dec.data(), Op::Decrypt); + + RSAKeyPair<2048> rsa_key{}; + std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); + std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); + std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); + + return rsa_key; +} + Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB); Key128 mac_key{}; @@ -237,7 +352,7 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke return Loader::ResultStatus::Success; } -std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { +std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) { if (!ticket_save.IsOpen()) return {}; @@ -246,14 +361,14 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { return {}; } - std::vector<TicketRaw> out; + std::vector<Ticket> out; for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && buffer[offset + 3] == 0x0) { out.emplace_back(); auto& next = out.back(); - std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw)); - offset += next.size(); + std::memcpy(&next, buffer.data() + offset, sizeof(Ticket)); + offset += FULL_TICKET_SIZE; } } @@ -305,29 +420,23 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { return offset; } -std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, +std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, const RSAKeyPair<2048>& key) { - u32 cert_authority; - std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority)); - if (cert_authority == 0) + const auto issuer = ticket.GetData().issuer; + if (issuer == std::array<u8, 0x40>{}) return {}; - if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) { - LOG_INFO(Crypto, - "Attempting to parse ticket with non-standard certificate authority {:08X}.", - cert_authority); + if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { + LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); } - Key128 rights_id; - std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128)); + Key128 rights_id = ticket.GetData().rights_id; if (rights_id == Key128{}) return {}; - Key128 key_temp{}; - - if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) { - std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size()); - return std::make_pair(rights_id, key_temp); + if (!std::any_of(ticket.GetData().title_key_common_pad.begin(), + ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) { + return std::make_pair(rights_id, ticket.GetData().title_key_common); } mbedtls_mpi D; // RSA Private Exponent @@ -342,7 +451,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); - mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100); + mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100); mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); @@ -366,6 +475,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, return {}; ASSERT(*offset > 0); + Key128 key_temp{}; std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); return std::make_pair(rights_id, key_temp); @@ -450,6 +560,8 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); + } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { + eticket_extended_kek = Common::HexStringToArray<576>(out[1]); } else { for (const auto& kv : KEYS_VARIABLE_LENGTH) { if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) @@ -862,20 +974,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { // Titlekeys data.DecryptProdInfo(GetBISKey(0)); - const auto eticket_extended_kek = data.GetETicketExtendedKek(); + eticket_extended_kek = data.GetETicketExtendedKek(); + WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek); + PopulateTickets(); +} - std::vector<u8> extended_iv(0x10); - std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size()); - std::array<u8, 0x230> extended_dec{}; - AESCipher<Key128> rsa_1(eticket_final, Mode::CTR); - rsa_1.SetIV(extended_iv); - rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, - extended_dec.data(), Op::Decrypt); +void KeyManager::PopulateTickets() { + const auto rsa_key = GetETicketRSAKey(); - RSAKeyPair<2048> rsa_key{}; - std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); - std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); - std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); + if (rsa_key == RSAKeyPair<2048>{}) + return; + + if (!common_tickets.empty() && !personal_tickets.empty()) + return; const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/80000000000000e1", @@ -886,19 +997,41 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { const auto blob2 = GetTicketblob(save2); auto res = GetTicketblob(save1); + const auto idx = res.size(); res.insert(res.end(), blob2.begin(), blob2.end()); - for (const auto& raw : res) { - const auto pair = ParseTicket(raw, rsa_key); + for (std::size_t i = 0; i < res.size(); ++i) { + const auto common = i < idx; + const auto pair = ParseTicket(res[i], rsa_key); if (!pair) continue; const auto& [rid, key] = *pair; u128 rights_id; std::memcpy(rights_id.data(), rid.data(), rid.size()); + + if (common) { + common_tickets[rights_id] = res[i]; + } else { + personal_tickets[rights_id] = res[i]; + } + SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); } } +void KeyManager::SynthesizeTickets() { + for (const auto& key : s128_keys) { + if (key.first.type != S128KeyType::Titlekey) { + continue; + } + u128 rights_id{key.first.field1, key.first.field2}; + Key128 rights_id_2; + std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size()); + const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2); + common_tickets.insert_or_assign(rights_id, ticket); + } +} + void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { if (key == Key128{}) return; @@ -997,6 +1130,46 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { DeriveBase(); } +const std::map<u128, Ticket>& KeyManager::GetCommonTickets() const { + return common_tickets; +} + +const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const { + return personal_tickets; +} + +bool KeyManager::AddTicketCommon(Ticket raw) { + const auto rsa_key = GetETicketRSAKey(); + if (rsa_key == RSAKeyPair<2048>{}) + return false; + + const auto pair = ParseTicket(raw, rsa_key); + if (!pair) + return false; + const auto& [rid, key] = *pair; + u128 rights_id; + std::memcpy(rights_id.data(), rid.data(), rid.size()); + common_tickets[rights_id] = raw; + SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + return true; +} + +bool KeyManager::AddTicketPersonalized(Ticket raw) { + const auto rsa_key = GetETicketRSAKey(); + if (rsa_key == RSAKeyPair<2048>{}) + return false; + + const auto pair = ParseTicket(raw, rsa_key); + if (!pair) + return false; + const auto& [rid, key] = *pair; + u128 rights_id; + std::memcpy(rights_id.data(), rid.data(), rid.size()); + common_tickets[rights_id] = raw; + SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + return true; +} + const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, {"eticket_rsa_kek_source", diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 22f268c65..7265c4171 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -9,8 +9,10 @@ #include <optional> #include <string> +#include <variant> #include <boost/container/flat_map.hpp> #include <fmt/format.h> +#include "common/common_funcs.h" #include "common/common_types.h" #include "core/crypto/partition_data_manager.h" #include "core/file_sys/vfs_types.h" @@ -30,7 +32,79 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180; using Key128 = std::array<u8, 0x10>; using Key256 = std::array<u8, 0x20>; using SHA256Hash = std::array<u8, 0x20>; -using TicketRaw = std::array<u8, 0x400>; + +enum class SignatureType { + RSA_4096_SHA1 = 0x10000, + RSA_2048_SHA1 = 0x10001, + ECDSA_SHA1 = 0x10002, + RSA_4096_SHA256 = 0x10003, + RSA_2048_SHA256 = 0x10004, + ECDSA_SHA256 = 0x10005, +}; + +u64 GetSignatureTypeDataSize(SignatureType type); +u64 GetSignatureTypePaddingSize(SignatureType type); + +enum class TitleKeyType : u8 { + Common = 0, + Personalized = 1, +}; + +struct TicketData { + std::array<u8, 0x40> issuer; + union { + std::array<u8, 0x100> title_key_block; + + struct { + Key128 title_key_common; + std::array<u8, 0xF0> title_key_common_pad; + }; + }; + + INSERT_PADDING_BYTES(0x1); + TitleKeyType type; + INSERT_PADDING_BYTES(0x3); + u8 revision; + INSERT_PADDING_BYTES(0xA); + u64 ticket_id; + u64 device_id; + std::array<u8, 0x10> rights_id; + u32 account_id; + INSERT_PADDING_BYTES(0x14C); +}; +static_assert(sizeof(TicketData) == 0x2C0, "TicketData has incorrect size."); + +struct RSA4096Ticket { + SignatureType sig_type; + std::array<u8, 0x200> sig_data; + INSERT_PADDING_BYTES(0x3C); + TicketData data; +}; + +struct RSA2048Ticket { + SignatureType sig_type; + std::array<u8, 0x100> sig_data; + INSERT_PADDING_BYTES(0x3C); + TicketData data; +}; + +struct ECDSATicket { + SignatureType sig_type; + std::array<u8, 0x3C> sig_data; + INSERT_PADDING_BYTES(0x40); + TicketData data; +}; + +struct Ticket { + std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data; + + SignatureType GetSignatureType() const; + TicketData& GetData(); + const TicketData& GetData() const; + u64 GetSize() const; + + static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id); +}; static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big."); @@ -43,6 +117,19 @@ struct RSAKeyPair { std::array<u8, 4> exponent; }; +template <size_t bit_size, size_t byte_size> +bool operator==(const RSAKeyPair<bit_size, byte_size>& lhs, + const RSAKeyPair<bit_size, byte_size>& rhs) { + return std::tie(lhs.encryption_key, lhs.decryption_key, lhs.modulus, lhs.exponent) == + std::tie(rhs.encryption_key, rhs.decryption_key, rhs.modulus, rhs.exponent); +} + +template <size_t bit_size, size_t byte_size> +bool operator!=(const RSAKeyPair<bit_size, byte_size>& lhs, + const RSAKeyPair<bit_size, byte_size>& rhs) { + return !(lhs == rhs); +} + enum class KeyCategory : u8 { Standard, Title, @@ -151,22 +238,35 @@ public: static bool KeyFileExists(bool title); - // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save - // 8*43 and the private file to exist. + // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system + // save 8*43 and the private file to exist. void DeriveSDSeedLazy(); bool BaseDeriveNecessary() const; void DeriveBase(); void DeriveETicket(PartitionDataManager& data); + void PopulateTickets(); + void SynthesizeTickets(); void PopulateFromPartitionData(PartitionDataManager& data); + const std::map<u128, Ticket>& GetCommonTickets() const; + const std::map<u128, Ticket>& GetPersonalizedTickets() const; + + bool AddTicketCommon(Ticket raw); + bool AddTicketPersonalized(Ticket raw); + private: std::map<KeyIndex<S128KeyType>, Key128> s128_keys; std::map<KeyIndex<S256KeyType>, Key256> s256_keys; + // Map from rights ID to ticket + std::map<u128, Ticket> common_tickets; + std::map<u128, Ticket> personal_tickets; + std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; + std::array<u8, 576> eticket_extended_kek{}; bool dev_mode; void LoadFromFile(const std::string& filename, bool is_title_keys); @@ -178,6 +278,8 @@ private: void DeriveGeneralPurposeKeys(std::size_t crypto_revision); + RSAKeyPair<2048> GetETicketRSAKey() const; + void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); @@ -195,11 +297,11 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo std::optional<Key128> DeriveSDSeed(); Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); -std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save); +std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save); -// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset -// 0x140-0x144 is zero) -std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, +// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority +// (offset 0x140-0x144 is zero) +std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, const RSAKeyPair<2048>& eticket_extended_key); } // namespace Core::Crypto diff --git a/src/core/file_sys/system_archive/mii_model.cpp b/src/core/file_sys/system_archive/mii_model.cpp new file mode 100644 index 000000000..6a9add87c --- /dev/null +++ b/src/core/file_sys/system_archive/mii_model.cpp @@ -0,0 +1,46 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/file_sys/system_archive/mii_model.h" +#include "core/file_sys/vfs_vector.h" + +namespace FileSys::SystemArchive { + +namespace MiiModelData { + +constexpr std::array<u8, 0x10> NFTR_STANDARD{'N', 'F', 'T', 'R', 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +constexpr std::array<u8, 0x10> NFSR_STANDARD{'N', 'F', 'S', 'R', 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +constexpr auto TEXTURE_LOW_LINEAR = NFTR_STANDARD; +constexpr auto TEXTURE_LOW_SRGB = NFTR_STANDARD; +constexpr auto TEXTURE_MID_LINEAR = NFTR_STANDARD; +constexpr auto TEXTURE_MID_SRGB = NFTR_STANDARD; +constexpr auto SHAPE_HIGH = NFSR_STANDARD; +constexpr auto SHAPE_MID = NFSR_STANDARD; + +} // namespace MiiModelData + +VirtualDir MiiModel() { + auto out = std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, + std::vector<VirtualDir>{}, "data"); + + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_LINEAR.size()>>( + MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_SRGB.size()>>( + MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_LINEAR.size()>>( + MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_SRGB.size()>>( + MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_HIGH.size()>>( + MiiModelData::SHAPE_HIGH, "ShapeHigh.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_MID.size()>>( + MiiModelData::SHAPE_MID, "ShapeMid.dat")); + + return std::move(out); +} + +} // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/mii_model.h b/src/core/file_sys/system_archive/mii_model.h new file mode 100644 index 000000000..6c2d9398b --- /dev/null +++ b/src/core/file_sys/system_archive/mii_model.h @@ -0,0 +1,13 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/file_sys/vfs_types.h" + +namespace FileSys::SystemArchive { + +VirtualDir MiiModel(); + +} // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp index c9722ed77..6d8445383 100644 --- a/src/core/file_sys/system_archive/system_archive.cpp +++ b/src/core/file_sys/system_archive/system_archive.cpp @@ -4,6 +4,7 @@ #include "common/logging/log.h" #include "core/file_sys/romfs.h" +#include "core/file_sys/system_archive/mii_model.h" #include "core/file_sys/system_archive/ng_word.h" #include "core/file_sys/system_archive/system_archive.h" #include "core/file_sys/system_archive/system_version.h" @@ -24,7 +25,7 @@ struct SystemArchiveDescriptor { constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{ {0x0100000000000800, "CertStore", nullptr}, {0x0100000000000801, "ErrorMessage", nullptr}, - {0x0100000000000802, "MiiModel", nullptr}, + {0x0100000000000802, "MiiModel", &MiiModel}, {0x0100000000000803, "BrowserDll", nullptr}, {0x0100000000000804, "Help", nullptr}, {0x0100000000000805, "SharedFont", nullptr}, diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 40cea1e7c..c7af87073 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -296,12 +296,6 @@ ResultVal<VAddr> VMManager::SetHeapSize(u64 size) { } ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { - const auto end_addr = target + size; - const auto last_addr = end_addr - 1; - VAddr cur_addr = target; - - ResultCode result = RESULT_SUCCESS; - // Check how much memory we've already mapped. const auto mapped_size_result = SizeOfAllocatedVMAsInRange(target, size); if (mapped_size_result.Failed()) { @@ -324,13 +318,16 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { // Keep track of the memory regions we unmap. std::vector<std::pair<u64, u64>> mapped_regions; + ResultCode result = RESULT_SUCCESS; // Iterate, trying to map memory. { - cur_addr = target; + const auto end_addr = target + size; + const auto last_addr = end_addr - 1; + VAddr cur_addr = target; auto iter = FindVMA(target); - ASSERT_MSG(iter != vma_map.end(), "MapPhysicalMemory iter != end"); + ASSERT(iter != vma_map.end()); while (true) { const auto& vma = iter->second; @@ -342,7 +339,7 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { const auto map_size = std::min(end_addr - cur_addr, vma_end - cur_addr); if (vma.state == MemoryState::Unmapped) { const auto map_res = - MapMemoryBlock(cur_addr, std::make_shared<PhysicalMemory>(map_size, 0), 0, + MapMemoryBlock(cur_addr, std::make_shared<PhysicalMemory>(map_size), 0, map_size, MemoryState::Heap, VMAPermission::ReadWrite); result = map_res.Code(); if (result.IsError()) { @@ -360,7 +357,7 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { // Advance to the next block. cur_addr = vma_end; iter = FindVMA(cur_addr); - ASSERT_MSG(iter != vma_map.end(), "MapPhysicalMemory iter != end"); + ASSERT(iter != vma_map.end()); } } @@ -368,7 +365,7 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { if (result.IsError()) { for (const auto [unmap_address, unmap_size] : mapped_regions) { ASSERT_MSG(UnmapRange(unmap_address, unmap_size).IsSuccess(), - "MapPhysicalMemory un-map on error"); + "Failed to unmap memory range."); } return result; @@ -381,12 +378,6 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { } ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { - const auto end_addr = target + size; - const auto last_addr = end_addr - 1; - VAddr cur_addr = target; - - ResultCode result = RESULT_SUCCESS; - // Check how much memory is currently mapped. const auto mapped_size_result = SizeOfUnmappablePhysicalMemoryInRange(target, size); if (mapped_size_result.Failed()) { @@ -401,13 +392,16 @@ ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { // Keep track of the memory regions we unmap. std::vector<std::pair<u64, u64>> unmapped_regions; + ResultCode result = RESULT_SUCCESS; // Try to unmap regions. { - cur_addr = target; + const auto end_addr = target + size; + const auto last_addr = end_addr - 1; + VAddr cur_addr = target; auto iter = FindVMA(target); - ASSERT_MSG(iter != vma_map.end(), "UnmapPhysicalMemory iter != end"); + ASSERT(iter != vma_map.end()); while (true) { const auto& vma = iter->second; @@ -434,7 +428,7 @@ ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { // Advance to the next block. cur_addr = vma_end; iter = FindVMA(cur_addr); - ASSERT_MSG(iter != vma_map.end(), "UnmapPhysicalMemory iter != end"); + ASSERT(iter != vma_map.end()); } } @@ -443,10 +437,12 @@ ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { if (result.IsError()) { for (const auto [map_address, map_size] : unmapped_regions) { const auto remap_res = - MapMemoryBlock(map_address, std::make_shared<PhysicalMemory>(map_size, 0), 0, - map_size, MemoryState::Heap, VMAPermission::None); - ASSERT_MSG(remap_res.Succeeded(), "UnmapPhysicalMemory re-map on error"); + MapMemoryBlock(map_address, std::make_shared<PhysicalMemory>(map_size), 0, map_size, + MemoryState::Heap, VMAPermission::None); + ASSERT_MSG(remap_res.Succeeded(), "Failed to remap a memory block."); } + + return result; } // Update mapped amount @@ -757,20 +753,26 @@ void VMManager::MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryAre // Always merge allocated memory blocks, even when they don't share the same backing block. if (left.type == VMAType::AllocatedMemoryBlock && (left.backing_block != right.backing_block || left.offset + left.size != right.offset)) { + const auto right_begin = right.backing_block->begin() + right.offset; + const auto right_end = right_begin + right.size; + // Check if we can save work. if (left.offset == 0 && left.size == left.backing_block->size()) { // Fast case: left is an entire backing block. - left.backing_block->insert(left.backing_block->end(), - right.backing_block->begin() + right.offset, - right.backing_block->begin() + right.offset + right.size); + left.backing_block->insert(left.backing_block->end(), right_begin, right_end); } else { // Slow case: make a new memory block for left and right. + const auto left_begin = left.backing_block->begin() + left.offset; + const auto left_end = left_begin + left.size; + const auto left_size = static_cast<std::size_t>(std::distance(left_begin, left_end)); + const auto right_size = static_cast<std::size_t>(std::distance(right_begin, right_end)); + auto new_memory = std::make_shared<PhysicalMemory>(); - new_memory->insert(new_memory->end(), left.backing_block->begin() + left.offset, - left.backing_block->begin() + left.offset + left.size); - new_memory->insert(new_memory->end(), right.backing_block->begin() + right.offset, - right.backing_block->begin() + right.offset + right.size); - left.backing_block = new_memory; + new_memory->reserve(left_size + right_size); + new_memory->insert(new_memory->end(), left_begin, left_end); + new_memory->insert(new_memory->end(), right_begin, right_end); + + left.backing_block = std::move(new_memory); left.offset = 0; } @@ -965,7 +967,7 @@ ResultVal<std::size_t> VMManager::SizeOfAllocatedVMAsInRange(VAddr address, VAddr cur_addr = address; auto iter = FindVMA(cur_addr); - ASSERT_MSG(iter != vma_map.end(), "SizeOfAllocatedVMAsInRange iter != end"); + ASSERT(iter != vma_map.end()); while (true) { const auto& vma = iter->second; @@ -986,7 +988,7 @@ ResultVal<std::size_t> VMManager::SizeOfAllocatedVMAsInRange(VAddr address, // Advance to the next block. cur_addr = vma_end; iter = std::next(iter); - ASSERT_MSG(iter != vma_map.end(), "SizeOfAllocatedVMAsInRange iter != end"); + ASSERT(iter != vma_map.end()); } return MakeResult(mapped_size); @@ -1000,7 +1002,7 @@ ResultVal<std::size_t> VMManager::SizeOfUnmappablePhysicalMemoryInRange(VAddr ad VAddr cur_addr = address; auto iter = FindVMA(cur_addr); - ASSERT_MSG(iter != vma_map.end(), "SizeOfUnmappablePhysicalMemoryInRange iter != end"); + ASSERT(iter != vma_map.end()); while (true) { const auto& vma = iter->second; @@ -1029,7 +1031,7 @@ ResultVal<std::size_t> VMManager::SizeOfUnmappablePhysicalMemoryInRange(VAddr ad // Advance to the next block. cur_addr = vma_end; iter = std::next(iter); - ASSERT_MSG(iter != vma_map.end(), "SizeOfUnmappablePhysicalMemoryInRange iter != end"); + ASSERT(iter != vma_map.end()); } return MakeResult(mapped_size); diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index b18cde619..850a7ebc3 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -454,8 +454,8 @@ public: /// Maps memory at a given address. /// - /// @param addr The virtual address to map memory at. - /// @param size The amount of memory to map. + /// @param target The virtual address to map memory at. + /// @param size The amount of memory to map. /// /// @note The destination address must lie within the Map region. /// @@ -468,8 +468,8 @@ public: /// Unmaps memory at a given address. /// - /// @param addr The virtual address to unmap memory at. - /// @param size The amount of memory to unmap. + /// @param target The virtual address to unmap memory at. + /// @param size The amount of memory to unmap. /// /// @note The destination address must lie within the Map region. /// diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index c01ee3eda..a7c55e116 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -31,6 +31,9 @@ namespace Service::Account { +constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 30}; +constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100}; + static std::string GetImagePath(Common::UUID uuid) { return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; @@ -41,20 +44,31 @@ static constexpr u32 SanitizeJPEGSize(std::size_t size) { return static_cast<u32>(std::min(size, max_jpeg_image_size)); } -class IProfile final : public ServiceFramework<IProfile> { +class IProfileCommon : public ServiceFramework<IProfileCommon> { public: - explicit IProfile(Common::UUID user_id, ProfileManager& profile_manager) - : ServiceFramework("IProfile"), profile_manager(profile_manager), user_id(user_id) { + explicit IProfileCommon(const char* name, bool editor_commands, Common::UUID user_id, + ProfileManager& profile_manager) + : ServiceFramework(name), profile_manager(profile_manager), user_id(user_id) { static const FunctionInfo functions[] = { - {0, &IProfile::Get, "Get"}, - {1, &IProfile::GetBase, "GetBase"}, - {10, &IProfile::GetImageSize, "GetImageSize"}, - {11, &IProfile::LoadImage, "LoadImage"}, + {0, &IProfileCommon::Get, "Get"}, + {1, &IProfileCommon::GetBase, "GetBase"}, + {10, &IProfileCommon::GetImageSize, "GetImageSize"}, + {11, &IProfileCommon::LoadImage, "LoadImage"}, }; + RegisterHandlers(functions); + + if (editor_commands) { + static const FunctionInfo editor_functions[] = { + {100, &IProfileCommon::Store, "Store"}, + {101, &IProfileCommon::StoreWithImage, "StoreWithImage"}, + }; + + RegisterHandlers(editor_functions); + } } -private: +protected: void Get(Kernel::HLERequestContext& ctx) { LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); ProfileBase profile_base{}; @@ -127,10 +141,91 @@ private: } } - const ProfileManager& profile_manager; + void Store(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto base = rp.PopRaw<ProfileBase>(); + + const auto user_data = ctx.ReadBuffer(); + + LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid={}", + Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(base.username.data()), base.username.size()), + base.timestamp, base.user_uuid.Format()); + + if (user_data.size() < sizeof(ProfileData)) { + LOG_ERROR(Service_ACC, "ProfileData buffer too small!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_BUFFER_SIZE); + return; + } + + ProfileData data; + std::memcpy(&data, user_data.data(), sizeof(ProfileData)); + + if (!profile_manager.SetProfileBaseAndData(user_id, base, data)) { + LOG_ERROR(Service_ACC, "Failed to update profile data and base!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_FAILED_SAVE_DATA); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void StoreWithImage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto base = rp.PopRaw<ProfileBase>(); + + const auto user_data = ctx.ReadBuffer(); + const auto image_data = ctx.ReadBuffer(1); + + LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid={}", + Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(base.username.data()), base.username.size()), + base.timestamp, base.user_uuid.Format()); + + if (user_data.size() < sizeof(ProfileData)) { + LOG_ERROR(Service_ACC, "ProfileData buffer too small!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_BUFFER_SIZE); + return; + } + + ProfileData data; + std::memcpy(&data, user_data.data(), sizeof(ProfileData)); + + FileUtil::IOFile image(GetImagePath(user_id), "wb"); + + if (!image.IsOpen() || !image.Resize(image_data.size()) || + image.WriteBytes(image_data.data(), image_data.size()) != image_data.size() || + !profile_manager.SetProfileBaseAndData(user_id, base, data)) { + LOG_ERROR(Service_ACC, "Failed to update profile data, base, and image!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_FAILED_SAVE_DATA); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + ProfileManager& profile_manager; Common::UUID user_id; ///< The user id this profile refers to. }; +class IProfile final : public IProfileCommon { +public: + IProfile(Common::UUID user_id, ProfileManager& profile_manager) + : IProfileCommon("IProfile", false, user_id, profile_manager) {} +}; + +class IProfileEditor final : public IProfileCommon { +public: + IProfileEditor(Common::UUID user_id, ProfileManager& profile_manager) + : IProfileCommon("IProfileEditor", true, user_id, profile_manager) {} +}; + class IManagerForApplication final : public ServiceFramework<IManagerForApplication> { public: IManagerForApplication() : ServiceFramework("IManagerForApplication") { @@ -322,6 +417,17 @@ void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx rb.Push(is_locked); } +void Module::Interface::GetProfileEditor(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + Common::UUID user_id = rp.PopRaw<Common::UUID>(); + + LOG_DEBUG(Service_ACC, "called, user_id={}", user_id.Format()); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IProfileEditor>(user_id, *profile_manager); +} + void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_ACC, "called"); // A u8 is passed into this function which we can safely ignore. It's to determine if we have diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index f651773b7..7a7dc9ec6 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.h @@ -32,6 +32,7 @@ public: void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx); void TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx); void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx); + void GetProfileEditor(Kernel::HLERequestContext& ctx); private: ResultCode InitializeApplicationInfoBase(u64 process_id); diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp index 1b7ec3ed0..0d1663657 100644 --- a/src/core/hle/service/acc/acc_su.cpp +++ b/src/core/hle/service/acc/acc_su.cpp @@ -41,7 +41,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p {202, nullptr, "CancelUserRegistration"}, {203, nullptr, "DeleteUser"}, {204, nullptr, "SetUserPosition"}, - {205, nullptr, "GetProfileEditor"}, + {205, &ACC_SU::GetProfileEditor, "GetProfileEditor"}, {206, nullptr, "CompleteUserRegistrationForcibly"}, {210, nullptr, "CreateFloatingRegistrationRequest"}, {230, nullptr, "AuthenticateServiceAsync"}, diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index 49aa5908b..8f9986326 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -305,6 +305,17 @@ bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) { return true; } +bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new, + const ProfileData& data_new) { + const auto index = GetUserIndex(uuid); + if (index.has_value() && SetProfileBase(uuid, profile_new)) { + profiles[*index].data = data_new; + return true; + } + + return false; +} + void ProfileManager::ParseUserSaveFile() { FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h index fd7abb541..5a6d28925 100644 --- a/src/core/hle/service/acc/profile_manager.h +++ b/src/core/hle/service/acc/profile_manager.h @@ -91,6 +91,8 @@ public: bool RemoveUser(Common::UUID uuid); bool SetProfileBase(Common::UUID uuid, const ProfileBase& profile_new); + bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new, + const ProfileData& data_new); private: void ParseUserSaveFile(); diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index a192a1f5f..aa2c83937 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -56,7 +56,8 @@ struct LaunchParameters { }; static_assert(sizeof(LaunchParameters) == 0x88); -IWindowController::IWindowController() : ServiceFramework("IWindowController") { +IWindowController::IWindowController(Core::System& system_) + : ServiceFramework("IWindowController"), system{system_} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateWindow"}, @@ -75,7 +76,7 @@ IWindowController::IWindowController() : ServiceFramework("IWindowController") { IWindowController::~IWindowController() = default; void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) { - const u64 process_id = Core::System::GetInstance().Kernel().CurrentProcess()->GetProcessID(); + const u64 process_id = system.CurrentProcess()->GetProcessID(); LOG_DEBUG(Service_AM, "called. Process ID=0x{:016X}", process_id); @@ -231,8 +232,9 @@ IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} { IDebugFunctions::~IDebugFunctions() = default; -ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) - : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger)) { +ISelfController::ISelfController(Core::System& system_, + std::shared_ptr<NVFlinger::NVFlinger> nvflinger_) + : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger_)) { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Exit"}, @@ -280,7 +282,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger RegisterHandlers(functions); - auto& kernel = Core::System::GetInstance().Kernel(); + auto& kernel = system_.Kernel(); launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, "ISelfController:LaunchableEvent"); @@ -501,8 +503,7 @@ void ISelfController::GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequest rb.PushCopyObjects(accumulated_suspended_tick_changed_event.readable); } -AppletMessageQueue::AppletMessageQueue() { - auto& kernel = Core::System::GetInstance().Kernel(); +AppletMessageQueue::AppletMessageQueue(Kernel::KernelCore& kernel) { on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, "AMMessageQueue:OnMessageRecieved"); on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair( @@ -937,9 +938,8 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } -ILibraryAppletCreator::ILibraryAppletCreator(u64 current_process_title_id) - : ServiceFramework("ILibraryAppletCreator"), - current_process_title_id(current_process_title_id) { +ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_) + : ServiceFramework("ILibraryAppletCreator"), system{system_} { static const FunctionInfo functions[] = { {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"}, {1, nullptr, "TerminateAllLibraryApplets"}, @@ -961,8 +961,8 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", static_cast<u32>(applet_id), applet_mode); - const auto& applet_manager{Core::System::GetInstance().GetAppletManager()}; - const auto applet = applet_manager.GetApplet(applet_id, current_process_title_id); + const auto& applet_manager{system.GetAppletManager()}; + const auto applet = applet_manager.GetApplet(applet_id); if (applet == nullptr) { LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id)); @@ -999,8 +999,7 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex const auto handle{rp.Pop<Kernel::Handle>()}; const auto transfer_mem = - Core::System::GetInstance().CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>( - handle); + system.CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>(handle); if (transfer_mem == nullptr) { LOG_ERROR(Service_AM, "shared_mem is a nullpr for handle={:08X}", handle); @@ -1018,7 +1017,8 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory))); } -IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") { +IApplicationFunctions::IApplicationFunctions(Core::System& system_) + : ServiceFramework("IApplicationFunctions"), system{system_} { // clang-format off static const FunctionInfo functions[] = { {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"}, @@ -1057,6 +1057,7 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF {120, nullptr, "ExecuteProgram"}, {121, nullptr, "ClearUserChannel"}, {122, nullptr, "UnpopToUserChannel"}, + {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"}, {500, nullptr, "StartContinuousRecordingFlushForDebug"}, {1000, nullptr, "CreateMovieMaker"}, {1001, nullptr, "PrepareForJit"}, @@ -1064,6 +1065,10 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF // clang-format on RegisterHandlers(functions); + + auto& kernel = Core::System::GetInstance().Kernel(); + gpu_error_detected_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::Manual, "IApplicationFunctions:GpuErrorDetectedSystemEvent"); } IApplicationFunctions::~IApplicationFunctions() = default; @@ -1175,7 +1180,7 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { // Get supported languages from NACP, if possible // Default to 0 (all languages supported) u32 supported_languages = 0; - FileSys::PatchManager pm{Core::System::GetInstance().CurrentProcess()->GetTitleID()}; + FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; const auto res = pm.GetControlMetadata(); if (res.first != nullptr) { @@ -1183,8 +1188,8 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { } // Call IApplicationManagerInterface implementation. - auto& service_manager = Core::System::GetInstance().ServiceManager(); - auto ns_am2 = service_manager.GetService<Service::NS::NS>("ns:am2"); + auto& service_manager = system.ServiceManager(); + auto ns_am2 = service_manager.GetService<NS::NS>("ns:am2"); auto app_man = ns_am2->GetApplicationManagerInterface(); // Get desired application language @@ -1256,8 +1261,8 @@ void IApplicationFunctions::ExtendSaveData(Kernel::HLERequestContext& ctx) { "new_journal={:016X}", static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size); - FileSystem::WriteSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id, - {new_normal_size, new_journal_size}); + const auto title_id = system.CurrentProcess()->GetTitleID(); + FileSystem::WriteSaveDataSize(type, title_id, user_id, {new_normal_size, new_journal_size}); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); @@ -1276,8 +1281,8 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type), user_id[1], user_id[0]); - const auto size = - FileSystem::ReadSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id); + const auto title_id = system.CurrentProcess()->GetTitleID(); + const auto size = FileSystem::ReadSaveDataSize(type, title_id, user_id); IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); @@ -1285,11 +1290,19 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) { rb.Push(size.journal); } +void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(gpu_error_detected_event.readable); +} + void InstallInterfaces(SM::ServiceManager& service_manager, std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) { - auto message_queue = std::make_shared<AppletMessageQueue>(); - message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); // Needed on - // game boot + auto message_queue = std::make_shared<AppletMessageQueue>(system.Kernel()); + // Needed on game boot + message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager); std::make_shared<AppletOE>(nvflinger, message_queue, system)->InstallAsService(service_manager); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 6cb582483..28f870302 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -10,12 +10,15 @@ #include "core/hle/kernel/writable_event.h" #include "core/hle/service/service.h" -namespace Service { -namespace NVFlinger { +namespace Kernel { +class KernelCore; +} + +namespace Service::NVFlinger { class NVFlinger; } -namespace AM { +namespace Service::AM { enum SystemLanguage { Japanese = 0, @@ -47,7 +50,7 @@ public: PerformanceModeChanged = 31, }; - AppletMessageQueue(); + explicit AppletMessageQueue(Kernel::KernelCore& kernel); ~AppletMessageQueue(); const Kernel::SharedPtr<Kernel::ReadableEvent>& GetMesssageRecieveEvent() const; @@ -65,12 +68,14 @@ private: class IWindowController final : public ServiceFramework<IWindowController> { public: - IWindowController(); + explicit IWindowController(Core::System& system_); ~IWindowController() override; private: void GetAppletResourceUserId(Kernel::HLERequestContext& ctx); void AcquireForegroundRights(Kernel::HLERequestContext& ctx); + + Core::System& system; }; class IAudioController final : public ServiceFramework<IAudioController> { @@ -113,7 +118,8 @@ public: class ISelfController final : public ServiceFramework<ISelfController> { public: - explicit ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger); + explicit ISelfController(Core::System& system_, + std::shared_ptr<NVFlinger::NVFlinger> nvflinger_); ~ISelfController() override; private: @@ -208,7 +214,7 @@ private: class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { public: - ILibraryAppletCreator(u64 current_process_title_id); + explicit ILibraryAppletCreator(Core::System& system_); ~ILibraryAppletCreator() override; private: @@ -216,12 +222,12 @@ private: void CreateStorage(Kernel::HLERequestContext& ctx); void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); - u64 current_process_title_id; + Core::System& system; }; class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { public: - IApplicationFunctions(); + explicit IApplicationFunctions(Core::System& system_); ~IApplicationFunctions() override; private: @@ -242,6 +248,10 @@ private: void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx); void EndBlockingHomeButton(Kernel::HLERequestContext& ctx); void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); + void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); + + Kernel::EventPair gpu_error_detected_event; + Core::System& system; }; class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { @@ -275,5 +285,4 @@ public: void InstallInterfaces(SM::ServiceManager& service_manager, std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system); -} // namespace AM -} // namespace Service +} // namespace Service::AM diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp index a34368c8b..e454b77d8 100644 --- a/src/core/hle/service/am/applet_ae.cpp +++ b/src/core/hle/service/am/applet_ae.cpp @@ -50,7 +50,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISelfController>(nvflinger); + rb.PushIpcInterface<ISelfController>(system, nvflinger); } void GetWindowController(Kernel::HLERequestContext& ctx) { @@ -58,7 +58,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IWindowController>(); + rb.PushIpcInterface<IWindowController>(system); } void GetAudioController(Kernel::HLERequestContext& ctx) { @@ -98,7 +98,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); + rb.PushIpcInterface<ILibraryAppletCreator>(system); } void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { @@ -106,7 +106,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IApplicationFunctions>(); + rb.PushIpcInterface<IApplicationFunctions>(system); } std::shared_ptr<NVFlinger::NVFlinger> nvflinger; @@ -154,7 +154,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISelfController>(nvflinger); + rb.PushIpcInterface<ISelfController>(system, nvflinger); } void GetWindowController(Kernel::HLERequestContext& ctx) { @@ -162,7 +162,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IWindowController>(); + rb.PushIpcInterface<IWindowController>(system); } void GetAudioController(Kernel::HLERequestContext& ctx) { @@ -194,7 +194,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); + rb.PushIpcInterface<ILibraryAppletCreator>(system); } void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp index 5d53ef113..a2ffaa440 100644 --- a/src/core/hle/service/am/applet_oe.cpp +++ b/src/core/hle/service/am/applet_oe.cpp @@ -4,7 +4,6 @@ #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/process.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_oe.h" #include "core/hle/service/nvflinger/nvflinger.h" @@ -64,7 +63,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IWindowController>(); + rb.PushIpcInterface<IWindowController>(system); } void GetSelfController(Kernel::HLERequestContext& ctx) { @@ -72,7 +71,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISelfController>(nvflinger); + rb.PushIpcInterface<ISelfController>(system, nvflinger); } void GetCommonStateGetter(Kernel::HLERequestContext& ctx) { @@ -88,7 +87,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); + rb.PushIpcInterface<ILibraryAppletCreator>(system); } void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { @@ -96,7 +95,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IApplicationFunctions>(); + rb.PushIpcInterface<IApplicationFunctions>(system); } std::shared_ptr<NVFlinger::NVFlinger> nvflinger; diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index 6bdba2468..d2e35362f 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -23,8 +23,7 @@ namespace Service::AM::Applets { -AppletDataBroker::AppletDataBroker() { - auto& kernel = Core::System::GetInstance().Kernel(); +AppletDataBroker::AppletDataBroker(Kernel::KernelCore& kernel) { state_changed_event = Kernel::WritableEvent::CreateEventPair( kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:StateChangedEvent"); pop_out_data_event = Kernel::WritableEvent::CreateEventPair( @@ -121,7 +120,7 @@ Kernel::SharedPtr<Kernel::ReadableEvent> AppletDataBroker::GetStateChangedEvent( return state_changed_event.readable; } -Applet::Applet() = default; +Applet::Applet(Kernel::KernelCore& kernel_) : broker{kernel_} {} Applet::~Applet() = default; @@ -154,7 +153,7 @@ AppletFrontendSet::AppletFrontendSet(AppletFrontendSet&&) noexcept = default; AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default; -AppletManager::AppletManager() = default; +AppletManager::AppletManager(Core::System& system_) : system{system_} {} AppletManager::~AppletManager() = default; @@ -216,28 +215,28 @@ void AppletManager::ClearAll() { frontend = {}; } -std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, u64 current_process_title_id) const { +std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const { switch (id) { case AppletId::Auth: - return std::make_shared<Auth>(*frontend.parental_controls); + return std::make_shared<Auth>(system, *frontend.parental_controls); case AppletId::Error: - return std::make_shared<Error>(*frontend.error); + return std::make_shared<Error>(system, *frontend.error); case AppletId::ProfileSelect: - return std::make_shared<ProfileSelect>(*frontend.profile_select); + return std::make_shared<ProfileSelect>(system, *frontend.profile_select); case AppletId::SoftwareKeyboard: - return std::make_shared<SoftwareKeyboard>(*frontend.software_keyboard); + return std::make_shared<SoftwareKeyboard>(system, *frontend.software_keyboard); case AppletId::PhotoViewer: - return std::make_shared<PhotoViewer>(*frontend.photo_viewer); + return std::make_shared<PhotoViewer>(system, *frontend.photo_viewer); case AppletId::LibAppletShop: - return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id, + return std::make_shared<WebBrowser>(system, *frontend.web_browser, frontend.e_commerce.get()); case AppletId::LibAppletOff: - return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id); + return std::make_shared<WebBrowser>(system, *frontend.web_browser); default: UNIMPLEMENTED_MSG( "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.", static_cast<u8>(id)); - return std::make_shared<StubApplet>(id); + return std::make_shared<StubApplet>(system, id); } } diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index adc973dad..764c3418c 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -12,6 +12,10 @@ union ResultCode; +namespace Core { +class System; +} + namespace Core::Frontend { class ECommerceApplet; class ErrorApplet; @@ -22,6 +26,10 @@ class SoftwareKeyboardApplet; class WebBrowserApplet; } // namespace Core::Frontend +namespace Kernel { +class KernelCore; +} + namespace Service::AM { class IStorage; @@ -53,7 +61,7 @@ enum class AppletId : u32 { class AppletDataBroker final { public: - AppletDataBroker(); + explicit AppletDataBroker(Kernel::KernelCore& kernel_); ~AppletDataBroker(); struct RawChannelData { @@ -108,7 +116,7 @@ private: class Applet { public: - Applet(); + explicit Applet(Kernel::KernelCore& kernel_); virtual ~Applet(); virtual void Initialize(); @@ -179,7 +187,7 @@ struct AppletFrontendSet { class AppletManager { public: - AppletManager(); + explicit AppletManager(Core::System& system_); ~AppletManager(); void SetAppletFrontendSet(AppletFrontendSet set); @@ -187,10 +195,11 @@ public: void SetDefaultAppletsIfMissing(); void ClearAll(); - std::shared_ptr<Applet> GetApplet(AppletId id, u64 current_process_title_id) const; + std::shared_ptr<Applet> GetApplet(AppletId id) const; private: AppletFrontendSet frontend; + Core::System& system; }; } // namespace Applets diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp index af3a900f8..a7db26725 100644 --- a/src/core/hle/service/am/applets/error.cpp +++ b/src/core/hle/service/am/applets/error.cpp @@ -85,7 +85,8 @@ ResultCode Decode64BitError(u64 error) { } // Anonymous namespace -Error::Error(const Core::Frontend::ErrorApplet& frontend) : frontend(frontend) {} +Error::Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_) + : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} Error::~Error() = default; @@ -145,8 +146,8 @@ void Error::Execute() { } const auto callback = [this] { DisplayCompleted(); }; - const auto title_id = Core::CurrentProcess()->GetTitleID(); - const auto& reporter{Core::System::GetInstance().GetReporter()}; + const auto title_id = system.CurrentProcess()->GetTitleID(); + const auto& reporter{system.GetReporter()}; switch (mode) { case ErrorAppletMode::ShowError: diff --git a/src/core/hle/service/am/applets/error.h b/src/core/hle/service/am/applets/error.h index a3590d181..a105cdb0c 100644 --- a/src/core/hle/service/am/applets/error.h +++ b/src/core/hle/service/am/applets/error.h @@ -7,6 +7,10 @@ #include "core/hle/result.h" #include "core/hle/service/am/applets/applets.h" +namespace Core { +class System; +} + namespace Service::AM::Applets { enum class ErrorAppletMode : u8 { @@ -21,7 +25,7 @@ enum class ErrorAppletMode : u8 { class Error final : public Applet { public: - explicit Error(const Core::Frontend::ErrorApplet& frontend); + explicit Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_); ~Error() override; void Initialize() override; @@ -42,6 +46,7 @@ private: std::unique_ptr<ErrorArguments> args; bool complete = false; + Core::System& system; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/general_backend.cpp b/src/core/hle/service/am/applets/general_backend.cpp index e0def8dff..328438a1d 100644 --- a/src/core/hle/service/am/applets/general_backend.cpp +++ b/src/core/hle/service/am/applets/general_backend.cpp @@ -37,7 +37,8 @@ static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) } } -Auth::Auth(Core::Frontend::ParentalControlsApplet& frontend) : frontend(frontend) {} +Auth::Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_) + : Applet{system_.Kernel()}, frontend(frontend_) {} Auth::~Auth() = default; @@ -151,7 +152,8 @@ void Auth::AuthFinished(bool successful) { broker.SignalStateChanged(); } -PhotoViewer::PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend) : frontend(frontend) {} +PhotoViewer::PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_) + : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} PhotoViewer::~PhotoViewer() = default; @@ -185,7 +187,7 @@ void PhotoViewer::Execute() { const auto callback = [this] { ViewFinished(); }; switch (mode) { case PhotoViewerAppletMode::CurrentApp: - frontend.ShowPhotosForApplication(Core::CurrentProcess()->GetTitleID(), callback); + frontend.ShowPhotosForApplication(system.CurrentProcess()->GetTitleID(), callback); break; case PhotoViewerAppletMode::AllApps: frontend.ShowAllPhotos(callback); @@ -200,7 +202,8 @@ void PhotoViewer::ViewFinished() { broker.SignalStateChanged(); } -StubApplet::StubApplet(AppletId id) : id(id) {} +StubApplet::StubApplet(Core::System& system_, AppletId id_) + : Applet{system_.Kernel()}, id(id_), system{system_} {} StubApplet::~StubApplet() = default; @@ -209,7 +212,7 @@ void StubApplet::Initialize() { Applet::Initialize(); const auto data = broker.PeekDataToAppletForDebug(); - Core::System::GetInstance().GetReporter().SaveUnimplementedAppletReport( + system.GetReporter().SaveUnimplementedAppletReport( static_cast<u32>(id), common_args.arguments_version, common_args.library_version, common_args.theme_color, common_args.play_startup_sound, common_args.system_tick, data.normal, data.interactive); diff --git a/src/core/hle/service/am/applets/general_backend.h b/src/core/hle/service/am/applets/general_backend.h index 0da252044..cfa2df369 100644 --- a/src/core/hle/service/am/applets/general_backend.h +++ b/src/core/hle/service/am/applets/general_backend.h @@ -6,6 +6,10 @@ #include "core/hle/service/am/applets/applets.h" +namespace Core { +class System; +} + namespace Service::AM::Applets { enum class AuthAppletType : u32 { @@ -16,7 +20,7 @@ enum class AuthAppletType : u32 { class Auth final : public Applet { public: - explicit Auth(Core::Frontend::ParentalControlsApplet& frontend); + explicit Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_); ~Auth() override; void Initialize() override; @@ -45,7 +49,7 @@ enum class PhotoViewerAppletMode : u8 { class PhotoViewer final : public Applet { public: - explicit PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend); + explicit PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_); ~PhotoViewer() override; void Initialize() override; @@ -60,11 +64,12 @@ private: const Core::Frontend::PhotoViewerApplet& frontend; bool complete = false; PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp; + Core::System& system; }; class StubApplet final : public Applet { public: - explicit StubApplet(AppletId id); + explicit StubApplet(Core::System& system_, AppletId id_); ~StubApplet() override; void Initialize() override; @@ -76,6 +81,7 @@ public: private: AppletId id; + Core::System& system; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp index 57b5419e8..3eba696ca 100644 --- a/src/core/hle/service/am/applets/profile_select.cpp +++ b/src/core/hle/service/am/applets/profile_select.cpp @@ -15,8 +15,9 @@ namespace Service::AM::Applets { constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; -ProfileSelect::ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend) - : frontend(frontend) {} +ProfileSelect::ProfileSelect(Core::System& system_, + const Core::Frontend::ProfileSelectApplet& frontend_) + : Applet{system_.Kernel()}, frontend(frontend_) {} ProfileSelect::~ProfileSelect() = default; diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h index 563cd744a..16364ead7 100644 --- a/src/core/hle/service/am/applets/profile_select.h +++ b/src/core/hle/service/am/applets/profile_select.h @@ -11,6 +11,10 @@ #include "core/hle/result.h" #include "core/hle/service/am/applets/applets.h" +namespace Core { +class System; +} + namespace Service::AM::Applets { struct UserSelectionConfig { @@ -29,7 +33,8 @@ static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has inco class ProfileSelect final : public Applet { public: - explicit ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend); + explicit ProfileSelect(Core::System& system_, + const Core::Frontend::ProfileSelectApplet& frontend_); ~ProfileSelect() override; void Initialize() override; diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp index e197990f7..748559cd0 100644 --- a/src/core/hle/service/am/applets/software_keyboard.cpp +++ b/src/core/hle/service/am/applets/software_keyboard.cpp @@ -39,8 +39,9 @@ static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters( return params; } -SoftwareKeyboard::SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend) - : frontend(frontend) {} +SoftwareKeyboard::SoftwareKeyboard(Core::System& system_, + const Core::Frontend::SoftwareKeyboardApplet& frontend_) + : Applet{system_.Kernel()}, frontend(frontend_) {} SoftwareKeyboard::~SoftwareKeyboard() = default; diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h index 0fbc43e51..ef4801fc6 100644 --- a/src/core/hle/service/am/applets/software_keyboard.h +++ b/src/core/hle/service/am/applets/software_keyboard.h @@ -16,6 +16,10 @@ union ResultCode; +namespace Core { +class System; +} + namespace Service::AM::Applets { enum class KeysetDisable : u32 { @@ -55,7 +59,8 @@ static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect siz class SoftwareKeyboard final : public Applet { public: - explicit SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend); + explicit SoftwareKeyboard(Core::System& system_, + const Core::Frontend::SoftwareKeyboardApplet& frontend_); ~SoftwareKeyboard() override; void Initialize() override; diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp index f3c9fef0e..32283e819 100644 --- a/src/core/hle/service/am/applets/web_browser.cpp +++ b/src/core/hle/service/am/applets/web_browser.cpp @@ -190,8 +190,9 @@ std::map<WebArgTLVType, std::vector<u8>> GetWebArguments(const std::vector<u8>& return out; } -FileSys::VirtualFile GetApplicationRomFS(u64 title_id, FileSys::ContentRecordType type) { - const auto& installed{Core::System::GetInstance().GetContentProvider()}; +FileSys::VirtualFile GetApplicationRomFS(const Core::System& system, u64 title_id, + FileSys::ContentRecordType type) { + const auto& installed{system.GetContentProvider()}; const auto res = installed.GetEntry(title_id, type); if (res != nullptr) { @@ -207,10 +208,10 @@ FileSys::VirtualFile GetApplicationRomFS(u64 title_id, FileSys::ContentRecordTyp } // Anonymous namespace -WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id, - Core::Frontend::ECommerceApplet* frontend_e_commerce) - : frontend(frontend), frontend_e_commerce(frontend_e_commerce), - current_process_title_id(current_process_title_id) {} +WebBrowser::WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_, + Core::Frontend::ECommerceApplet* frontend_e_commerce_) + : Applet{system_.Kernel()}, frontend(frontend_), + frontend_e_commerce(frontend_e_commerce_), system{system_} {} WebBrowser::~WebBrowser() = default; @@ -266,7 +267,7 @@ void WebBrowser::UnpackRomFS() { ASSERT(offline_romfs != nullptr); const auto dir = FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); - const auto& vfs{Core::System::GetInstance().GetFilesystem()}; + const auto& vfs{system.GetFilesystem()}; const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite); FileSys::VfsRawCopyD(dir, temp_dir); @@ -470,10 +471,10 @@ void WebBrowser::InitializeOffline() { } if (title_id == 0) { - title_id = current_process_title_id; + title_id = system.CurrentProcess()->GetTitleID(); } - offline_romfs = GetApplicationRomFS(title_id, type); + offline_romfs = GetApplicationRomFS(system, title_id, type); if (offline_romfs == nullptr) { status = ResultCode(-1); LOG_ERROR(Service_AM, "Failed to find offline data for request!"); diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h index 870f57b64..8d4027411 100644 --- a/src/core/hle/service/am/applets/web_browser.h +++ b/src/core/hle/service/am/applets/web_browser.h @@ -9,6 +9,10 @@ #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/applets.h" +namespace Core { +class System; +} + namespace Service::AM::Applets { enum class ShimKind : u32; @@ -17,8 +21,8 @@ enum class WebArgTLVType : u16; class WebBrowser final : public Applet { public: - WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id, - Core::Frontend::ECommerceApplet* frontend_e_commerce = nullptr); + WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_, + Core::Frontend::ECommerceApplet* frontend_e_commerce_ = nullptr); ~WebBrowser() override; @@ -59,8 +63,6 @@ private: bool unpacked = false; ResultCode status = RESULT_SUCCESS; - u64 current_process_title_id; - ShimKind kind; std::map<WebArgTLVType, std::vector<u8>> args; @@ -74,6 +76,8 @@ private: std::optional<u128> user_id; std::optional<bool> shop_full_display; std::string shop_extra_parameter; + + Core::System& system; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 5b0b7f17e..f162249ed 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -165,15 +165,15 @@ public: static const FunctionInfo functions[] = { {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, - {2, nullptr, "GetAudioDeviceOutputVolume"}, + {2, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolume"}, {3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"}, {4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"}, {5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"}, {6, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceNameAuto"}, {7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"}, - {8, nullptr, "GetAudioDeviceOutputVolumeAuto"}, + {8, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolumeAuto"}, {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, - {11, nullptr, "QueryAudioDeviceInputEvent"}, + {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"}, {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, {13, nullptr, "GetAudioSystemMasterVolumeSetting"}, }; @@ -183,6 +183,10 @@ public: buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic, "IAudioOutBufferReleasedEvent"); + // Should be similar to audio_output_device_switch_event + audio_input_device_switch_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::Automatic, "IAudioDevice:AudioInputDeviceSwitchedEvent"); + // Should only be signalled when an audio output device has been changed, example: speaker // to headset audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair( @@ -246,6 +250,19 @@ private: rb.Push(RESULT_SUCCESS); } + void GetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto device_name_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(device_name_buffer); + + LOG_WARNING(Service_Audio, "(STUBBED) called. name={}", name); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(1.0f); + } + void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_Audio, "(STUBBED) called"); @@ -279,6 +296,15 @@ private: rb.Push<u32>(1); } + // Should be similar to QueryAudioDeviceOutputEvent + void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(audio_input_device_switch_event.readable); + } + void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); @@ -289,6 +315,7 @@ private: u32_le revision = 0; Kernel::EventPair buffer_event; + Kernel::EventPair audio_input_device_switch_event; Kernel::EventPair audio_output_device_switch_event; }; // namespace Audio diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 6701cb913..af70d174d 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -2,32 +2,37 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/crypto/key_manager.h" +#include "core/hle/ipc_helpers.h" #include "core/hle/service/service.h" namespace Service::ES { +constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::ETicket, 2}; +constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3}; + class ETicket final : public ServiceFramework<ETicket> { public: explicit ETicket() : ServiceFramework{"es"} { // clang-format off static const FunctionInfo functions[] = { - {1, nullptr, "ImportTicket"}, + {1, &ETicket::ImportTicket, "ImportTicket"}, {2, nullptr, "ImportTicketCertificateSet"}, {3, nullptr, "DeleteTicket"}, {4, nullptr, "DeletePersonalizedTicket"}, {5, nullptr, "DeleteAllCommonTicket"}, {6, nullptr, "DeleteAllPersonalizedTicket"}, {7, nullptr, "DeleteAllPersonalizedTicketEx"}, - {8, nullptr, "GetTitleKey"}, - {9, nullptr, "CountCommonTicket"}, - {10, nullptr, "CountPersonalizedTicket"}, - {11, nullptr, "ListCommonTicket"}, - {12, nullptr, "ListPersonalizedTicket"}, + {8, &ETicket::GetTitleKey, "GetTitleKey"}, + {9, &ETicket::CountCommonTicket, "CountCommonTicket"}, + {10, &ETicket::CountPersonalizedTicket, "CountPersonalizedTicket"}, + {11, &ETicket::ListCommonTicket, "ListCommonTicket"}, + {12, &ETicket::ListPersonalizedTicket, "ListPersonalizedTicket"}, {13, nullptr, "ListMissingPersonalizedTicket"}, - {14, nullptr, "GetCommonTicketSize"}, - {15, nullptr, "GetPersonalizedTicketSize"}, - {16, nullptr, "GetCommonTicketData"}, - {17, nullptr, "GetPersonalizedTicketData"}, + {14, &ETicket::GetCommonTicketSize, "GetCommonTicketSize"}, + {15, &ETicket::GetPersonalizedTicketSize, "GetPersonalizedTicketSize"}, + {16, &ETicket::GetCommonTicketData, "GetCommonTicketData"}, + {17, &ETicket::GetPersonalizedTicketData, "GetPersonalizedTicketData"}, {18, nullptr, "OwnTicket"}, {19, nullptr, "GetTicketInfo"}, {20, nullptr, "ListLightTicketInfo"}, @@ -51,7 +56,212 @@ public: }; // clang-format on RegisterHandlers(functions); + + keys.PopulateTickets(); + keys.SynthesizeTickets(); + } + +private: + bool CheckRightsId(Kernel::HLERequestContext& ctx, const u128& rights_id) { + if (rights_id == u128{}) { + LOG_ERROR(Service_ETicket, "The rights ID was invalid!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_RIGHTS_ID); + return false; + } + + return true; + } + + void ImportTicket(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto ticket = ctx.ReadBuffer(); + const auto cert = ctx.ReadBuffer(1); + + if (ticket.size() < sizeof(Core::Crypto::Ticket)) { + LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return; + } + + Core::Crypto::Ticket raw{}; + std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket)); + + if (!keys.AddTicketPersonalized(raw)) { + LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetTitleKey(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw<u128>(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto key = + keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); + + if (key == Core::Crypto::Key128{}) { + LOG_ERROR(Service_ETicket, + "The titlekey doesn't exist in the KeyManager or the rights ID was invalid!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_RIGHTS_ID); + return; + } + + ctx.WriteBuffer(key.data(), key.size()); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void CountCommonTicket(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ETicket, "called"); + + const auto count = keys.GetCommonTickets().size(); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(count); + } + + void CountPersonalizedTicket(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ETicket, "called"); + + const auto count = keys.GetPersonalizedTickets().size(); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(count); + } + + void ListCommonTicket(Kernel::HLERequestContext& ctx) { + u32 out_entries; + if (keys.GetCommonTickets().empty()) + out_entries = 0; + else + out_entries = ctx.GetWriteBufferSize() / sizeof(u128); + + LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries); + + keys.PopulateTickets(); + const auto tickets = keys.GetCommonTickets(); + std::vector<u128> ids; + std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids), + [](const auto& pair) { return pair.first; }); + + out_entries = std::min<u32>(ids.size(), out_entries); + ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128)); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(out_entries); } + + void ListPersonalizedTicket(Kernel::HLERequestContext& ctx) { + u32 out_entries; + if (keys.GetPersonalizedTickets().empty()) + out_entries = 0; + else + out_entries = ctx.GetWriteBufferSize() / sizeof(u128); + + LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries); + + keys.PopulateTickets(); + const auto tickets = keys.GetPersonalizedTickets(); + std::vector<u128> ids; + std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids), + [](const auto& pair) { return pair.first; }); + + out_entries = std::min<u32>(ids.size(), out_entries); + ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128)); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(out_entries); + } + + void GetCommonTicketSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw<u128>(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto ticket = keys.GetCommonTickets().at(rights_id); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(ticket.GetSize()); + } + + void GetPersonalizedTicketSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw<u128>(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto ticket = keys.GetPersonalizedTickets().at(rights_id); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(ticket.GetSize()); + } + + void GetCommonTicketData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw<u128>(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto ticket = keys.GetCommonTickets().at(rights_id); + + const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize()); + ctx.WriteBuffer(&ticket, write_size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(write_size); + } + + void GetPersonalizedTicketData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw<u128>(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto ticket = keys.GetPersonalizedTickets().at(rights_id); + + const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize()); + ctx.WriteBuffer(&ticket, write_size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(write_size); + } + + Core::Crypto::KeyManager keys; }; void InstallInterfaces(SM::ServiceManager& service_manager) { diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index fe49c2161..01fa06ad3 100644 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp @@ -5,7 +5,7 @@ #include <array> #include <cstring> #include <ctime> -#include <fmt/time.h> +#include <fmt/chrono.h> #include "common/file_util.h" #include "common/logging/log.h" #include "common/scm_rev.h" diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 1e81f776f..e47fe8188 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -636,10 +636,15 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) { return LedPattern{0, 0, 0, 0}; }; } + void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { can_controllers_vibrate = can_vibrate; } +bool Controller_NPad::IsVibrationEnabled() const { + return can_controllers_vibrate; +} + void Controller_NPad::ClearAllConnectedControllers() { for (auto& controller : connected_controllers) { if (controller.is_connected && controller.type != NPadControllerType::None) { @@ -648,6 +653,7 @@ void Controller_NPad::ClearAllConnectedControllers() { } } } + void Controller_NPad::DisconnectAllConnectedControllers() { std::for_each(connected_controllers.begin(), connected_controllers.end(), [](ControllerHolder& controller) { controller.is_connected = false; }); diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 4b6c1083f..f28b36806 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -119,6 +119,7 @@ public: void DisconnectNPad(u32 npad_id); LedPattern GetLedPattern(u32 npad_id); void SetVibrationEnabled(bool can_vibrate); + bool IsVibrationEnabled() const; void ClearAllConnectedControllers(); void DisconnectAllConnectedControllers(); void ConnectAllDisconnectedControllers(); diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 0bd24b8eb..f8b1ca816 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -216,8 +216,8 @@ Hid::Hid() : ServiceFramework("hid") { {201, &Hid::SendVibrationValue, "SendVibrationValue"}, {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"}, {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"}, - {204, nullptr, "PermitVibration"}, - {205, nullptr, "IsVibrationPermitted"}, + {204, &Hid::PermitVibration, "PermitVibration"}, + {205, &Hid::IsVibrationPermitted, "IsVibrationPermitted"}, {206, &Hid::SendVibrationValues, "SendVibrationValues"}, {207, nullptr, "SendVibrationGcErmCommand"}, {208, nullptr, "GetActualVibrationGcErmCommand"}, @@ -679,6 +679,27 @@ void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface<IActiveVibrationDeviceList>(); } +void Hid::PermitVibration(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto can_vibrate{rp.Pop<bool>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetVibrationEnabled(can_vibrate); + + LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push( + applet_resource->GetController<Controller_NPad>(HidController::NPad).IsVibrationEnabled()); +} + void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 28260ef1b..2fd6d9fc7 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -114,6 +114,8 @@ private: void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx); void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx); + void PermitVibration(Kernel::HLERequestContext& ctx); + void IsVibrationPermitted(Kernel::HLERequestContext& ctx); void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); void StopSixAxisSensor(Kernel::HLERequestContext& ctx); diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index e92e2e06e..3a5361fdd 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -258,6 +258,15 @@ ResultStatus AppLoader_NRO::ReadTitle(std::string& title) { return ResultStatus::Success; } +ResultStatus AppLoader_NRO::ReadControlData(FileSys::NACP& control) { + if (nacp == nullptr) { + return ResultStatus::ErrorNoControl; + } + + control = *nacp; + return ResultStatus::Success; +} + bool AppLoader_NRO::IsRomFSUpdatable() const { return false; } diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h index 1ffdae805..71811bc29 100644 --- a/src/core/loader/nro.h +++ b/src/core/loader/nro.h @@ -43,6 +43,7 @@ public: ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; ResultStatus ReadTitle(std::string& title) override; + ResultStatus ReadControlData(FileSys::NACP& control) override; bool IsRomFSUpdatable() const override; private: diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index 5d4c3e6ea..cfe0771e2 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp @@ -5,8 +5,8 @@ #include <ctime> #include <fstream> +#include <fmt/chrono.h> #include <fmt/format.h> -#include <fmt/time.h> #include <json.hpp> #include "common/file_util.h" diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp index 08586d33c..63d449135 100644 --- a/src/video_core/engines/kepler_compute.cpp +++ b/src/video_core/engines/kepler_compute.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <bitset> #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" @@ -49,6 +50,33 @@ void KeplerCompute::CallMethod(const GPU::MethodCall& method_call) { } } +Tegra::Texture::FullTextureInfo KeplerCompute::GetTexture(std::size_t offset) const { + const std::bitset<8> cbuf_mask = launch_description.const_buffer_enable_mask.Value(); + ASSERT(cbuf_mask[regs.tex_cb_index]); + + const auto& texinfo = launch_description.const_buffer_config[regs.tex_cb_index]; + ASSERT(texinfo.Address() != 0); + + const GPUVAddr address = texinfo.Address() + offset * sizeof(Texture::TextureHandle); + ASSERT(address < texinfo.Address() + texinfo.size); + + const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(address)}; + return GetTextureInfo(tex_handle, offset); +} + +Texture::FullTextureInfo KeplerCompute::GetTextureInfo(const Texture::TextureHandle tex_handle, + std::size_t offset) const { + return Texture::FullTextureInfo{static_cast<u32>(offset), GetTICEntry(tex_handle.tic_id), + GetTSCEntry(tex_handle.tsc_id)}; +} + +u32 KeplerCompute::AccessConstBuffer32(u64 const_buffer, u64 offset) const { + const auto& buffer = launch_description.const_buffer_config[const_buffer]; + u32 result; + std::memcpy(&result, memory_manager.GetPointer(buffer.Address() + offset), sizeof(u32)); + return result; +} + void KeplerCompute::ProcessLaunch() { const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address(); memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description, @@ -60,4 +88,29 @@ void KeplerCompute::ProcessLaunch() { rasterizer.DispatchCompute(code_addr); } +Texture::TICEntry KeplerCompute::GetTICEntry(u32 tic_index) const { + const GPUVAddr tic_address_gpu{regs.tic.Address() + tic_index * sizeof(Texture::TICEntry)}; + + Texture::TICEntry tic_entry; + memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry)); + + const auto r_type{tic_entry.r_type.Value()}; + const auto g_type{tic_entry.g_type.Value()}; + const auto b_type{tic_entry.b_type.Value()}; + const auto a_type{tic_entry.a_type.Value()}; + + // TODO(Subv): Different data types for separate components are not supported + DEBUG_ASSERT(r_type == g_type && r_type == b_type && r_type == a_type); + + return tic_entry; +} + +Texture::TSCEntry KeplerCompute::GetTSCEntry(u32 tsc_index) const { + const GPUVAddr tsc_address_gpu{regs.tsc.Address() + tsc_index * sizeof(Texture::TSCEntry)}; + + Texture::TSCEntry tsc_entry; + memory_manager.ReadBlockUnsafe(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry)); + return tsc_entry; +} + } // namespace Tegra::Engines diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h index 6a3309a2c..90cf650d2 100644 --- a/src/video_core/engines/kepler_compute.h +++ b/src/video_core/engines/kepler_compute.h @@ -12,6 +12,7 @@ #include "common/common_types.h" #include "video_core/engines/engine_upload.h" #include "video_core/gpu.h" +#include "video_core/textures/texture.h" namespace Core { class System; @@ -111,7 +112,7 @@ public: INSERT_PADDING_WORDS(0x3FE); - u32 texture_const_buffer_index; + u32 tex_cb_index; INSERT_PADDING_WORDS(0x374); }; @@ -149,7 +150,7 @@ public: union { BitField<0, 8, u32> const_buffer_enable_mask; BitField<29, 2, u32> cache_layout; - } memory_config; + }; INSERT_PADDING_WORDS(0x8); @@ -194,6 +195,14 @@ public: /// Write the value to the register identified by method. void CallMethod(const GPU::MethodCall& method_call); + Tegra::Texture::FullTextureInfo GetTexture(std::size_t offset) const; + + /// Given a Texture Handle, returns the TSC and TIC entries. + Texture::FullTextureInfo GetTextureInfo(const Texture::TextureHandle tex_handle, + std::size_t offset) const; + + u32 AccessConstBuffer32(u64 const_buffer, u64 offset) const; + private: Core::System& system; VideoCore::RasterizerInterface& rasterizer; @@ -201,6 +210,12 @@ private: Upload::State upload_state; void ProcessLaunch(); + + /// Retrieves information about a specific TIC entry from the TIC buffer. + Texture::TICEntry GetTICEntry(u32 tic_index) const; + + /// Retrieves information about a specific TSC entry from the TSC buffer. + Texture::TSCEntry GetTSCEntry(u32 tsc_index) const; }; #define ASSERT_REG_POSITION(field_name, position) \ @@ -218,12 +233,12 @@ ASSERT_REG_POSITION(launch, 0xAF); ASSERT_REG_POSITION(tsc, 0x557); ASSERT_REG_POSITION(tic, 0x55D); ASSERT_REG_POSITION(code_loc, 0x582); -ASSERT_REG_POSITION(texture_const_buffer_index, 0x982); +ASSERT_REG_POSITION(tex_cb_index, 0x982); ASSERT_LAUNCH_PARAM_POSITION(program_start, 0x8); ASSERT_LAUNCH_PARAM_POSITION(grid_dim_x, 0xC); ASSERT_LAUNCH_PARAM_POSITION(shared_alloc, 0x11); ASSERT_LAUNCH_PARAM_POSITION(block_dim_x, 0x12); -ASSERT_LAUNCH_PARAM_POSITION(memory_config, 0x14); +ASSERT_LAUNCH_PARAM_POSITION(const_buffer_enable_mask, 0x14); ASSERT_LAUNCH_PARAM_POSITION(const_buffer_config, 0x1D); #undef ASSERT_REG_POSITION diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 5f7738e7b..5d516cdb3 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -244,7 +244,7 @@ void Maxwell3D::InitDirtySettings() { dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_clamp)] = polygon_offset_dirty_reg; } -void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { +void Maxwell3D::CallMacroMethod(u32 method, std::size_t num_parameters, const u32* parameters) { // Reset the current macro. executing_macro = 0; @@ -252,7 +252,7 @@ void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { const u32 entry = ((method - MacroRegistersStart) >> 1) % macro_positions.size(); // Execute the current macro. - macro_interpreter.Execute(macro_positions[entry], std::move(parameters)); + macro_interpreter.Execute(macro_positions[entry], num_parameters, parameters); } void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { @@ -289,7 +289,8 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { // Call the macro when there are no more parameters in the command buffer if (method_call.IsLastCall()) { - CallMacroMethod(executing_macro, std::move(macro_params)); + CallMacroMethod(executing_macro, macro_params.size(), macro_params.data()); + macro_params.clear(); } return; } diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 95d434b40..e5ec90717 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -62,6 +62,7 @@ public: static constexpr std::size_t NumVertexAttributes = 32; static constexpr std::size_t NumVaryings = 31; static constexpr std::size_t NumTextureSamplers = 32; + static constexpr std::size_t NumImages = 8; // TODO(Rodrigo): Investigate this number static constexpr std::size_t NumClipDistances = 8; static constexpr std::size_t MaxShaderProgram = 6; static constexpr std::size_t MaxShaderStage = 5; @@ -1309,9 +1310,10 @@ private: /** * Call a macro on this engine. * @param method Method to call + * @param num_parameters Number of arguments * @param parameters Arguments to the method call */ - void CallMacroMethod(u32 method, std::vector<u32> parameters); + void CallMacroMethod(u32 method, std::size_t num_parameters, const u32* parameters); /// Handles writes to the macro uploading register. void ProcessMacroUpload(u32 data); diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index c3678b9ea..052e6d24e 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -544,6 +544,28 @@ enum class VoteOperation : u64 { Eq = 2, // allThreadsEqualNV }; +enum class ImageAtomicSize : u64 { + U32 = 0, + S32 = 1, + U64 = 2, + F32 = 3, + S64 = 5, + SD32 = 6, + SD64 = 7, +}; + +enum class ImageAtomicOperation : u64 { + Add = 0, + Min = 1, + Max = 2, + Inc = 3, + Dec = 4, + And = 5, + Or = 6, + Xor = 7, + Exch = 8, +}; + union Instruction { Instruction& operator=(const Instruction& instr) { value = instr.value; @@ -675,6 +697,10 @@ union Instruction { } shift; union { + BitField<39, 1, u64> wrap; + } shr; + + union { BitField<39, 5, u64> shift_amount; BitField<48, 1, u64> negate_b; BitField<49, 1, u64> negate_a; @@ -1388,6 +1414,14 @@ union Instruction { } sust; union { + BitField<28, 1, u64> is_ba; + BitField<51, 3, ImageAtomicSize> size; + BitField<33, 3, ImageType> image_type; + BitField<29, 4, ImageAtomicOperation> operation; + BitField<49, 2, OutOfBoundsStore> out_of_bounds_store; + } suatom_d; + + union { BitField<20, 24, u64> target; BitField<5, 1, u64> constant_buffer; @@ -1539,6 +1573,7 @@ public: TMML_B, // Texture Mip Map Level TMML, // Texture Mip Map Level SUST, // Surface Store + SUATOM, // Surface Atomic Operation EXIT, NOP, IPA, @@ -1822,6 +1857,7 @@ private: INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"), INST("1101111101011---", Id::TMML, Type::Texture, "TMML"), INST("11101011001-----", Id::SUST, Type::Image, "SUST"), + INST("1110101000------", Id::SUATOM, Type::Image, "SUATOM_D"), INST("0101000010110---", Id::NOP, Type::Trivial, "NOP"), INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"), diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp index a891e412a..62afc0d11 100644 --- a/src/video_core/macro_interpreter.cpp +++ b/src/video_core/macro_interpreter.cpp @@ -14,11 +14,18 @@ namespace Tegra { MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {} -void MacroInterpreter::Execute(u32 offset, std::vector<u32> parameters) { +void MacroInterpreter::Execute(u32 offset, std::size_t num_parameters, const u32* parameters) { MICROPROFILE_SCOPE(MacroInterp); Reset(); + registers[1] = parameters[0]; - this->parameters = std::move(parameters); + + if (num_parameters > parameters_capacity) { + parameters_capacity = num_parameters; + this->parameters = std::make_unique<u32[]>(num_parameters); + } + std::memcpy(this->parameters.get(), parameters, num_parameters * sizeof(u32)); + this->num_parameters = num_parameters; // Execute the code until we hit an exit condition. bool keep_executing = true; @@ -27,7 +34,7 @@ void MacroInterpreter::Execute(u32 offset, std::vector<u32> parameters) { } // Assert the the macro used all the input parameters - ASSERT(next_parameter_index == this->parameters.size()); + ASSERT(next_parameter_index == num_parameters); } void MacroInterpreter::Reset() { @@ -35,7 +42,7 @@ void MacroInterpreter::Reset() { pc = 0; delayed_pc = {}; method_address.raw = 0; - parameters.clear(); + num_parameters = 0; // The next parameter index starts at 1, because $r1 already has the value of the first // parameter. next_parameter_index = 1; @@ -227,7 +234,8 @@ void MacroInterpreter::ProcessResult(ResultOperation operation, u32 reg, u32 res } u32 MacroInterpreter::FetchParameter() { - return parameters.at(next_parameter_index++); + ASSERT(next_parameter_index < num_parameters); + return parameters[next_parameter_index++]; } u32 MacroInterpreter::GetRegister(u32 register_id) const { diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h index cde360288..76b6a895b 100644 --- a/src/video_core/macro_interpreter.h +++ b/src/video_core/macro_interpreter.h @@ -25,7 +25,7 @@ public: * @param offset Offset to start execution at. * @param parameters The parameters of the macro. */ - void Execute(u32 offset, std::vector<u32> parameters); + void Execute(u32 offset, std::size_t num_parameters, const u32* parameters); private: enum class Operation : u32 { @@ -162,10 +162,12 @@ private: MethodAddress method_address = {}; /// Input parameters of the current macro. - std::vector<u32> parameters; + std::unique_ptr<u32[]> parameters; + std::size_t num_parameters = 0; + std::size_t parameters_capacity = 0; /// Index of the next parameter that will be fetched by the 'parm' instruction. u32 next_parameter_index = 0; - bool carry_flag{}; + bool carry_flag = false; }; } // namespace Tegra diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index 03d434b28..4f59a87b4 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -14,12 +14,22 @@ namespace OpenGL { namespace { + template <typename T> T GetInteger(GLenum pname) { GLint temporary; glGetIntegerv(pname, &temporary); return static_cast<T>(temporary); } + +bool TestProgram(const GLchar* glsl) { + const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &glsl)}; + GLint link_status; + glGetProgramiv(shader, GL_LINK_STATUS, &link_status); + glDeleteProgram(shader); + return link_status == GL_TRUE; +} + } // Anonymous namespace Device::Device() { @@ -32,6 +42,11 @@ Device::Device() { has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array; has_variable_aoffi = TestVariableAoffi(); has_component_indexing_bug = TestComponentIndexingBug(); + has_precise_bug = TestPreciseBug(); + + LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi); + LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug); + LOG_INFO(Render_OpenGL, "Renderer_PreciseBug: {}", has_precise_bug); } Device::Device(std::nullptr_t) { @@ -42,30 +57,21 @@ Device::Device(std::nullptr_t) { has_vertex_viewport_layer = true; has_variable_aoffi = true; has_component_indexing_bug = false; + has_precise_bug = false; } bool Device::TestVariableAoffi() { - const GLchar* AOFFI_TEST = R"(#version 430 core + return TestProgram(R"(#version 430 core // This is a unit test, please ignore me on apitrace bug reports. uniform sampler2D tex; uniform ivec2 variable_offset; out vec4 output_attribute; void main() { output_attribute = textureOffset(tex, vec2(0), variable_offset); -} -)"; - const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &AOFFI_TEST)}; - GLint link_status{}; - glGetProgramiv(shader, GL_LINK_STATUS, &link_status); - glDeleteProgram(shader); - - const bool supported{link_status == GL_TRUE}; - LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", supported); - return supported; +})"); } bool Device::TestComponentIndexingBug() { - constexpr char log_message[] = "Renderer_ComponentIndexingBug: {}"; const GLchar* COMPONENT_TEST = R"(#version 430 core layout (std430, binding = 0) buffer OutputBuffer { uint output_value; @@ -105,12 +111,21 @@ void main() { GLuint result; glGetNamedBufferSubData(ssbo.handle, 0, sizeof(result), &result); if (result != values.at(index)) { - LOG_INFO(Render_OpenGL, log_message, true); return true; } } - LOG_INFO(Render_OpenGL, log_message, false); return false; } +bool Device::TestPreciseBug() { + return !TestProgram(R"(#version 430 core +in vec3 coords; +out float out_value; +uniform sampler2DShadow tex; +void main() { + precise float tmp_value = vec4(texture(tex, coords)).x; + out_value = tmp_value; +})"); +} + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h index 3ef7c6dd8..ba6dcd3be 100644 --- a/src/video_core/renderer_opengl/gl_device.h +++ b/src/video_core/renderer_opengl/gl_device.h @@ -46,9 +46,14 @@ public: return has_component_indexing_bug; } + bool HasPreciseBug() const { + return has_precise_bug; + } + private: static bool TestVariableAoffi(); static bool TestComponentIndexingBug(); + static bool TestPreciseBug(); std::size_t uniform_buffer_alignment{}; std::size_t shader_storage_alignment{}; @@ -58,6 +63,7 @@ private: bool has_vertex_viewport_layer{}; bool has_variable_aoffi{}; bool has_component_indexing_bug{}; + bool has_precise_bug{}; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index bb09ecd52..4e266cdad 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -331,7 +331,7 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { const auto stage_enum = static_cast<Maxwell::ShaderStage>(stage); SetupDrawConstBuffers(stage_enum, shader); SetupDrawGlobalMemory(stage_enum, shader); - const auto texture_buffer_usage{SetupTextures(stage_enum, shader, base_bindings)}; + const auto texture_buffer_usage{SetupDrawTextures(stage_enum, shader, base_bindings)}; const ProgramVariant variant{base_bindings, primitive_mode, texture_buffer_usage}; const auto [program_handle, next_bindings] = shader->GetProgramHandle(variant); @@ -537,8 +537,7 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers( texture_cache.MarkDepthBufferInUse(); fbkey.zeta = depth_surface; - fbkey.stencil_enable = regs.stencil_enable && - depth_surface->GetSurfaceParams().type == SurfaceType::DepthStencil; + fbkey.stencil_enable = depth_surface->GetSurfaceParams().type == SurfaceType::DepthStencil; } texture_cache.GuardRenderTargets(false); @@ -577,16 +576,15 @@ void RasterizerOpenGL::ConfigureClearFramebuffer(OpenGLState& current_state, boo if (depth_surface) { const auto& params = depth_surface->GetSurfaceParams(); switch (params.type) { - case VideoCore::Surface::SurfaceType::Depth: { + case VideoCore::Surface::SurfaceType::Depth: depth_surface->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); break; - } - case VideoCore::Surface::SurfaceType::DepthStencil: { - depth_surface->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER); + case VideoCore::Surface::SurfaceType::DepthStencil: + depth_surface->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_DRAW_FRAMEBUFFER); break; - } - default: { UNIMPLEMENTED(); } + default: + UNIMPLEMENTED(); } } else { glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, @@ -639,6 +637,7 @@ void RasterizerOpenGL::Clear() { ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!"); use_stencil = true; clear_state.stencil.test_enabled = true; + if (regs.clear_flags.stencil) { // Stencil affects the clear so fill it with the used masks clear_state.stencil.front.test_func = GL_ALWAYS; @@ -802,7 +801,11 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { } auto kernel = shader_cache.GetComputeKernel(code_addr); - const auto [program, next_bindings] = kernel->GetProgramHandle({}); + ProgramVariant variant; + variant.texture_buffer_usage = SetupComputeTextures(kernel); + SetupComputeImages(kernel); + + const auto [program, next_bindings] = kernel->GetProgramHandle(variant); state.draw.shader_program = program; state.draw.program_pipeline = 0; @@ -817,13 +820,13 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { SetupComputeConstBuffers(kernel); SetupComputeGlobalMemory(kernel); - // TODO(Rodrigo): Bind images and samplers - buffer_cache.Unmap(); bind_ubo_pushbuffer.Bind(); bind_ssbo_pushbuffer.Bind(); + state.ApplyTextures(); + state.ApplyImages(); state.ApplyShaderProgram(); state.ApplyProgramPipeline(); @@ -923,7 +926,7 @@ void RasterizerOpenGL::SetupComputeConstBuffers(const Shader& kernel) { const auto& launch_desc = system.GPU().KeplerCompute().launch_description; for (const auto& entry : kernel->GetShaderEntries().const_buffers) { const auto& config = launch_desc.const_buffer_config[entry.GetIndex()]; - const std::bitset<8> mask = launch_desc.memory_config.const_buffer_enable_mask.Value(); + const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value(); Tegra::Engines::ConstBufferInfo buffer; buffer.address = config.Address(); buffer.size = config.size; @@ -982,53 +985,125 @@ void RasterizerOpenGL::SetupGlobalMemory(const GLShader::GlobalMemoryEntry& entr bind_ssbo_pushbuffer.Push(ssbo, buffer_offset, static_cast<GLsizeiptr>(size)); } -TextureBufferUsage RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader, - BaseBindings base_bindings) { +TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stage, + const Shader& shader, + BaseBindings base_bindings) { MICROPROFILE_SCOPE(OpenGL_Texture); const auto& gpu = system.GPU(); const auto& maxwell3d = gpu.Maxwell3D(); const auto& entries = shader->GetShaderEntries().samplers; - ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.texture_units), + ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.textures), "Exceeded the number of active textures."); TextureBufferUsage texture_buffer_usage{0}; for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { const auto& entry = entries[bindpoint]; - Tegra::Texture::FullTextureInfo texture; - if (entry.IsBindless()) { + const auto texture = [&]() { + if (!entry.IsBindless()) { + return maxwell3d.GetStageTexture(stage, entry.GetOffset()); + } const auto cbuf = entry.GetBindlessCBuf(); Tegra::Texture::TextureHandle tex_handle; tex_handle.raw = maxwell3d.AccessConstBuffer32(stage, cbuf.first, cbuf.second); - texture = maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset()); - } else { - texture = maxwell3d.GetStageTexture(stage, entry.GetOffset()); + return maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset()); + }(); + + if (SetupTexture(base_bindings.sampler + bindpoint, texture, entry)) { + texture_buffer_usage.set(bindpoint); } - const u32 current_bindpoint = base_bindings.sampler + bindpoint; + } - auto& unit{state.texture_units[current_bindpoint]}; - unit.sampler = sampler_cache.GetSampler(texture.tsc); + return texture_buffer_usage; +} - if (const auto view{texture_cache.GetTextureSurface(texture, entry)}; view) { - if (view->GetSurfaceParams().IsBuffer()) { - // Record that this texture is a texture buffer. - texture_buffer_usage.set(bindpoint); - } else { - // Apply swizzle to textures that are not buffers. - view->ApplySwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source, - texture.tic.w_source); +TextureBufferUsage RasterizerOpenGL::SetupComputeTextures(const Shader& kernel) { + MICROPROFILE_SCOPE(OpenGL_Texture); + const auto& compute = system.GPU().KeplerCompute(); + const auto& entries = kernel->GetShaderEntries().samplers; + + ASSERT_MSG(entries.size() <= std::size(state.textures), + "Exceeded the number of active textures."); + + TextureBufferUsage texture_buffer_usage{0}; + + for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { + const auto& entry = entries[bindpoint]; + const auto texture = [&]() { + if (!entry.IsBindless()) { + return compute.GetTexture(entry.GetOffset()); } - state.texture_units[current_bindpoint].texture = view->GetTexture(); - } else { - // Can occur when texture addr is null or its memory is unmapped/invalid - unit.texture = 0; + const auto cbuf = entry.GetBindlessCBuf(); + Tegra::Texture::TextureHandle tex_handle; + tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second); + return compute.GetTextureInfo(tex_handle, entry.GetOffset()); + }(); + + if (SetupTexture(bindpoint, texture, entry)) { + texture_buffer_usage.set(bindpoint); } } return texture_buffer_usage; } +bool RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, + const GLShader::SamplerEntry& entry) { + state.samplers[binding] = sampler_cache.GetSampler(texture.tsc); + + const auto view = texture_cache.GetTextureSurface(texture.tic, entry); + if (!view) { + // Can occur when texture addr is null or its memory is unmapped/invalid + state.textures[binding] = 0; + return false; + } + state.textures[binding] = view->GetTexture(); + + if (view->GetSurfaceParams().IsBuffer()) { + return true; + } + + // Apply swizzle to textures that are not buffers. + view->ApplySwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source, + texture.tic.w_source); + return false; +} + +void RasterizerOpenGL::SetupComputeImages(const Shader& shader) { + const auto& compute = system.GPU().KeplerCompute(); + const auto& entries = shader->GetShaderEntries().images; + for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { + const auto& entry = entries[bindpoint]; + const auto tic = [&]() { + if (!entry.IsBindless()) { + return compute.GetTexture(entry.GetOffset()).tic; + } + const auto cbuf = entry.GetBindlessCBuf(); + Tegra::Texture::TextureHandle tex_handle; + tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second); + return compute.GetTextureInfo(tex_handle, entry.GetOffset()).tic; + }(); + SetupImage(bindpoint, tic, entry); + } +} + +void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, + const GLShader::ImageEntry& entry) { + const auto view = texture_cache.GetImageSurface(tic, entry); + if (!view) { + state.images[binding] = 0; + return; + } + if (!tic.IsBuffer()) { + view->ApplySwizzle(tic.x_source, tic.y_source, tic.z_source, tic.w_source); + } + if (entry.IsWritten()) { + view->MarkAsModified(texture_cache.Tick()); + } + state.images[binding] = view->GetTexture(); +} + void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { const auto& regs = system.GPU().Maxwell3D().regs; const bool geometry_shaders_enabled = @@ -1119,9 +1194,12 @@ void RasterizerOpenGL::SyncStencilTestState() { if (!maxwell3d.dirty.stencil_test) { return; } - const auto& regs = maxwell3d.regs; + maxwell3d.dirty.stencil_test = false; + const auto& regs = maxwell3d.regs; state.stencil.test_enabled = regs.stencil_enable != 0; + state.MarkDirtyStencilState(); + if (!regs.stencil_enable) { return; } @@ -1150,8 +1228,6 @@ void RasterizerOpenGL::SyncStencilTestState() { state.stencil.back.action_depth_fail = GL_KEEP; state.stencil.back.action_depth_pass = GL_KEEP; } - state.MarkDirtyStencilState(); - maxwell3d.dirty.stencil_test = false; } void RasterizerOpenGL::SyncColorMask() { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 9d20a4fbf..eada752e0 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -32,6 +32,7 @@ #include "video_core/renderer_opengl/gl_state.h" #include "video_core/renderer_opengl/gl_texture_cache.h" #include "video_core/renderer_opengl/utils.h" +#include "video_core/textures/texture.h" namespace Core { class System; @@ -137,8 +138,22 @@ private: /// Configures the current textures to use for the draw command. Returns shaders texture buffer /// usage. - TextureBufferUsage SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, - const Shader& shader, BaseBindings base_bindings); + TextureBufferUsage SetupDrawTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, + const Shader& shader, BaseBindings base_bindings); + + /// Configures the textures used in a compute shader. Returns texture buffer usage. + TextureBufferUsage SetupComputeTextures(const Shader& kernel); + + /// Configures a texture. Returns true when the texture is a texture buffer. + bool SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, + const GLShader::SamplerEntry& entry); + + /// Configures images in a compute shader. + void SetupComputeImages(const Shader& shader); + + /// Configures an image. + void SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, + const GLShader::ImageEntry& entry); /// Syncs the viewport and depth range to match the guest state void SyncViewport(OpenGLState& current_state); diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index cf6a5cddf..909ccb82c 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -348,23 +348,16 @@ Shader CachedShader::CreateKernelFromCache(const ShaderParameters& params, } std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVariant& variant) { - GLuint handle{}; - if (program_type == ProgramType::Geometry) { - handle = GetGeometryShader(variant); - } else { - const auto [entry, is_cache_miss] = programs.try_emplace(variant); - auto& program = entry->second; - if (is_cache_miss) { - program = TryLoadProgram(variant); - if (!program) { - program = SpecializeShader(code, entries, program_type, variant); - disk_cache.SaveUsage(GetUsage(variant)); - } - - LabelGLObject(GL_PROGRAM, program->handle, cpu_addr); + const auto [entry, is_cache_miss] = programs.try_emplace(variant); + auto& program = entry->second; + if (is_cache_miss) { + program = TryLoadProgram(variant); + if (!program) { + program = SpecializeShader(code, entries, program_type, variant); + disk_cache.SaveUsage(GetUsage(variant)); } - handle = program->handle; + LabelGLObject(GL_PROGRAM, program->handle, cpu_addr); } auto base_bindings = variant.base_bindings; @@ -375,52 +368,9 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVar base_bindings.gmem += static_cast<u32>(entries.global_memory_entries.size()); base_bindings.sampler += static_cast<u32>(entries.samplers.size()); - return {handle, base_bindings}; + return {program->handle, base_bindings}; } -GLuint CachedShader::GetGeometryShader(const ProgramVariant& variant) { - const auto [entry, is_cache_miss] = geometry_programs.try_emplace(variant); - auto& programs = entry->second; - - switch (variant.primitive_mode) { - case GL_POINTS: - return LazyGeometryProgram(programs.points, variant); - case GL_LINES: - case GL_LINE_STRIP: - return LazyGeometryProgram(programs.lines, variant); - case GL_LINES_ADJACENCY: - case GL_LINE_STRIP_ADJACENCY: - return LazyGeometryProgram(programs.lines_adjacency, variant); - case GL_TRIANGLES: - case GL_TRIANGLE_STRIP: - case GL_TRIANGLE_FAN: - return LazyGeometryProgram(programs.triangles, variant); - case GL_TRIANGLES_ADJACENCY: - case GL_TRIANGLE_STRIP_ADJACENCY: - return LazyGeometryProgram(programs.triangles_adjacency, variant); - default: - UNREACHABLE_MSG("Unknown primitive mode."); - return LazyGeometryProgram(programs.points, variant); - } -} - -GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, - const ProgramVariant& variant) { - if (target_program) { - return target_program->handle; - } - const auto [glsl_name, debug_name, vertices] = GetPrimitiveDescription(variant.primitive_mode); - target_program = TryLoadProgram(variant); - if (!target_program) { - target_program = SpecializeShader(code, entries, program_type, variant); - disk_cache.SaveUsage(GetUsage(variant)); - } - - LabelGLObject(GL_PROGRAM, target_program->handle, cpu_addr, debug_name); - - return target_program->handle; -}; - CachedProgram CachedShader::TryLoadProgram(const ProgramVariant& variant) const { const auto found = precompiled_programs.find(GetUsage(variant)); if (found == precompiled_programs.end()) { diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 2c8faf855..de195cc5d 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -86,22 +86,6 @@ private: explicit CachedShader(const ShaderParameters& params, ProgramType program_type, GLShader::ProgramResult result); - // Geometry programs. These are needed because GLSL needs an input topology but it's not - // declared by the hardware. Workaround this issue by generating a different shader per input - // topology class. - struct GeometryPrograms { - CachedProgram points; - CachedProgram lines; - CachedProgram lines_adjacency; - CachedProgram triangles; - CachedProgram triangles_adjacency; - }; - - GLuint GetGeometryShader(const ProgramVariant& variant); - - /// Generates a geometry shader or returns one that already exists. - GLuint LazyGeometryProgram(CachedProgram& target_program, const ProgramVariant& variant); - CachedProgram TryLoadProgram(const ProgramVariant& variant) const; ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant) const; @@ -117,11 +101,6 @@ private: std::size_t shader_length{}; std::unordered_map<ProgramVariant, CachedProgram> programs; - std::unordered_map<ProgramVariant, GeometryPrograms> geometry_programs; - - std::unordered_map<u32, GLuint> cbuf_resource_cache; - std::unordered_map<u32, GLuint> gmem_resource_cache; - std::unordered_map<u32, GLint> uniform_cache; }; class ShaderCacheOpenGL final : public RasterizerCache<Shader> { diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 359d58cbe..137b23740 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -39,7 +39,7 @@ using namespace VideoCommon::Shader; using Maxwell = Tegra::Engines::Maxwell3D::Regs; using Operation = const OperationNode&; -enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat }; +enum class Type { Void, Bool, Bool2, Float, Int, Uint, HalfFloat }; struct TextureAoffi {}; using TextureArgument = std::pair<Type, Node>; @@ -48,7 +48,7 @@ using TextureIR = std::variant<TextureAoffi, TextureArgument>; constexpr u32 MAX_CONSTBUFFER_ELEMENTS = static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float)); -class ShaderWriter { +class ShaderWriter final { public: void AddExpression(std::string_view text) { DEBUG_ASSERT(scope >= 0); @@ -93,9 +93,157 @@ private: u32 temporary_index = 1; }; +class Expression final { +public: + Expression(std::string code, Type type) : code{std::move(code)}, type{type} { + ASSERT(type != Type::Void); + } + Expression() : type{Type::Void} {} + + Type GetType() const { + return type; + } + + std::string GetCode() const { + return code; + } + + void CheckVoid() const { + ASSERT(type == Type::Void); + } + + std::string As(Type type) const { + switch (type) { + case Type::Bool: + return AsBool(); + case Type::Bool2: + return AsBool2(); + case Type::Float: + return AsFloat(); + case Type::Int: + return AsInt(); + case Type::Uint: + return AsUint(); + case Type::HalfFloat: + return AsHalfFloat(); + default: + UNREACHABLE_MSG("Invalid type"); + return code; + } + } + + std::string AsBool() const { + switch (type) { + case Type::Bool: + return code; + default: + UNREACHABLE_MSG("Incompatible types"); + return code; + } + } + + std::string AsBool2() const { + switch (type) { + case Type::Bool2: + return code; + default: + UNREACHABLE_MSG("Incompatible types"); + return code; + } + } + + std::string AsFloat() const { + switch (type) { + case Type::Float: + return code; + case Type::Uint: + return fmt::format("utof({})", code); + case Type::Int: + return fmt::format("itof({})", code); + case Type::HalfFloat: + return fmt::format("utof(packHalf2x16({}))", code); + default: + UNREACHABLE_MSG("Incompatible types"); + return code; + } + } + + std::string AsInt() const { + switch (type) { + case Type::Float: + return fmt::format("ftoi({})", code); + case Type::Uint: + return fmt::format("int({})", code); + case Type::Int: + return code; + case Type::HalfFloat: + return fmt::format("int(packHalf2x16({}))", code); + default: + UNREACHABLE_MSG("Incompatible types"); + return code; + } + } + + std::string AsUint() const { + switch (type) { + case Type::Float: + return fmt::format("ftou({})", code); + case Type::Uint: + return code; + case Type::Int: + return fmt::format("uint({})", code); + case Type::HalfFloat: + return fmt::format("packHalf2x16({})", code); + default: + UNREACHABLE_MSG("Incompatible types"); + return code; + } + } + + std::string AsHalfFloat() const { + switch (type) { + case Type::Float: + return fmt::format("unpackHalf2x16(ftou({}))", code); + case Type::Uint: + return fmt::format("unpackHalf2x16({})", code); + case Type::Int: + return fmt::format("unpackHalf2x16(int({}))", code); + case Type::HalfFloat: + return code; + default: + UNREACHABLE_MSG("Incompatible types"); + return code; + } + } + +private: + std::string code; + Type type{}; +}; + +constexpr const char* GetTypeString(Type type) { + switch (type) { + case Type::Bool: + return "bool"; + case Type::Bool2: + return "bvec2"; + case Type::Float: + return "float"; + case Type::Int: + return "int"; + case Type::Uint: + return "uint"; + case Type::HalfFloat: + return "vec2"; + default: + UNREACHABLE_MSG("Invalid type"); + return "<invalid type>"; + } +} + /// Generates code to use for a swizzle operation. constexpr const char* GetSwizzle(u32 element) { - constexpr std::array<const char*, 4> swizzle = {".x", ".y", ".z", ".w"}; + constexpr std::array swizzle = {".x", ".y", ".z", ".w"}; return swizzle.at(element); } @@ -134,8 +282,8 @@ constexpr bool IsGenericAttribute(Attribute::Index index) { return index >= Attribute::Index::Attribute_0 && index <= Attribute::Index::Attribute_31; } -constexpr Attribute::Index ToGenericAttribute(u32 value) { - return static_cast<Attribute::Index>(value + static_cast<u32>(Attribute::Index::Attribute_0)); +constexpr Attribute::Index ToGenericAttribute(u64 value) { + return static_cast<Attribute::Index>(value + static_cast<u64>(Attribute::Index::Attribute_0)); } u32 GetGenericAttributeIndex(Attribute::Index index) { @@ -191,7 +339,7 @@ public: // VM's program counter const auto first_address = ir.GetBasicBlocks().begin()->first; - code.AddLine("uint jmp_to = {}u;", first_address); + code.AddLine("uint jmp_to = {}U;", first_address); // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems // unlikely that shaders will use 20 nested SSYs and PBKs. @@ -199,7 +347,7 @@ public: constexpr u32 FLOW_STACK_SIZE = 20; for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) { code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE); - code.AddLine("uint {} = 0u;", FlowStackTopName(stack)); + code.AddLine("uint {} = 0U;", FlowStackTopName(stack)); } } @@ -210,7 +358,7 @@ public: for (const auto& pair : ir.GetBasicBlocks()) { const auto [address, bb] = pair; - code.AddLine("case 0x{:x}u: {{", address); + code.AddLine("case 0x{:X}U: {{", address); ++code.scope; VisitBlock(bb); @@ -241,11 +389,10 @@ public: for (const auto& sampler : ir.GetSamplers()) { entries.samplers.emplace_back(sampler); } - for (const auto& image : ir.GetImages()) { + for (const auto& [offset, image] : ir.GetImages()) { entries.images.emplace_back(image); } - for (const auto& gmem_pair : ir.GetGlobalMemory()) { - const auto& [base, usage] = gmem_pair; + for (const auto& [base, usage] : ir.GetGlobalMemory()) { entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset, usage.is_read, usage.is_written); } @@ -322,7 +469,7 @@ private: void DeclareRegisters() { const auto& registers = ir.GetRegisters(); for (const u32 gpr : registers) { - code.AddLine("float {} = 0;", GetRegister(gpr)); + code.AddLine("float {} = 0.0f;", GetRegister(gpr)); } if (!registers.empty()) { code.AddNewLine(); @@ -348,7 +495,7 @@ private: return; } const auto element_count = Common::AlignUp(local_memory_size, 4) / 4; - code.AddLine("float {}[{}];", GetLocalMemory(), element_count); + code.AddLine("uint {}[{}];", GetLocalMemory(), element_count); code.AddNewLine(); } @@ -371,8 +518,6 @@ private: return "noperspective "; default: case AttributeUse::Unused: - UNREACHABLE_MSG("Unused attribute being fetched"); - return {}; UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<u32>(attribute)); return {}; } @@ -449,7 +594,7 @@ private: const auto [index, size] = entry; code.AddLine("layout (std140, binding = CBUF_BINDING_{}) uniform {} {{", index, GetConstBufferBlock(index)); - code.AddLine(" vec4 {}[MAX_CONSTBUFFER_ELEMENTS];", GetConstBuffer(index)); + code.AddLine(" uvec4 {}[{}];", GetConstBuffer(index), MAX_CONSTBUFFER_ELEMENTS); code.AddLine("}};"); code.AddNewLine(); } @@ -470,7 +615,7 @@ private: code.AddLine("layout (std430, binding = GMEM_BINDING_{}_{}) {} buffer {} {{", base.cbuf_index, base.cbuf_offset, qualifier, GetGlobalMemoryBlock(base)); - code.AddLine(" float {}[];", GetGlobalMemory(base)); + code.AddLine(" uint {}[];", GetGlobalMemory(base)); code.AddLine("}};"); code.AddNewLine(); } @@ -528,7 +673,7 @@ private: if (!ir.HasPhysicalAttributes()) { return; } - code.AddLine("float readPhysicalAttribute(uint physical_address) {{"); + code.AddLine("float ReadPhysicalAttribute(uint physical_address) {{"); ++code.scope; code.AddLine("switch (physical_address) {{"); @@ -537,15 +682,16 @@ private: for (u32 index = 0; index < num_attributes; ++index) { const auto attribute{ToGenericAttribute(index)}; for (u32 element = 0; element < 4; ++element) { - constexpr u32 generic_base{0x80}; - constexpr u32 generic_stride{16}; - constexpr u32 element_stride{4}; + constexpr u32 generic_base = 0x80; + constexpr u32 generic_stride = 16; + constexpr u32 element_stride = 4; const u32 address{generic_base + index * generic_stride + element * element_stride}; - const bool declared{stage != ProgramType::Fragment || - header.ps.GetAttributeUse(index) != AttributeUse::Unused}; - const std::string value{declared ? ReadAttribute(attribute, element) : "0"}; - code.AddLine("case 0x{:x}: return {};", address, value); + const bool declared = stage != ProgramType::Fragment || + header.ps.GetAttributeUse(index) != AttributeUse::Unused; + const std::string value = + declared ? ReadAttribute(attribute, element).AsFloat() : "0.0f"; + code.AddLine("case 0x{:X}U: return {};", address, value); } } @@ -559,8 +705,8 @@ private: void DeclareImages() { const auto& images{ir.GetImages()}; - for (const auto& image : images) { - const std::string image_type = [&]() { + for (const auto& [offset, image] : images) { + const char* image_type = [&] { switch (image.GetType()) { case Tegra::Shader::ImageType::Texture1D: return "image1D"; @@ -579,9 +725,33 @@ private: return "image1D"; } }(); - code.AddLine("layout (binding = IMAGE_BINDING_{}) coherent volatile writeonly uniform " + + const auto [type_prefix, format] = [&]() -> std::pair<const char*, const char*> { + if (!image.IsSizeKnown()) { + return {"", ""}; + } + switch (image.GetSize()) { + case Tegra::Shader::ImageAtomicSize::U32: + return {"u", "r32ui, "}; + case Tegra::Shader::ImageAtomicSize::S32: + return {"i", "r32i, "}; + default: + UNIMPLEMENTED_MSG("Unimplemented atomic size={}", + static_cast<u32>(image.GetSize())); + return {"", ""}; + } + }(); + + std::string qualifier = "coherent volatile"; + if (image.IsRead() && !image.IsWritten()) { + qualifier += " readonly"; + } else if (image.IsWritten() && !image.IsRead()) { + qualifier += " writeonly"; + } + + code.AddLine("layout (binding = IMAGE_BINDING_{}) {} uniform " "{} {};", - image.GetIndex(), image_type, GetImage(image)); + image.GetIndex(), qualifier, image_type, GetImage(image)); } if (!images.empty()) { code.AddNewLine(); @@ -590,13 +760,11 @@ private: void VisitBlock(const NodeBlock& bb) { for (const auto& node : bb) { - if (const std::string expr = Visit(node); !expr.empty()) { - code.AddLine(expr); - } + Visit(node).CheckVoid(); } } - std::string Visit(const Node& node) { + Expression Visit(const Node& node) { if (const auto operation = std::get_if<OperationNode>(&*node)) { const auto operation_index = static_cast<std::size_t>(operation->GetCode()); if (operation_index >= operation_decompilers.size()) { @@ -614,18 +782,18 @@ private: if (const auto gpr = std::get_if<GprNode>(&*node)) { const u32 index = gpr->GetIndex(); if (index == Register::ZeroIndex) { - return "0"; + return {"0U", Type::Uint}; } - return GetRegister(index); + return {GetRegister(index), Type::Float}; } if (const auto immediate = std::get_if<ImmediateNode>(&*node)) { const u32 value = immediate->GetValue(); if (value < 10) { // For eyecandy avoid using hex numbers on single digits - return fmt::format("utof({}u)", immediate->GetValue()); + return {fmt::format("{}U", immediate->GetValue()), Type::Uint}; } - return fmt::format("utof(0x{:x}u)", immediate->GetValue()); + return {fmt::format("0x{:X}U", immediate->GetValue()), Type::Uint}; } if (const auto predicate = std::get_if<PredicateNode>(&*node)) { @@ -640,17 +808,18 @@ private: } }(); if (predicate->IsNegated()) { - return fmt::format("!({})", value); + return {fmt::format("!({})", value), Type::Bool}; } - return value; + return {value, Type::Bool}; } if (const auto abuf = std::get_if<AbufNode>(&*node)) { UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ProgramType::Geometry, "Physical attributes in geometry shaders are not implemented"); if (abuf->IsPhysicalBuffer()) { - return fmt::format("readPhysicalAttribute(ftou({}))", - Visit(abuf->GetPhysicalAddress())); + return {fmt::format("ReadPhysicalAttribute({})", + Visit(abuf->GetPhysicalAddress()).AsUint()), + Type::Float}; } return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer()); } @@ -661,59 +830,64 @@ private: // Direct access const u32 offset_imm = immediate->GetValue(); ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access"); - return fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), - offset_imm / (4 * 4), (offset_imm / 4) % 4); + return {fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), + offset_imm / (4 * 4), (offset_imm / 4) % 4), + Type::Uint}; } if (std::holds_alternative<OperationNode>(*offset)) { // Indirect access const std::string final_offset = code.GenerateTemporary(); - code.AddLine("uint {} = ftou({}) >> 2;", final_offset, Visit(offset)); + code.AddLine("uint {} = {} >> 2;", final_offset, Visit(offset).AsUint()); if (!device.HasComponentIndexingBug()) { - return fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()), - final_offset, final_offset); + return {fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()), + final_offset, final_offset), + Type::Uint}; } // AMD's proprietary GLSL compiler emits ill code for variable component access. // To bypass this driver bug generate 4 ifs, one per each component. const std::string pack = code.GenerateTemporary(); - code.AddLine("vec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()), + code.AddLine("uvec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()), final_offset); const std::string result = code.GenerateTemporary(); - code.AddLine("float {};", result); + code.AddLine("uint {};", result); for (u32 swizzle = 0; swizzle < 4; ++swizzle) { code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result, pack, GetSwizzle(swizzle)); } - return result; + return {result, Type::Uint}; } UNREACHABLE_MSG("Unmanaged offset node type"); } if (const auto gmem = std::get_if<GmemNode>(&*node)) { - const std::string real = Visit(gmem->GetRealAddress()); - const std::string base = Visit(gmem->GetBaseAddress()); - const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); - return fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); + const std::string real = Visit(gmem->GetRealAddress()).AsUint(); + const std::string base = Visit(gmem->GetBaseAddress()).AsUint(); + const std::string final_offset = fmt::format("({} - {}) >> 2", real, base); + return {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset), + Type::Uint}; } if (const auto lmem = std::get_if<LmemNode>(&*node)) { if (stage == ProgramType::Compute) { LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); } - return fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); + return { + fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()), + Type::Uint}; } if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) { - return GetInternalFlag(internal_flag->GetFlag()); + return {GetInternalFlag(internal_flag->GetFlag()), Type::Bool}; } if (const auto conditional = std::get_if<ConditionalNode>(&*node)) { // It's invalid to call conditional on nested nodes, use an operation instead - code.AddLine("if ({}) {{", Visit(conditional->GetCondition())); + code.AddLine("if ({}) {{", Visit(conditional->GetCondition()).AsBool()); ++code.scope; VisitBlock(conditional->GetCode()); @@ -724,20 +898,21 @@ private: } if (const auto comment = std::get_if<CommentNode>(&*node)) { - return "// " + comment->GetText(); + code.AddLine("// " + comment->GetText()); + return {}; } UNREACHABLE(); return {}; } - std::string ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) { + Expression ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) { const auto GeometryPass = [&](std::string_view name) { if (stage == ProgramType::Geometry && buffer) { // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games // set an 0x80000000 index for those and the shader fails to build. Find out why // this happens and what's its intent. - return fmt::format("gs_{}[ftou({}) % MAX_VERTEX_INPUT]", name, Visit(buffer)); + return fmt::format("gs_{}[{} % MAX_VERTEX_INPUT]", name, Visit(buffer).AsUint()); } return std::string(name); }; @@ -746,25 +921,27 @@ private: case Attribute::Index::Position: switch (stage) { case ProgramType::Geometry: - return fmt::format("gl_in[ftou({})].gl_Position{}", Visit(buffer), - GetSwizzle(element)); + return {fmt::format("gl_in[{}].gl_Position{}", Visit(buffer).AsUint(), + GetSwizzle(element)), + Type::Float}; case ProgramType::Fragment: - return element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)); + return {element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)), + Type::Float}; default: UNREACHABLE(); } case Attribute::Index::PointCoord: switch (element) { case 0: - return "gl_PointCoord.x"; + return {"gl_PointCoord.x", Type::Float}; case 1: - return "gl_PointCoord.y"; + return {"gl_PointCoord.y", Type::Float}; case 2: case 3: - return "0"; + return {"0.0f", Type::Float}; } UNREACHABLE(); - return "0"; + return {"0", Type::Int}; case Attribute::Index::TessCoordInstanceIDVertexID: // TODO(Subv): Find out what the values are for the first two elements when inside a // vertex shader, and what's the value of the fourth element when inside a Tess Eval @@ -773,44 +950,49 @@ private: switch (element) { case 2: // Config pack's first value is instance_id. - return "uintBitsToFloat(config_pack[0])"; + return {"config_pack[0]", Type::Uint}; case 3: - return "uintBitsToFloat(gl_VertexID)"; + return {"gl_VertexID", Type::Int}; } UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element); - return "0"; + return {"0", Type::Int}; case Attribute::Index::FrontFacing: // TODO(Subv): Find out what the values are for the other elements. ASSERT(stage == ProgramType::Fragment); switch (element) { case 3: - return "itof(gl_FrontFacing ? -1 : 0)"; + return {"(gl_FrontFacing ? -1 : 0)", Type::Int}; } UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element); - return "0"; + return {"0", Type::Int}; default: if (IsGenericAttribute(attribute)) { - return GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element); + return {GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element), + Type::Float}; } break; } UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); - return "0"; + return {"0", Type::Int}; } - std::string ApplyPrecise(Operation operation, const std::string& value) { + Expression ApplyPrecise(Operation operation, std::string value, Type type) { if (!IsPrecise(operation)) { - return value; + return {std::move(value), type}; } - // There's a bug in NVidia's proprietary drivers that makes precise fail on fragment shaders - const std::string precise = stage != ProgramType::Fragment ? "precise " : ""; + // Old Nvidia drivers have a bug with precise and texture sampling. These are more likely to + // be found in fragment shaders, so we disable precise there. There are vertex shaders that + // also fail to build but nobody seems to care about those. + // Note: Only bugged drivers will skip precise. + const bool disable_precise = device.HasPreciseBug() && stage == ProgramType::Fragment; - const std::string temporary = code.GenerateTemporary(); - code.AddLine("{}float {} = {};", precise, temporary, value); - return temporary; + std::string temporary = code.GenerateTemporary(); + code.AddLine("{}{} {} = {};", disable_precise ? "" : "precise ", GetTypeString(type), + temporary, value); + return {std::move(temporary), type}; } - std::string VisitOperand(Operation operation, std::size_t operand_index) { + Expression VisitOperand(Operation operation, std::size_t operand_index) { const auto& operand = operation[operand_index]; const bool parent_precise = IsPrecise(operation); const bool child_precise = IsPrecise(operand); @@ -819,19 +1001,16 @@ private: return Visit(operand); } - const std::string temporary = code.GenerateTemporary(); - code.AddLine("float {} = {};", temporary, Visit(operand)); - return temporary; - } - - std::string VisitOperand(Operation operation, std::size_t operand_index, Type type) { - return CastOperand(VisitOperand(operation, operand_index), type); + Expression value = Visit(operand); + std::string temporary = code.GenerateTemporary(); + code.AddLine("{} {} = {};", GetTypeString(value.GetType()), temporary, value.GetCode()); + return {std::move(temporary), value.GetType()}; } - std::optional<std::pair<std::string, bool>> GetOutputAttribute(const AbufNode* abuf) { + Expression GetOutputAttribute(const AbufNode* abuf) { switch (const auto attribute = abuf->GetIndex()) { case Attribute::Index::Position: - return std::make_pair("gl_Position"s + GetSwizzle(abuf->GetElement()), false); + return {"gl_Position"s + GetSwizzle(abuf->GetElement()), Type::Float}; case Attribute::Index::LayerViewportPointSize: switch (abuf->GetElement()) { case 0: @@ -841,119 +1020,79 @@ private: if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { return {}; } - return std::make_pair("gl_Layer", true); + return {"gl_Layer", Type::Int}; case 2: if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { return {}; } - return std::make_pair("gl_ViewportIndex", true); + return {"gl_ViewportIndex", Type::Int}; case 3: UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader"); - return std::make_pair("gl_PointSize", false); + return {"gl_PointSize", Type::Float}; } return {}; case Attribute::Index::ClipDistances0123: - return std::make_pair(fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), false); + return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), Type::Float}; case Attribute::Index::ClipDistances4567: - return std::make_pair(fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), - false); + return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), Type::Float}; default: if (IsGenericAttribute(attribute)) { - return std::make_pair( - GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()), false); + return {GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()), + Type::Float}; } UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute)); return {}; } } - std::string CastOperand(const std::string& value, Type type) const { - switch (type) { - case Type::Bool: - case Type::Bool2: - case Type::Float: - return value; - case Type::Int: - return fmt::format("ftoi({})", value); - case Type::Uint: - return fmt::format("ftou({})", value); - case Type::HalfFloat: - return fmt::format("toHalf2({})", value); - } - UNREACHABLE(); - return value; - } - - std::string BitwiseCastResult(const std::string& value, Type type, - bool needs_parenthesis = false) { - switch (type) { - case Type::Bool: - case Type::Bool2: - case Type::Float: - if (needs_parenthesis) { - return fmt::format("({})", value); - } - return value; - case Type::Int: - return fmt::format("itof({})", value); - case Type::Uint: - return fmt::format("utof({})", value); - case Type::HalfFloat: - return fmt::format("fromHalf2({})", value); - } - UNREACHABLE(); - return value; - } - - std::string GenerateUnary(Operation operation, const std::string& func, Type result_type, - Type type_a, bool needs_parenthesis = true) { - const std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0, type_a)); - - return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type, needs_parenthesis)); + Expression GenerateUnary(Operation operation, std::string_view func, Type result_type, + Type type_a) { + std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0).As(type_a)); + return ApplyPrecise(operation, std::move(op_str), result_type); } - std::string GenerateBinaryInfix(Operation operation, const std::string& func, Type result_type, - Type type_a, Type type_b) { - const std::string op_a = VisitOperand(operation, 0, type_a); - const std::string op_b = VisitOperand(operation, 1, type_b); - const std::string op_str = fmt::format("({} {} {})", op_a, func, op_b); + Expression GenerateBinaryInfix(Operation operation, std::string_view func, Type result_type, + Type type_a, Type type_b) { + const std::string op_a = VisitOperand(operation, 0).As(type_a); + const std::string op_b = VisitOperand(operation, 1).As(type_b); + std::string op_str = fmt::format("({} {} {})", op_a, func, op_b); - return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); + return ApplyPrecise(operation, std::move(op_str), result_type); } - std::string GenerateBinaryCall(Operation operation, const std::string& func, Type result_type, - Type type_a, Type type_b) { - const std::string op_a = VisitOperand(operation, 0, type_a); - const std::string op_b = VisitOperand(operation, 1, type_b); - const std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b); + Expression GenerateBinaryCall(Operation operation, std::string_view func, Type result_type, + Type type_a, Type type_b) { + const std::string op_a = VisitOperand(operation, 0).As(type_a); + const std::string op_b = VisitOperand(operation, 1).As(type_b); + std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b); - return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); + return ApplyPrecise(operation, std::move(op_str), result_type); } - std::string GenerateTernary(Operation operation, const std::string& func, Type result_type, - Type type_a, Type type_b, Type type_c) { - const std::string op_a = VisitOperand(operation, 0, type_a); - const std::string op_b = VisitOperand(operation, 1, type_b); - const std::string op_c = VisitOperand(operation, 2, type_c); - const std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c); + Expression GenerateTernary(Operation operation, std::string_view func, Type result_type, + Type type_a, Type type_b, Type type_c) { + const std::string op_a = VisitOperand(operation, 0).As(type_a); + const std::string op_b = VisitOperand(operation, 1).As(type_b); + const std::string op_c = VisitOperand(operation, 2).As(type_c); + std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c); - return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); + return ApplyPrecise(operation, std::move(op_str), result_type); } - std::string GenerateQuaternary(Operation operation, const std::string& func, Type result_type, - Type type_a, Type type_b, Type type_c, Type type_d) { - const std::string op_a = VisitOperand(operation, 0, type_a); - const std::string op_b = VisitOperand(operation, 1, type_b); - const std::string op_c = VisitOperand(operation, 2, type_c); - const std::string op_d = VisitOperand(operation, 3, type_d); - const std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d); + Expression GenerateQuaternary(Operation operation, const std::string& func, Type result_type, + Type type_a, Type type_b, Type type_c, Type type_d) { + const std::string op_a = VisitOperand(operation, 0).As(type_a); + const std::string op_b = VisitOperand(operation, 1).As(type_b); + const std::string op_c = VisitOperand(operation, 2).As(type_c); + const std::string op_d = VisitOperand(operation, 3).As(type_d); + std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d); - return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); + return ApplyPrecise(operation, std::move(op_str), result_type); } std::string GenerateTexture(Operation operation, const std::string& function_suffix, const std::vector<TextureIR>& extras) { - constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"}; + constexpr std::array coord_constructors = {"float", "vec2", "vec3", "vec4"}; const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); @@ -970,17 +1109,17 @@ private: expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1); expr += '('; for (std::size_t i = 0; i < count; ++i) { - expr += Visit(operation[i]); + expr += Visit(operation[i]).AsFloat(); const std::size_t next = i + 1; if (next < count) expr += ", "; } if (has_array) { - expr += ", float(ftoi(" + Visit(meta->array) + "))"; + expr += ", float(" + Visit(meta->array).AsInt() + ')'; } if (has_shadow) { - expr += ", " + Visit(meta->depth_compare); + expr += ", " + Visit(meta->depth_compare).AsFloat(); } expr += ')'; @@ -1011,11 +1150,11 @@ private: // required to be constant) expr += std::to_string(static_cast<s32>(immediate->GetValue())); } else { - expr += fmt::format("ftoi({})", Visit(operand)); + expr += Visit(operand).AsInt(); } break; case Type::Float: - expr += Visit(operand); + expr += Visit(operand).AsFloat(); break; default: { const auto type_int = static_cast<u32>(type); @@ -1031,7 +1170,7 @@ private: if (aoffi.empty()) { return {}; } - constexpr std::array<const char*, 3> coord_constructors = {"int", "ivec2", "ivec3"}; + constexpr std::array coord_constructors = {"int", "ivec2", "ivec3"}; std::string expr = ", "; expr += coord_constructors.at(aoffi.size() - 1); expr += '('; @@ -1044,7 +1183,7 @@ private: expr += std::to_string(static_cast<s32>(immediate->GetValue())); } else if (device.HasVariableAoffi()) { // Avoid using variable AOFFI on unsupported devices. - expr += fmt::format("ftoi({})", Visit(operand)); + expr += Visit(operand).AsInt(); } else { // Insert 0 on devices not supporting variable AOFFI. expr += '0'; @@ -1058,328 +1197,382 @@ private: return expr; } - std::string Assign(Operation operation) { + std::string BuildIntegerCoordinates(Operation operation) { + constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("}; + const std::size_t coords_count{operation.GetOperandsCount()}; + std::string expr = constructors.at(coords_count - 1); + for (std::size_t i = 0; i < coords_count; ++i) { + expr += VisitOperand(operation, i).AsInt(); + if (i + 1 < coords_count) { + expr += ", "; + } + } + expr += ')'; + return expr; + } + + std::string BuildImageValues(Operation operation) { + const auto meta{std::get<MetaImage>(operation.GetMeta())}; + const auto [constructors, type] = [&]() -> std::pair<std::array<const char*, 4>, Type> { + constexpr std::array float_constructors{"float", "vec2", "vec3", "vec4"}; + if (!meta.image.IsSizeKnown()) { + return {float_constructors, Type::Float}; + } + switch (meta.image.GetSize()) { + case Tegra::Shader::ImageAtomicSize::U32: + return {{"uint", "uvec2", "uvec3", "uvec4"}, Type::Uint}; + case Tegra::Shader::ImageAtomicSize::S32: + return {{"int", "ivec2", "ivec3", "ivec4"}, Type::Uint}; + default: + UNIMPLEMENTED_MSG("Unimplemented image size={}", + static_cast<u32>(meta.image.GetSize())); + return {float_constructors, Type::Float}; + } + }(); + + const std::size_t values_count{meta.values.size()}; + std::string expr = fmt::format("{}(", constructors.at(values_count - 1)); + for (std::size_t i = 0; i < values_count; ++i) { + expr += Visit(meta.values.at(i)).As(type); + if (i + 1 < values_count) { + expr += ", "; + } + } + expr += ')'; + return expr; + } + + Expression AtomicImage(Operation operation, const char* opname) { + constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("}; + const auto meta{std::get<MetaImage>(operation.GetMeta())}; + ASSERT(meta.values.size() == 1); + ASSERT(meta.image.IsSizeKnown()); + + const auto type = [&]() { + switch (const auto size = meta.image.GetSize()) { + case Tegra::Shader::ImageAtomicSize::U32: + return Type::Uint; + case Tegra::Shader::ImageAtomicSize::S32: + return Type::Int; + default: + UNIMPLEMENTED_MSG("Unimplemented image size={}", static_cast<u32>(size)); + return Type::Uint; + } + }(); + + return {fmt::format("{}({}, {}, {})", opname, GetImage(meta.image), + BuildIntegerCoordinates(operation), Visit(meta.values[0]).As(type)), + type}; + } + + Expression Assign(Operation operation) { const Node& dest = operation[0]; const Node& src = operation[1]; - std::string target; - bool is_integer = false; - + Expression target; if (const auto gpr = std::get_if<GprNode>(&*dest)) { if (gpr->GetIndex() == Register::ZeroIndex) { // Writing to Register::ZeroIndex is a no op return {}; } - target = GetRegister(gpr->GetIndex()); + target = {GetRegister(gpr->GetIndex()), Type::Float}; } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) { UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer()); - const auto result = GetOutputAttribute(abuf); - if (!result) { - return {}; - } - target = result->first; - is_integer = result->second; + target = GetOutputAttribute(abuf); } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) { if (stage == ProgramType::Compute) { LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); } - target = fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); + target = { + fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()), + Type::Uint}; } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) { - const std::string real = Visit(gmem->GetRealAddress()); - const std::string base = Visit(gmem->GetBaseAddress()); - const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); - target = fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); + const std::string real = Visit(gmem->GetRealAddress()).AsUint(); + const std::string base = Visit(gmem->GetBaseAddress()).AsUint(); + const std::string final_offset = fmt::format("({} - {}) >> 2", real, base); + target = {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset), + Type::Uint}; } else { UNREACHABLE_MSG("Assign called without a proper target"); } - if (is_integer) { - code.AddLine("{} = ftoi({});", target, Visit(src)); - } else { - code.AddLine("{} = {};", target, Visit(src)); - } + code.AddLine("{} = {};", target.GetCode(), Visit(src).As(target.GetType())); return {}; } template <Type type> - std::string Add(Operation operation) { + Expression Add(Operation operation) { return GenerateBinaryInfix(operation, "+", type, type, type); } template <Type type> - std::string Mul(Operation operation) { + Expression Mul(Operation operation) { return GenerateBinaryInfix(operation, "*", type, type, type); } template <Type type> - std::string Div(Operation operation) { + Expression Div(Operation operation) { return GenerateBinaryInfix(operation, "/", type, type, type); } template <Type type> - std::string Fma(Operation operation) { + Expression Fma(Operation operation) { return GenerateTernary(operation, "fma", type, type, type, type); } template <Type type> - std::string Negate(Operation operation) { - return GenerateUnary(operation, "-", type, type, true); + Expression Negate(Operation operation) { + return GenerateUnary(operation, "-", type, type); } template <Type type> - std::string Absolute(Operation operation) { - return GenerateUnary(operation, "abs", type, type, false); + Expression Absolute(Operation operation) { + return GenerateUnary(operation, "abs", type, type); } - std::string FClamp(Operation operation) { + Expression FClamp(Operation operation) { return GenerateTernary(operation, "clamp", Type::Float, Type::Float, Type::Float, Type::Float); } - std::string FCastHalf0(Operation operation) { - const std::string op_a = VisitOperand(operation, 0, Type::HalfFloat); - return fmt::format("({})[0]", op_a); + Expression FCastHalf0(Operation operation) { + return {fmt::format("({})[0]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float}; } - std::string FCastHalf1(Operation operation) { - const std::string op_a = VisitOperand(operation, 0, Type::HalfFloat); - return fmt::format("({})[1]", op_a); + Expression FCastHalf1(Operation operation) { + return {fmt::format("({})[1]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float}; } template <Type type> - std::string Min(Operation operation) { + Expression Min(Operation operation) { return GenerateBinaryCall(operation, "min", type, type, type); } template <Type type> - std::string Max(Operation operation) { + Expression Max(Operation operation) { return GenerateBinaryCall(operation, "max", type, type, type); } - std::string Select(Operation operation) { - const std::string condition = Visit(operation[0]); - const std::string true_case = Visit(operation[1]); - const std::string false_case = Visit(operation[2]); - const std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case); + Expression Select(Operation operation) { + const std::string condition = Visit(operation[0]).AsBool(); + const std::string true_case = Visit(operation[1]).AsUint(); + const std::string false_case = Visit(operation[2]).AsUint(); + std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case); - return ApplyPrecise(operation, op_str); + return ApplyPrecise(operation, std::move(op_str), Type::Uint); } - std::string FCos(Operation operation) { - return GenerateUnary(operation, "cos", Type::Float, Type::Float, false); + Expression FCos(Operation operation) { + return GenerateUnary(operation, "cos", Type::Float, Type::Float); } - std::string FSin(Operation operation) { - return GenerateUnary(operation, "sin", Type::Float, Type::Float, false); + Expression FSin(Operation operation) { + return GenerateUnary(operation, "sin", Type::Float, Type::Float); } - std::string FExp2(Operation operation) { - return GenerateUnary(operation, "exp2", Type::Float, Type::Float, false); + Expression FExp2(Operation operation) { + return GenerateUnary(operation, "exp2", Type::Float, Type::Float); } - std::string FLog2(Operation operation) { - return GenerateUnary(operation, "log2", Type::Float, Type::Float, false); + Expression FLog2(Operation operation) { + return GenerateUnary(operation, "log2", Type::Float, Type::Float); } - std::string FInverseSqrt(Operation operation) { - return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float, false); + Expression FInverseSqrt(Operation operation) { + return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float); } - std::string FSqrt(Operation operation) { - return GenerateUnary(operation, "sqrt", Type::Float, Type::Float, false); + Expression FSqrt(Operation operation) { + return GenerateUnary(operation, "sqrt", Type::Float, Type::Float); } - std::string FRoundEven(Operation operation) { - return GenerateUnary(operation, "roundEven", Type::Float, Type::Float, false); + Expression FRoundEven(Operation operation) { + return GenerateUnary(operation, "roundEven", Type::Float, Type::Float); } - std::string FFloor(Operation operation) { - return GenerateUnary(operation, "floor", Type::Float, Type::Float, false); + Expression FFloor(Operation operation) { + return GenerateUnary(operation, "floor", Type::Float, Type::Float); } - std::string FCeil(Operation operation) { - return GenerateUnary(operation, "ceil", Type::Float, Type::Float, false); + Expression FCeil(Operation operation) { + return GenerateUnary(operation, "ceil", Type::Float, Type::Float); } - std::string FTrunc(Operation operation) { - return GenerateUnary(operation, "trunc", Type::Float, Type::Float, false); + Expression FTrunc(Operation operation) { + return GenerateUnary(operation, "trunc", Type::Float, Type::Float); } template <Type type> - std::string FCastInteger(Operation operation) { - return GenerateUnary(operation, "float", Type::Float, type, false); + Expression FCastInteger(Operation operation) { + return GenerateUnary(operation, "float", Type::Float, type); } - std::string ICastFloat(Operation operation) { - return GenerateUnary(operation, "int", Type::Int, Type::Float, false); + Expression ICastFloat(Operation operation) { + return GenerateUnary(operation, "int", Type::Int, Type::Float); } - std::string ICastUnsigned(Operation operation) { - return GenerateUnary(operation, "int", Type::Int, Type::Uint, false); + Expression ICastUnsigned(Operation operation) { + return GenerateUnary(operation, "int", Type::Int, Type::Uint); } template <Type type> - std::string LogicalShiftLeft(Operation operation) { + Expression LogicalShiftLeft(Operation operation) { return GenerateBinaryInfix(operation, "<<", type, type, Type::Uint); } - std::string ILogicalShiftRight(Operation operation) { - const std::string op_a = VisitOperand(operation, 0, Type::Uint); - const std::string op_b = VisitOperand(operation, 1, Type::Uint); - const std::string op_str = fmt::format("int({} >> {})", op_a, op_b); + Expression ILogicalShiftRight(Operation operation) { + const std::string op_a = VisitOperand(operation, 0).AsUint(); + const std::string op_b = VisitOperand(operation, 1).AsUint(); + std::string op_str = fmt::format("int({} >> {})", op_a, op_b); - return ApplyPrecise(operation, BitwiseCastResult(op_str, Type::Int)); + return ApplyPrecise(operation, std::move(op_str), Type::Int); } - std::string IArithmeticShiftRight(Operation operation) { + Expression IArithmeticShiftRight(Operation operation) { return GenerateBinaryInfix(operation, ">>", Type::Int, Type::Int, Type::Uint); } template <Type type> - std::string BitwiseAnd(Operation operation) { + Expression BitwiseAnd(Operation operation) { return GenerateBinaryInfix(operation, "&", type, type, type); } template <Type type> - std::string BitwiseOr(Operation operation) { + Expression BitwiseOr(Operation operation) { return GenerateBinaryInfix(operation, "|", type, type, type); } template <Type type> - std::string BitwiseXor(Operation operation) { + Expression BitwiseXor(Operation operation) { return GenerateBinaryInfix(operation, "^", type, type, type); } template <Type type> - std::string BitwiseNot(Operation operation) { - return GenerateUnary(operation, "~", type, type, false); + Expression BitwiseNot(Operation operation) { + return GenerateUnary(operation, "~", type, type); } - std::string UCastFloat(Operation operation) { - return GenerateUnary(operation, "uint", Type::Uint, Type::Float, false); + Expression UCastFloat(Operation operation) { + return GenerateUnary(operation, "uint", Type::Uint, Type::Float); } - std::string UCastSigned(Operation operation) { - return GenerateUnary(operation, "uint", Type::Uint, Type::Int, false); + Expression UCastSigned(Operation operation) { + return GenerateUnary(operation, "uint", Type::Uint, Type::Int); } - std::string UShiftRight(Operation operation) { + Expression UShiftRight(Operation operation) { return GenerateBinaryInfix(operation, ">>", Type::Uint, Type::Uint, Type::Uint); } template <Type type> - std::string BitfieldInsert(Operation operation) { + Expression BitfieldInsert(Operation operation) { return GenerateQuaternary(operation, "bitfieldInsert", type, type, type, Type::Int, Type::Int); } template <Type type> - std::string BitfieldExtract(Operation operation) { + Expression BitfieldExtract(Operation operation) { return GenerateTernary(operation, "bitfieldExtract", type, type, Type::Int, Type::Int); } template <Type type> - std::string BitCount(Operation operation) { - return GenerateUnary(operation, "bitCount", type, type, false); + Expression BitCount(Operation operation) { + return GenerateUnary(operation, "bitCount", type, type); } - std::string HNegate(Operation operation) { + Expression HNegate(Operation operation) { const auto GetNegate = [&](std::size_t index) { - return VisitOperand(operation, index, Type::Bool) + " ? -1 : 1"; + return VisitOperand(operation, index).AsBool() + " ? -1 : 1"; }; - const std::string value = - fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0, Type::HalfFloat), - GetNegate(1), GetNegate(2)); - return BitwiseCastResult(value, Type::HalfFloat); - } - - std::string HClamp(Operation operation) { - const std::string value = VisitOperand(operation, 0, Type::HalfFloat); - const std::string min = VisitOperand(operation, 1, Type::Float); - const std::string max = VisitOperand(operation, 2, Type::Float); - const std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max); - - return ApplyPrecise(operation, BitwiseCastResult(clamped, Type::HalfFloat)); - } - - std::string HCastFloat(Operation operation) { - const std::string op_a = VisitOperand(operation, 0, Type::Float); - return fmt::format("fromHalf2(vec2({}, 0.0f))", op_a); - } - - std::string HUnpack(Operation operation) { - const std::string operand{VisitOperand(operation, 0, Type::HalfFloat)}; - const auto value = [&]() -> std::string { - switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) { - case Tegra::Shader::HalfType::H0_H1: - return operand; - case Tegra::Shader::HalfType::F32: - return fmt::format("vec2(fromHalf2({}))", operand); - case Tegra::Shader::HalfType::H0_H0: - return fmt::format("vec2({}[0])", operand); - case Tegra::Shader::HalfType::H1_H1: - return fmt::format("vec2({}[1])", operand); - } - UNREACHABLE(); - return "0"; - }(); - return fmt::format("fromHalf2({})", value); + return {fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0).AsHalfFloat(), + GetNegate(1), GetNegate(2)), + Type::HalfFloat}; + } + + Expression HClamp(Operation operation) { + const std::string value = VisitOperand(operation, 0).AsHalfFloat(); + const std::string min = VisitOperand(operation, 1).AsFloat(); + const std::string max = VisitOperand(operation, 2).AsFloat(); + std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max); + + return ApplyPrecise(operation, std::move(clamped), Type::HalfFloat); } - std::string HMergeF32(Operation operation) { - return fmt::format("float(toHalf2({})[0])", Visit(operation[0])); + Expression HCastFloat(Operation operation) { + return {fmt::format("vec2({})", VisitOperand(operation, 0).AsFloat()), Type::HalfFloat}; } - std::string HMergeH0(Operation operation) { - return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[1]), - Visit(operation[0])); + Expression HUnpack(Operation operation) { + Expression operand = VisitOperand(operation, 0); + switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) { + case Tegra::Shader::HalfType::H0_H1: + return operand; + case Tegra::Shader::HalfType::F32: + return {fmt::format("vec2({})", operand.AsFloat()), Type::HalfFloat}; + case Tegra::Shader::HalfType::H0_H0: + return {fmt::format("vec2({}[0])", operand.AsHalfFloat()), Type::HalfFloat}; + case Tegra::Shader::HalfType::H1_H1: + return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat}; + } + } + + Expression HMergeF32(Operation operation) { + return {fmt::format("float({}[0])", VisitOperand(operation, 0).AsHalfFloat()), Type::Float}; } - std::string HMergeH1(Operation operation) { - return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[0]), - Visit(operation[1])); + Expression HMergeH0(Operation operation) { + std::string dest = VisitOperand(operation, 0).AsUint(); + std::string src = VisitOperand(operation, 1).AsUint(); + return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", src, dest), Type::Uint}; } - std::string HPack2(Operation operation) { - return fmt::format("utof(packHalf2x16(vec2({}, {})))", Visit(operation[0]), - Visit(operation[1])); + Expression HMergeH1(Operation operation) { + std::string dest = VisitOperand(operation, 0).AsUint(); + std::string src = VisitOperand(operation, 1).AsUint(); + return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", dest, src), Type::Uint}; + } + + Expression HPack2(Operation operation) { + return {fmt::format("vec2({}, {})", VisitOperand(operation, 0).AsFloat(), + VisitOperand(operation, 1).AsFloat()), + Type::HalfFloat}; } template <Type type> - std::string LogicalLessThan(Operation operation) { + Expression LogicalLessThan(Operation operation) { return GenerateBinaryInfix(operation, "<", Type::Bool, type, type); } template <Type type> - std::string LogicalEqual(Operation operation) { + Expression LogicalEqual(Operation operation) { return GenerateBinaryInfix(operation, "==", Type::Bool, type, type); } template <Type type> - std::string LogicalLessEqual(Operation operation) { + Expression LogicalLessEqual(Operation operation) { return GenerateBinaryInfix(operation, "<=", Type::Bool, type, type); } template <Type type> - std::string LogicalGreaterThan(Operation operation) { + Expression LogicalGreaterThan(Operation operation) { return GenerateBinaryInfix(operation, ">", Type::Bool, type, type); } template <Type type> - std::string LogicalNotEqual(Operation operation) { + Expression LogicalNotEqual(Operation operation) { return GenerateBinaryInfix(operation, "!=", Type::Bool, type, type); } template <Type type> - std::string LogicalGreaterEqual(Operation operation) { + Expression LogicalGreaterEqual(Operation operation) { return GenerateBinaryInfix(operation, ">=", Type::Bool, type, type); } - std::string LogicalFIsNan(Operation operation) { - return GenerateUnary(operation, "isnan", Type::Bool, Type::Float, false); + Expression LogicalFIsNan(Operation operation) { + return GenerateUnary(operation, "isnan", Type::Bool, Type::Float); } - std::string LogicalAssign(Operation operation) { + Expression LogicalAssign(Operation operation) { const Node& dest = operation[0]; const Node& src = operation[1]; @@ -1400,78 +1593,80 @@ private: target = GetInternalFlag(flag->GetFlag()); } - code.AddLine("{} = {};", target, Visit(src)); + code.AddLine("{} = {};", target, Visit(src).AsBool()); return {}; } - std::string LogicalAnd(Operation operation) { + Expression LogicalAnd(Operation operation) { return GenerateBinaryInfix(operation, "&&", Type::Bool, Type::Bool, Type::Bool); } - std::string LogicalOr(Operation operation) { + Expression LogicalOr(Operation operation) { return GenerateBinaryInfix(operation, "||", Type::Bool, Type::Bool, Type::Bool); } - std::string LogicalXor(Operation operation) { + Expression LogicalXor(Operation operation) { return GenerateBinaryInfix(operation, "^^", Type::Bool, Type::Bool, Type::Bool); } - std::string LogicalNegate(Operation operation) { - return GenerateUnary(operation, "!", Type::Bool, Type::Bool, false); + Expression LogicalNegate(Operation operation) { + return GenerateUnary(operation, "!", Type::Bool, Type::Bool); } - std::string LogicalPick2(Operation operation) { - const std::string pair = VisitOperand(operation, 0, Type::Bool2); - return fmt::format("{}[{}]", pair, VisitOperand(operation, 1, Type::Uint)); + Expression LogicalPick2(Operation operation) { + return {fmt::format("{}[{}]", VisitOperand(operation, 0).AsBool2(), + VisitOperand(operation, 1).AsUint()), + Type::Bool}; } - std::string LogicalAnd2(Operation operation) { + Expression LogicalAnd2(Operation operation) { return GenerateUnary(operation, "all", Type::Bool, Type::Bool2); } template <bool with_nan> - std::string GenerateHalfComparison(Operation operation, const std::string& compare_op) { - const std::string comparison{GenerateBinaryCall(operation, compare_op, Type::Bool2, - Type::HalfFloat, Type::HalfFloat)}; + Expression GenerateHalfComparison(Operation operation, std::string_view compare_op) { + Expression comparison = GenerateBinaryCall(operation, compare_op, Type::Bool2, + Type::HalfFloat, Type::HalfFloat); if constexpr (!with_nan) { return comparison; } - return fmt::format("halfFloatNanComparison({}, {}, {})", comparison, - VisitOperand(operation, 0, Type::HalfFloat), - VisitOperand(operation, 1, Type::HalfFloat)); + return {fmt::format("HalfFloatNanComparison({}, {}, {})", comparison.AsBool2(), + VisitOperand(operation, 0).AsHalfFloat(), + VisitOperand(operation, 1).AsHalfFloat()), + Type::Bool2}; } template <bool with_nan> - std::string Logical2HLessThan(Operation operation) { + Expression Logical2HLessThan(Operation operation) { return GenerateHalfComparison<with_nan>(operation, "lessThan"); } template <bool with_nan> - std::string Logical2HEqual(Operation operation) { + Expression Logical2HEqual(Operation operation) { return GenerateHalfComparison<with_nan>(operation, "equal"); } template <bool with_nan> - std::string Logical2HLessEqual(Operation operation) { + Expression Logical2HLessEqual(Operation operation) { return GenerateHalfComparison<with_nan>(operation, "lessThanEqual"); } template <bool with_nan> - std::string Logical2HGreaterThan(Operation operation) { + Expression Logical2HGreaterThan(Operation operation) { return GenerateHalfComparison<with_nan>(operation, "greaterThan"); } template <bool with_nan> - std::string Logical2HNotEqual(Operation operation) { + Expression Logical2HNotEqual(Operation operation) { return GenerateHalfComparison<with_nan>(operation, "notEqual"); } template <bool with_nan> - std::string Logical2HGreaterEqual(Operation operation) { + Expression Logical2HGreaterEqual(Operation operation) { return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual"); } - std::string Texture(Operation operation) { + Expression Texture(Operation operation) { const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); @@ -1480,10 +1675,10 @@ private: if (meta->sampler.IsShadow()) { expr = "vec4(" + expr + ')'; } - return expr + GetSwizzle(meta->element); + return {expr + GetSwizzle(meta->element), Type::Float}; } - std::string TextureLod(Operation operation) { + Expression TextureLod(Operation operation) { const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); @@ -1492,54 +1687,54 @@ private: if (meta->sampler.IsShadow()) { expr = "vec4(" + expr + ')'; } - return expr + GetSwizzle(meta->element); + return {expr + GetSwizzle(meta->element), Type::Float}; } - std::string TextureGather(Operation operation) { + Expression TextureGather(Operation operation) { const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int; - return GenerateTexture(operation, "Gather", - {TextureArgument{type, meta->component}, TextureAoffi{}}) + - GetSwizzle(meta->element); + return {GenerateTexture(operation, "Gather", + {TextureArgument{type, meta->component}, TextureAoffi{}}) + + GetSwizzle(meta->element), + Type::Float}; } - std::string TextureQueryDimensions(Operation operation) { + Expression TextureQueryDimensions(Operation operation) { const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); const std::string sampler = GetSampler(meta->sampler); - const std::string lod = VisitOperand(operation, 0, Type::Int); + const std::string lod = VisitOperand(operation, 0).AsInt(); switch (meta->element) { case 0: case 1: - return fmt::format("itof(int(textureSize({}, {}){}))", sampler, lod, - GetSwizzle(meta->element)); - case 2: - return "0"; + return {fmt::format("textureSize({}, {}){}", sampler, lod, GetSwizzle(meta->element)), + Type::Int}; case 3: - return fmt::format("itof(textureQueryLevels({}))", sampler); + return {fmt::format("textureQueryLevels({})", sampler), Type::Int}; } UNREACHABLE(); - return "0"; + return {"0", Type::Int}; } - std::string TextureQueryLod(Operation operation) { + Expression TextureQueryLod(Operation operation) { const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); if (meta->element < 2) { - return fmt::format("itof(int(({} * vec2(256)){}))", - GenerateTexture(operation, "QueryLod", {}), - GetSwizzle(meta->element)); + return {fmt::format("int(({} * vec2(256)){})", + GenerateTexture(operation, "QueryLod", {}), + GetSwizzle(meta->element)), + Type::Int}; } - return "0"; + return {"0", Type::Int}; } - std::string TexelFetch(Operation operation) { - constexpr std::array<const char*, 4> constructors = {"int", "ivec2", "ivec3", "ivec4"}; + Expression TexelFetch(Operation operation) { + constexpr std::array constructors = {"int", "ivec2", "ivec3", "ivec4"}; const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); UNIMPLEMENTED_IF(meta->sampler.IsArray()); @@ -1552,7 +1747,7 @@ private: expr += constructors.at(operation.GetOperandsCount() - 1); expr += '('; for (std::size_t i = 0; i < count; ++i) { - expr += VisitOperand(operation, i, Type::Int); + expr += VisitOperand(operation, i).AsInt(); const std::size_t next = i + 1; if (next == count) expr += ')'; @@ -1565,7 +1760,7 @@ private: if (meta->lod) { expr += ", "; - expr += CastOperand(Visit(meta->lod), Type::Int); + expr += Visit(meta->lod).AsInt(); } expr += ')'; expr += GetSwizzle(meta->element); @@ -1580,88 +1775,89 @@ private: code.AddLine("float {} = {};", tmp, expr); code.AddLine("#endif"); - return tmp; + return {tmp, Type::Float}; } - std::string ImageStore(Operation operation) { - constexpr std::array<const char*, 4> constructors{"int(", "ivec2(", "ivec3(", "ivec4("}; + Expression ImageStore(Operation operation) { const auto meta{std::get<MetaImage>(operation.GetMeta())}; + code.AddLine("imageStore({}, {}, {});", GetImage(meta.image), + BuildIntegerCoordinates(operation), BuildImageValues(operation)); + return {}; + } - std::string expr = "imageStore("; - expr += GetImage(meta.image); - expr += ", "; + Expression AtomicImageAdd(Operation operation) { + return AtomicImage(operation, "imageAtomicAdd"); + } - const std::size_t coords_count{operation.GetOperandsCount()}; - expr += constructors.at(coords_count - 1); - for (std::size_t i = 0; i < coords_count; ++i) { - expr += VisitOperand(operation, i, Type::Int); - if (i + 1 < coords_count) { - expr += ", "; - } - } - expr += "), "; + Expression AtomicImageMin(Operation operation) { + return AtomicImage(operation, "imageAtomicMin"); + } - const std::size_t values_count{meta.values.size()}; - UNIMPLEMENTED_IF(values_count != 4); - expr += "vec4("; - for (std::size_t i = 0; i < values_count; ++i) { - expr += Visit(meta.values.at(i)); - if (i + 1 < values_count) { - expr += ", "; - } - } - expr += "));"; + Expression AtomicImageMax(Operation operation) { + return AtomicImage(operation, "imageAtomicMax"); + } + Expression AtomicImageAnd(Operation operation) { + return AtomicImage(operation, "imageAtomicAnd"); + } - code.AddLine(expr); - return {}; + Expression AtomicImageOr(Operation operation) { + return AtomicImage(operation, "imageAtomicOr"); + } + + Expression AtomicImageXor(Operation operation) { + return AtomicImage(operation, "imageAtomicXor"); + } + + Expression AtomicImageExchange(Operation operation) { + return AtomicImage(operation, "imageAtomicExchange"); } - std::string Branch(Operation operation) { + Expression Branch(Operation operation) { const auto target = std::get_if<ImmediateNode>(&*operation[0]); UNIMPLEMENTED_IF(!target); - code.AddLine("jmp_to = 0x{:x}u;", target->GetValue()); + code.AddLine("jmp_to = 0x{:X}U;", target->GetValue()); code.AddLine("break;"); return {}; } - std::string BranchIndirect(Operation operation) { - const std::string op_a = VisitOperand(operation, 0, Type::Uint); + Expression BranchIndirect(Operation operation) { + const std::string op_a = VisitOperand(operation, 0).AsUint(); code.AddLine("jmp_to = {};", op_a); code.AddLine("break;"); return {}; } - std::string PushFlowStack(Operation operation) { + Expression PushFlowStack(Operation operation) { const auto stack = std::get<MetaStackClass>(operation.GetMeta()); const auto target = std::get_if<ImmediateNode>(&*operation[0]); UNIMPLEMENTED_IF(!target); - code.AddLine("{}[{}++] = 0x{:x}u;", FlowStackName(stack), FlowStackTopName(stack), + code.AddLine("{}[{}++] = 0x{:X}U;", FlowStackName(stack), FlowStackTopName(stack), target->GetValue()); return {}; } - std::string PopFlowStack(Operation operation) { + Expression PopFlowStack(Operation operation) { const auto stack = std::get<MetaStackClass>(operation.GetMeta()); code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack)); code.AddLine("break;"); return {}; } - std::string Exit(Operation operation) { + Expression Exit(Operation operation) { if (stage != ProgramType::Fragment) { code.AddLine("return;"); return {}; } const auto& used_registers = ir.GetRegisters(); - const auto SafeGetRegister = [&](u32 reg) -> std::string { + const auto SafeGetRegister = [&](u32 reg) -> Expression { // TODO(Rodrigo): Replace with contains once C++20 releases if (used_registers.find(reg) != used_registers.end()) { - return GetRegister(reg); + return {GetRegister(reg), Type::Float}; } - return "0.0f"; + return {"0.0f", Type::Float}; }; UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented"); @@ -1674,7 +1870,7 @@ private: for (u32 component = 0; component < 4; ++component) { if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { code.AddLine("FragColor{}[{}] = {};", render_target, component, - SafeGetRegister(current_reg)); + SafeGetRegister(current_reg).AsFloat()); ++current_reg; } } @@ -1683,14 +1879,14 @@ private: if (header.ps.omap.depth) { // The depth output is always 2 registers after the last color output, and current_reg // already contains one past the last color register. - code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1)); + code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1).AsFloat()); } code.AddLine("return;"); return {}; } - std::string Discard(Operation operation) { + Expression Discard(Operation operation) { // Enclose "discard" in a conditional, so that GLSL compilation does not complain // about unexecuted instructions that may follow this. code.AddLine("if (true) {{"); @@ -1701,7 +1897,7 @@ private: return {}; } - std::string EmitVertex(Operation operation) { + Expression EmitVertex(Operation operation) { ASSERT_MSG(stage == ProgramType::Geometry, "EmitVertex is expected to be used in a geometry shader."); @@ -1712,7 +1908,7 @@ private: return {}; } - std::string EndPrimitive(Operation operation) { + Expression EndPrimitive(Operation operation) { ASSERT_MSG(stage == ProgramType::Geometry, "EndPrimitive is expected to be used in a geometry shader."); @@ -1720,59 +1916,59 @@ private: return {}; } - std::string YNegate(Operation operation) { + Expression YNegate(Operation operation) { // Config pack's third value is Y_NEGATE's state. - return "uintBitsToFloat(config_pack[2])"; + return {"config_pack[2]", Type::Uint}; } template <u32 element> - std::string LocalInvocationId(Operation) { - return "utof(gl_LocalInvocationID"s + GetSwizzle(element) + ')'; + Expression LocalInvocationId(Operation) { + return {"gl_LocalInvocationID"s + GetSwizzle(element), Type::Uint}; } template <u32 element> - std::string WorkGroupId(Operation) { - return "utof(gl_WorkGroupID"s + GetSwizzle(element) + ')'; + Expression WorkGroupId(Operation) { + return {"gl_WorkGroupID"s + GetSwizzle(element), Type::Uint}; } - std::string BallotThread(Operation operation) { - const std::string value = VisitOperand(operation, 0, Type::Bool); + Expression BallotThread(Operation operation) { + const std::string value = VisitOperand(operation, 0).AsBool(); if (!device.HasWarpIntrinsics()) { LOG_ERROR(Render_OpenGL, "Nvidia warp intrinsics are not available and its required by a shader"); // Stub on non-Nvidia devices by simulating all threads voting the same as the active // one. - return fmt::format("utof({} ? 0xFFFFFFFFU : 0U)", value); + return {fmt::format("({} ? 0xFFFFFFFFU : 0U)", value), Type::Uint}; } - return fmt::format("utof(ballotThreadNV({}))", value); + return {fmt::format("ballotThreadNV({})", value), Type::Uint}; } - std::string Vote(Operation operation, const char* func) { - const std::string value = VisitOperand(operation, 0, Type::Bool); + Expression Vote(Operation operation, const char* func) { + const std::string value = VisitOperand(operation, 0).AsBool(); if (!device.HasWarpIntrinsics()) { LOG_ERROR(Render_OpenGL, "Nvidia vote intrinsics are not available and its required by a shader"); // Stub with a warp size of one. - return value; + return {value, Type::Bool}; } - return fmt::format("{}({})", func, value); + return {fmt::format("{}({})", func, value), Type::Bool}; } - std::string VoteAll(Operation operation) { + Expression VoteAll(Operation operation) { return Vote(operation, "allThreadsNV"); } - std::string VoteAny(Operation operation) { + Expression VoteAny(Operation operation) { return Vote(operation, "anyThreadNV"); } - std::string VoteEqual(Operation operation) { + Expression VoteEqual(Operation operation) { if (!device.HasWarpIntrinsics()) { LOG_ERROR(Render_OpenGL, "Nvidia vote intrinsics are not available and its required by a shader"); // We must return true here since a stub for a theoretical warp size of 1 will always // return an equal result for all its votes. - return "true"; + return {"true", Type::Bool}; } return Vote(operation, "allThreadsEqualNV"); } @@ -1909,6 +2105,13 @@ private: &GLSLDecompiler::TexelFetch, &GLSLDecompiler::ImageStore, + &GLSLDecompiler::AtomicImageAdd, + &GLSLDecompiler::AtomicImageMin, + &GLSLDecompiler::AtomicImageMax, + &GLSLDecompiler::AtomicImageAnd, + &GLSLDecompiler::AtomicImageOr, + &GLSLDecompiler::AtomicImageXor, + &GLSLDecompiler::AtomicImageExchange, &GLSLDecompiler::Branch, &GLSLDecompiler::BranchIndirect, @@ -1973,8 +2176,8 @@ private: } std::string GetInternalFlag(InternalFlag flag) const { - constexpr std::array<const char*, 4> InternalFlagNames = {"zero_flag", "sign_flag", - "carry_flag", "overflow_flag"}; + constexpr std::array InternalFlagNames = {"zero_flag", "sign_flag", "carry_flag", + "overflow_flag"}; const auto index = static_cast<u32>(flag); ASSERT(index < static_cast<u32>(InternalFlag::Amount)); @@ -2022,24 +2225,16 @@ private: std::string GetCommonDeclarations() { return fmt::format( - "#define MAX_CONSTBUFFER_ELEMENTS {}\n" "#define ftoi floatBitsToInt\n" "#define ftou floatBitsToUint\n" "#define itof intBitsToFloat\n" "#define utof uintBitsToFloat\n\n" - "float fromHalf2(vec2 pair) {{\n" - " return utof(packHalf2x16(pair));\n" - "}}\n\n" - "vec2 toHalf2(float value) {{\n" - " return unpackHalf2x16(ftou(value));\n" - "}}\n\n" - "bvec2 halfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{\n" + "bvec2 HalfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{\n" " bvec2 is_nan1 = isnan(pair1);\n" " bvec2 is_nan2 = isnan(pair2);\n" " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || " "is_nan2.y);\n" - "}}\n", - MAX_CONSTBUFFER_ELEMENTS); + "}}\n\n"); } ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage, diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp index 969fe9ced..f141c4e3b 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp @@ -341,13 +341,22 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn u64 index{}; u32 type{}; u8 is_bindless{}; + u8 is_written{}; + u8 is_read{}; + u8 is_size_known{}; + u32 size{}; if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) || - !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless)) { + !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) || + !LoadObjectFromPrecompiled(is_written) || !LoadObjectFromPrecompiled(is_read) || + !LoadObjectFromPrecompiled(is_size_known) || !LoadObjectFromPrecompiled(size)) { return {}; } entry.entries.images.emplace_back( static_cast<std::size_t>(offset), static_cast<std::size_t>(index), - static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0); + static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0, is_written != 0, + is_read != 0, + is_size_known ? std::make_optional(static_cast<Tegra::Shader::ImageAtomicSize>(size)) + : std::nullopt); } u32 global_memory_count{}; @@ -426,10 +435,14 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std: return false; } for (const auto& image : entries.images) { + const u32 size = image.IsSizeKnown() ? static_cast<u32>(image.GetSize()) : 0U; if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) || !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) || !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) || - !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0))) { + !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0)) || + !SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0)) || + !SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) || + !SaveObjectToPrecompiled(image.IsSizeKnown()) || !SaveObjectToPrecompiled(size)) { return false; } } diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index f4777d0b0..6eabf4fac 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -34,6 +34,25 @@ bool UpdateTie(T1 current_value, const T2 new_value) { return changed; } +template <typename T> +std::optional<std::pair<GLuint, GLsizei>> UpdateArray(T& current_values, const T& new_values) { + std::optional<std::size_t> first; + std::size_t last; + for (std::size_t i = 0; i < std::size(current_values); ++i) { + if (!UpdateValue(current_values[i], new_values[i])) { + continue; + } + if (!first) { + first = i; + } + last = i; + } + if (!first) { + return std::nullopt; + } + return std::make_pair(static_cast<GLuint>(*first), static_cast<GLsizei>(last - *first + 1)); +} + void Enable(GLenum cap, bool enable) { if (enable) { glEnable(cap); @@ -134,10 +153,6 @@ OpenGLState::OpenGLState() { logic_op.enabled = false; logic_op.operation = GL_COPY; - for (auto& texture_unit : texture_units) { - texture_unit.Reset(); - } - draw.read_framebuffer = 0; draw.draw_framebuffer = 0; draw.vertex_array = 0; @@ -496,52 +511,20 @@ void OpenGLState::ApplyAlphaTest() const { } void OpenGLState::ApplyTextures() const { - bool has_delta{}; - std::size_t first{}; - std::size_t last{}; - std::array<GLuint, Maxwell::NumTextureSamplers> textures; - - for (std::size_t i = 0; i < std::size(texture_units); ++i) { - const auto& texture_unit = texture_units[i]; - auto& cur_state_texture_unit = cur_state.texture_units[i]; - textures[i] = texture_unit.texture; - if (cur_state_texture_unit.texture == textures[i]) { - continue; - } - cur_state_texture_unit.texture = textures[i]; - if (!has_delta) { - first = i; - has_delta = true; - } - last = i; - } - if (has_delta) { - glBindTextures(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1), - textures.data() + first); + if (const auto update = UpdateArray(cur_state.textures, textures)) { + glBindTextures(update->first, update->second, textures.data() + update->first); } } void OpenGLState::ApplySamplers() const { - bool has_delta{}; - std::size_t first{}; - std::size_t last{}; - std::array<GLuint, Maxwell::NumTextureSamplers> samplers; - - for (std::size_t i = 0; i < std::size(samplers); ++i) { - samplers[i] = texture_units[i].sampler; - if (cur_state.texture_units[i].sampler == texture_units[i].sampler) { - continue; - } - cur_state.texture_units[i].sampler = texture_units[i].sampler; - if (!has_delta) { - first = i; - has_delta = true; - } - last = i; + if (const auto update = UpdateArray(cur_state.samplers, samplers)) { + glBindSamplers(update->first, update->second, samplers.data() + update->first); } - if (has_delta) { - glBindSamplers(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1), - samplers.data() + first); +} + +void OpenGLState::ApplyImages() const { + if (const auto update = UpdateArray(cur_state.images, images)) { + glBindImageTextures(update->first, update->second, images.data() + update->first); } } @@ -576,6 +559,7 @@ void OpenGLState::Apply() { ApplyLogicOp(); ApplyTextures(); ApplySamplers(); + ApplyImages(); if (dirty.polygon_offset) { ApplyPolygonOffset(); dirty.polygon_offset = false; @@ -606,18 +590,18 @@ void OpenGLState::EmulateViewportWithScissor() { } OpenGLState& OpenGLState::UnbindTexture(GLuint handle) { - for (auto& unit : texture_units) { - if (unit.texture == handle) { - unit.Unbind(); + for (auto& texture : textures) { + if (texture == handle) { + texture = 0; } } return *this; } OpenGLState& OpenGLState::ResetSampler(GLuint handle) { - for (auto& unit : texture_units) { - if (unit.sampler == handle) { - unit.sampler = 0; + for (auto& sampler : samplers) { + if (sampler == handle) { + sampler = 0; } } return *this; diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index fdf9a8a12..949b13051 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -118,21 +118,9 @@ public: GLenum operation; } logic_op; - // 3 texture units - one for each that is used in PICA fragment shader emulation - struct TextureUnit { - GLuint texture; // GL_TEXTURE_BINDING_2D - GLuint sampler; // GL_SAMPLER_BINDING - - void Unbind() { - texture = 0; - } - - void Reset() { - Unbind(); - sampler = 0; - } - }; - std::array<TextureUnit, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_units; + std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> textures{}; + std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> samplers{}; + std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumImages> images{}; struct { GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING @@ -220,6 +208,7 @@ public: void ApplyLogicOp() const; void ApplyTextures() const; void ApplySamplers() const; + void ApplyImages() const; void ApplyDepthClamp() const; void ApplyPolygonOffset() const; void ApplyAlphaTest() const; diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index 21324488a..8e13ab38b 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -78,6 +78,17 @@ public: /// Attaches this texture view to the current bound GL_DRAW_FRAMEBUFFER void Attach(GLenum attachment, GLenum target) const; + void ApplySwizzle(Tegra::Texture::SwizzleSource x_source, + Tegra::Texture::SwizzleSource y_source, + Tegra::Texture::SwizzleSource z_source, + Tegra::Texture::SwizzleSource w_source); + + void DecorateViewName(GPUVAddr gpu_addr, std::string prefix); + + void MarkAsModified(u64 tick) { + surface.MarkAsModified(true, tick); + } + GLuint GetTexture() const { if (is_proxy) { return surface.GetTexture(); @@ -89,13 +100,6 @@ public: return surface.GetSurfaceParams(); } - void ApplySwizzle(Tegra::Texture::SwizzleSource x_source, - Tegra::Texture::SwizzleSource y_source, - Tegra::Texture::SwizzleSource z_source, - Tegra::Texture::SwizzleSource w_source); - - void DecorateViewName(GPUVAddr gpu_addr, std::string prefix); - private: u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, Tegra::Texture::SwizzleSource y_source, @@ -111,8 +115,8 @@ private: GLenum target{}; OGLTextureView texture_view; - u32 swizzle; - bool is_proxy; + u32 swizzle{}; + bool is_proxy{}; }; class TextureCacheOpenGL final : public TextureCacheBase { diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index af9684839..839178152 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -342,7 +342,7 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v), }}; - state.texture_units[0].texture = screen_info.display_texture; + state.textures[0] = screen_info.display_texture; // Workaround brigthness problems in SMO by enabling sRGB in the final output // if it has been used in the frame. Needed because of this bug in QT: QTBUG-50987 state.framebuffer_srgb.enabled = OpenGLState::GetsRGBUsed(); @@ -352,7 +352,7 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // Restore default state state.framebuffer_srgb.enabled = false; - state.texture_units[0].texture = 0; + state.textures[0] = 0; state.AllDirty(); state.Apply(); // Clear sRGB state for the next frame diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp index 3b966ddc3..897cbb4e8 100644 --- a/src/video_core/renderer_vulkan/vk_device.cpp +++ b/src/video_core/renderer_vulkan/vk_device.cpp @@ -2,9 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <map> +#include <bitset> #include <optional> #include <set> +#include <string_view> #include <vector> #include "common/assert.h" #include "video_core/renderer_vulkan/declarations.h" @@ -12,13 +13,32 @@ namespace Vulkan { +namespace { + +template <typename T> +void SetNext(void**& next, T& data) { + *next = &data; + next = &data.pNext; +} + +template <typename T> +T GetFeatures(vk::PhysicalDevice physical, vk::DispatchLoaderDynamic dldi) { + vk::PhysicalDeviceFeatures2 features; + T extension_features; + features.pNext = &extension_features; + physical.getFeatures2(&features, dldi); + return extension_features; +} + +} // Anonymous namespace + namespace Alternatives { -constexpr std::array<vk::Format, 3> Depth24UnormS8Uint = { - vk::Format::eD32SfloatS8Uint, vk::Format::eD16UnormS8Uint, {}}; -constexpr std::array<vk::Format, 3> Depth16UnormS8Uint = { - vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint, {}}; -constexpr std::array<vk::Format, 2> Astc = {vk::Format::eA8B8G8R8UnormPack32, {}}; +constexpr std::array Depth24UnormS8Uint = {vk::Format::eD32SfloatS8Uint, + vk::Format::eD16UnormS8Uint, vk::Format{}}; +constexpr std::array Depth16UnormS8Uint = {vk::Format::eD24UnormS8Uint, + vk::Format::eD32SfloatS8Uint, vk::Format{}}; +constexpr std::array Astc = {vk::Format::eA8B8G8R8UnormPack32, vk::Format{}}; } // namespace Alternatives @@ -58,16 +78,53 @@ VKDevice::VKDevice(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice phy VKDevice::~VKDevice() = default; bool VKDevice::Create(const vk::DispatchLoaderDynamic& dldi, vk::Instance instance) { - vk::PhysicalDeviceFeatures device_features; - device_features.vertexPipelineStoresAndAtomics = true; - device_features.independentBlend = true; - device_features.textureCompressionASTC_LDR = is_optimal_astc_supported; - const auto queue_cis = GetDeviceQueueCreateInfos(); - const std::vector<const char*> extensions = LoadExtensions(dldi); - const vk::DeviceCreateInfo device_ci({}, static_cast<u32>(queue_cis.size()), queue_cis.data(), - 0, nullptr, static_cast<u32>(extensions.size()), - extensions.data(), &device_features); + const std::vector extensions = LoadExtensions(dldi); + + vk::PhysicalDeviceFeatures2 features2; + void** next = &features2.pNext; + auto& features = features2.features; + features.vertexPipelineStoresAndAtomics = true; + features.independentBlend = true; + features.depthClamp = true; + features.samplerAnisotropy = true; + features.largePoints = true; + features.textureCompressionASTC_LDR = is_optimal_astc_supported; + + vk::PhysicalDeviceVertexAttributeDivisorFeaturesEXT vertex_divisor; + vertex_divisor.vertexAttributeInstanceRateDivisor = true; + vertex_divisor.vertexAttributeInstanceRateZeroDivisor = true; + SetNext(next, vertex_divisor); + + vk::PhysicalDeviceFloat16Int8FeaturesKHR float16_int8; + if (is_float16_supported) { + float16_int8.shaderFloat16 = true; + SetNext(next, float16_int8); + } else { + LOG_INFO(Render_Vulkan, "Device doesn't support float16 natively"); + } + + vk::PhysicalDeviceUniformBufferStandardLayoutFeaturesKHR std430_layout; + if (khr_uniform_buffer_standard_layout) { + std430_layout.uniformBufferStandardLayout = true; + SetNext(next, std430_layout); + } else { + LOG_INFO(Render_Vulkan, "Device doesn't support packed UBOs"); + } + + vk::PhysicalDeviceIndexTypeUint8FeaturesEXT index_type_uint8; + if (ext_index_type_uint8) { + index_type_uint8.indexTypeUint8 = true; + SetNext(next, index_type_uint8); + } else { + LOG_INFO(Render_Vulkan, "Device doesn't support uint8 indexes"); + } + + vk::DeviceCreateInfo device_ci({}, static_cast<u32>(queue_cis.size()), queue_cis.data(), 0, + nullptr, static_cast<u32>(extensions.size()), extensions.data(), + nullptr); + device_ci.pNext = &features2; + vk::Device dummy_logical; if (physical.createDevice(&device_ci, nullptr, &dummy_logical, dldi) != vk::Result::eSuccess) { LOG_CRITICAL(Render_Vulkan, "Logical device failed to be created!"); @@ -78,6 +135,17 @@ bool VKDevice::Create(const vk::DispatchLoaderDynamic& dldi, vk::Instance instan logical = UniqueDevice( dummy_logical, vk::ObjectDestroy<vk::NoParent, vk::DispatchLoaderDynamic>(nullptr, dld)); + if (khr_driver_properties) { + vk::PhysicalDeviceDriverPropertiesKHR driver; + vk::PhysicalDeviceProperties2 properties; + properties.pNext = &driver; + physical.getProperties2(&properties, dld); + driver_id = driver.driverID; + LOG_INFO(Render_Vulkan, "Driver: {} {}", driver.driverName, driver.driverInfo); + } else { + LOG_INFO(Render_Vulkan, "Driver: Unknown"); + } + graphics_queue = logical->getQueue(graphics_family, 0, dld); present_queue = logical->getQueue(present_family, 0, dld); return true; @@ -92,20 +160,19 @@ vk::Format VKDevice::GetSupportedFormat(vk::Format wanted_format, // The wanted format is not supported by hardware, search for alternatives const vk::Format* alternatives = GetFormatAlternatives(wanted_format); if (alternatives == nullptr) { - LOG_CRITICAL(Render_Vulkan, - "Format={} with usage={} and type={} has no defined alternatives and host " - "hardware does not support it", - vk::to_string(wanted_format), vk::to_string(wanted_usage), - static_cast<u32>(format_type)); - UNREACHABLE(); + UNREACHABLE_MSG("Format={} with usage={} and type={} has no defined alternatives and host " + "hardware does not support it", + vk::to_string(wanted_format), vk::to_string(wanted_usage), + static_cast<u32>(format_type)); return wanted_format; } std::size_t i = 0; for (vk::Format alternative = alternatives[0]; alternative != vk::Format{}; alternative = alternatives[++i]) { - if (!IsFormatSupported(alternative, wanted_usage, format_type)) + if (!IsFormatSupported(alternative, wanted_usage, format_type)) { continue; + } LOG_WARNING(Render_Vulkan, "Emulating format={} with alternative format={} with usage={} and type={}", static_cast<u32>(wanted_format), static_cast<u32>(alternative), @@ -114,12 +181,10 @@ vk::Format VKDevice::GetSupportedFormat(vk::Format wanted_format, } // No alternatives found, panic - LOG_CRITICAL(Render_Vulkan, - "Format={} with usage={} and type={} is not supported by the host hardware and " - "doesn't support any of the alternatives", - static_cast<u32>(wanted_format), static_cast<u32>(wanted_usage), - static_cast<u32>(format_type)); - UNREACHABLE(); + UNREACHABLE_MSG("Format={} with usage={} and type={} is not supported by the host hardware and " + "doesn't support any of the alternatives", + static_cast<u32>(wanted_format), static_cast<u32>(wanted_usage), + static_cast<u32>(format_type)); return wanted_format; } @@ -132,7 +197,7 @@ bool VKDevice::IsOptimalAstcSupported(const vk::PhysicalDeviceFeatures& features vk::FormatFeatureFlagBits::eSampledImage | vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eBlitDst | vk::FormatFeatureFlagBits::eTransferSrc | vk::FormatFeatureFlagBits::eTransferDst}; - constexpr std::array<vk::Format, 9> astc_formats = { + constexpr std::array astc_formats = { vk::Format::eAstc4x4UnormBlock, vk::Format::eAstc4x4SrgbBlock, vk::Format::eAstc8x8SrgbBlock, vk::Format::eAstc8x6SrgbBlock, vk::Format::eAstc5x4SrgbBlock, vk::Format::eAstc5x5UnormBlock, @@ -151,76 +216,120 @@ bool VKDevice::IsFormatSupported(vk::Format wanted_format, vk::FormatFeatureFlag FormatType format_type) const { const auto it = format_properties.find(wanted_format); if (it == format_properties.end()) { - LOG_CRITICAL(Render_Vulkan, "Unimplemented format query={}", vk::to_string(wanted_format)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented format query={}", vk::to_string(wanted_format)); return true; } - const vk::FormatFeatureFlags supported_usage = GetFormatFeatures(it->second, format_type); + const auto supported_usage = GetFormatFeatures(it->second, format_type); return (supported_usage & wanted_usage) == wanted_usage; } bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical, vk::SurfaceKHR surface) { - bool has_swapchain{}; + LOG_INFO(Render_Vulkan, "{}", physical.getProperties(dldi).deviceName); + bool is_suitable = true; + + constexpr std::array required_extensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME}; + std::bitset<required_extensions.size()> available_extensions{}; + for (const auto& prop : physical.enumerateDeviceExtensionProperties(nullptr, dldi)) { - has_swapchain |= prop.extensionName == std::string(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + for (std::size_t i = 0; i < required_extensions.size(); ++i) { + if (available_extensions[i]) { + continue; + } + available_extensions[i] = + required_extensions[i] == std::string_view{prop.extensionName}; + } } - if (!has_swapchain) { - // The device doesn't support creating swapchains. - return false; + if (!available_extensions.all()) { + for (std::size_t i = 0; i < required_extensions.size(); ++i) { + if (available_extensions[i]) { + continue; + } + LOG_INFO(Render_Vulkan, "Missing required extension: {}", required_extensions[i]); + is_suitable = false; + } } bool has_graphics{}, has_present{}; const auto queue_family_properties = physical.getQueueFamilyProperties(dldi); for (u32 i = 0; i < static_cast<u32>(queue_family_properties.size()); ++i) { const auto& family = queue_family_properties[i]; - if (family.queueCount == 0) + if (family.queueCount == 0) { continue; - + } has_graphics |= (family.queueFlags & vk::QueueFlagBits::eGraphics) != static_cast<vk::QueueFlagBits>(0); has_present |= physical.getSurfaceSupportKHR(i, surface, dldi) != 0; } if (!has_graphics || !has_present) { - // The device doesn't have a graphics and present queue. - return false; + LOG_INFO(Render_Vulkan, "Device lacks a graphics and present queue"); + is_suitable = false; } // TODO(Rodrigo): Check if the device matches all requeriments. const auto properties{physical.getProperties(dldi)}; - const auto limits{properties.limits}; - if (limits.maxUniformBufferRange < 65536) { - return false; + const auto& limits{properties.limits}; + + constexpr u32 required_ubo_size = 65536; + if (limits.maxUniformBufferRange < required_ubo_size) { + LOG_INFO(Render_Vulkan, "Device UBO size {} is too small, {} is required)", + limits.maxUniformBufferRange, required_ubo_size); + is_suitable = false; } - const vk::PhysicalDeviceFeatures features{physical.getFeatures(dldi)}; - if (!features.vertexPipelineStoresAndAtomics || !features.independentBlend) { - return false; + const auto features{physical.getFeatures(dldi)}; + const std::array feature_report = { + std::make_pair(features.vertexPipelineStoresAndAtomics, "vertexPipelineStoresAndAtomics"), + std::make_pair(features.independentBlend, "independentBlend"), + std::make_pair(features.depthClamp, "depthClamp"), + std::make_pair(features.samplerAnisotropy, "samplerAnisotropy"), + std::make_pair(features.largePoints, "largePoints"), + }; + for (const auto& [supported, name] : feature_report) { + if (supported) { + continue; + } + LOG_INFO(Render_Vulkan, "Missing required feature: {}", name); + is_suitable = false; } - // Device is suitable. - return true; + return is_suitable; } std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynamic& dldi) { std::vector<const char*> extensions; - extensions.reserve(2); + extensions.reserve(7); extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + extensions.push_back(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME); const auto Test = [&](const vk::ExtensionProperties& extension, std::optional<std::reference_wrapper<bool>> status, const char* name, - u32 revision) { - if (extension.extensionName != std::string(name)) { + bool push) { + if (extension.extensionName != std::string_view(name)) { return; } - extensions.push_back(name); + if (push) { + extensions.push_back(name); + } if (status) { status->get() = true; } }; + bool khr_shader_float16_int8{}; for (const auto& extension : physical.enumerateDeviceExtensionProperties(nullptr, dldi)) { - Test(extension, ext_scalar_block_layout, VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME, 1); + Test(extension, khr_uniform_buffer_standard_layout, + VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, true); + Test(extension, ext_index_type_uint8, VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME, true); + Test(extension, khr_driver_properties, VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, true); + Test(extension, khr_shader_float16_int8, VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, false); + } + + if (khr_shader_float16_int8) { + is_float16_supported = + GetFeatures<vk::PhysicalDeviceFloat16Int8FeaturesKHR>(physical, dldi).shaderFloat16; + extensions.push_back(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME); } return extensions; @@ -250,9 +359,10 @@ void VKDevice::SetupFamilies(const vk::DispatchLoaderDynamic& dldi, vk::SurfaceK } void VKDevice::SetupProperties(const vk::DispatchLoaderDynamic& dldi) { - const vk::PhysicalDeviceProperties props = physical.getProperties(dldi); + const auto props = physical.getProperties(dldi); device_type = props.deviceType; uniform_buffer_alignment = static_cast<u64>(props.limits.minUniformBufferOffsetAlignment); + storage_buffer_alignment = static_cast<u64>(props.limits.minStorageBufferOffsetAlignment); max_storage_buffer_range = static_cast<u64>(props.limits.maxStorageBufferRange); } @@ -273,42 +383,53 @@ std::vector<vk::DeviceQueueCreateInfo> VKDevice::GetDeviceQueueCreateInfos() con return queue_cis; } -std::map<vk::Format, vk::FormatProperties> VKDevice::GetFormatProperties( +std::unordered_map<vk::Format, vk::FormatProperties> VKDevice::GetFormatProperties( const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical) { - static constexpr std::array formats{vk::Format::eA8B8G8R8UnormPack32, - vk::Format::eB5G6R5UnormPack16, - vk::Format::eA2B10G10R10UnormPack32, - vk::Format::eR32G32B32A32Sfloat, - vk::Format::eR16G16Unorm, - vk::Format::eR16G16Snorm, - vk::Format::eR8G8B8A8Srgb, - vk::Format::eR8Unorm, - vk::Format::eB10G11R11UfloatPack32, - vk::Format::eR32Sfloat, - vk::Format::eR16Sfloat, - vk::Format::eR16G16B16A16Sfloat, - vk::Format::eD32Sfloat, - vk::Format::eD16Unorm, - vk::Format::eD16UnormS8Uint, - vk::Format::eD24UnormS8Uint, - vk::Format::eD32SfloatS8Uint, - vk::Format::eBc1RgbaUnormBlock, - vk::Format::eBc2UnormBlock, - vk::Format::eBc3UnormBlock, - vk::Format::eBc4UnormBlock, - vk::Format::eBc5UnormBlock, - vk::Format::eBc5SnormBlock, - vk::Format::eBc7UnormBlock, - vk::Format::eAstc4x4UnormBlock, - vk::Format::eAstc4x4SrgbBlock, - vk::Format::eAstc8x8SrgbBlock, - vk::Format::eAstc8x6SrgbBlock, - vk::Format::eAstc5x4SrgbBlock, - vk::Format::eAstc5x5UnormBlock, - vk::Format::eAstc5x5SrgbBlock, - vk::Format::eAstc10x8UnormBlock, - vk::Format::eAstc10x8SrgbBlock}; - std::map<vk::Format, vk::FormatProperties> format_properties; + constexpr std::array formats{vk::Format::eA8B8G8R8UnormPack32, + vk::Format::eA8B8G8R8SnormPack32, + vk::Format::eA8B8G8R8SrgbPack32, + vk::Format::eB5G6R5UnormPack16, + vk::Format::eA2B10G10R10UnormPack32, + vk::Format::eR32G32B32A32Sfloat, + vk::Format::eR16G16B16A16Uint, + vk::Format::eR16G16Unorm, + vk::Format::eR16G16Snorm, + vk::Format::eR16G16Sfloat, + vk::Format::eR16Unorm, + vk::Format::eR8G8B8A8Srgb, + vk::Format::eR8G8Unorm, + vk::Format::eR8G8Snorm, + vk::Format::eR8Unorm, + vk::Format::eB10G11R11UfloatPack32, + vk::Format::eR32Sfloat, + vk::Format::eR16Sfloat, + vk::Format::eR16G16B16A16Sfloat, + vk::Format::eB8G8R8A8Unorm, + vk::Format::eD32Sfloat, + vk::Format::eD16Unorm, + vk::Format::eD16UnormS8Uint, + vk::Format::eD24UnormS8Uint, + vk::Format::eD32SfloatS8Uint, + vk::Format::eBc1RgbaUnormBlock, + vk::Format::eBc2UnormBlock, + vk::Format::eBc3UnormBlock, + vk::Format::eBc4UnormBlock, + vk::Format::eBc5UnormBlock, + vk::Format::eBc5SnormBlock, + vk::Format::eBc7UnormBlock, + vk::Format::eBc1RgbaSrgbBlock, + vk::Format::eBc3SrgbBlock, + vk::Format::eBc7SrgbBlock, + vk::Format::eAstc4x4UnormBlock, + vk::Format::eAstc4x4SrgbBlock, + vk::Format::eAstc8x8SrgbBlock, + vk::Format::eAstc8x6SrgbBlock, + vk::Format::eAstc5x4SrgbBlock, + vk::Format::eAstc5x5UnormBlock, + vk::Format::eAstc5x5SrgbBlock, + vk::Format::eAstc10x8UnormBlock, + vk::Format::eAstc10x8SrgbBlock}; + std::unordered_map<vk::Format, vk::FormatProperties> format_properties; for (const auto format : formats) { format_properties.emplace(format, physical.getFormatProperties(format, dldi)); } diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h index 537825d8b..010d4c3d6 100644 --- a/src/video_core/renderer_vulkan/vk_device.h +++ b/src/video_core/renderer_vulkan/vk_device.h @@ -4,7 +4,7 @@ #pragma once -#include <map> +#include <unordered_map> #include <vector> #include "common/common_types.h" #include "video_core/renderer_vulkan/declarations.h" @@ -69,16 +69,26 @@ public: return present_family; } - /// Returns if the device is integrated with the host CPU. + /// Returns true if the device is integrated with the host CPU. bool IsIntegrated() const { return device_type == vk::PhysicalDeviceType::eIntegratedGpu; } + /// Returns the driver ID. + vk::DriverIdKHR GetDriverID() const { + return driver_id; + } + /// Returns uniform buffer alignment requeriment. u64 GetUniformBufferAlignment() const { return uniform_buffer_alignment; } + /// Returns storage alignment requeriment. + u64 GetStorageBufferAlignment() const { + return storage_buffer_alignment; + } + /// Returns the maximum range for storage buffers. u64 GetMaxStorageBufferRange() const { return max_storage_buffer_range; @@ -89,9 +99,19 @@ public: return is_optimal_astc_supported; } + /// Returns true if the device supports float16 natively + bool IsFloat16Supported() const { + return is_float16_supported; + } + /// Returns true if the device supports VK_EXT_scalar_block_layout. - bool IsExtScalarBlockLayoutSupported() const { - return ext_scalar_block_layout; + bool IsKhrUniformBufferStandardLayoutSupported() const { + return khr_uniform_buffer_standard_layout; + } + + /// Returns true if the device supports VK_EXT_index_type_uint8. + bool IsExtIndexTypeUint8Supported() const { + return ext_index_type_uint8; } /// Checks if the physical device is suitable. @@ -123,22 +143,28 @@ private: FormatType format_type) const; /// Returns the device properties for Vulkan formats. - static std::map<vk::Format, vk::FormatProperties> GetFormatProperties( + static std::unordered_map<vk::Format, vk::FormatProperties> GetFormatProperties( const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical); - const vk::PhysicalDevice physical; ///< Physical device. - vk::DispatchLoaderDynamic dld; ///< Device function pointers. - UniqueDevice logical; ///< Logical device. - vk::Queue graphics_queue; ///< Main graphics queue. - vk::Queue present_queue; ///< Main present queue. - u32 graphics_family{}; ///< Main graphics queue family index. - u32 present_family{}; ///< Main present queue family index. - vk::PhysicalDeviceType device_type; ///< Physical device type. - u64 uniform_buffer_alignment{}; ///< Uniform buffer alignment requeriment. - u64 max_storage_buffer_range{}; ///< Max storage buffer size. - bool is_optimal_astc_supported{}; ///< Support for native ASTC. - bool ext_scalar_block_layout{}; ///< Support for VK_EXT_scalar_block_layout. - std::map<vk::Format, vk::FormatProperties> format_properties; ///< Format properties dictionary. + const vk::PhysicalDevice physical; ///< Physical device. + vk::DispatchLoaderDynamic dld; ///< Device function pointers. + UniqueDevice logical; ///< Logical device. + vk::Queue graphics_queue; ///< Main graphics queue. + vk::Queue present_queue; ///< Main present queue. + u32 graphics_family{}; ///< Main graphics queue family index. + u32 present_family{}; ///< Main present queue family index. + vk::PhysicalDeviceType device_type; ///< Physical device type. + vk::DriverIdKHR driver_id{}; ///< Driver ID. + u64 uniform_buffer_alignment{}; ///< Uniform buffer alignment requeriment. + u64 storage_buffer_alignment{}; ///< Storage buffer alignment requeriment. + u64 max_storage_buffer_range{}; ///< Max storage buffer size. + bool is_optimal_astc_supported{}; ///< Support for native ASTC. + bool is_float16_supported{}; ///< Support for float16 arithmetics. + bool khr_uniform_buffer_standard_layout{}; ///< Support for std430 on UBOs. + bool ext_index_type_uint8{}; ///< Support for VK_EXT_index_type_uint8. + bool khr_driver_properties{}; ///< Support for VK_KHR_driver_properties. + std::unordered_map<vk::Format, vk::FormatProperties> + format_properties; ///< Format properties dictionary. }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp index a35b45c9c..b9153934e 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -370,8 +370,8 @@ private: u32 binding = const_buffers_base_binding; for (const auto& entry : ir.GetConstantBuffers()) { const auto [index, size] = entry; - const Id type = - device.IsExtScalarBlockLayoutSupported() ? t_cbuf_scalar_ubo : t_cbuf_std140_ubo; + const Id type = device.IsKhrUniformBufferStandardLayoutSupported() ? t_cbuf_scalar_ubo + : t_cbuf_std140_ubo; const Id id = OpVariable(type, spv::StorageClass::Uniform); AddGlobalVariable(Name(id, fmt::format("cbuf_{}", index))); @@ -565,7 +565,7 @@ private: const Id buffer_id = constant_buffers.at(cbuf->GetIndex()); Id pointer{}; - if (device.IsExtScalarBlockLayoutSupported()) { + if (device.IsKhrUniformBufferStandardLayoutSupported()) { const Id buffer_offset = Emit(OpShiftRightLogical( t_uint, BitcastTo<Type::Uint>(Visit(offset)), Constant(t_uint, 2u))); pointer = Emit( @@ -944,6 +944,41 @@ private: return {}; } + Id AtomicImageAdd(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + + Id AtomicImageMin(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + + Id AtomicImageMax(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + + Id AtomicImageAnd(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + + Id AtomicImageOr(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + + Id AtomicImageXor(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + + Id AtomicImageExchange(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + Id Branch(Operation operation) { const auto target = std::get_if<ImmediateNode>(&*operation[0]); UNIMPLEMENTED_IF(!target); @@ -1366,6 +1401,13 @@ private: &SPIRVDecompiler::TexelFetch, &SPIRVDecompiler::ImageStore, + &SPIRVDecompiler::AtomicImageAdd, + &SPIRVDecompiler::AtomicImageMin, + &SPIRVDecompiler::AtomicImageMax, + &SPIRVDecompiler::AtomicImageAnd, + &SPIRVDecompiler::AtomicImageOr, + &SPIRVDecompiler::AtomicImageXor, + &SPIRVDecompiler::AtomicImageExchange, &SPIRVDecompiler::Branch, &SPIRVDecompiler::BranchIndirect, diff --git a/src/video_core/shader/decode/half_set_predicate.cpp b/src/video_core/shader/decode/half_set_predicate.cpp index afea33e5f..840694527 100644 --- a/src/video_core/shader/decode/half_set_predicate.cpp +++ b/src/video_core/shader/decode/half_set_predicate.cpp @@ -42,9 +42,8 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) { cond = instr.hsetp2.reg.cond; h_and = instr.hsetp2.reg.h_and; op_b = - UnpackHalfFloat(GetOperandAbsNegHalf(GetRegister(instr.gpr20), instr.hsetp2.reg.abs_b, - instr.hsetp2.reg.negate_b), - instr.hsetp2.reg.type_b); + GetOperandAbsNegHalf(UnpackHalfFloat(GetRegister(instr.gpr20), instr.hsetp2.reg.type_b), + instr.hsetp2.reg.abs_b, instr.hsetp2.reg.negate_b); break; default: UNREACHABLE(); @@ -52,22 +51,22 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) { } const OperationCode combiner = GetPredicateCombiner(instr.hsetp2.op); - const Node combined_pred = GetPredicate(instr.hsetp2.pred3, instr.hsetp2.neg_pred); + const Node combined_pred = GetPredicate(instr.hsetp2.pred39, instr.hsetp2.neg_pred); const auto Write = [&](u64 dest, Node src) { SetPredicate(bb, dest, Operation(combiner, std::move(src), combined_pred)); }; const Node comparison = GetPredicateComparisonHalf(cond, op_a, op_b); - const u64 first = instr.hsetp2.pred0; - const u64 second = instr.hsetp2.pred39; + const u64 first = instr.hsetp2.pred3; + const u64 second = instr.hsetp2.pred0; if (h_and) { - const Node joined = Operation(OperationCode::LogicalAnd2, comparison); + Node joined = Operation(OperationCode::LogicalAnd2, comparison); Write(first, joined); - Write(second, Operation(OperationCode::LogicalNegate, joined)); + Write(second, Operation(OperationCode::LogicalNegate, std::move(joined))); } else { - Write(first, Operation(OperationCode::LogicalPick2, comparison, Immediate(0u))); - Write(second, Operation(OperationCode::LogicalPick2, comparison, Immediate(1u))); + Write(first, Operation(OperationCode::LogicalPick2, comparison, Immediate(0U))); + Write(second, Operation(OperationCode::LogicalPick2, comparison, Immediate(1U))); } return pc; diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp index 77151a24b..d54fb88c9 100644 --- a/src/video_core/shader/decode/image.cpp +++ b/src/video_core/shader/decode/image.cpp @@ -44,7 +44,6 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { switch (opcode->get().GetId()) { case OpCode::Id::SUST: { UNIMPLEMENTED_IF(instr.sust.mode != Tegra::Shader::SurfaceDataMode::P); - UNIMPLEMENTED_IF(instr.sust.image_type == Tegra::Shader::ImageType::TextureBuffer); UNIMPLEMENTED_IF(instr.sust.out_of_bounds_store != Tegra::Shader::OutOfBoundsStore::Ignore); UNIMPLEMENTED_IF(instr.sust.component_mask_selector != 0xf); // Ensure we have an RGBA store @@ -61,56 +60,105 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { } const auto type{instr.sust.image_type}; - const auto& image{instr.sust.is_immediate ? GetImage(instr.image, type) - : GetBindlessImage(instr.gpr39, type)}; + auto& image{instr.sust.is_immediate ? GetImage(instr.image, type) + : GetBindlessImage(instr.gpr39, type)}; + image.MarkWrite(); + MetaImage meta{image, values}; - const Node store{Operation(OperationCode::ImageStore, meta, std::move(coords))}; - bb.push_back(store); + bb.push_back(Operation(OperationCode::ImageStore, meta, std::move(coords))); + break; + } + case OpCode::Id::SUATOM: { + UNIMPLEMENTED_IF(instr.suatom_d.is_ba != 0); + + Node value = GetRegister(instr.gpr0); + + std::vector<Node> coords; + const std::size_t num_coords{GetImageTypeNumCoordinates(instr.sust.image_type)}; + for (std::size_t i = 0; i < num_coords; ++i) { + coords.push_back(GetRegister(instr.gpr8.Value() + i)); + } + + const OperationCode operation_code = [instr] { + switch (instr.suatom_d.operation) { + case Tegra::Shader::ImageAtomicOperation::Add: + return OperationCode::AtomicImageAdd; + case Tegra::Shader::ImageAtomicOperation::Min: + return OperationCode::AtomicImageMin; + case Tegra::Shader::ImageAtomicOperation::Max: + return OperationCode::AtomicImageMax; + case Tegra::Shader::ImageAtomicOperation::And: + return OperationCode::AtomicImageAnd; + case Tegra::Shader::ImageAtomicOperation::Or: + return OperationCode::AtomicImageOr; + case Tegra::Shader::ImageAtomicOperation::Xor: + return OperationCode::AtomicImageXor; + case Tegra::Shader::ImageAtomicOperation::Exch: + return OperationCode::AtomicImageExchange; + default: + UNIMPLEMENTED_MSG("Unimplemented operation={}", + static_cast<u32>(instr.suatom_d.operation.Value())); + return OperationCode::AtomicImageAdd; + } + }(); + + const auto& image{GetImage(instr.image, instr.suatom_d.image_type, instr.suatom_d.size)}; + MetaImage meta{image, {std::move(value)}}; + SetRegister(bb, instr.gpr0, Operation(operation_code, meta, std::move(coords))); break; } default: - UNIMPLEMENTED_MSG("Unhandled conversion instruction: {}", opcode->get().GetName()); + UNIMPLEMENTED_MSG("Unhandled image instruction: {}", opcode->get().GetName()); } return pc; } -const Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { +Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type, + std::optional<Tegra::Shader::ImageAtomicSize> size) { const auto offset{static_cast<std::size_t>(image.index.Value())}; - - // If this image has already been used, return the existing mapping. - const auto itr{std::find_if(used_images.begin(), used_images.end(), - [=](const Image& entry) { return entry.GetOffset() == offset; })}; - if (itr != used_images.end()) { - ASSERT(itr->GetType() == type); - return *itr; + if (const auto image = TryUseExistingImage(offset, type, size)) { + return *image; } - // Otherwise create a new mapping for this image. const std::size_t next_index{used_images.size()}; - const Image entry{offset, next_index, type}; - return *used_images.emplace(entry).first; + return used_images.emplace(offset, Image{offset, next_index, type, size}).first->second; } -const Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, - Tegra::Shader::ImageType type) { +Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type, + std::optional<Tegra::Shader::ImageAtomicSize> size) { const Node image_register{GetRegister(reg)}; const auto [base_image, cbuf_index, cbuf_offset]{ TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))}; const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)}; - // If this image has already been used, return the existing mapping. - const auto itr{std::find_if(used_images.begin(), used_images.end(), - [=](const Image& entry) { return entry.GetOffset() == cbuf_key; })}; - if (itr != used_images.end()) { - ASSERT(itr->GetType() == type); - return *itr; + if (const auto image = TryUseExistingImage(cbuf_key, type, size)) { + return *image; } - // Otherwise create a new mapping for this image. const std::size_t next_index{used_images.size()}; - const Image entry{cbuf_index, cbuf_offset, next_index, type}; - return *used_images.emplace(entry).first; + return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type, size}) + .first->second; +} + +Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type, + std::optional<Tegra::Shader::ImageAtomicSize> size) { + auto it = used_images.find(offset); + if (it == used_images.end()) { + return nullptr; + } + auto& image = it->second; + ASSERT(image.GetType() == type); + + if (size) { + // We know the size, if it's known it has to be the same as before, otherwise we can set it. + if (image.IsSizeKnown()) { + ASSERT(image.GetSize() == size); + } else { + image.SetSize(*size); + } + } + return ℑ } } // namespace VideoCommon::Shader diff --git a/src/video_core/shader/decode/shift.cpp b/src/video_core/shader/decode/shift.cpp index 2ac16eeb0..f6ee68a54 100644 --- a/src/video_core/shader/decode/shift.cpp +++ b/src/video_core/shader/decode/shift.cpp @@ -17,8 +17,8 @@ u32 ShaderIR::DecodeShift(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; const auto opcode = OpCode::Decode(instr); - const Node op_a = GetRegister(instr.gpr8); - const Node op_b = [&]() { + Node op_a = GetRegister(instr.gpr8); + Node op_b = [&]() { if (instr.is_b_imm) { return Immediate(instr.alu.GetSignedImm20_20()); } else if (instr.is_b_gpr) { @@ -32,16 +32,23 @@ u32 ShaderIR::DecodeShift(NodeBlock& bb, u32 pc) { case OpCode::Id::SHR_C: case OpCode::Id::SHR_R: case OpCode::Id::SHR_IMM: { - const Node value = SignedOperation(OperationCode::IArithmeticShiftRight, - instr.shift.is_signed, PRECISE, op_a, op_b); + if (instr.shr.wrap) { + op_b = Operation(OperationCode::UBitwiseAnd, std::move(op_b), Immediate(0x1f)); + } else { + op_b = Operation(OperationCode::IMax, std::move(op_b), Immediate(0)); + op_b = Operation(OperationCode::IMin, std::move(op_b), Immediate(31)); + } + + Node value = SignedOperation(OperationCode::IArithmeticShiftRight, instr.shift.is_signed, + std::move(op_a), std::move(op_b)); SetInternalFlagsFromInteger(bb, value, instr.generates_cc); - SetRegister(bb, instr.gpr0, value); + SetRegister(bb, instr.gpr0, std::move(value)); break; } case OpCode::Id::SHL_C: case OpCode::Id::SHL_R: case OpCode::Id::SHL_IMM: { - const Node value = Operation(OperationCode::ILogicalShiftLeft, PRECISE, op_a, op_b); + const Node value = Operation(OperationCode::ILogicalShiftLeft, op_a, op_b); SetInternalFlagsFromInteger(bb, value, instr.generates_cc); SetRegister(bb, instr.gpr0, value); break; diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h index 5db9313c4..b47b201cf 100644 --- a/src/video_core/shader/node.h +++ b/src/video_core/shader/node.h @@ -7,6 +7,7 @@ #include <array> #include <cstddef> #include <memory> +#include <optional> #include <string> #include <tuple> #include <utility> @@ -148,7 +149,14 @@ enum class OperationCode { TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4 TexelFetch, /// (MetaTexture, int[N], int) -> float4 - ImageStore, /// (MetaImage, float[N] coords) -> void + ImageStore, /// (MetaImage, int[N] values) -> void + AtomicImageAdd, /// (MetaImage, int[N] coords) -> void + AtomicImageMin, /// (MetaImage, int[N] coords) -> void + AtomicImageMax, /// (MetaImage, int[N] coords) -> void + AtomicImageAnd, /// (MetaImage, int[N] coords) -> void + AtomicImageOr, /// (MetaImage, int[N] coords) -> void + AtomicImageXor, /// (MetaImage, int[N] coords) -> void + AtomicImageExchange, /// (MetaImage, int[N] coords) -> void Branch, /// (uint branch_target) -> void BranchIndirect, /// (uint branch_target) -> void @@ -273,46 +281,85 @@ private: bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not. }; -class Image { +class Image final { public: - explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type) - : offset{offset}, index{index}, type{type}, is_bindless{false} {} + constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, + std::optional<Tegra::Shader::ImageAtomicSize> size) + : offset{offset}, index{index}, type{type}, is_bindless{false}, size{size} {} - explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, - Tegra::Shader::ImageType type) + constexpr explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, + Tegra::Shader::ImageType type, + std::optional<Tegra::Shader::ImageAtomicSize> size) : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type}, - is_bindless{true} {} + is_bindless{true}, size{size} {} - explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, - bool is_bindless) - : offset{offset}, index{index}, type{type}, is_bindless{is_bindless} {} + constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, + bool is_bindless, bool is_written, bool is_read, + std::optional<Tegra::Shader::ImageAtomicSize> size) + : offset{offset}, index{index}, type{type}, is_bindless{is_bindless}, + is_written{is_written}, is_read{is_read}, size{size} {} - std::size_t GetOffset() const { + void MarkWrite() { + is_written = true; + } + + void MarkRead() { + is_read = true; + } + + void SetSize(Tegra::Shader::ImageAtomicSize size_) { + size = size_; + } + + constexpr std::size_t GetOffset() const { return offset; } - std::size_t GetIndex() const { + constexpr std::size_t GetIndex() const { return index; } - Tegra::Shader::ImageType GetType() const { + constexpr Tegra::Shader::ImageType GetType() const { return type; } - bool IsBindless() const { + constexpr bool IsBindless() const { return is_bindless; } - bool operator<(const Image& rhs) const { - return std::tie(offset, index, type, is_bindless) < - std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless); + constexpr bool IsWritten() const { + return is_written; + } + + constexpr bool IsRead() const { + return is_read; + } + + constexpr std::pair<u32, u32> GetBindlessCBuf() const { + return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)}; + } + + constexpr bool IsSizeKnown() const { + return size.has_value(); + } + + constexpr Tegra::Shader::ImageAtomicSize GetSize() const { + return size.value(); + } + + constexpr bool operator<(const Image& rhs) const { + return std::tie(offset, index, type, size, is_bindless) < + std::tie(rhs.offset, rhs.index, rhs.type, rhs.size, rhs.is_bindless); } private: - std::size_t offset{}; + u64 offset{}; std::size_t index{}; Tegra::Shader::ImageType type{}; bool is_bindless{}; + bool is_written{}; + bool is_read{}; + std::optional<Tegra::Shader::ImageAtomicSize> size{}; }; struct GlobalMemoryBase { diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index bcc9b79b6..62816bd56 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h @@ -95,7 +95,7 @@ public: return used_samplers; } - const std::set<Image>& GetImages() const { + const std::map<u64, Image>& GetImages() const { return used_images; } @@ -272,10 +272,16 @@ private: bool is_shadow); /// Accesses an image. - const Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type); + Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type, + std::optional<Tegra::Shader::ImageAtomicSize> size = {}); /// Access a bindless image sampler. - const Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type); + Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type, + std::optional<Tegra::Shader::ImageAtomicSize> size = {}); + + /// Tries to access an existing image, updating it's state as needed + Image* TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type, + std::optional<Tegra::Shader::ImageAtomicSize> size); /// Extracts a sequence of bits from a node Node BitfieldExtract(Node value, u32 offset, u32 bits); @@ -356,7 +362,7 @@ private: std::set<Tegra::Shader::Attribute::Index> used_output_attributes; std::map<u32, ConstBuffer> used_cbufs; std::set<Sampler> used_samplers; - std::set<Image> used_images; + std::map<u64, Image> used_images; std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{}; std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory; bool uses_layer{}; diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 4ceb219be..53d0142cb 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -513,6 +513,26 @@ bool IsPixelFormatASTC(PixelFormat format) { } } +bool IsPixelFormatSRGB(PixelFormat format) { + switch (format) { + case PixelFormat::RGBA8_SRGB: + case PixelFormat::BGRA8_SRGB: + case PixelFormat::DXT1_SRGB: + case PixelFormat::DXT23_SRGB: + case PixelFormat::DXT45_SRGB: + case PixelFormat::BC7U_SRGB: + case PixelFormat::ASTC_2D_4X4_SRGB: + case PixelFormat::ASTC_2D_8X8_SRGB: + case PixelFormat::ASTC_2D_8X5_SRGB: + case PixelFormat::ASTC_2D_5X4_SRGB: + case PixelFormat::ASTC_2D_5X5_SRGB: + case PixelFormat::ASTC_2D_10X8_SRGB: + return true; + default: + return false; + } +} + std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) { return {GetDefaultBlockWidth(format), GetDefaultBlockHeight(format)}; } diff --git a/src/video_core/surface.h b/src/video_core/surface.h index 83f31c12c..19268b7cd 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -547,6 +547,8 @@ SurfaceType GetFormatType(PixelFormat pixel_format); bool IsPixelFormatASTC(PixelFormat format); +bool IsPixelFormatSRGB(PixelFormat format); + std::pair<u32, u32> GetASTCBlockSize(PixelFormat format); /// Returns true if the specified PixelFormat is a BCn format, e.g. DXT or DXN diff --git a/src/video_core/texture_cache/surface_base.h b/src/video_core/texture_cache/surface_base.h index bcce8d863..5e497e49f 100644 --- a/src/video_core/texture_cache/surface_base.h +++ b/src/video_core/texture_cache/surface_base.h @@ -195,18 +195,18 @@ public: virtual void DownloadTexture(std::vector<u8>& staging_buffer) = 0; - void MarkAsModified(const bool is_modified_, const u64 tick) { + void MarkAsModified(bool is_modified_, u64 tick) { is_modified = is_modified_ || is_target; modification_tick = tick; } - void MarkAsRenderTarget(const bool is_target, const u32 index) { - this->is_target = is_target; - this->index = index; + void MarkAsRenderTarget(bool is_target_, u32 index_) { + is_target = is_target_; + index = index_; } - void MarkAsPicked(const bool is_picked) { - this->is_picked = is_picked; + void MarkAsPicked(bool is_picked_) { + is_picked = is_picked_; } bool IsModified() const { diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp index fd5472451..1e4d3fb79 100644 --- a/src/video_core/texture_cache/surface_params.cpp +++ b/src/video_core/texture_cache/surface_params.cpp @@ -24,55 +24,62 @@ using VideoCore::Surface::SurfaceTarget; using VideoCore::Surface::SurfaceTargetFromTextureType; using VideoCore::Surface::SurfaceType; -SurfaceTarget TextureType2SurfaceTarget(Tegra::Shader::TextureType type, bool is_array) { +namespace { + +SurfaceTarget TextureTypeToSurfaceTarget(Tegra::Shader::TextureType type, bool is_array) { switch (type) { - case Tegra::Shader::TextureType::Texture1D: { - if (is_array) - return SurfaceTarget::Texture1DArray; - else - return SurfaceTarget::Texture1D; - } - case Tegra::Shader::TextureType::Texture2D: { - if (is_array) - return SurfaceTarget::Texture2DArray; - else - return SurfaceTarget::Texture2D; - } - case Tegra::Shader::TextureType::Texture3D: { + case Tegra::Shader::TextureType::Texture1D: + return is_array ? SurfaceTarget::Texture1DArray : SurfaceTarget::Texture1D; + case Tegra::Shader::TextureType::Texture2D: + return is_array ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D; + case Tegra::Shader::TextureType::Texture3D: ASSERT(!is_array); return SurfaceTarget::Texture3D; - } - case Tegra::Shader::TextureType::TextureCube: { - if (is_array) - return SurfaceTarget::TextureCubeArray; - else - return SurfaceTarget::TextureCubemap; - } - default: { + case Tegra::Shader::TextureType::TextureCube: + return is_array ? SurfaceTarget::TextureCubeArray : SurfaceTarget::TextureCubemap; + default: UNREACHABLE(); return SurfaceTarget::Texture2D; } +} + +SurfaceTarget ImageTypeToSurfaceTarget(Tegra::Shader::ImageType type) { + switch (type) { + case Tegra::Shader::ImageType::Texture1D: + return SurfaceTarget::Texture1D; + case Tegra::Shader::ImageType::TextureBuffer: + return SurfaceTarget::TextureBuffer; + case Tegra::Shader::ImageType::Texture1DArray: + return SurfaceTarget::Texture1DArray; + case Tegra::Shader::ImageType::Texture2D: + return SurfaceTarget::Texture2D; + case Tegra::Shader::ImageType::Texture2DArray: + return SurfaceTarget::Texture2DArray; + case Tegra::Shader::ImageType::Texture3D: + return SurfaceTarget::Texture3D; + default: + UNREACHABLE(); + return SurfaceTarget::Texture2D; } } -namespace { constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); } + } // Anonymous namespace -SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, - const Tegra::Texture::FullTextureInfo& config, +SurfaceParams SurfaceParams::CreateForTexture(const Tegra::Texture::TICEntry& tic, const VideoCommon::Shader::Sampler& entry) { SurfaceParams params; - params.is_tiled = config.tic.IsTiled(); - params.srgb_conversion = config.tic.IsSrgbConversionEnabled(); - params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, - params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, - params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, - params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; - params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), - params.srgb_conversion); + params.is_tiled = tic.IsTiled(); + params.srgb_conversion = tic.IsSrgbConversionEnabled(); + params.block_width = params.is_tiled ? tic.BlockWidth() : 0, + params.block_height = params.is_tiled ? tic.BlockHeight() : 0, + params.block_depth = params.is_tiled ? tic.BlockDepth() : 0, + params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1; + params.pixel_format = + PixelFormatFromTextureFormat(tic.format, tic.r_type.Value(), params.srgb_conversion); params.type = GetFormatType(params.pixel_format); if (entry.IsShadow() && params.type == SurfaceType::ColorTexture) { switch (params.pixel_format) { @@ -92,31 +99,72 @@ SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, } params.type = GetFormatType(params.pixel_format); } - params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); + params.component_type = ComponentTypeFromTexture(tic.r_type.Value()); params.type = GetFormatType(params.pixel_format); // TODO: on 1DBuffer we should use the tic info. - if (!config.tic.IsBuffer()) { - params.target = TextureType2SurfaceTarget(entry.GetType(), entry.IsArray()); - params.width = config.tic.Width(); - params.height = config.tic.Height(); - params.depth = config.tic.Depth(); - params.pitch = params.is_tiled ? 0 : config.tic.Pitch(); + if (tic.IsBuffer()) { + params.target = SurfaceTarget::TextureBuffer; + params.width = tic.Width(); + params.pitch = params.width * params.GetBytesPerPixel(); + params.height = 1; + params.depth = 1; + params.num_levels = 1; + params.emulated_levels = 1; + params.is_layered = false; + } else { + params.target = TextureTypeToSurfaceTarget(entry.GetType(), entry.IsArray()); + params.width = tic.Width(); + params.height = tic.Height(); + params.depth = tic.Depth(); + params.pitch = params.is_tiled ? 0 : tic.Pitch(); if (params.target == SurfaceTarget::TextureCubemap || params.target == SurfaceTarget::TextureCubeArray) { params.depth *= 6; } - params.num_levels = config.tic.max_mip_level + 1; + params.num_levels = tic.max_mip_level + 1; params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); params.is_layered = params.IsLayered(); - } else { + } + return params; +} + +SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Image& entry) { + SurfaceParams params; + params.is_tiled = tic.IsTiled(); + params.srgb_conversion = tic.IsSrgbConversionEnabled(); + params.block_width = params.is_tiled ? tic.BlockWidth() : 0, + params.block_height = params.is_tiled ? tic.BlockHeight() : 0, + params.block_depth = params.is_tiled ? tic.BlockDepth() : 0, + params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1; + params.pixel_format = + PixelFormatFromTextureFormat(tic.format, tic.r_type.Value(), params.srgb_conversion); + params.type = GetFormatType(params.pixel_format); + params.component_type = ComponentTypeFromTexture(tic.r_type.Value()); + params.type = GetFormatType(params.pixel_format); + params.target = ImageTypeToSurfaceTarget(entry.GetType()); + // TODO: on 1DBuffer we should use the tic info. + if (tic.IsBuffer()) { params.target = SurfaceTarget::TextureBuffer; - params.width = config.tic.Width(); + params.width = tic.Width(); params.pitch = params.width * params.GetBytesPerPixel(); params.height = 1; params.depth = 1; params.num_levels = 1; params.emulated_levels = 1; params.is_layered = false; + } else { + params.width = tic.Width(); + params.height = tic.Height(); + params.depth = tic.Depth(); + params.pitch = params.is_tiled ? 0 : tic.Pitch(); + if (params.target == SurfaceTarget::TextureCubemap || + params.target == SurfaceTarget::TextureCubeArray) { + params.depth *= 6; + } + params.num_levels = tic.max_mip_level + 1; + params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); + params.is_layered = params.IsLayered(); } return params; } diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h index e7ef66ee2..c58e7f8a4 100644 --- a/src/video_core/texture_cache/surface_params.h +++ b/src/video_core/texture_cache/surface_params.h @@ -4,8 +4,6 @@ #pragma once -#include <map> - #include "common/alignment.h" #include "common/bit_util.h" #include "common/cityhash.h" @@ -23,10 +21,13 @@ using VideoCore::Surface::SurfaceCompression; class SurfaceParams { public: /// Creates SurfaceCachedParams from a texture configuration. - static SurfaceParams CreateForTexture(Core::System& system, - const Tegra::Texture::FullTextureInfo& config, + static SurfaceParams CreateForTexture(const Tegra::Texture::TICEntry& tic, const VideoCommon::Shader::Sampler& entry); + /// Creates SurfaceCachedParams from an image configuration. + static SurfaceParams CreateForImage(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Image& entry); + /// Creates SurfaceCachedParams for a depth buffer configuration. static SurfaceParams CreateForDepthBuffer( Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, diff --git a/src/video_core/texture_cache/surface_view.cpp b/src/video_core/texture_cache/surface_view.cpp index 467696a4c..57a1f5803 100644 --- a/src/video_core/texture_cache/surface_view.cpp +++ b/src/video_core/texture_cache/surface_view.cpp @@ -10,7 +10,7 @@ namespace VideoCommon { std::size_t ViewParams::Hash() const { - return static_cast<std::size_t>(base_layer) ^ static_cast<std::size_t>(num_layers << 16) ^ + return static_cast<std::size_t>(base_layer) ^ (static_cast<std::size_t>(num_layers) << 16) ^ (static_cast<std::size_t>(base_level) << 24) ^ (static_cast<std::size_t>(num_levels) << 32) ^ (static_cast<std::size_t>(target) << 36); } diff --git a/src/video_core/texture_cache/surface_view.h b/src/video_core/texture_cache/surface_view.h index 04ca5639b..b17fd11a9 100644 --- a/src/video_core/texture_cache/surface_view.h +++ b/src/video_core/texture_cache/surface_view.h @@ -13,8 +13,8 @@ namespace VideoCommon { struct ViewParams { - ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer, u32 num_layers, - u32 base_level, u32 num_levels) + constexpr explicit ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer, + u32 num_layers, u32 base_level, u32 num_levels) : target{target}, base_layer{base_layer}, num_layers{num_layers}, base_level{base_level}, num_levels{num_levels} {} @@ -22,12 +22,6 @@ struct ViewParams { bool operator==(const ViewParams& rhs) const; - VideoCore::Surface::SurfaceTarget target{}; - u32 base_layer{}; - u32 num_layers{}; - u32 base_level{}; - u32 num_levels{}; - bool IsLayered() const { switch (target) { case VideoCore::Surface::SurfaceTarget::Texture1DArray: @@ -39,13 +33,19 @@ struct ViewParams { return false; } } + + VideoCore::Surface::SurfaceTarget target{}; + u32 base_layer{}; + u32 num_layers{}; + u32 base_level{}; + u32 num_levels{}; }; class ViewBase { public: - ViewBase(const ViewParams& params) : params{params} {} + constexpr explicit ViewBase(const ViewParams& params) : params{params} {} - const ViewParams& GetViewParams() const { + constexpr const ViewParams& GetViewParams() const { return params; } diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 2ec0203d1..877c6635d 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -89,14 +89,29 @@ public: } } - TView GetTextureSurface(const Tegra::Texture::FullTextureInfo& config, + TView GetTextureSurface(const Tegra::Texture::TICEntry& tic, const VideoCommon::Shader::Sampler& entry) { std::lock_guard lock{mutex}; - const auto gpu_addr{config.tic.Address()}; + const auto gpu_addr{tic.Address()}; if (!gpu_addr) { return {}; } - const auto params{SurfaceParams::CreateForTexture(system, config, entry)}; + const auto params{SurfaceParams::CreateForTexture(tic, entry)}; + const auto [surface, view] = GetSurface(gpu_addr, params, true, false); + if (guard_samplers) { + sampled_textures.push_back(surface); + } + return view; + } + + TView GetImageSurface(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Image& entry) { + std::lock_guard lock{mutex}; + const auto gpu_addr{tic.Address()}; + if (!gpu_addr) { + return {}; + } + const auto params{SurfaceParams::CreateForImage(tic, entry)}; const auto [surface, view] = GetSurface(gpu_addr, params, true, false); if (guard_samplers) { sampled_textures.push_back(surface); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 0456248ac..f594106bf 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -517,10 +517,37 @@ void Config::ReadPathValues() { UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString(); UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString(); UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString(); - UISettings::values.game_directory_path = + UISettings::values.game_dir_deprecated = ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString(); - UISettings::values.game_directory_deepscan = + UISettings::values.game_dir_deprecated_deepscan = ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool(); + const int gamedirs_size = qt_config->beginReadArray(QStringLiteral("gamedirs")); + for (int i = 0; i < gamedirs_size; ++i) { + qt_config->setArrayIndex(i); + UISettings::GameDir game_dir; + game_dir.path = ReadSetting(QStringLiteral("path")).toString(); + game_dir.deep_scan = ReadSetting(QStringLiteral("deep_scan"), false).toBool(); + game_dir.expanded = ReadSetting(QStringLiteral("expanded"), true).toBool(); + UISettings::values.game_dirs.append(game_dir); + } + qt_config->endArray(); + // create NAND and SD card directories if empty, these are not removable through the UI, + // also carries over old game list settings if present + if (UISettings::values.game_dirs.isEmpty()) { + UISettings::GameDir game_dir; + game_dir.path = QStringLiteral("SDMC"); + game_dir.expanded = true; + UISettings::values.game_dirs.append(game_dir); + game_dir.path = QStringLiteral("UserNAND"); + UISettings::values.game_dirs.append(game_dir); + game_dir.path = QStringLiteral("SysNAND"); + UISettings::values.game_dirs.append(game_dir); + if (UISettings::values.game_dir_deprecated != QStringLiteral(".")) { + game_dir.path = UISettings::values.game_dir_deprecated; + game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan; + UISettings::values.game_dirs.append(game_dir); + } + } UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); qt_config->endGroup(); @@ -899,10 +926,15 @@ void Config::SavePathValues() { WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path); WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path); - WriteSetting(QStringLiteral("gameListRootDir"), UISettings::values.game_directory_path, - QStringLiteral(".")); - WriteSetting(QStringLiteral("gameListDeepScan"), UISettings::values.game_directory_deepscan, - false); + qt_config->beginWriteArray(QStringLiteral("gamedirs")); + for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) { + qt_config->setArrayIndex(i); + const auto& game_dir = UISettings::values.game_dirs[i]; + WriteSetting(QStringLiteral("path"), game_dir.path); + WriteSetting(QStringLiteral("deep_scan"), game_dir.deep_scan, false); + WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true); + } + qt_config->endArray(); WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); qt_config->endGroup(); diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index e636964e3..775e3f2ea 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -68,12 +68,14 @@ void ConfigureDialog::RetranslateUI() { ui->tabWidget->setCurrentIndex(old_index); } +Q_DECLARE_METATYPE(QList<QWidget*>); + void ConfigureDialog::PopulateSelectionList() { - const std::array<std::pair<QString, QStringList>, 4> items{ - {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}}, - {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}}, - {tr("Graphics"), {tr("Graphics")}}, - {tr("Controls"), {tr("Input"), tr("Hotkeys")}}}, + const std::array<std::pair<QString, QList<QWidget*>>, 4> items{ + {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}}, + {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->audioTab}}, + {tr("Graphics"), {ui->graphicsTab}}, + {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}}, }; [[maybe_unused]] const QSignalBlocker blocker(ui->selectorList); @@ -81,7 +83,7 @@ void ConfigureDialog::PopulateSelectionList() { ui->selectorList->clear(); for (const auto& entry : items) { auto* const item = new QListWidgetItem(entry.first); - item->setData(Qt::UserRole, entry.second); + item->setData(Qt::UserRole, QVariant::fromValue(entry.second)); ui->selectorList->addItem(item); } @@ -93,24 +95,26 @@ void ConfigureDialog::UpdateVisibleTabs() { return; } - const std::map<QString, QWidget*> widgets = { - {tr("General"), ui->generalTab}, - {tr("System"), ui->systemTab}, - {tr("Profiles"), ui->profileManagerTab}, - {tr("Input"), ui->inputTab}, - {tr("Hotkeys"), ui->hotkeysTab}, - {tr("Graphics"), ui->graphicsTab}, - {tr("Audio"), ui->audioTab}, - {tr("Debug"), ui->debugTab}, - {tr("Web"), ui->webTab}, - {tr("Game List"), ui->gameListTab}, + const std::map<QWidget*, QString> widgets = { + {ui->generalTab, tr("General")}, + {ui->systemTab, tr("System")}, + {ui->profileManagerTab, tr("Profiles")}, + {ui->inputTab, tr("Input")}, + {ui->hotkeysTab, tr("Hotkeys")}, + {ui->graphicsTab, tr("Graphics")}, + {ui->audioTab, tr("Audio")}, + {ui->debugTab, tr("Debug")}, + {ui->webTab, tr("Web")}, + {ui->gameListTab, tr("Game List")}, }; [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget); ui->tabWidget->clear(); - const QStringList tabs = items[0]->data(Qt::UserRole).toStringList(); - for (const auto& tab : tabs) { - ui->tabWidget->addTab(widgets.find(tab)->second, tab); + + const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole)); + + for (const auto tab : tabs) { + ui->tabWidget->addTab(tab, widgets.at(tab)); } } diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 75fcbfea3..10bcd650e 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -20,25 +20,29 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) SetConfiguration(); - connect(ui->toggle_deepscan, &QCheckBox::stateChanged, this, - [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); + connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled); } ConfigureGeneral::~ConfigureGeneral() = default; void ConfigureGeneral::SetConfiguration() { - ui->toggle_deepscan->setChecked(UISettings::values.game_directory_deepscan); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot); ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); + + ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); + ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); + ui->frame_limit->setValue(Settings::values.frame_limit); } void ConfigureGeneral::ApplyConfiguration() { - UISettings::values.game_directory_deepscan = ui->toggle_deepscan->isChecked(); UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); UISettings::values.theme = ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); + + Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); + Settings::values.frame_limit = ui->frame_limit->value(); } void ConfigureGeneral::changeEvent(QEvent* event) { diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index 184fdd329..0bb91d64b 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -25,11 +25,31 @@ <item> <layout class="QVBoxLayout" name="GeneralVerticalLayout"> <item> - <widget class="QCheckBox" name="toggle_deepscan"> - <property name="text"> - <string>Search sub-directories for games</string> - </property> - </widget> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QCheckBox" name="toggle_frame_limit"> + <property name="text"> + <string>Limit Speed Percent</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="frame_limit"> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>9999</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> </item> <item> <widget class="QCheckBox" name="toggle_check_exit"> diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 2b17b250c..2c9e322c9 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -55,7 +55,6 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) SetConfiguration(); - connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled); connect(ui->bg_button, &QPushButton::clicked, this, [this] { const QColor new_bg_color = QColorDialog::getColor(bg_color); if (!new_bg_color.isValid()) { @@ -72,9 +71,6 @@ void ConfigureGraphics::SetConfiguration() { ui->resolution_factor_combobox->setCurrentIndex( static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); - ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); - ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); - ui->frame_limit->setValue(Settings::values.frame_limit); ui->use_disk_shader_cache->setEnabled(runtime_lock); ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache); ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); @@ -89,8 +85,6 @@ void ConfigureGraphics::SetConfiguration() { void ConfigureGraphics::ApplyConfiguration() { Settings::values.resolution_factor = ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); - Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); - Settings::values.frame_limit = ui->frame_limit->value(); Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked(); Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); Settings::values.use_asynchronous_gpu_emulation = diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 15ab18ecd..0309ee300 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -23,33 +23,6 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QCheckBox" name="toggle_frame_limit"> - <property name="text"> - <string>Limit Speed Percent</string> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="frame_limit"> - <property name="suffix"> - <string>%</string> - </property> - <property name="minimum"> - <number>1</number> - </property> - <property name="maximum"> - <number>9999</number> - </property> - <property name="value"> - <number>100</number> - </property> - </widget> - </item> - </layout> - </item> - <item> <widget class="QCheckBox" name="use_disk_shader_cache"> <property name="text"> <string>Use disk shader cache</string> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 7b70f307c..a968cfb5d 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -301,13 +301,16 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i }); } connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] { - QMessageBox::information(this, tr("Information"), - tr("After pressing OK, first move your joystick horizontally, " - "and then vertically.")); - HandleClick( - analog_map_stick[analog_id], - [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, - InputCommon::Polling::DeviceType::Analog); + if (QMessageBox::information( + this, tr("Information"), + tr("After pressing OK, first move your joystick horizontally, " + "and then vertically."), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { + HandleClick( + analog_map_stick[analog_id], + [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, + InputCommon::Polling::DeviceType::Analog); + } }); } diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index d18b96519..d5fab2f1f 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -34,7 +34,6 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve return QObject::eventFilter(obj, event); QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); - int rowCount = gamelist->tree_view->model()->rowCount(); QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower(); // If the searchfield's text hasn't changed special function keys get checked @@ -56,19 +55,9 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve // If there is only one result launch this game case Qt::Key_Return: case Qt::Key_Enter: { - QStandardItemModel* item_model = new QStandardItemModel(gamelist->tree_view); - QModelIndex root_index = item_model->invisibleRootItem()->index(); - QStandardItem* child_file; - QString file_path; - int resultCount = 0; - for (int i = 0; i < rowCount; ++i) { - if (!gamelist->tree_view->isRowHidden(i, root_index)) { - ++resultCount; - child_file = gamelist->item_model->item(i, 0); - file_path = child_file->data(GameListItemPath::FullPathRole).toString(); - } - } - if (resultCount == 1) { + if (gamelist->search_field->visible == 1) { + QString file_path = gamelist->getLastFilterResultItem(); + // To avoid loading error dialog loops while confirming them using enter // Also users usually want to run a different game after closing one gamelist->search_field->edit_filter->clear(); @@ -88,9 +77,31 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve } void GameListSearchField::setFilterResult(int visible, int total) { + this->visible = visible; + this->total = total; + label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); } +QString GameList::getLastFilterResultItem() const { + QStandardItem* folder; + QStandardItem* child; + QString file_path; + const int folder_count = item_model->rowCount(); + for (int i = 0; i < folder_count; ++i) { + folder = item_model->item(i, 0); + const QModelIndex folder_index = folder->index(); + const int children_count = folder->rowCount(); + for (int j = 0; j < children_count; ++j) { + if (!tree_view->isRowHidden(j, folder_index)) { + child = folder->child(j, 0); + file_path = child->data(GameListItemPath::FullPathRole).toString(); + } + } + } + return file_path; +} + void GameListSearchField::clear() { edit_filter->clear(); } @@ -147,45 +158,120 @@ static bool ContainsAllWords(const QString& haystack, const QString& userinput) [&haystack](const QString& s) { return haystack.contains(s); }); } +// Syncs the expanded state of Game Directories with settings to persist across sessions +void GameList::onItemExpanded(const QModelIndex& item) { + const auto type = item.data(GameListItem::TypeRole).value<GameListItemType>(); + if (type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir || + type == GameListItemType::UserNandDir || type == GameListItemType::SysNandDir) + item.data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded = + tree_view->isExpanded(item); +} + // Event in order to filter the gamelist after editing the searchfield void GameList::onTextChanged(const QString& new_text) { - const int row_count = tree_view->model()->rowCount(); - const QString edit_filter_text = new_text.toLower(); - const QModelIndex root_index = item_model->invisibleRootItem()->index(); + const int folder_count = tree_view->model()->rowCount(); + QString edit_filter_text = new_text.toLower(); + QStandardItem* folder; + QStandardItem* child; + int children_total = 0; + QModelIndex root_index = item_model->invisibleRootItem()->index(); // If the searchfield is empty every item is visible // Otherwise the filter gets applied if (edit_filter_text.isEmpty()) { - for (int i = 0; i < row_count; ++i) { - tree_view->setRowHidden(i, root_index, false); + for (int i = 0; i < folder_count; ++i) { + folder = item_model->item(i, 0); + const QModelIndex folder_index = folder->index(); + const int children_count = folder->rowCount(); + for (int j = 0; j < children_count; ++j) { + ++children_total; + tree_view->setRowHidden(j, folder_index, false); + } } - search_field->setFilterResult(row_count, row_count); + search_field->setFilterResult(children_total, children_total); } else { int result_count = 0; - for (int i = 0; i < row_count; ++i) { - const QStandardItem* child_file = item_model->item(i, 0); - const QString file_path = - child_file->data(GameListItemPath::FullPathRole).toString().toLower(); - const QString file_title = - child_file->data(GameListItemPath::TitleRole).toString().toLower(); - const QString file_program_id = - child_file->data(GameListItemPath::ProgramIdRole).toString().toLower(); - - // Only items which filename in combination with its title contains all words - // that are in the searchfield will be visible in the gamelist - // The search is case insensitive because of toLower() - // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent - // multiple conversions of edit_filter_text for each game in the gamelist - const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + - QLatin1Char{' '} + file_title; - if (ContainsAllWords(file_name, edit_filter_text) || - (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) { - tree_view->setRowHidden(i, root_index, false); - ++result_count; - } else { - tree_view->setRowHidden(i, root_index, true); + for (int i = 0; i < folder_count; ++i) { + folder = item_model->item(i, 0); + const QModelIndex folder_index = folder->index(); + const int children_count = folder->rowCount(); + for (int j = 0; j < children_count; ++j) { + ++children_total; + const QStandardItem* child = folder->child(j, 0); + const QString file_path = + child->data(GameListItemPath::FullPathRole).toString().toLower(); + const QString file_title = + child->data(GameListItemPath::TitleRole).toString().toLower(); + const QString file_program_id = + child->data(GameListItemPath::ProgramIdRole).toString().toLower(); + + // Only items which filename in combination with its title contains all words + // that are in the searchfield will be visible in the gamelist + // The search is case insensitive because of toLower() + // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent + // multiple conversions of edit_filter_text for each game in the gamelist + const QString file_name = + file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + + file_title; + if (ContainsAllWords(file_name, edit_filter_text) || + (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) { + tree_view->setRowHidden(j, folder_index, false); + ++result_count; + } else { + tree_view->setRowHidden(j, folder_index, true); + } + search_field->setFilterResult(result_count, children_total); } - search_field->setFilterResult(result_count, row_count); + } + } +} + +void GameList::onUpdateThemedIcons() { + for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) { + QStandardItem* child = item_model->invisibleRootItem()->child(i); + + const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64); + switch (child->data(GameListItem::TypeRole).value<GameListItemType>()) { + case GameListItemType::SdmcDir: + child->setData( + QIcon::fromTheme(QStringLiteral("sd_card")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + break; + case GameListItemType::UserNandDir: + child->setData( + QIcon::fromTheme(QStringLiteral("chip")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + break; + case GameListItemType::SysNandDir: + child->setData( + QIcon::fromTheme(QStringLiteral("chip")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + break; + case GameListItemType::CustomDir: { + const UISettings::GameDir* game_dir = + child->data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); + const QString icon_name = QFileInfo::exists(game_dir->path) + ? QStringLiteral("folder") + : QStringLiteral("bad_folder"); + child->setData( + QIcon::fromTheme(icon_name).pixmap(icon_size).scaled( + icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + break; + } + case GameListItemType::AddDir: + child->setData( + QIcon::fromTheme(QStringLiteral("plus")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + break; } } } @@ -214,7 +300,6 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); tree_view->setSortingEnabled(true); tree_view->setEditTriggers(QHeaderView::NoEditTriggers); - tree_view->setUniformRowHeights(true); tree_view->setContextMenuPolicy(Qt::CustomContextMenu); tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); @@ -230,12 +315,16 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide item_model->setHeaderData(COLUMN_FILE_TYPE - 1, Qt::Horizontal, tr("File type")); item_model->setHeaderData(COLUMN_SIZE - 1, Qt::Horizontal, tr("Size")); } + item_model->setSortRole(GameListItemPath::TitleRole); + connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::onUpdateThemedIcons); connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); + connect(tree_view, &QTreeView::expanded, this, &GameList::onItemExpanded); + connect(tree_view, &QTreeView::collapsed, this, &GameList::onItemExpanded); - // We must register all custom types with the Qt Automoc system so that we are able to use it - // with signals/slots. In this case, QList falls under the umbrells of custom types. + // We must register all custom types with the Qt Automoc system so that we are able to use + // it with signals/slots. In this case, QList falls under the umbrells of custom types. qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); layout->setContentsMargins(0, 0, 0, 0); @@ -263,38 +352,68 @@ void GameList::clearFilter() { search_field->clear(); } -void GameList::AddEntry(const QList<QStandardItem*>& entry_items) { +void GameList::AddDirEntry(GameListDir* entry_items) { item_model->invisibleRootItem()->appendRow(entry_items); + tree_view->setExpanded( + entry_items->index(), + entry_items->data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded); } -void GameList::ValidateEntry(const QModelIndex& item) { - // We don't care about the individual QStandardItem that was selected, but its row. - const int row = item_model->itemFromIndex(item)->row(); - const QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); - const QString file_path = child_file->data(GameListItemPath::FullPathRole).toString(); - - if (file_path.isEmpty()) - return; - - if (!QFileInfo::exists(file_path)) - return; +void GameList::AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent) { + parent->appendRow(entry_items); +} - const QFileInfo file_info{file_path}; - if (file_info.isDir()) { - const QDir dir{file_path}; - const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files); - if (matching_main.size() == 1) { - emit GameChosen(dir.path() + QDir::separator() + matching_main[0]); +void GameList::ValidateEntry(const QModelIndex& item) { + const auto selected = item.sibling(item.row(), 0); + + switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) { + case GameListItemType::Game: { + const QString file_path = selected.data(GameListItemPath::FullPathRole).toString(); + if (file_path.isEmpty()) + return; + const QFileInfo file_info(file_path); + if (!file_info.exists()) + return; + + if (file_info.isDir()) { + const QDir dir{file_path}; + const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files); + if (matching_main.size() == 1) { + emit GameChosen(dir.path() + QDir::separator() + matching_main[0]); + } + return; } - return; + + // Users usually want to run a different game after closing one + search_field->clear(); + emit GameChosen(file_path); + break; } + case GameListItemType::AddDir: + emit AddDirectory(); + break; + } +} - // Users usually want to run a diffrent game after closing one - search_field->clear(); - emit GameChosen(file_path); +bool GameList::isEmpty() const { + for (int i = 0; i < item_model->rowCount(); i++) { + const QStandardItem* child = item_model->invisibleRootItem()->child(i); + const auto type = static_cast<GameListItemType>(child->type()); + if (!child->hasChildren() && + (type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir || + type == GameListItemType::SysNandDir)) { + item_model->invisibleRootItem()->removeRow(child->row()); + i--; + }; + } + return !item_model->invisibleRootItem()->hasChildren(); } void GameList::DonePopulating(QStringList watch_list) { + emit ShowList(!isEmpty()); + + item_model->invisibleRootItem()->appendRow(new GameListAddDir()); + // Clear out the old directories to watch for changes and add the new ones auto watch_dirs = watcher->directories(); if (!watch_dirs.isEmpty()) { @@ -311,9 +430,13 @@ void GameList::DonePopulating(QStringList watch_list) { QCoreApplication::processEvents(); } tree_view->setEnabled(true); - int rowCount = tree_view->model()->rowCount(); - search_field->setFilterResult(rowCount, rowCount); - if (rowCount > 0) { + const int folder_count = tree_view->model()->rowCount(); + int children_total = 0; + for (int i = 0; i < folder_count; ++i) { + children_total += item_model->item(i, 0)->rowCount(); + } + search_field->setFilterResult(children_total, children_total); + if (children_total > 0) { search_field->setFocus(); } } @@ -323,12 +446,27 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { if (!item.isValid()) return; - int row = item_model->itemFromIndex(item)->row(); - QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); - u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); - std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString(); - + const auto selected = item.sibling(item.row(), 0); QMenu context_menu; + switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) { + case GameListItemType::Game: + AddGamePopup(context_menu, selected.data(GameListItemPath::ProgramIdRole).toULongLong(), + selected.data(GameListItemPath::FullPathRole).toString().toStdString()); + break; + case GameListItemType::CustomDir: + AddPermDirPopup(context_menu, selected); + AddCustomDirPopup(context_menu, selected); + break; + case GameListItemType::SdmcDir: + case GameListItemType::UserNandDir: + case GameListItemType::SysNandDir: + AddPermDirPopup(context_menu, selected); + break; + } + context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); +} + +void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) { QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); QAction* open_transferable_shader_cache = @@ -344,19 +482,86 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); - connect(open_save_location, &QAction::triggered, - [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); - connect(open_lfs_location, &QAction::triggered, - [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); }); + connect(open_save_location, &QAction::triggered, [this, program_id]() { + emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); + }); + connect(open_lfs_location, &QAction::triggered, [this, program_id]() { + emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); + }); connect(open_transferable_shader_cache, &QAction::triggered, - [&]() { emit OpenTransferableShaderCacheRequested(program_id); }); - connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); }); - connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); }); - connect(navigate_to_gamedb_entry, &QAction::triggered, - [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); - connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); }); + [this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); }); + connect(dump_romfs, &QAction::triggered, + [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); }); + connect(copy_tid, &QAction::triggered, + [this, program_id]() { emit CopyTIDRequested(program_id); }); + connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { + emit NavigateToGamedbEntryRequested(program_id, compatibility_list); + }); + connect(properties, &QAction::triggered, + [this, path]() { emit OpenPerGameGeneralRequested(path); }); +}; + +void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) { + UISettings::GameDir& game_dir = + *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); + + QAction* deep_scan = context_menu.addAction(tr("Scan Subfolders")); + QAction* delete_dir = context_menu.addAction(tr("Remove Game Directory")); + + deep_scan->setCheckable(true); + deep_scan->setChecked(game_dir.deep_scan); + + connect(deep_scan, &QAction::triggered, [this, &game_dir] { + game_dir.deep_scan = !game_dir.deep_scan; + PopulateAsync(UISettings::values.game_dirs); + }); + connect(delete_dir, &QAction::triggered, [this, &game_dir, selected] { + UISettings::values.game_dirs.removeOne(game_dir); + item_model->invisibleRootItem()->removeRow(selected.row()); + }); +} - context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); +void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) { + UISettings::GameDir& game_dir = + *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); + + QAction* move_up = context_menu.addAction(tr(u8"\U000025b2 Move Up")); + QAction* move_down = context_menu.addAction(tr(u8"\U000025bc Move Down ")); + QAction* open_directory_location = context_menu.addAction(tr("Open Directory Location")); + + const int row = selected.row(); + + move_up->setEnabled(row > 0); + move_down->setEnabled(row < item_model->rowCount() - 2); + + connect(move_up, &QAction::triggered, [this, selected, row, &game_dir] { + // find the indices of the items in settings and swap them + std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)], + UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf( + *selected.sibling(row - 1, 0) + .data(GameListDir::GameDirRole) + .value<UISettings::GameDir*>())]); + // move the treeview items + QList<QStandardItem*> item = item_model->takeRow(row); + item_model->invisibleRootItem()->insertRow(row - 1, item); + tree_view->setExpanded(selected, game_dir.expanded); + }); + + connect(move_down, &QAction::triggered, [this, selected, row, &game_dir] { + // find the indices of the items in settings and swap them + std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)], + UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf( + *selected.sibling(row + 1, 0) + .data(GameListDir::GameDirRole) + .value<UISettings::GameDir*>())]); + // move the treeview items + const QList<QStandardItem*> item = item_model->takeRow(row); + item_model->invisibleRootItem()->insertRow(row + 1, item); + tree_view->setExpanded(selected, game_dir.expanded); + }); + + connect(open_directory_location, &QAction::triggered, + [this, game_dir] { emit OpenDirectory(game_dir.path); }); } void GameList::LoadCompatibilityList() { @@ -403,14 +608,7 @@ void GameList::LoadCompatibilityList() { } } -void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { - const QFileInfo dir_info{dir_path}; - if (!dir_info.exists() || !dir_info.isDir()) { - LOG_ERROR(Frontend, "Could not find game list folder at {}", dir_path.toStdString()); - search_field->setFilterResult(0, 0); - return; - } - +void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { tree_view->setEnabled(false); // Update the columns in case UISettings has changed @@ -433,17 +631,19 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { // Delete any rows that might already exist if we're repopulating item_model->removeRows(0, item_model->rowCount()); + search_field->clear(); emit ShouldCancelWorker(); - GameListWorker* worker = - new GameListWorker(vfs, provider, dir_path, deep_scan, compatibility_list); + GameListWorker* worker = new GameListWorker(vfs, provider, game_dirs, compatibility_list); connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); + connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, + Qt::QueuedConnection); connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, Qt::QueuedConnection); - // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to cancel - // without delay. + // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to + // cancel without delay. connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel, Qt::DirectConnection); @@ -471,10 +671,40 @@ const QStringList GameList::supported_file_extensions = { QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; void GameList::RefreshGameDirectory() { - if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) { + if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) { LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); - search_field->clear(); - PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + PopulateAsync(UISettings::values.game_dirs); } } + +GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} { + connect(parent, &GMainWindow::UpdateThemedIcons, this, + &GameListPlaceholder::onUpdateThemedIcons); + + layout = new QVBoxLayout; + image = new QLabel; + text = new QLabel; + layout->setAlignment(Qt::AlignCenter); + image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); + + text->setText(tr("Double-click to add a new folder to the game list")); + QFont font = text->font(); + font.setPointSize(20); + text->setFont(font); + text->setAlignment(Qt::AlignHCenter); + image->setAlignment(Qt::AlignHCenter); + + layout->addWidget(image); + layout->addWidget(text); + setLayout(layout); +} + +GameListPlaceholder::~GameListPlaceholder() = default; + +void GameListPlaceholder::onUpdateThemedIcons() { + image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); +} + +void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) { + emit GameListPlaceholder::AddDirectory(); +} diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index f8f8bd6c5..878d94413 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -8,6 +8,7 @@ #include <QHBoxLayout> #include <QLabel> #include <QLineEdit> +#include <QList> #include <QModelIndex> #include <QSettings> #include <QStandardItem> @@ -16,13 +17,16 @@ #include <QToolButton> #include <QTreeView> #include <QVBoxLayout> +#include <QVector> #include <QWidget> #include "common/common_types.h" +#include "uisettings.h" #include "yuzu/compatibility_list.h" class GameListWorker; class GameListSearchField; +class GameListDir; class GMainWindow; namespace FileSys { @@ -52,12 +56,14 @@ public: FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr); ~GameList() override; + QString getLastFilterResultItem() const; void clearFilter(); void setFilterFocus(); void setFilterVisible(bool visibility); + bool isEmpty() const; void LoadCompatibilityList(); - void PopulateAsync(const QString& dir_path, bool deep_scan); + void PopulateAsync(QVector<UISettings::GameDir>& game_dirs); void SaveInterfaceLayout(); void LoadInterfaceLayout(); @@ -74,19 +80,29 @@ signals: void NavigateToGamedbEntryRequested(u64 program_id, const CompatibilityList& compatibility_list); void OpenPerGameGeneralRequested(const std::string& file); + void OpenDirectory(const QString& directory); + void AddDirectory(); + void ShowList(bool show); private slots: + void onItemExpanded(const QModelIndex& item); void onTextChanged(const QString& new_text); void onFilterCloseClicked(); + void onUpdateThemedIcons(); private: - void AddEntry(const QList<QStandardItem*>& entry_items); + void AddDirEntry(GameListDir* entry_items); + void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent); void ValidateEntry(const QModelIndex& item); void DonePopulating(QStringList watch_list); - void PopupContextMenu(const QPoint& menu_location); void RefreshGameDirectory(); + void PopupContextMenu(const QPoint& menu_location); + void AddGamePopup(QMenu& context_menu, u64 program_id, std::string path); + void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected); + void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); + std::shared_ptr<FileSys::VfsFilesystem> vfs; FileSys::ManualContentProvider* provider; GameListSearchField* search_field; @@ -102,3 +118,24 @@ private: }; Q_DECLARE_METATYPE(GameListOpenTarget); + +class GameListPlaceholder : public QWidget { + Q_OBJECT +public: + explicit GameListPlaceholder(GMainWindow* parent = nullptr); + ~GameListPlaceholder(); + +signals: + void AddDirectory(); + +private slots: + void onUpdateThemedIcons(); + +protected: + void mouseDoubleClickEvent(QMouseEvent* event) override; + +private: + QVBoxLayout* layout = nullptr; + QLabel* image = nullptr; + QLabel* text = nullptr; +}; diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index ece534dd6..a8d888fee 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -10,6 +10,7 @@ #include <utility> #include <QCoreApplication> +#include <QFileInfo> #include <QImage> #include <QObject> #include <QStandardItem> @@ -22,6 +23,17 @@ #include "yuzu/uisettings.h" #include "yuzu/util/util.h" +enum class GameListItemType { + Game = QStandardItem::UserType + 1, + CustomDir = QStandardItem::UserType + 2, + SdmcDir = QStandardItem::UserType + 3, + UserNandDir = QStandardItem::UserType + 4, + SysNandDir = QStandardItem::UserType + 5, + AddDir = QStandardItem::UserType + 6 +}; + +Q_DECLARE_METATYPE(GameListItemType); + /** * Gets the default icon (for games without valid title metadata) * @param size The desired width and height of the default icon. @@ -36,8 +48,13 @@ static QPixmap GetDefaultIcon(u32 size) { class GameListItem : public QStandardItem { public: + // used to access type from item index + static const int TypeRole = Qt::UserRole + 1; + static const int SortRole = Qt::UserRole + 2; GameListItem() = default; - explicit GameListItem(const QString& string) : QStandardItem(string) {} + GameListItem(const QString& string) : QStandardItem(string) { + setData(string, SortRole); + } }; /** @@ -48,14 +65,15 @@ public: */ class GameListItemPath : public GameListItem { public: - static const int FullPathRole = Qt::UserRole + 1; - static const int TitleRole = Qt::UserRole + 2; - static const int ProgramIdRole = Qt::UserRole + 3; - static const int FileTypeRole = Qt::UserRole + 4; + static const int TitleRole = SortRole; + static const int FullPathRole = SortRole + 1; + static const int ProgramIdRole = SortRole + 2; + static const int FileTypeRole = SortRole + 3; GameListItemPath() = default; GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data, const QString& game_name, const QString& game_type, u64 program_id) { + setData(type(), TypeRole); setData(game_path, FullPathRole); setData(game_name, TitleRole); setData(qulonglong(program_id), ProgramIdRole); @@ -72,6 +90,10 @@ public: setData(picture, Qt::DecorationRole); } + int type() const override { + return static_cast<int>(GameListItemType::Game); + } + QVariant data(int role) const override { if (role == Qt::DisplayRole) { std::string filename; @@ -103,9 +125,11 @@ public: class GameListItemCompat : public GameListItem { Q_DECLARE_TR_FUNCTIONS(GameListItemCompat) public: - static const int CompatNumberRole = Qt::UserRole + 1; + static const int CompatNumberRole = SortRole; GameListItemCompat() = default; explicit GameListItemCompat(const QString& compatibility) { + setData(type(), TypeRole); + struct CompatStatus { QString color; const char* text; @@ -135,6 +159,10 @@ public: setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); } + int type() const override { + return static_cast<int>(GameListItemType::Game); + } + bool operator<(const QStandardItem& other) const override { return data(CompatNumberRole) < other.data(CompatNumberRole); } @@ -146,12 +174,12 @@ public: * human-readable string representation will be displayed to the user. */ class GameListItemSize : public GameListItem { - public: - static const int SizeRole = Qt::UserRole + 1; + static const int SizeRole = SortRole; GameListItemSize() = default; explicit GameListItemSize(const qulonglong size_bytes) { + setData(type(), TypeRole); setData(size_bytes, SizeRole); } @@ -167,6 +195,10 @@ public: } } + int type() const override { + return static_cast<int>(GameListItemType::Game); + } + /** * This operator is, in practice, only used by the TreeView sorting systems. * Override it so that it will correctly sort by numerical value instead of by string @@ -177,6 +209,82 @@ public: } }; +class GameListDir : public GameListItem { +public: + static const int GameDirRole = Qt::UserRole + 2; + + explicit GameListDir(UISettings::GameDir& directory, + GameListItemType dir_type = GameListItemType::CustomDir) + : dir_type{dir_type} { + setData(type(), TypeRole); + + UISettings::GameDir* game_dir = &directory; + setData(QVariant::fromValue(game_dir), GameDirRole); + + const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64); + switch (dir_type) { + case GameListItemType::SdmcDir: + setData( + QIcon::fromTheme(QStringLiteral("sd_card")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + setData(QObject::tr("Installed SD Titles"), Qt::DisplayRole); + break; + case GameListItemType::UserNandDir: + setData( + QIcon::fromTheme(QStringLiteral("chip")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + setData(QObject::tr("Installed NAND Titles"), Qt::DisplayRole); + break; + case GameListItemType::SysNandDir: + setData( + QIcon::fromTheme(QStringLiteral("chip")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + setData(QObject::tr("System Titles"), Qt::DisplayRole); + break; + case GameListItemType::CustomDir: + const QString icon_name = QFileInfo::exists(game_dir->path) + ? QStringLiteral("folder") + : QStringLiteral("bad_folder"); + setData(QIcon::fromTheme(icon_name).pixmap(icon_size).scaled( + icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + setData(game_dir->path, Qt::DisplayRole); + break; + }; + }; + + int type() const override { + return static_cast<int>(dir_type); + } + +private: + GameListItemType dir_type; +}; + +class GameListAddDir : public GameListItem { +public: + explicit GameListAddDir() { + setData(type(), TypeRole); + + const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64); + setData(QIcon::fromTheme(QStringLiteral("plus")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole); + } + + int type() const override { + return static_cast<int>(GameListItemType::AddDir); + } +}; + class GameList; class QHBoxLayout; class QTreeView; @@ -208,6 +316,9 @@ private: // EventFilter in order to process systemkeys while editing the searchfield bool eventFilter(QObject* obj, QEvent* event) override; }; + int visible; + int total; + QHBoxLayout* layout_filter = nullptr; QTreeView* tree_view = nullptr; QLabel* label_filter = nullptr; diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 77f358630..fd21a9761 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -223,21 +223,37 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri } // Anonymous namespace GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, - FileSys::ManualContentProvider* provider, QString dir_path, - bool deep_scan, const CompatibilityList& compatibility_list) - : vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan), + FileSys::ManualContentProvider* provider, + QVector<UISettings::GameDir>& game_dirs, + const CompatibilityList& compatibility_list) + : vfs(std::move(vfs)), provider(provider), game_dirs(game_dirs), compatibility_list(compatibility_list) {} GameListWorker::~GameListWorker() = default; -void GameListWorker::AddTitlesToGameList() { - const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>( - Core::System::GetInstance().GetContentProvider()); - const auto installed_games = cache.ListEntriesFilterOrigin( - std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program); +void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { + using namespace FileSys; + + const auto& cache = + dynamic_cast<ContentProviderUnion&>(Core::System::GetInstance().GetContentProvider()); + + std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> installed_games; + installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application, + ContentRecordType::Program); + + if (parent_dir->type() == static_cast<int>(GameListItemType::SdmcDir)) { + installed_games = cache.ListEntriesFilterOrigin( + ContentProviderUnionSlot::SDMC, TitleType::Application, ContentRecordType::Program); + } else if (parent_dir->type() == static_cast<int>(GameListItemType::UserNandDir)) { + installed_games = cache.ListEntriesFilterOrigin( + ContentProviderUnionSlot::UserNAND, TitleType::Application, ContentRecordType::Program); + } else if (parent_dir->type() == static_cast<int>(GameListItemType::SysNandDir)) { + installed_games = cache.ListEntriesFilterOrigin( + ContentProviderUnionSlot::SysNAND, TitleType::Application, ContentRecordType::Program); + } for (const auto& [slot, game] : installed_games) { - if (slot == FileSys::ContentProviderUnionSlot::FrontendManual) + if (slot == ContentProviderUnionSlot::FrontendManual) continue; const auto file = cache.GetEntryUnparsed(game.title_id, game.type); @@ -250,21 +266,22 @@ void GameListWorker::AddTitlesToGameList() { u64 program_id = 0; loader->ReadProgramId(program_id); - const FileSys::PatchManager patch{program_id}; - const auto control = cache.GetEntry(game.title_id, FileSys::ContentRecordType::Control); + const PatchManager patch{program_id}; + const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control); if (control != nullptr) GetMetadataFromControlNCA(patch, *control, icon, name); emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id, - compatibility_list, patch)); + compatibility_list, patch), + parent_dir); } } void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, - unsigned int recursion) { - const auto callback = [this, target, recursion](u64* num_entries_out, - const std::string& directory, - const std::string& virtual_name) -> bool { + unsigned int recursion, GameListDir* parent_dir) { + const auto callback = [this, target, recursion, + parent_dir](u64* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { if (stop_processing) { // Breaks the callback loop. return false; @@ -317,11 +334,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa const FileSys::PatchManager patch{program_id}; emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, - compatibility_list, patch)); + compatibility_list, patch), + parent_dir); } } else if (is_dir && recursion > 0) { watch_list.append(QString::fromStdString(physical_name)); - ScanFileSystem(target, physical_name, recursion - 1); + ScanFileSystem(target, physical_name, recursion - 1, parent_dir); } return true; @@ -332,12 +350,32 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa void GameListWorker::run() { stop_processing = false; - watch_list.append(dir_path); - provider->ClearAllEntries(); - ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(), - deep_scan ? 256 : 0); - AddTitlesToGameList(); - ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0); + + for (UISettings::GameDir& game_dir : game_dirs) { + if (game_dir.path == QStringLiteral("SDMC")) { + auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); + emit DirEntryReady({game_list_dir}); + AddTitlesToGameList(game_list_dir); + } else if (game_dir.path == QStringLiteral("UserNAND")) { + auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); + emit DirEntryReady({game_list_dir}); + AddTitlesToGameList(game_list_dir); + } else if (game_dir.path == QStringLiteral("SysNAND")) { + auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); + emit DirEntryReady({game_list_dir}); + AddTitlesToGameList(game_list_dir); + } else { + watch_list.append(game_dir.path); + auto* const game_list_dir = new GameListDir(game_dir); + emit DirEntryReady({game_list_dir}); + provider->ClearAllEntries(); + ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 2, + game_list_dir); + ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), + game_dir.deep_scan ? 256 : 0, game_list_dir); + } + }; + emit Finished(watch_list); } diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 7c3074af9..6e52fca89 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h @@ -14,6 +14,7 @@ #include <QObject> #include <QRunnable> #include <QString> +#include <QVector> #include "common/common_types.h" #include "yuzu/compatibility_list.h" @@ -33,9 +34,10 @@ class GameListWorker : public QObject, public QRunnable { Q_OBJECT public: - GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, - FileSys::ManualContentProvider* provider, QString dir_path, bool deep_scan, - const CompatibilityList& compatibility_list); + explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, + FileSys::ManualContentProvider* provider, + QVector<UISettings::GameDir>& game_dirs, + const CompatibilityList& compatibility_list); ~GameListWorker() override; /// Starts the processing of directory tree information. @@ -48,31 +50,33 @@ signals: /** * The `EntryReady` signal is emitted once an entry has been prepared and is ready * to be added to the game list. - * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. + * @param entry_items a list with `QStandardItem`s that make up the columns of the new + * entry. */ - void EntryReady(QList<QStandardItem*> entry_items); + void DirEntryReady(GameListDir* entry_items); + void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir); /** - * After the worker has traversed the game directory looking for entries, this signal is emitted - * with a list of folders that should be watched for changes as well. + * After the worker has traversed the game directory looking for entries, this signal is + * emitted with a list of folders that should be watched for changes as well. */ void Finished(QStringList watch_list); private: - void AddTitlesToGameList(); + void AddTitlesToGameList(GameListDir* parent_dir); enum class ScanTarget { FillManualContentProvider, PopulateGameList, }; - void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0); + void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion, + GameListDir* parent_dir); std::shared_ptr<FileSys::VfsFilesystem> vfs; FileSys::ManualContentProvider* provider; QStringList watch_list; - QString dir_path; - bool deep_scan; const CompatibilityList& compatibility_list; + QVector<UISettings::GameDir>& game_dirs; std::atomic_bool stop_processing; }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ac57229d5..8304c6517 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -6,6 +6,9 @@ #include <clocale> #include <memory> #include <thread> +#ifdef __APPLE__ +#include <unistd.h> // for chdir +#endif // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. #include "applets/error.h" @@ -216,8 +219,7 @@ GMainWindow::GMainWindow() OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); game_list->LoadCompatibilityList(); - game_list->PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + game_list->PopulateAsync(UISettings::values.game_dirs); // Show one-time "callout" messages to the user ShowTelemetryCallout(); @@ -427,6 +429,10 @@ void GMainWindow::InitializeWidgets() { game_list = new GameList(vfs, provider.get(), this); ui.horizontalLayout->addWidget(game_list); + game_list_placeholder = new GameListPlaceholder(this); + ui.horizontalLayout->addWidget(game_list_placeholder); + game_list_placeholder->setVisible(false); + loading_screen = new LoadingScreen(this); loading_screen->hide(); ui.horizontalLayout->addWidget(loading_screen); @@ -660,6 +666,7 @@ void GMainWindow::RestoreUIState() { void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); + connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory); connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, &GMainWindow::OnTransferableShaderCacheOpenFile); @@ -667,6 +674,11 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, &GMainWindow::OnGameListNavigateToGamedbEntry); + connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory); + connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this, + &GMainWindow::OnGameListAddDirectory); + connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList); + connect(game_list, &GameList::OpenPerGameGeneralRequested, this, &GMainWindow::OnGameListOpenPerGameProperties); @@ -684,8 +696,6 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); connect(ui.action_Install_File_NAND, &QAction::triggered, this, &GMainWindow::OnMenuInstallToNAND); - connect(ui.action_Select_Game_List_Root, &QAction::triggered, this, - &GMainWindow::OnMenuSelectGameListRoot); connect(ui.action_Select_NAND_Directory, &QAction::triggered, this, [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); }); connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this, @@ -950,6 +960,7 @@ void GMainWindow::BootGame(const QString& filename) { // Update the GUI if (ui.action_Single_Window_Mode->isChecked()) { game_list->hide(); + game_list_placeholder->hide(); } status_bar_update_timer.start(2000); @@ -1007,7 +1018,10 @@ void GMainWindow::ShutdownGame() { render_window->hide(); loading_screen->hide(); loading_screen->Clear(); - game_list->show(); + if (game_list->isEmpty()) + game_list_placeholder->show(); + else + game_list->show(); game_list->setFilterFocus(); UpdateWindowTitle(); @@ -1298,6 +1312,47 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); } +void GMainWindow::OnGameListOpenDirectory(const QString& directory) { + QString path; + if (directory == QStringLiteral("SDMC")) { + path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + + "Nintendo/Contents/registered"); + } else if (directory == QStringLiteral("UserNAND")) { + path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + "user/Contents/registered"); + } else if (directory == QStringLiteral("SysNAND")) { + path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + "system/Contents/registered"); + } else { + path = directory; + } + if (!QFileInfo::exists(path)) { + QMessageBox::critical(this, tr("Error Opening %1").arg(path), tr("Folder does not exist!")); + return; + } + QDesktopServices::openUrl(QUrl::fromLocalFile(path)); +} + +void GMainWindow::OnGameListAddDirectory() { + const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); + if (dir_path.isEmpty()) + return; + UISettings::GameDir game_dir{dir_path, false, true}; + if (!UISettings::values.game_dirs.contains(game_dir)) { + UISettings::values.game_dirs.append(game_dir); + game_list->PopulateAsync(UISettings::values.game_dirs); + } else { + LOG_WARNING(Frontend, "Selected directory is already in the game list"); + } +} + +void GMainWindow::OnGameListShowList(bool show) { + if (emulation_running && ui.action_Single_Window_Mode->isChecked()) + return; + game_list->setVisible(show); + game_list_placeholder->setVisible(!show); +}; + void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { u64 title_id{}; const auto v_file = Core::GetGameFileFromPath(vfs, file); @@ -1316,8 +1371,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); if (reload) { - game_list->PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + game_list->PopulateAsync(UISettings::values.game_dirs); } config->Save(); @@ -1407,8 +1461,7 @@ void GMainWindow::OnMenuInstallToNAND() { const auto success = [this]() { QMessageBox::information(this, tr("Successfully Installed"), tr("The file was successfully installed.")); - game_list->PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + game_list->PopulateAsync(UISettings::values.game_dirs); FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list"); }; @@ -1533,14 +1586,6 @@ void GMainWindow::OnMenuInstallToNAND() { } } -void GMainWindow::OnMenuSelectGameListRoot() { - QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); - if (!dir_path.isEmpty()) { - UISettings::values.game_directory_path = dir_path; - game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan); - } -} - void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) { const auto res = QMessageBox::information( this, tr("Changing Emulated Directory"), @@ -1559,8 +1604,7 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) : FileUtil::UserPath::NANDDir, dir_path.toStdString()); Service::FileSystem::CreateFactories(*vfs); - game_list->PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + game_list->PopulateAsync(UISettings::values.game_dirs); } } @@ -1724,11 +1768,11 @@ void GMainWindow::OnConfigure() { if (UISettings::values.enable_discord_presence != old_discord_presence) { SetDiscordEnabled(UISettings::values.enable_discord_presence); } + emit UpdateThemedIcons(); const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); if (reload) { - game_list->PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + game_list->PopulateAsync(UISettings::values.game_dirs); } config->Save(); @@ -1992,8 +2036,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { Service::FileSystem::CreateFactories(*vfs); if (behavior == ReinitializeKeyBehavior::Warning) { - game_list->PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + game_list->PopulateAsync(UISettings::values.game_dirs); } } @@ -2158,7 +2201,6 @@ void GMainWindow::UpdateUITheme() { } QIcon::setThemeSearchPaths(theme_paths); - emit UpdateThemedIcons(); } void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { @@ -2187,6 +2229,14 @@ int main(int argc, char* argv[]) { QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); QCoreApplication::setApplicationName(QStringLiteral("yuzu")); +#ifdef __APPLE__ + // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/". + // But since we require the working directory to be the executable path for the location of the + // user folder in the Qt Frontend, we need to cd into that working directory + const std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; + chdir(bin_path.c_str()); +#endif + // Enables the core to make the qt created contexts current on std::threads QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); QApplication app(argc, argv); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 501608ddc..7d16188cb 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -30,6 +30,7 @@ class ProfilerWidget; class QLabel; class WaitTreeWidget; enum class GameListOpenTarget; +class GameListPlaceholder; namespace Core::Frontend { struct SoftwareKeyboardParameters; @@ -186,12 +187,13 @@ private slots: void OnGameListCopyTID(u64 program_id); void OnGameListNavigateToGamedbEntry(u64 program_id, const CompatibilityList& compatibility_list); + void OnGameListOpenDirectory(const QString& directory); + void OnGameListAddDirectory(); + void OnGameListShowList(bool show); void OnGameListOpenPerGameProperties(const std::string& file); void OnMenuLoadFile(); void OnMenuLoadFolder(); void OnMenuInstallToNAND(); - /// Called whenever a user selects the "File->Select Game List Root" menu item - void OnMenuSelectGameListRoot(); /// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target); void OnMenuRecentFile(); @@ -223,6 +225,8 @@ private: GameList* game_list; LoadingScreen* loading_screen; + GameListPlaceholder* game_list_placeholder; + // Status bar elements QLabel* message_label = nullptr; QLabel* emu_speed_label = nullptr; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index ffcabb495..a1ce3c0c3 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -62,7 +62,6 @@ <addaction name="action_Load_File"/> <addaction name="action_Load_Folder"/> <addaction name="separator"/> - <addaction name="action_Select_Game_List_Root"/> <addaction name="menu_recent_files"/> <addaction name="separator"/> <addaction name="action_Select_NAND_Directory"/> diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index a62cd6911..c57290006 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -8,8 +8,10 @@ #include <atomic> #include <vector> #include <QByteArray> +#include <QMetaType> #include <QString> #include <QStringList> +#include <QVector> #include "common/common_types.h" namespace UISettings { @@ -25,6 +27,18 @@ struct Shortcut { using Themes = std::array<std::pair<const char*, const char*>, 2>; extern const Themes themes; +struct GameDir { + QString path; + bool deep_scan; + bool expanded; + bool operator==(const GameDir& rhs) const { + return path == rhs.path; + }; + bool operator!=(const GameDir& rhs) const { + return !operator==(rhs); + }; +}; + struct Values { QByteArray geometry; QByteArray state; @@ -55,8 +69,9 @@ struct Values { QString roms_path; QString symbols_path; QString screenshot_path; - QString game_directory_path; - bool game_directory_deepscan; + QString game_dir_deprecated; + bool game_dir_deprecated_deepscan; + QVector<UISettings::GameDir> game_dirs; QStringList recent_files; QString theme; @@ -84,3 +99,5 @@ struct Values { extern Values values; } // namespace UISettings + +Q_DECLARE_METATYPE(UISettings::GameDir*); |