diff options
Diffstat (limited to 'src')
77 files changed, 2344 insertions, 1205 deletions
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 | |||
| 70 | file_sys/sdmc_factory.h | 70 | file_sys/sdmc_factory.h |
| 71 | file_sys/submission_package.cpp | 71 | file_sys/submission_package.cpp |
| 72 | file_sys/submission_package.h | 72 | file_sys/submission_package.h |
| 73 | file_sys/system_archive/mii_model.cpp | ||
| 74 | file_sys/system_archive/mii_model.h | ||
| 73 | file_sys/system_archive/ng_word.cpp | 75 | file_sys/system_archive/ng_word.cpp |
| 74 | file_sys/system_archive/ng_word.h | 76 | file_sys/system_archive/ng_word.h |
| 75 | file_sys/system_archive/system_archive.cpp | 77 | 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, | |||
| 104 | return vfs->OpenFile(path, FileSys::Mode::Read); | 104 | return vfs->OpenFile(path, FileSys::Mode::Read); |
| 105 | } | 105 | } |
| 106 | struct System::Impl { | 106 | struct System::Impl { |
| 107 | explicit Impl(System& system) : kernel{system}, cpu_core_manager{system}, reporter{system} {} | 107 | explicit Impl(System& system) |
| 108 | : kernel{system}, cpu_core_manager{system}, applet_manager{system}, reporter{system} {} | ||
| 108 | 109 | ||
| 109 | Cpu& CurrentCpuCore() { | 110 | Cpu& CurrentCpuCore() { |
| 110 | return cpu_core_manager.GetCurrentCore(); | 111 | 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 @@ | |||
| 37 | namespace Core::Crypto { | 37 | namespace Core::Crypto { |
| 38 | 38 | ||
| 39 | constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; | 39 | constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; |
| 40 | constexpr u64 FULL_TICKET_SIZE = 0x400; | ||
| 40 | 41 | ||
| 41 | using namespace Common; | 42 | using namespace Common; |
| 42 | 43 | ||
| @@ -55,6 +56,99 @@ const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{ | |||
| 55 | {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, | 56 | {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, |
| 56 | }; | 57 | }; |
| 57 | 58 | ||
| 59 | namespace { | ||
| 60 | template <std::size_t Size> | ||
| 61 | bool IsAllZeroArray(const std::array<u8, Size>& array) { | ||
| 62 | return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; }); | ||
| 63 | } | ||
| 64 | } // namespace | ||
| 65 | |||
| 66 | u64 GetSignatureTypeDataSize(SignatureType type) { | ||
| 67 | switch (type) { | ||
| 68 | case SignatureType::RSA_4096_SHA1: | ||
| 69 | case SignatureType::RSA_4096_SHA256: | ||
| 70 | return 0x200; | ||
| 71 | case SignatureType::RSA_2048_SHA1: | ||
| 72 | case SignatureType::RSA_2048_SHA256: | ||
| 73 | return 0x100; | ||
| 74 | case SignatureType::ECDSA_SHA1: | ||
| 75 | case SignatureType::ECDSA_SHA256: | ||
| 76 | return 0x3C; | ||
| 77 | } | ||
| 78 | UNREACHABLE(); | ||
| 79 | } | ||
| 80 | |||
| 81 | u64 GetSignatureTypePaddingSize(SignatureType type) { | ||
| 82 | switch (type) { | ||
| 83 | case SignatureType::RSA_4096_SHA1: | ||
| 84 | case SignatureType::RSA_4096_SHA256: | ||
| 85 | case SignatureType::RSA_2048_SHA1: | ||
| 86 | case SignatureType::RSA_2048_SHA256: | ||
| 87 | return 0x3C; | ||
| 88 | case SignatureType::ECDSA_SHA1: | ||
| 89 | case SignatureType::ECDSA_SHA256: | ||
| 90 | return 0x40; | ||
| 91 | } | ||
| 92 | UNREACHABLE(); | ||
| 93 | } | ||
| 94 | |||
| 95 | SignatureType Ticket::GetSignatureType() const { | ||
| 96 | if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { | ||
| 97 | return ticket->sig_type; | ||
| 98 | } | ||
| 99 | if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { | ||
| 100 | return ticket->sig_type; | ||
| 101 | } | ||
| 102 | if (auto ticket = std::get_if<ECDSATicket>(&data)) { | ||
| 103 | return ticket->sig_type; | ||
| 104 | } | ||
| 105 | |||
| 106 | UNREACHABLE(); | ||
| 107 | } | ||
| 108 | |||
| 109 | TicketData& Ticket::GetData() { | ||
| 110 | if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { | ||
| 111 | return ticket->data; | ||
| 112 | } | ||
| 113 | if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { | ||
| 114 | return ticket->data; | ||
| 115 | } | ||
| 116 | if (auto ticket = std::get_if<ECDSATicket>(&data)) { | ||
| 117 | return ticket->data; | ||
| 118 | } | ||
| 119 | |||
| 120 | UNREACHABLE(); | ||
| 121 | } | ||
| 122 | |||
| 123 | const TicketData& Ticket::GetData() const { | ||
| 124 | if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { | ||
| 125 | return ticket->data; | ||
| 126 | } | ||
| 127 | if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { | ||
| 128 | return ticket->data; | ||
| 129 | } | ||
| 130 | if (auto ticket = std::get_if<ECDSATicket>(&data)) { | ||
| 131 | return ticket->data; | ||
| 132 | } | ||
| 133 | |||
| 134 | UNREACHABLE(); | ||
| 135 | } | ||
| 136 | |||
| 137 | u64 Ticket::GetSize() const { | ||
| 138 | const auto sig_type = GetSignatureType(); | ||
| 139 | |||
| 140 | return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) + | ||
| 141 | GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData); | ||
| 142 | } | ||
| 143 | |||
| 144 | Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& rights_id) { | ||
| 145 | RSA2048Ticket out{}; | ||
| 146 | out.sig_type = SignatureType::RSA_2048_SHA256; | ||
| 147 | out.data.rights_id = rights_id; | ||
| 148 | out.data.title_key_common = title_key; | ||
| 149 | return Ticket{out}; | ||
| 150 | } | ||
| 151 | |||
| 58 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { | 152 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { |
| 59 | Key128 out{}; | 153 | Key128 out{}; |
| 60 | 154 | ||
| @@ -135,6 +229,27 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) { | |||
| 135 | } | 229 | } |
| 136 | } | 230 | } |
| 137 | 231 | ||
| 232 | RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { | ||
| 233 | if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) | ||
| 234 | return {}; | ||
| 235 | |||
| 236 | const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); | ||
| 237 | |||
| 238 | std::vector<u8> extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10); | ||
| 239 | std::array<u8, 0x230> extended_dec{}; | ||
| 240 | AESCipher<Key128> rsa_1(eticket_final, Mode::CTR); | ||
| 241 | rsa_1.SetIV(extended_iv); | ||
| 242 | rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, | ||
| 243 | extended_dec.data(), Op::Decrypt); | ||
| 244 | |||
| 245 | RSAKeyPair<2048> rsa_key{}; | ||
| 246 | std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); | ||
| 247 | std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); | ||
| 248 | std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); | ||
| 249 | |||
| 250 | return rsa_key; | ||
| 251 | } | ||
| 252 | |||
| 138 | Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { | 253 | Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { |
| 139 | AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB); | 254 | AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB); |
| 140 | Key128 mac_key{}; | 255 | Key128 mac_key{}; |
| @@ -237,7 +352,7 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke | |||
| 237 | return Loader::ResultStatus::Success; | 352 | return Loader::ResultStatus::Success; |
| 238 | } | 353 | } |
| 239 | 354 | ||
| 240 | std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { | 355 | std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) { |
| 241 | if (!ticket_save.IsOpen()) | 356 | if (!ticket_save.IsOpen()) |
| 242 | return {}; | 357 | return {}; |
| 243 | 358 | ||
| @@ -246,14 +361,14 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { | |||
| 246 | return {}; | 361 | return {}; |
| 247 | } | 362 | } |
| 248 | 363 | ||
| 249 | std::vector<TicketRaw> out; | 364 | std::vector<Ticket> out; |
| 250 | for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { | 365 | for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { |
| 251 | if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && | 366 | if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && |
| 252 | buffer[offset + 3] == 0x0) { | 367 | buffer[offset + 3] == 0x0) { |
| 253 | out.emplace_back(); | 368 | out.emplace_back(); |
| 254 | auto& next = out.back(); | 369 | auto& next = out.back(); |
| 255 | std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw)); | 370 | std::memcpy(&next, buffer.data() + offset, sizeof(Ticket)); |
| 256 | offset += next.size(); | 371 | offset += FULL_TICKET_SIZE; |
| 257 | } | 372 | } |
| 258 | } | 373 | } |
| 259 | 374 | ||
| @@ -305,29 +420,23 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { | |||
| 305 | return offset; | 420 | return offset; |
| 306 | } | 421 | } |
| 307 | 422 | ||
| 308 | std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, | 423 | std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, |
| 309 | const RSAKeyPair<2048>& key) { | 424 | const RSAKeyPair<2048>& key) { |
| 310 | u32 cert_authority; | 425 | const auto issuer = ticket.GetData().issuer; |
| 311 | std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority)); | 426 | if (issuer == std::array<u8, 0x40>{}) |
| 312 | if (cert_authority == 0) | ||
| 313 | return {}; | 427 | return {}; |
| 314 | if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) { | 428 | if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { |
| 315 | LOG_INFO(Crypto, | 429 | LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); |
| 316 | "Attempting to parse ticket with non-standard certificate authority {:08X}.", | ||
| 317 | cert_authority); | ||
| 318 | } | 430 | } |
| 319 | 431 | ||
| 320 | Key128 rights_id; | 432 | Key128 rights_id = ticket.GetData().rights_id; |
| 321 | std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128)); | ||
| 322 | 433 | ||
| 323 | if (rights_id == Key128{}) | 434 | if (rights_id == Key128{}) |
| 324 | return {}; | 435 | return {}; |
| 325 | 436 | ||
| 326 | Key128 key_temp{}; | 437 | if (!std::any_of(ticket.GetData().title_key_common_pad.begin(), |
| 327 | 438 | ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) { | |
| 328 | if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) { | 439 | return std::make_pair(rights_id, ticket.GetData().title_key_common); |
| 329 | std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size()); | ||
| 330 | return std::make_pair(rights_id, key_temp); | ||
| 331 | } | 440 | } |
| 332 | 441 | ||
| 333 | mbedtls_mpi D; // RSA Private Exponent | 442 | mbedtls_mpi D; // RSA Private Exponent |
| @@ -342,7 +451,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, | |||
| 342 | 451 | ||
| 343 | mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); | 452 | mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); |
| 344 | mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); | 453 | mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); |
| 345 | mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100); | 454 | mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100); |
| 346 | 455 | ||
| 347 | mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); | 456 | mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); |
| 348 | 457 | ||
| @@ -366,6 +475,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, | |||
| 366 | return {}; | 475 | return {}; |
| 367 | ASSERT(*offset > 0); | 476 | ASSERT(*offset > 0); |
| 368 | 477 | ||
| 478 | Key128 key_temp{}; | ||
| 369 | std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); | 479 | std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); |
| 370 | 480 | ||
| 371 | return std::make_pair(rights_id, key_temp); | 481 | return std::make_pair(rights_id, key_temp); |
| @@ -450,6 +560,8 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { | |||
| 450 | 560 | ||
| 451 | const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); | 561 | const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); |
| 452 | encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); | 562 | encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); |
| 563 | } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { | ||
| 564 | eticket_extended_kek = Common::HexStringToArray<576>(out[1]); | ||
| 453 | } else { | 565 | } else { |
| 454 | for (const auto& kv : KEYS_VARIABLE_LENGTH) { | 566 | for (const auto& kv : KEYS_VARIABLE_LENGTH) { |
| 455 | if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) | 567 | if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) |
| @@ -862,20 +974,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { | |||
| 862 | // Titlekeys | 974 | // Titlekeys |
| 863 | data.DecryptProdInfo(GetBISKey(0)); | 975 | data.DecryptProdInfo(GetBISKey(0)); |
| 864 | 976 | ||
| 865 | const auto eticket_extended_kek = data.GetETicketExtendedKek(); | 977 | eticket_extended_kek = data.GetETicketExtendedKek(); |
| 978 | WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek); | ||
| 979 | PopulateTickets(); | ||
| 980 | } | ||
| 866 | 981 | ||
| 867 | std::vector<u8> extended_iv(0x10); | 982 | void KeyManager::PopulateTickets() { |
| 868 | std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size()); | 983 | const auto rsa_key = GetETicketRSAKey(); |
| 869 | std::array<u8, 0x230> extended_dec{}; | ||
| 870 | AESCipher<Key128> rsa_1(eticket_final, Mode::CTR); | ||
| 871 | rsa_1.SetIV(extended_iv); | ||
| 872 | rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, | ||
| 873 | extended_dec.data(), Op::Decrypt); | ||
| 874 | 984 | ||
| 875 | RSAKeyPair<2048> rsa_key{}; | 985 | if (rsa_key == RSAKeyPair<2048>{}) |
| 876 | std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); | 986 | return; |
| 877 | std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); | 987 | |
| 878 | std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); | 988 | if (!common_tickets.empty() && !personal_tickets.empty()) |
| 989 | return; | ||
| 879 | 990 | ||
| 880 | const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + | 991 | const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + |
| 881 | "/system/save/80000000000000e1", | 992 | "/system/save/80000000000000e1", |
| @@ -886,19 +997,41 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { | |||
| 886 | 997 | ||
| 887 | const auto blob2 = GetTicketblob(save2); | 998 | const auto blob2 = GetTicketblob(save2); |
| 888 | auto res = GetTicketblob(save1); | 999 | auto res = GetTicketblob(save1); |
| 1000 | const auto idx = res.size(); | ||
| 889 | res.insert(res.end(), blob2.begin(), blob2.end()); | 1001 | res.insert(res.end(), blob2.begin(), blob2.end()); |
| 890 | 1002 | ||
| 891 | for (const auto& raw : res) { | 1003 | for (std::size_t i = 0; i < res.size(); ++i) { |
| 892 | const auto pair = ParseTicket(raw, rsa_key); | 1004 | const auto common = i < idx; |
| 1005 | const auto pair = ParseTicket(res[i], rsa_key); | ||
| 893 | if (!pair) | 1006 | if (!pair) |
| 894 | continue; | 1007 | continue; |
| 895 | const auto& [rid, key] = *pair; | 1008 | const auto& [rid, key] = *pair; |
| 896 | u128 rights_id; | 1009 | u128 rights_id; |
| 897 | std::memcpy(rights_id.data(), rid.data(), rid.size()); | 1010 | std::memcpy(rights_id.data(), rid.data(), rid.size()); |
| 1011 | |||
| 1012 | if (common) { | ||
| 1013 | common_tickets[rights_id] = res[i]; | ||
| 1014 | } else { | ||
| 1015 | personal_tickets[rights_id] = res[i]; | ||
| 1016 | } | ||
| 1017 | |||
| 898 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | 1018 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); |
| 899 | } | 1019 | } |
| 900 | } | 1020 | } |
| 901 | 1021 | ||
| 1022 | void KeyManager::SynthesizeTickets() { | ||
| 1023 | for (const auto& key : s128_keys) { | ||
| 1024 | if (key.first.type != S128KeyType::Titlekey) { | ||
| 1025 | continue; | ||
| 1026 | } | ||
| 1027 | u128 rights_id{key.first.field1, key.first.field2}; | ||
| 1028 | Key128 rights_id_2; | ||
| 1029 | std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size()); | ||
| 1030 | const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2); | ||
| 1031 | common_tickets.insert_or_assign(rights_id, ticket); | ||
| 1032 | } | ||
| 1033 | } | ||
| 1034 | |||
| 902 | void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { | 1035 | void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { |
| 903 | if (key == Key128{}) | 1036 | if (key == Key128{}) |
| 904 | return; | 1037 | return; |
| @@ -997,6 +1130,46 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { | |||
| 997 | DeriveBase(); | 1130 | DeriveBase(); |
| 998 | } | 1131 | } |
| 999 | 1132 | ||
| 1133 | const std::map<u128, Ticket>& KeyManager::GetCommonTickets() const { | ||
| 1134 | return common_tickets; | ||
| 1135 | } | ||
| 1136 | |||
| 1137 | const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const { | ||
| 1138 | return personal_tickets; | ||
| 1139 | } | ||
| 1140 | |||
| 1141 | bool KeyManager::AddTicketCommon(Ticket raw) { | ||
| 1142 | const auto rsa_key = GetETicketRSAKey(); | ||
| 1143 | if (rsa_key == RSAKeyPair<2048>{}) | ||
| 1144 | return false; | ||
| 1145 | |||
| 1146 | const auto pair = ParseTicket(raw, rsa_key); | ||
| 1147 | if (!pair) | ||
| 1148 | return false; | ||
| 1149 | const auto& [rid, key] = *pair; | ||
| 1150 | u128 rights_id; | ||
| 1151 | std::memcpy(rights_id.data(), rid.data(), rid.size()); | ||
| 1152 | common_tickets[rights_id] = raw; | ||
| 1153 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||
| 1154 | return true; | ||
| 1155 | } | ||
| 1156 | |||
| 1157 | bool KeyManager::AddTicketPersonalized(Ticket raw) { | ||
| 1158 | const auto rsa_key = GetETicketRSAKey(); | ||
| 1159 | if (rsa_key == RSAKeyPair<2048>{}) | ||
| 1160 | return false; | ||
| 1161 | |||
| 1162 | const auto pair = ParseTicket(raw, rsa_key); | ||
| 1163 | if (!pair) | ||
| 1164 | return false; | ||
| 1165 | const auto& [rid, key] = *pair; | ||
| 1166 | u128 rights_id; | ||
| 1167 | std::memcpy(rights_id.data(), rid.data(), rid.size()); | ||
| 1168 | common_tickets[rights_id] = raw; | ||
| 1169 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||
| 1170 | return true; | ||
| 1171 | } | ||
| 1172 | |||
| 1000 | const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { | 1173 | const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { |
| 1001 | {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, | 1174 | {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, |
| 1002 | {"eticket_rsa_kek_source", | 1175 | {"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 @@ | |||
| 9 | #include <optional> | 9 | #include <optional> |
| 10 | #include <string> | 10 | #include <string> |
| 11 | 11 | ||
| 12 | #include <variant> | ||
| 12 | #include <boost/container/flat_map.hpp> | 13 | #include <boost/container/flat_map.hpp> |
| 13 | #include <fmt/format.h> | 14 | #include <fmt/format.h> |
| 15 | #include "common/common_funcs.h" | ||
| 14 | #include "common/common_types.h" | 16 | #include "common/common_types.h" |
| 15 | #include "core/crypto/partition_data_manager.h" | 17 | #include "core/crypto/partition_data_manager.h" |
| 16 | #include "core/file_sys/vfs_types.h" | 18 | #include "core/file_sys/vfs_types.h" |
| @@ -30,7 +32,79 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180; | |||
| 30 | using Key128 = std::array<u8, 0x10>; | 32 | using Key128 = std::array<u8, 0x10>; |
| 31 | using Key256 = std::array<u8, 0x20>; | 33 | using Key256 = std::array<u8, 0x20>; |
| 32 | using SHA256Hash = std::array<u8, 0x20>; | 34 | using SHA256Hash = std::array<u8, 0x20>; |
| 33 | using TicketRaw = std::array<u8, 0x400>; | 35 | |
| 36 | enum class SignatureType { | ||
| 37 | RSA_4096_SHA1 = 0x10000, | ||
| 38 | RSA_2048_SHA1 = 0x10001, | ||
| 39 | ECDSA_SHA1 = 0x10002, | ||
| 40 | RSA_4096_SHA256 = 0x10003, | ||
| 41 | RSA_2048_SHA256 = 0x10004, | ||
| 42 | ECDSA_SHA256 = 0x10005, | ||
| 43 | }; | ||
| 44 | |||
| 45 | u64 GetSignatureTypeDataSize(SignatureType type); | ||
| 46 | u64 GetSignatureTypePaddingSize(SignatureType type); | ||
| 47 | |||
| 48 | enum class TitleKeyType : u8 { | ||
| 49 | Common = 0, | ||
| 50 | Personalized = 1, | ||
| 51 | }; | ||
| 52 | |||
| 53 | struct TicketData { | ||
| 54 | std::array<u8, 0x40> issuer; | ||
| 55 | union { | ||
| 56 | std::array<u8, 0x100> title_key_block; | ||
| 57 | |||
| 58 | struct { | ||
| 59 | Key128 title_key_common; | ||
| 60 | std::array<u8, 0xF0> title_key_common_pad; | ||
| 61 | }; | ||
| 62 | }; | ||
| 63 | |||
| 64 | INSERT_PADDING_BYTES(0x1); | ||
| 65 | TitleKeyType type; | ||
| 66 | INSERT_PADDING_BYTES(0x3); | ||
| 67 | u8 revision; | ||
| 68 | INSERT_PADDING_BYTES(0xA); | ||
| 69 | u64 ticket_id; | ||
| 70 | u64 device_id; | ||
| 71 | std::array<u8, 0x10> rights_id; | ||
| 72 | u32 account_id; | ||
| 73 | INSERT_PADDING_BYTES(0x14C); | ||
| 74 | }; | ||
| 75 | static_assert(sizeof(TicketData) == 0x2C0, "TicketData has incorrect size."); | ||
| 76 | |||
| 77 | struct RSA4096Ticket { | ||
| 78 | SignatureType sig_type; | ||
| 79 | std::array<u8, 0x200> sig_data; | ||
| 80 | INSERT_PADDING_BYTES(0x3C); | ||
| 81 | TicketData data; | ||
| 82 | }; | ||
| 83 | |||
| 84 | struct RSA2048Ticket { | ||
| 85 | SignatureType sig_type; | ||
| 86 | std::array<u8, 0x100> sig_data; | ||
| 87 | INSERT_PADDING_BYTES(0x3C); | ||
| 88 | TicketData data; | ||
| 89 | }; | ||
| 90 | |||
| 91 | struct ECDSATicket { | ||
| 92 | SignatureType sig_type; | ||
| 93 | std::array<u8, 0x3C> sig_data; | ||
| 94 | INSERT_PADDING_BYTES(0x40); | ||
| 95 | TicketData data; | ||
| 96 | }; | ||
| 97 | |||
| 98 | struct Ticket { | ||
| 99 | std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data; | ||
| 100 | |||
| 101 | SignatureType GetSignatureType() const; | ||
| 102 | TicketData& GetData(); | ||
| 103 | const TicketData& GetData() const; | ||
| 104 | u64 GetSize() const; | ||
| 105 | |||
| 106 | static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id); | ||
| 107 | }; | ||
| 34 | 108 | ||
| 35 | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); | 109 | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); |
| 36 | static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big."); | 110 | static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big."); |
| @@ -43,6 +117,19 @@ struct RSAKeyPair { | |||
| 43 | std::array<u8, 4> exponent; | 117 | std::array<u8, 4> exponent; |
| 44 | }; | 118 | }; |
| 45 | 119 | ||
| 120 | template <size_t bit_size, size_t byte_size> | ||
| 121 | bool operator==(const RSAKeyPair<bit_size, byte_size>& lhs, | ||
| 122 | const RSAKeyPair<bit_size, byte_size>& rhs) { | ||
| 123 | return std::tie(lhs.encryption_key, lhs.decryption_key, lhs.modulus, lhs.exponent) == | ||
| 124 | std::tie(rhs.encryption_key, rhs.decryption_key, rhs.modulus, rhs.exponent); | ||
| 125 | } | ||
| 126 | |||
| 127 | template <size_t bit_size, size_t byte_size> | ||
| 128 | bool operator!=(const RSAKeyPair<bit_size, byte_size>& lhs, | ||
| 129 | const RSAKeyPair<bit_size, byte_size>& rhs) { | ||
| 130 | return !(lhs == rhs); | ||
| 131 | } | ||
| 132 | |||
| 46 | enum class KeyCategory : u8 { | 133 | enum class KeyCategory : u8 { |
| 47 | Standard, | 134 | Standard, |
| 48 | Title, | 135 | Title, |
| @@ -151,22 +238,35 @@ public: | |||
| 151 | 238 | ||
| 152 | static bool KeyFileExists(bool title); | 239 | static bool KeyFileExists(bool title); |
| 153 | 240 | ||
| 154 | // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save | 241 | // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system |
| 155 | // 8*43 and the private file to exist. | 242 | // save 8*43 and the private file to exist. |
| 156 | void DeriveSDSeedLazy(); | 243 | void DeriveSDSeedLazy(); |
| 157 | 244 | ||
| 158 | bool BaseDeriveNecessary() const; | 245 | bool BaseDeriveNecessary() const; |
| 159 | void DeriveBase(); | 246 | void DeriveBase(); |
| 160 | void DeriveETicket(PartitionDataManager& data); | 247 | void DeriveETicket(PartitionDataManager& data); |
| 248 | void PopulateTickets(); | ||
| 249 | void SynthesizeTickets(); | ||
| 161 | 250 | ||
| 162 | void PopulateFromPartitionData(PartitionDataManager& data); | 251 | void PopulateFromPartitionData(PartitionDataManager& data); |
| 163 | 252 | ||
| 253 | const std::map<u128, Ticket>& GetCommonTickets() const; | ||
| 254 | const std::map<u128, Ticket>& GetPersonalizedTickets() const; | ||
| 255 | |||
| 256 | bool AddTicketCommon(Ticket raw); | ||
| 257 | bool AddTicketPersonalized(Ticket raw); | ||
| 258 | |||
| 164 | private: | 259 | private: |
| 165 | std::map<KeyIndex<S128KeyType>, Key128> s128_keys; | 260 | std::map<KeyIndex<S128KeyType>, Key128> s128_keys; |
| 166 | std::map<KeyIndex<S256KeyType>, Key256> s256_keys; | 261 | std::map<KeyIndex<S256KeyType>, Key256> s256_keys; |
| 167 | 262 | ||
| 263 | // Map from rights ID to ticket | ||
| 264 | std::map<u128, Ticket> common_tickets; | ||
| 265 | std::map<u128, Ticket> personal_tickets; | ||
| 266 | |||
| 168 | std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; | 267 | std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; |
| 169 | std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; | 268 | std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; |
| 269 | std::array<u8, 576> eticket_extended_kek{}; | ||
| 170 | 270 | ||
| 171 | bool dev_mode; | 271 | bool dev_mode; |
| 172 | void LoadFromFile(const std::string& filename, bool is_title_keys); | 272 | void LoadFromFile(const std::string& filename, bool is_title_keys); |
| @@ -178,6 +278,8 @@ private: | |||
| 178 | 278 | ||
| 179 | void DeriveGeneralPurposeKeys(std::size_t crypto_revision); | 279 | void DeriveGeneralPurposeKeys(std::size_t crypto_revision); |
| 180 | 280 | ||
| 281 | RSAKeyPair<2048> GetETicketRSAKey() const; | ||
| 282 | |||
| 181 | void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); | 283 | void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); |
| 182 | void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); | 284 | void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); |
| 183 | 285 | ||
| @@ -195,11 +297,11 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo | |||
| 195 | std::optional<Key128> DeriveSDSeed(); | 297 | std::optional<Key128> DeriveSDSeed(); |
| 196 | Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); | 298 | Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); |
| 197 | 299 | ||
| 198 | std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save); | 300 | std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save); |
| 199 | 301 | ||
| 200 | // Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset | 302 | // Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority |
| 201 | // 0x140-0x144 is zero) | 303 | // (offset 0x140-0x144 is zero) |
| 202 | std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, | 304 | std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, |
| 203 | const RSAKeyPair<2048>& eticket_extended_key); | 305 | const RSAKeyPair<2048>& eticket_extended_key); |
| 204 | 306 | ||
| 205 | } // namespace Core::Crypto | 307 | } // 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 @@ | |||
| 1 | // Copyright 2019 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "core/file_sys/system_archive/mii_model.h" | ||
| 6 | #include "core/file_sys/vfs_vector.h" | ||
| 7 | |||
| 8 | namespace FileSys::SystemArchive { | ||
| 9 | |||
| 10 | namespace MiiModelData { | ||
| 11 | |||
| 12 | constexpr std::array<u8, 0x10> NFTR_STANDARD{'N', 'F', 'T', 'R', 0x01, 0x00, 0x00, 0x00, | ||
| 13 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| 14 | constexpr std::array<u8, 0x10> NFSR_STANDARD{'N', 'F', 'S', 'R', 0x01, 0x00, 0x00, 0x00, | ||
| 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| 16 | |||
| 17 | constexpr auto TEXTURE_LOW_LINEAR = NFTR_STANDARD; | ||
| 18 | constexpr auto TEXTURE_LOW_SRGB = NFTR_STANDARD; | ||
| 19 | constexpr auto TEXTURE_MID_LINEAR = NFTR_STANDARD; | ||
| 20 | constexpr auto TEXTURE_MID_SRGB = NFTR_STANDARD; | ||
| 21 | constexpr auto SHAPE_HIGH = NFSR_STANDARD; | ||
| 22 | constexpr auto SHAPE_MID = NFSR_STANDARD; | ||
| 23 | |||
| 24 | } // namespace MiiModelData | ||
| 25 | |||
| 26 | VirtualDir MiiModel() { | ||
| 27 | auto out = std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, | ||
| 28 | std::vector<VirtualDir>{}, "data"); | ||
| 29 | |||
| 30 | out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_LINEAR.size()>>( | ||
| 31 | MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat")); | ||
| 32 | out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_SRGB.size()>>( | ||
| 33 | MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat")); | ||
| 34 | out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_LINEAR.size()>>( | ||
| 35 | MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat")); | ||
| 36 | out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_SRGB.size()>>( | ||
| 37 | MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat")); | ||
| 38 | out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_HIGH.size()>>( | ||
| 39 | MiiModelData::SHAPE_HIGH, "ShapeHigh.dat")); | ||
| 40 | out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_MID.size()>>( | ||
| 41 | MiiModelData::SHAPE_MID, "ShapeMid.dat")); | ||
| 42 | |||
| 43 | return std::move(out); | ||
| 44 | } | ||
| 45 | |||
| 46 | } // 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 @@ | |||
| 1 | // Copyright 2019 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "core/file_sys/vfs_types.h" | ||
| 8 | |||
| 9 | namespace FileSys::SystemArchive { | ||
| 10 | |||
| 11 | VirtualDir MiiModel(); | ||
| 12 | |||
| 13 | } // 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 @@ | |||
| 4 | 4 | ||
| 5 | #include "common/logging/log.h" | 5 | #include "common/logging/log.h" |
| 6 | #include "core/file_sys/romfs.h" | 6 | #include "core/file_sys/romfs.h" |
| 7 | #include "core/file_sys/system_archive/mii_model.h" | ||
| 7 | #include "core/file_sys/system_archive/ng_word.h" | 8 | #include "core/file_sys/system_archive/ng_word.h" |
| 8 | #include "core/file_sys/system_archive/system_archive.h" | 9 | #include "core/file_sys/system_archive/system_archive.h" |
| 9 | #include "core/file_sys/system_archive/system_version.h" | 10 | #include "core/file_sys/system_archive/system_version.h" |
| @@ -24,7 +25,7 @@ struct SystemArchiveDescriptor { | |||
| 24 | constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{ | 25 | constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{ |
| 25 | {0x0100000000000800, "CertStore", nullptr}, | 26 | {0x0100000000000800, "CertStore", nullptr}, |
| 26 | {0x0100000000000801, "ErrorMessage", nullptr}, | 27 | {0x0100000000000801, "ErrorMessage", nullptr}, |
| 27 | {0x0100000000000802, "MiiModel", nullptr}, | 28 | {0x0100000000000802, "MiiModel", &MiiModel}, |
| 28 | {0x0100000000000803, "BrowserDll", nullptr}, | 29 | {0x0100000000000803, "BrowserDll", nullptr}, |
| 29 | {0x0100000000000804, "Help", nullptr}, | 30 | {0x0100000000000804, "Help", nullptr}, |
| 30 | {0x0100000000000805, "SharedFont", nullptr}, | 31 | {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) { | |||
| 296 | } | 296 | } |
| 297 | 297 | ||
| 298 | ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { | 298 | ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { |
| 299 | const auto end_addr = target + size; | ||
| 300 | const auto last_addr = end_addr - 1; | ||
| 301 | VAddr cur_addr = target; | ||
| 302 | |||
| 303 | ResultCode result = RESULT_SUCCESS; | ||
| 304 | |||
| 305 | // Check how much memory we've already mapped. | 299 | // Check how much memory we've already mapped. |
| 306 | const auto mapped_size_result = SizeOfAllocatedVMAsInRange(target, size); | 300 | const auto mapped_size_result = SizeOfAllocatedVMAsInRange(target, size); |
| 307 | if (mapped_size_result.Failed()) { | 301 | if (mapped_size_result.Failed()) { |
| @@ -324,13 +318,16 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { | |||
| 324 | 318 | ||
| 325 | // Keep track of the memory regions we unmap. | 319 | // Keep track of the memory regions we unmap. |
| 326 | std::vector<std::pair<u64, u64>> mapped_regions; | 320 | std::vector<std::pair<u64, u64>> mapped_regions; |
| 321 | ResultCode result = RESULT_SUCCESS; | ||
| 327 | 322 | ||
| 328 | // Iterate, trying to map memory. | 323 | // Iterate, trying to map memory. |
| 329 | { | 324 | { |
| 330 | cur_addr = target; | 325 | const auto end_addr = target + size; |
| 326 | const auto last_addr = end_addr - 1; | ||
| 327 | VAddr cur_addr = target; | ||
| 331 | 328 | ||
| 332 | auto iter = FindVMA(target); | 329 | auto iter = FindVMA(target); |
| 333 | ASSERT_MSG(iter != vma_map.end(), "MapPhysicalMemory iter != end"); | 330 | ASSERT(iter != vma_map.end()); |
| 334 | 331 | ||
| 335 | while (true) { | 332 | while (true) { |
| 336 | const auto& vma = iter->second; | 333 | const auto& vma = iter->second; |
| @@ -342,7 +339,7 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { | |||
| 342 | const auto map_size = std::min(end_addr - cur_addr, vma_end - cur_addr); | 339 | const auto map_size = std::min(end_addr - cur_addr, vma_end - cur_addr); |
| 343 | if (vma.state == MemoryState::Unmapped) { | 340 | if (vma.state == MemoryState::Unmapped) { |
| 344 | const auto map_res = | 341 | const auto map_res = |
| 345 | MapMemoryBlock(cur_addr, std::make_shared<PhysicalMemory>(map_size, 0), 0, | 342 | MapMemoryBlock(cur_addr, std::make_shared<PhysicalMemory>(map_size), 0, |
| 346 | map_size, MemoryState::Heap, VMAPermission::ReadWrite); | 343 | map_size, MemoryState::Heap, VMAPermission::ReadWrite); |
| 347 | result = map_res.Code(); | 344 | result = map_res.Code(); |
| 348 | if (result.IsError()) { | 345 | if (result.IsError()) { |
| @@ -360,7 +357,7 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { | |||
| 360 | // Advance to the next block. | 357 | // Advance to the next block. |
| 361 | cur_addr = vma_end; | 358 | cur_addr = vma_end; |
| 362 | iter = FindVMA(cur_addr); | 359 | iter = FindVMA(cur_addr); |
| 363 | ASSERT_MSG(iter != vma_map.end(), "MapPhysicalMemory iter != end"); | 360 | ASSERT(iter != vma_map.end()); |
| 364 | } | 361 | } |
| 365 | } | 362 | } |
| 366 | 363 | ||
| @@ -368,7 +365,7 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { | |||
| 368 | if (result.IsError()) { | 365 | if (result.IsError()) { |
| 369 | for (const auto [unmap_address, unmap_size] : mapped_regions) { | 366 | for (const auto [unmap_address, unmap_size] : mapped_regions) { |
| 370 | ASSERT_MSG(UnmapRange(unmap_address, unmap_size).IsSuccess(), | 367 | ASSERT_MSG(UnmapRange(unmap_address, unmap_size).IsSuccess(), |
| 371 | "MapPhysicalMemory un-map on error"); | 368 | "Failed to unmap memory range."); |
| 372 | } | 369 | } |
| 373 | 370 | ||
| 374 | return result; | 371 | return result; |
| @@ -381,12 +378,6 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { | |||
| 381 | } | 378 | } |
| 382 | 379 | ||
| 383 | ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { | 380 | ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { |
| 384 | const auto end_addr = target + size; | ||
| 385 | const auto last_addr = end_addr - 1; | ||
| 386 | VAddr cur_addr = target; | ||
| 387 | |||
| 388 | ResultCode result = RESULT_SUCCESS; | ||
| 389 | |||
| 390 | // Check how much memory is currently mapped. | 381 | // Check how much memory is currently mapped. |
| 391 | const auto mapped_size_result = SizeOfUnmappablePhysicalMemoryInRange(target, size); | 382 | const auto mapped_size_result = SizeOfUnmappablePhysicalMemoryInRange(target, size); |
| 392 | if (mapped_size_result.Failed()) { | 383 | if (mapped_size_result.Failed()) { |
| @@ -401,13 +392,16 @@ ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { | |||
| 401 | 392 | ||
| 402 | // Keep track of the memory regions we unmap. | 393 | // Keep track of the memory regions we unmap. |
| 403 | std::vector<std::pair<u64, u64>> unmapped_regions; | 394 | std::vector<std::pair<u64, u64>> unmapped_regions; |
| 395 | ResultCode result = RESULT_SUCCESS; | ||
| 404 | 396 | ||
| 405 | // Try to unmap regions. | 397 | // Try to unmap regions. |
| 406 | { | 398 | { |
| 407 | cur_addr = target; | 399 | const auto end_addr = target + size; |
| 400 | const auto last_addr = end_addr - 1; | ||
| 401 | VAddr cur_addr = target; | ||
| 408 | 402 | ||
| 409 | auto iter = FindVMA(target); | 403 | auto iter = FindVMA(target); |
| 410 | ASSERT_MSG(iter != vma_map.end(), "UnmapPhysicalMemory iter != end"); | 404 | ASSERT(iter != vma_map.end()); |
| 411 | 405 | ||
| 412 | while (true) { | 406 | while (true) { |
| 413 | const auto& vma = iter->second; | 407 | const auto& vma = iter->second; |
| @@ -434,7 +428,7 @@ ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { | |||
| 434 | // Advance to the next block. | 428 | // Advance to the next block. |
| 435 | cur_addr = vma_end; | 429 | cur_addr = vma_end; |
| 436 | iter = FindVMA(cur_addr); | 430 | iter = FindVMA(cur_addr); |
| 437 | ASSERT_MSG(iter != vma_map.end(), "UnmapPhysicalMemory iter != end"); | 431 | ASSERT(iter != vma_map.end()); |
| 438 | } | 432 | } |
| 439 | } | 433 | } |
| 440 | 434 | ||
| @@ -443,10 +437,12 @@ ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { | |||
| 443 | if (result.IsError()) { | 437 | if (result.IsError()) { |
| 444 | for (const auto [map_address, map_size] : unmapped_regions) { | 438 | for (const auto [map_address, map_size] : unmapped_regions) { |
| 445 | const auto remap_res = | 439 | const auto remap_res = |
| 446 | MapMemoryBlock(map_address, std::make_shared<PhysicalMemory>(map_size, 0), 0, | 440 | MapMemoryBlock(map_address, std::make_shared<PhysicalMemory>(map_size), 0, map_size, |
| 447 | map_size, MemoryState::Heap, VMAPermission::None); | 441 | MemoryState::Heap, VMAPermission::None); |
| 448 | ASSERT_MSG(remap_res.Succeeded(), "UnmapPhysicalMemory re-map on error"); | 442 | ASSERT_MSG(remap_res.Succeeded(), "Failed to remap a memory block."); |
| 449 | } | 443 | } |
| 444 | |||
| 445 | return result; | ||
| 450 | } | 446 | } |
| 451 | 447 | ||
| 452 | // Update mapped amount | 448 | // Update mapped amount |
| @@ -757,20 +753,26 @@ void VMManager::MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryAre | |||
| 757 | // Always merge allocated memory blocks, even when they don't share the same backing block. | 753 | // Always merge allocated memory blocks, even when they don't share the same backing block. |
| 758 | if (left.type == VMAType::AllocatedMemoryBlock && | 754 | if (left.type == VMAType::AllocatedMemoryBlock && |
| 759 | (left.backing_block != right.backing_block || left.offset + left.size != right.offset)) { | 755 | (left.backing_block != right.backing_block || left.offset + left.size != right.offset)) { |
| 756 | const auto right_begin = right.backing_block->begin() + right.offset; | ||
| 757 | const auto right_end = right_begin + right.size; | ||
| 758 | |||
| 760 | // Check if we can save work. | 759 | // Check if we can save work. |
| 761 | if (left.offset == 0 && left.size == left.backing_block->size()) { | 760 | if (left.offset == 0 && left.size == left.backing_block->size()) { |
| 762 | // Fast case: left is an entire backing block. | 761 | // Fast case: left is an entire backing block. |
| 763 | left.backing_block->insert(left.backing_block->end(), | 762 | left.backing_block->insert(left.backing_block->end(), right_begin, right_end); |
| 764 | right.backing_block->begin() + right.offset, | ||
| 765 | right.backing_block->begin() + right.offset + right.size); | ||
| 766 | } else { | 763 | } else { |
| 767 | // Slow case: make a new memory block for left and right. | 764 | // Slow case: make a new memory block for left and right. |
| 765 | const auto left_begin = left.backing_block->begin() + left.offset; | ||
| 766 | const auto left_end = left_begin + left.size; | ||
| 767 | const auto left_size = static_cast<std::size_t>(std::distance(left_begin, left_end)); | ||
| 768 | const auto right_size = static_cast<std::size_t>(std::distance(right_begin, right_end)); | ||
| 769 | |||
| 768 | auto new_memory = std::make_shared<PhysicalMemory>(); | 770 | auto new_memory = std::make_shared<PhysicalMemory>(); |
| 769 | new_memory->insert(new_memory->end(), left.backing_block->begin() + left.offset, | 771 | new_memory->reserve(left_size + right_size); |
| 770 | left.backing_block->begin() + left.offset + left.size); | 772 | new_memory->insert(new_memory->end(), left_begin, left_end); |
| 771 | new_memory->insert(new_memory->end(), right.backing_block->begin() + right.offset, | 773 | new_memory->insert(new_memory->end(), right_begin, right_end); |
| 772 | right.backing_block->begin() + right.offset + right.size); | 774 | |
| 773 | left.backing_block = new_memory; | 775 | left.backing_block = std::move(new_memory); |
| 774 | left.offset = 0; | 776 | left.offset = 0; |
| 775 | } | 777 | } |
| 776 | 778 | ||
| @@ -965,7 +967,7 @@ ResultVal<std::size_t> VMManager::SizeOfAllocatedVMAsInRange(VAddr address, | |||
| 965 | 967 | ||
| 966 | VAddr cur_addr = address; | 968 | VAddr cur_addr = address; |
| 967 | auto iter = FindVMA(cur_addr); | 969 | auto iter = FindVMA(cur_addr); |
| 968 | ASSERT_MSG(iter != vma_map.end(), "SizeOfAllocatedVMAsInRange iter != end"); | 970 | ASSERT(iter != vma_map.end()); |
| 969 | 971 | ||
| 970 | while (true) { | 972 | while (true) { |
| 971 | const auto& vma = iter->second; | 973 | const auto& vma = iter->second; |
| @@ -986,7 +988,7 @@ ResultVal<std::size_t> VMManager::SizeOfAllocatedVMAsInRange(VAddr address, | |||
| 986 | // Advance to the next block. | 988 | // Advance to the next block. |
| 987 | cur_addr = vma_end; | 989 | cur_addr = vma_end; |
| 988 | iter = std::next(iter); | 990 | iter = std::next(iter); |
| 989 | ASSERT_MSG(iter != vma_map.end(), "SizeOfAllocatedVMAsInRange iter != end"); | 991 | ASSERT(iter != vma_map.end()); |
| 990 | } | 992 | } |
| 991 | 993 | ||
| 992 | return MakeResult(mapped_size); | 994 | return MakeResult(mapped_size); |
| @@ -1000,7 +1002,7 @@ ResultVal<std::size_t> VMManager::SizeOfUnmappablePhysicalMemoryInRange(VAddr ad | |||
| 1000 | 1002 | ||
| 1001 | VAddr cur_addr = address; | 1003 | VAddr cur_addr = address; |
| 1002 | auto iter = FindVMA(cur_addr); | 1004 | auto iter = FindVMA(cur_addr); |
| 1003 | ASSERT_MSG(iter != vma_map.end(), "SizeOfUnmappablePhysicalMemoryInRange iter != end"); | 1005 | ASSERT(iter != vma_map.end()); |
| 1004 | 1006 | ||
| 1005 | while (true) { | 1007 | while (true) { |
| 1006 | const auto& vma = iter->second; | 1008 | const auto& vma = iter->second; |
| @@ -1029,7 +1031,7 @@ ResultVal<std::size_t> VMManager::SizeOfUnmappablePhysicalMemoryInRange(VAddr ad | |||
| 1029 | // Advance to the next block. | 1031 | // Advance to the next block. |
| 1030 | cur_addr = vma_end; | 1032 | cur_addr = vma_end; |
| 1031 | iter = std::next(iter); | 1033 | iter = std::next(iter); |
| 1032 | ASSERT_MSG(iter != vma_map.end(), "SizeOfUnmappablePhysicalMemoryInRange iter != end"); | 1034 | ASSERT(iter != vma_map.end()); |
| 1033 | } | 1035 | } |
| 1034 | 1036 | ||
| 1035 | return MakeResult(mapped_size); | 1037 | 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: | |||
| 454 | 454 | ||
| 455 | /// Maps memory at a given address. | 455 | /// Maps memory at a given address. |
| 456 | /// | 456 | /// |
| 457 | /// @param addr The virtual address to map memory at. | 457 | /// @param target The virtual address to map memory at. |
| 458 | /// @param size The amount of memory to map. | 458 | /// @param size The amount of memory to map. |
| 459 | /// | 459 | /// |
| 460 | /// @note The destination address must lie within the Map region. | 460 | /// @note The destination address must lie within the Map region. |
| 461 | /// | 461 | /// |
| @@ -468,8 +468,8 @@ public: | |||
| 468 | 468 | ||
| 469 | /// Unmaps memory at a given address. | 469 | /// Unmaps memory at a given address. |
| 470 | /// | 470 | /// |
| 471 | /// @param addr The virtual address to unmap memory at. | 471 | /// @param target The virtual address to unmap memory at. |
| 472 | /// @param size The amount of memory to unmap. | 472 | /// @param size The amount of memory to unmap. |
| 473 | /// | 473 | /// |
| 474 | /// @note The destination address must lie within the Map region. | 474 | /// @note The destination address must lie within the Map region. |
| 475 | /// | 475 | /// |
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 { | |||
| 56 | }; | 56 | }; |
| 57 | static_assert(sizeof(LaunchParameters) == 0x88); | 57 | static_assert(sizeof(LaunchParameters) == 0x88); |
| 58 | 58 | ||
| 59 | IWindowController::IWindowController() : ServiceFramework("IWindowController") { | 59 | IWindowController::IWindowController(Core::System& system_) |
| 60 | : ServiceFramework("IWindowController"), system{system_} { | ||
| 60 | // clang-format off | 61 | // clang-format off |
| 61 | static const FunctionInfo functions[] = { | 62 | static const FunctionInfo functions[] = { |
| 62 | {0, nullptr, "CreateWindow"}, | 63 | {0, nullptr, "CreateWindow"}, |
| @@ -75,7 +76,7 @@ IWindowController::IWindowController() : ServiceFramework("IWindowController") { | |||
| 75 | IWindowController::~IWindowController() = default; | 76 | IWindowController::~IWindowController() = default; |
| 76 | 77 | ||
| 77 | void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) { | 78 | void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) { |
| 78 | const u64 process_id = Core::System::GetInstance().Kernel().CurrentProcess()->GetProcessID(); | 79 | const u64 process_id = system.CurrentProcess()->GetProcessID(); |
| 79 | 80 | ||
| 80 | LOG_DEBUG(Service_AM, "called. Process ID=0x{:016X}", process_id); | 81 | LOG_DEBUG(Service_AM, "called. Process ID=0x{:016X}", process_id); |
| 81 | 82 | ||
| @@ -231,8 +232,9 @@ IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} { | |||
| 231 | 232 | ||
| 232 | IDebugFunctions::~IDebugFunctions() = default; | 233 | IDebugFunctions::~IDebugFunctions() = default; |
| 233 | 234 | ||
| 234 | ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) | 235 | ISelfController::ISelfController(Core::System& system_, |
| 235 | : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger)) { | 236 | std::shared_ptr<NVFlinger::NVFlinger> nvflinger_) |
| 237 | : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger_)) { | ||
| 236 | // clang-format off | 238 | // clang-format off |
| 237 | static const FunctionInfo functions[] = { | 239 | static const FunctionInfo functions[] = { |
| 238 | {0, nullptr, "Exit"}, | 240 | {0, nullptr, "Exit"}, |
| @@ -280,7 +282,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger | |||
| 280 | 282 | ||
| 281 | RegisterHandlers(functions); | 283 | RegisterHandlers(functions); |
| 282 | 284 | ||
| 283 | auto& kernel = Core::System::GetInstance().Kernel(); | 285 | auto& kernel = system_.Kernel(); |
| 284 | launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, | 286 | launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, |
| 285 | "ISelfController:LaunchableEvent"); | 287 | "ISelfController:LaunchableEvent"); |
| 286 | 288 | ||
| @@ -501,8 +503,7 @@ void ISelfController::GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequest | |||
| 501 | rb.PushCopyObjects(accumulated_suspended_tick_changed_event.readable); | 503 | rb.PushCopyObjects(accumulated_suspended_tick_changed_event.readable); |
| 502 | } | 504 | } |
| 503 | 505 | ||
| 504 | AppletMessageQueue::AppletMessageQueue() { | 506 | AppletMessageQueue::AppletMessageQueue(Kernel::KernelCore& kernel) { |
| 505 | auto& kernel = Core::System::GetInstance().Kernel(); | ||
| 506 | on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, | 507 | on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, |
| 507 | "AMMessageQueue:OnMessageRecieved"); | 508 | "AMMessageQueue:OnMessageRecieved"); |
| 508 | on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair( | 509 | on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair( |
| @@ -937,9 +938,8 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) { | |||
| 937 | rb.Push(RESULT_SUCCESS); | 938 | rb.Push(RESULT_SUCCESS); |
| 938 | } | 939 | } |
| 939 | 940 | ||
| 940 | ILibraryAppletCreator::ILibraryAppletCreator(u64 current_process_title_id) | 941 | ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_) |
| 941 | : ServiceFramework("ILibraryAppletCreator"), | 942 | : ServiceFramework("ILibraryAppletCreator"), system{system_} { |
| 942 | current_process_title_id(current_process_title_id) { | ||
| 943 | static const FunctionInfo functions[] = { | 943 | static const FunctionInfo functions[] = { |
| 944 | {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"}, | 944 | {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"}, |
| 945 | {1, nullptr, "TerminateAllLibraryApplets"}, | 945 | {1, nullptr, "TerminateAllLibraryApplets"}, |
| @@ -961,8 +961,8 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) | |||
| 961 | LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", | 961 | LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", |
| 962 | static_cast<u32>(applet_id), applet_mode); | 962 | static_cast<u32>(applet_id), applet_mode); |
| 963 | 963 | ||
| 964 | const auto& applet_manager{Core::System::GetInstance().GetAppletManager()}; | 964 | const auto& applet_manager{system.GetAppletManager()}; |
| 965 | const auto applet = applet_manager.GetApplet(applet_id, current_process_title_id); | 965 | const auto applet = applet_manager.GetApplet(applet_id); |
| 966 | 966 | ||
| 967 | if (applet == nullptr) { | 967 | if (applet == nullptr) { |
| 968 | LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id)); | 968 | LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id)); |
| @@ -999,8 +999,7 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex | |||
| 999 | const auto handle{rp.Pop<Kernel::Handle>()}; | 999 | const auto handle{rp.Pop<Kernel::Handle>()}; |
| 1000 | 1000 | ||
| 1001 | const auto transfer_mem = | 1001 | const auto transfer_mem = |
| 1002 | Core::System::GetInstance().CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>( | 1002 | system.CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>(handle); |
| 1003 | handle); | ||
| 1004 | 1003 | ||
| 1005 | if (transfer_mem == nullptr) { | 1004 | if (transfer_mem == nullptr) { |
| 1006 | LOG_ERROR(Service_AM, "shared_mem is a nullpr for handle={:08X}", handle); | 1005 | LOG_ERROR(Service_AM, "shared_mem is a nullpr for handle={:08X}", handle); |
| @@ -1018,7 +1017,8 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex | |||
| 1018 | rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory))); | 1017 | rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory))); |
| 1019 | } | 1018 | } |
| 1020 | 1019 | ||
| 1021 | IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") { | 1020 | IApplicationFunctions::IApplicationFunctions(Core::System& system_) |
| 1021 | : ServiceFramework("IApplicationFunctions"), system{system_} { | ||
| 1022 | // clang-format off | 1022 | // clang-format off |
| 1023 | static const FunctionInfo functions[] = { | 1023 | static const FunctionInfo functions[] = { |
| 1024 | {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"}, | 1024 | {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"}, |
| @@ -1057,6 +1057,7 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF | |||
| 1057 | {120, nullptr, "ExecuteProgram"}, | 1057 | {120, nullptr, "ExecuteProgram"}, |
| 1058 | {121, nullptr, "ClearUserChannel"}, | 1058 | {121, nullptr, "ClearUserChannel"}, |
| 1059 | {122, nullptr, "UnpopToUserChannel"}, | 1059 | {122, nullptr, "UnpopToUserChannel"}, |
| 1060 | {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"}, | ||
| 1060 | {500, nullptr, "StartContinuousRecordingFlushForDebug"}, | 1061 | {500, nullptr, "StartContinuousRecordingFlushForDebug"}, |
| 1061 | {1000, nullptr, "CreateMovieMaker"}, | 1062 | {1000, nullptr, "CreateMovieMaker"}, |
| 1062 | {1001, nullptr, "PrepareForJit"}, | 1063 | {1001, nullptr, "PrepareForJit"}, |
| @@ -1064,6 +1065,10 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF | |||
| 1064 | // clang-format on | 1065 | // clang-format on |
| 1065 | 1066 | ||
| 1066 | RegisterHandlers(functions); | 1067 | RegisterHandlers(functions); |
| 1068 | |||
| 1069 | auto& kernel = Core::System::GetInstance().Kernel(); | ||
| 1070 | gpu_error_detected_event = Kernel::WritableEvent::CreateEventPair( | ||
| 1071 | kernel, Kernel::ResetType::Manual, "IApplicationFunctions:GpuErrorDetectedSystemEvent"); | ||
| 1067 | } | 1072 | } |
| 1068 | 1073 | ||
| 1069 | IApplicationFunctions::~IApplicationFunctions() = default; | 1074 | IApplicationFunctions::~IApplicationFunctions() = default; |
| @@ -1175,7 +1180,7 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { | |||
| 1175 | // Get supported languages from NACP, if possible | 1180 | // Get supported languages from NACP, if possible |
| 1176 | // Default to 0 (all languages supported) | 1181 | // Default to 0 (all languages supported) |
| 1177 | u32 supported_languages = 0; | 1182 | u32 supported_languages = 0; |
| 1178 | FileSys::PatchManager pm{Core::System::GetInstance().CurrentProcess()->GetTitleID()}; | 1183 | FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; |
| 1179 | 1184 | ||
| 1180 | const auto res = pm.GetControlMetadata(); | 1185 | const auto res = pm.GetControlMetadata(); |
| 1181 | if (res.first != nullptr) { | 1186 | if (res.first != nullptr) { |
| @@ -1183,8 +1188,8 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { | |||
| 1183 | } | 1188 | } |
| 1184 | 1189 | ||
| 1185 | // Call IApplicationManagerInterface implementation. | 1190 | // Call IApplicationManagerInterface implementation. |
| 1186 | auto& service_manager = Core::System::GetInstance().ServiceManager(); | 1191 | auto& service_manager = system.ServiceManager(); |
| 1187 | auto ns_am2 = service_manager.GetService<Service::NS::NS>("ns:am2"); | 1192 | auto ns_am2 = service_manager.GetService<NS::NS>("ns:am2"); |
| 1188 | auto app_man = ns_am2->GetApplicationManagerInterface(); | 1193 | auto app_man = ns_am2->GetApplicationManagerInterface(); |
| 1189 | 1194 | ||
| 1190 | // Get desired application language | 1195 | // Get desired application language |
| @@ -1256,8 +1261,8 @@ void IApplicationFunctions::ExtendSaveData(Kernel::HLERequestContext& ctx) { | |||
| 1256 | "new_journal={:016X}", | 1261 | "new_journal={:016X}", |
| 1257 | static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size); | 1262 | static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size); |
| 1258 | 1263 | ||
| 1259 | FileSystem::WriteSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id, | 1264 | const auto title_id = system.CurrentProcess()->GetTitleID(); |
| 1260 | {new_normal_size, new_journal_size}); | 1265 | FileSystem::WriteSaveDataSize(type, title_id, user_id, {new_normal_size, new_journal_size}); |
| 1261 | 1266 | ||
| 1262 | IPC::ResponseBuilder rb{ctx, 4}; | 1267 | IPC::ResponseBuilder rb{ctx, 4}; |
| 1263 | rb.Push(RESULT_SUCCESS); | 1268 | rb.Push(RESULT_SUCCESS); |
| @@ -1276,8 +1281,8 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) { | |||
| 1276 | LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type), | 1281 | LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type), |
| 1277 | user_id[1], user_id[0]); | 1282 | user_id[1], user_id[0]); |
| 1278 | 1283 | ||
| 1279 | const auto size = | 1284 | const auto title_id = system.CurrentProcess()->GetTitleID(); |
| 1280 | FileSystem::ReadSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id); | 1285 | const auto size = FileSystem::ReadSaveDataSize(type, title_id, user_id); |
| 1281 | 1286 | ||
| 1282 | IPC::ResponseBuilder rb{ctx, 6}; | 1287 | IPC::ResponseBuilder rb{ctx, 6}; |
| 1283 | rb.Push(RESULT_SUCCESS); | 1288 | rb.Push(RESULT_SUCCESS); |
| @@ -1285,11 +1290,19 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) { | |||
| 1285 | rb.Push(size.journal); | 1290 | rb.Push(size.journal); |
| 1286 | } | 1291 | } |
| 1287 | 1292 | ||
| 1293 | void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) { | ||
| 1294 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 1295 | |||
| 1296 | IPC::ResponseBuilder rb{ctx, 2, 1}; | ||
| 1297 | rb.Push(RESULT_SUCCESS); | ||
| 1298 | rb.PushCopyObjects(gpu_error_detected_event.readable); | ||
| 1299 | } | ||
| 1300 | |||
| 1288 | void InstallInterfaces(SM::ServiceManager& service_manager, | 1301 | void InstallInterfaces(SM::ServiceManager& service_manager, |
| 1289 | std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) { | 1302 | std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) { |
| 1290 | auto message_queue = std::make_shared<AppletMessageQueue>(); | 1303 | auto message_queue = std::make_shared<AppletMessageQueue>(system.Kernel()); |
| 1291 | message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); // Needed on | 1304 | // Needed on game boot |
| 1292 | // game boot | 1305 | message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); |
| 1293 | 1306 | ||
| 1294 | std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager); | 1307 | std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager); |
| 1295 | std::make_shared<AppletOE>(nvflinger, message_queue, system)->InstallAsService(service_manager); | 1308 | 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 @@ | |||
| 10 | #include "core/hle/kernel/writable_event.h" | 10 | #include "core/hle/kernel/writable_event.h" |
| 11 | #include "core/hle/service/service.h" | 11 | #include "core/hle/service/service.h" |
| 12 | 12 | ||
| 13 | namespace Service { | 13 | namespace Kernel { |
| 14 | namespace NVFlinger { | 14 | class KernelCore; |
| 15 | } | ||
| 16 | |||
| 17 | namespace Service::NVFlinger { | ||
| 15 | class NVFlinger; | 18 | class NVFlinger; |
| 16 | } | 19 | } |
| 17 | 20 | ||
| 18 | namespace AM { | 21 | namespace Service::AM { |
| 19 | 22 | ||
| 20 | enum SystemLanguage { | 23 | enum SystemLanguage { |
| 21 | Japanese = 0, | 24 | Japanese = 0, |
| @@ -47,7 +50,7 @@ public: | |||
| 47 | PerformanceModeChanged = 31, | 50 | PerformanceModeChanged = 31, |
| 48 | }; | 51 | }; |
| 49 | 52 | ||
| 50 | AppletMessageQueue(); | 53 | explicit AppletMessageQueue(Kernel::KernelCore& kernel); |
| 51 | ~AppletMessageQueue(); | 54 | ~AppletMessageQueue(); |
| 52 | 55 | ||
| 53 | const Kernel::SharedPtr<Kernel::ReadableEvent>& GetMesssageRecieveEvent() const; | 56 | const Kernel::SharedPtr<Kernel::ReadableEvent>& GetMesssageRecieveEvent() const; |
| @@ -65,12 +68,14 @@ private: | |||
| 65 | 68 | ||
| 66 | class IWindowController final : public ServiceFramework<IWindowController> { | 69 | class IWindowController final : public ServiceFramework<IWindowController> { |
| 67 | public: | 70 | public: |
| 68 | IWindowController(); | 71 | explicit IWindowController(Core::System& system_); |
| 69 | ~IWindowController() override; | 72 | ~IWindowController() override; |
| 70 | 73 | ||
| 71 | private: | 74 | private: |
| 72 | void GetAppletResourceUserId(Kernel::HLERequestContext& ctx); | 75 | void GetAppletResourceUserId(Kernel::HLERequestContext& ctx); |
| 73 | void AcquireForegroundRights(Kernel::HLERequestContext& ctx); | 76 | void AcquireForegroundRights(Kernel::HLERequestContext& ctx); |
| 77 | |||
| 78 | Core::System& system; | ||
| 74 | }; | 79 | }; |
| 75 | 80 | ||
| 76 | class IAudioController final : public ServiceFramework<IAudioController> { | 81 | class IAudioController final : public ServiceFramework<IAudioController> { |
| @@ -113,7 +118,8 @@ public: | |||
| 113 | 118 | ||
| 114 | class ISelfController final : public ServiceFramework<ISelfController> { | 119 | class ISelfController final : public ServiceFramework<ISelfController> { |
| 115 | public: | 120 | public: |
| 116 | explicit ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger); | 121 | explicit ISelfController(Core::System& system_, |
| 122 | std::shared_ptr<NVFlinger::NVFlinger> nvflinger_); | ||
| 117 | ~ISelfController() override; | 123 | ~ISelfController() override; |
| 118 | 124 | ||
| 119 | private: | 125 | private: |
| @@ -208,7 +214,7 @@ private: | |||
| 208 | 214 | ||
| 209 | class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { | 215 | class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { |
| 210 | public: | 216 | public: |
| 211 | ILibraryAppletCreator(u64 current_process_title_id); | 217 | explicit ILibraryAppletCreator(Core::System& system_); |
| 212 | ~ILibraryAppletCreator() override; | 218 | ~ILibraryAppletCreator() override; |
| 213 | 219 | ||
| 214 | private: | 220 | private: |
| @@ -216,12 +222,12 @@ private: | |||
| 216 | void CreateStorage(Kernel::HLERequestContext& ctx); | 222 | void CreateStorage(Kernel::HLERequestContext& ctx); |
| 217 | void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); | 223 | void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); |
| 218 | 224 | ||
| 219 | u64 current_process_title_id; | 225 | Core::System& system; |
| 220 | }; | 226 | }; |
| 221 | 227 | ||
| 222 | class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { | 228 | class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { |
| 223 | public: | 229 | public: |
| 224 | IApplicationFunctions(); | 230 | explicit IApplicationFunctions(Core::System& system_); |
| 225 | ~IApplicationFunctions() override; | 231 | ~IApplicationFunctions() override; |
| 226 | 232 | ||
| 227 | private: | 233 | private: |
| @@ -242,6 +248,10 @@ private: | |||
| 242 | void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx); | 248 | void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx); |
| 243 | void EndBlockingHomeButton(Kernel::HLERequestContext& ctx); | 249 | void EndBlockingHomeButton(Kernel::HLERequestContext& ctx); |
| 244 | void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); | 250 | void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); |
| 251 | void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); | ||
| 252 | |||
| 253 | Kernel::EventPair gpu_error_detected_event; | ||
| 254 | Core::System& system; | ||
| 245 | }; | 255 | }; |
| 246 | 256 | ||
| 247 | class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { | 257 | class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { |
| @@ -275,5 +285,4 @@ public: | |||
| 275 | void InstallInterfaces(SM::ServiceManager& service_manager, | 285 | void InstallInterfaces(SM::ServiceManager& service_manager, |
| 276 | std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system); | 286 | std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system); |
| 277 | 287 | ||
| 278 | } // namespace AM | 288 | } // namespace Service::AM |
| 279 | } // namespace Service | ||
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: | |||
| 50 | 50 | ||
| 51 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 51 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 52 | rb.Push(RESULT_SUCCESS); | 52 | rb.Push(RESULT_SUCCESS); |
| 53 | rb.PushIpcInterface<ISelfController>(nvflinger); | 53 | rb.PushIpcInterface<ISelfController>(system, nvflinger); |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | void GetWindowController(Kernel::HLERequestContext& ctx) { | 56 | void GetWindowController(Kernel::HLERequestContext& ctx) { |
| @@ -58,7 +58,7 @@ private: | |||
| 58 | 58 | ||
| 59 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 59 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 60 | rb.Push(RESULT_SUCCESS); | 60 | rb.Push(RESULT_SUCCESS); |
| 61 | rb.PushIpcInterface<IWindowController>(); | 61 | rb.PushIpcInterface<IWindowController>(system); |
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | void GetAudioController(Kernel::HLERequestContext& ctx) { | 64 | void GetAudioController(Kernel::HLERequestContext& ctx) { |
| @@ -98,7 +98,7 @@ private: | |||
| 98 | 98 | ||
| 99 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 99 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 100 | rb.Push(RESULT_SUCCESS); | 100 | rb.Push(RESULT_SUCCESS); |
| 101 | rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); | 101 | rb.PushIpcInterface<ILibraryAppletCreator>(system); |
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { | 104 | void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { |
| @@ -106,7 +106,7 @@ private: | |||
| 106 | 106 | ||
| 107 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 107 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 108 | rb.Push(RESULT_SUCCESS); | 108 | rb.Push(RESULT_SUCCESS); |
| 109 | rb.PushIpcInterface<IApplicationFunctions>(); | 109 | rb.PushIpcInterface<IApplicationFunctions>(system); |
| 110 | } | 110 | } |
| 111 | 111 | ||
| 112 | std::shared_ptr<NVFlinger::NVFlinger> nvflinger; | 112 | std::shared_ptr<NVFlinger::NVFlinger> nvflinger; |
| @@ -154,7 +154,7 @@ private: | |||
| 154 | 154 | ||
| 155 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 155 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 156 | rb.Push(RESULT_SUCCESS); | 156 | rb.Push(RESULT_SUCCESS); |
| 157 | rb.PushIpcInterface<ISelfController>(nvflinger); | 157 | rb.PushIpcInterface<ISelfController>(system, nvflinger); |
| 158 | } | 158 | } |
| 159 | 159 | ||
| 160 | void GetWindowController(Kernel::HLERequestContext& ctx) { | 160 | void GetWindowController(Kernel::HLERequestContext& ctx) { |
| @@ -162,7 +162,7 @@ private: | |||
| 162 | 162 | ||
| 163 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 163 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 164 | rb.Push(RESULT_SUCCESS); | 164 | rb.Push(RESULT_SUCCESS); |
| 165 | rb.PushIpcInterface<IWindowController>(); | 165 | rb.PushIpcInterface<IWindowController>(system); |
| 166 | } | 166 | } |
| 167 | 167 | ||
| 168 | void GetAudioController(Kernel::HLERequestContext& ctx) { | 168 | void GetAudioController(Kernel::HLERequestContext& ctx) { |
| @@ -194,7 +194,7 @@ private: | |||
| 194 | 194 | ||
| 195 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 195 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 196 | rb.Push(RESULT_SUCCESS); | 196 | rb.Push(RESULT_SUCCESS); |
| 197 | rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); | 197 | rb.PushIpcInterface<ILibraryAppletCreator>(system); |
| 198 | } | 198 | } |
| 199 | 199 | ||
| 200 | void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) { | 200 | 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 @@ | |||
| 4 | 4 | ||
| 5 | #include "common/logging/log.h" | 5 | #include "common/logging/log.h" |
| 6 | #include "core/hle/ipc_helpers.h" | 6 | #include "core/hle/ipc_helpers.h" |
| 7 | #include "core/hle/kernel/process.h" | ||
| 8 | #include "core/hle/service/am/am.h" | 7 | #include "core/hle/service/am/am.h" |
| 9 | #include "core/hle/service/am/applet_oe.h" | 8 | #include "core/hle/service/am/applet_oe.h" |
| 10 | #include "core/hle/service/nvflinger/nvflinger.h" | 9 | #include "core/hle/service/nvflinger/nvflinger.h" |
| @@ -64,7 +63,7 @@ private: | |||
| 64 | 63 | ||
| 65 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 64 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 66 | rb.Push(RESULT_SUCCESS); | 65 | rb.Push(RESULT_SUCCESS); |
| 67 | rb.PushIpcInterface<IWindowController>(); | 66 | rb.PushIpcInterface<IWindowController>(system); |
| 68 | } | 67 | } |
| 69 | 68 | ||
| 70 | void GetSelfController(Kernel::HLERequestContext& ctx) { | 69 | void GetSelfController(Kernel::HLERequestContext& ctx) { |
| @@ -72,7 +71,7 @@ private: | |||
| 72 | 71 | ||
| 73 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 72 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 74 | rb.Push(RESULT_SUCCESS); | 73 | rb.Push(RESULT_SUCCESS); |
| 75 | rb.PushIpcInterface<ISelfController>(nvflinger); | 74 | rb.PushIpcInterface<ISelfController>(system, nvflinger); |
| 76 | } | 75 | } |
| 77 | 76 | ||
| 78 | void GetCommonStateGetter(Kernel::HLERequestContext& ctx) { | 77 | void GetCommonStateGetter(Kernel::HLERequestContext& ctx) { |
| @@ -88,7 +87,7 @@ private: | |||
| 88 | 87 | ||
| 89 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 88 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 90 | rb.Push(RESULT_SUCCESS); | 89 | rb.Push(RESULT_SUCCESS); |
| 91 | rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); | 90 | rb.PushIpcInterface<ILibraryAppletCreator>(system); |
| 92 | } | 91 | } |
| 93 | 92 | ||
| 94 | void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { | 93 | void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { |
| @@ -96,7 +95,7 @@ private: | |||
| 96 | 95 | ||
| 97 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 96 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 98 | rb.Push(RESULT_SUCCESS); | 97 | rb.Push(RESULT_SUCCESS); |
| 99 | rb.PushIpcInterface<IApplicationFunctions>(); | 98 | rb.PushIpcInterface<IApplicationFunctions>(system); |
| 100 | } | 99 | } |
| 101 | 100 | ||
| 102 | std::shared_ptr<NVFlinger::NVFlinger> nvflinger; | 101 | 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 @@ | |||
| 23 | 23 | ||
| 24 | namespace Service::AM::Applets { | 24 | namespace Service::AM::Applets { |
| 25 | 25 | ||
| 26 | AppletDataBroker::AppletDataBroker() { | 26 | AppletDataBroker::AppletDataBroker(Kernel::KernelCore& kernel) { |
| 27 | auto& kernel = Core::System::GetInstance().Kernel(); | ||
| 28 | state_changed_event = Kernel::WritableEvent::CreateEventPair( | 27 | state_changed_event = Kernel::WritableEvent::CreateEventPair( |
| 29 | kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:StateChangedEvent"); | 28 | kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:StateChangedEvent"); |
| 30 | pop_out_data_event = Kernel::WritableEvent::CreateEventPair( | 29 | pop_out_data_event = Kernel::WritableEvent::CreateEventPair( |
| @@ -121,7 +120,7 @@ Kernel::SharedPtr<Kernel::ReadableEvent> AppletDataBroker::GetStateChangedEvent( | |||
| 121 | return state_changed_event.readable; | 120 | return state_changed_event.readable; |
| 122 | } | 121 | } |
| 123 | 122 | ||
| 124 | Applet::Applet() = default; | 123 | Applet::Applet(Kernel::KernelCore& kernel_) : broker{kernel_} {} |
| 125 | 124 | ||
| 126 | Applet::~Applet() = default; | 125 | Applet::~Applet() = default; |
| 127 | 126 | ||
| @@ -154,7 +153,7 @@ AppletFrontendSet::AppletFrontendSet(AppletFrontendSet&&) noexcept = default; | |||
| 154 | 153 | ||
| 155 | AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default; | 154 | AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default; |
| 156 | 155 | ||
| 157 | AppletManager::AppletManager() = default; | 156 | AppletManager::AppletManager(Core::System& system_) : system{system_} {} |
| 158 | 157 | ||
| 159 | AppletManager::~AppletManager() = default; | 158 | AppletManager::~AppletManager() = default; |
| 160 | 159 | ||
| @@ -216,28 +215,28 @@ void AppletManager::ClearAll() { | |||
| 216 | frontend = {}; | 215 | frontend = {}; |
| 217 | } | 216 | } |
| 218 | 217 | ||
| 219 | std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, u64 current_process_title_id) const { | 218 | std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const { |
| 220 | switch (id) { | 219 | switch (id) { |
| 221 | case AppletId::Auth: | 220 | case AppletId::Auth: |
| 222 | return std::make_shared<Auth>(*frontend.parental_controls); | 221 | return std::make_shared<Auth>(system, *frontend.parental_controls); |
| 223 | case AppletId::Error: | 222 | case AppletId::Error: |
| 224 | return std::make_shared<Error>(*frontend.error); | 223 | return std::make_shared<Error>(system, *frontend.error); |
| 225 | case AppletId::ProfileSelect: | 224 | case AppletId::ProfileSelect: |
| 226 | return std::make_shared<ProfileSelect>(*frontend.profile_select); | 225 | return std::make_shared<ProfileSelect>(system, *frontend.profile_select); |
| 227 | case AppletId::SoftwareKeyboard: | 226 | case AppletId::SoftwareKeyboard: |
| 228 | return std::make_shared<SoftwareKeyboard>(*frontend.software_keyboard); | 227 | return std::make_shared<SoftwareKeyboard>(system, *frontend.software_keyboard); |
| 229 | case AppletId::PhotoViewer: | 228 | case AppletId::PhotoViewer: |
| 230 | return std::make_shared<PhotoViewer>(*frontend.photo_viewer); | 229 | return std::make_shared<PhotoViewer>(system, *frontend.photo_viewer); |
| 231 | case AppletId::LibAppletShop: | 230 | case AppletId::LibAppletShop: |
| 232 | return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id, | 231 | return std::make_shared<WebBrowser>(system, *frontend.web_browser, |
| 233 | frontend.e_commerce.get()); | 232 | frontend.e_commerce.get()); |
| 234 | case AppletId::LibAppletOff: | 233 | case AppletId::LibAppletOff: |
| 235 | return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id); | 234 | return std::make_shared<WebBrowser>(system, *frontend.web_browser); |
| 236 | default: | 235 | default: |
| 237 | UNIMPLEMENTED_MSG( | 236 | UNIMPLEMENTED_MSG( |
| 238 | "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.", | 237 | "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.", |
| 239 | static_cast<u8>(id)); | 238 | static_cast<u8>(id)); |
| 240 | return std::make_shared<StubApplet>(id); | 239 | return std::make_shared<StubApplet>(system, id); |
| 241 | } | 240 | } |
| 242 | } | 241 | } |
| 243 | 242 | ||
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 @@ | |||
| 12 | 12 | ||
| 13 | union ResultCode; | 13 | union ResultCode; |
| 14 | 14 | ||
| 15 | namespace Core { | ||
| 16 | class System; | ||
| 17 | } | ||
| 18 | |||
| 15 | namespace Core::Frontend { | 19 | namespace Core::Frontend { |
| 16 | class ECommerceApplet; | 20 | class ECommerceApplet; |
| 17 | class ErrorApplet; | 21 | class ErrorApplet; |
| @@ -22,6 +26,10 @@ class SoftwareKeyboardApplet; | |||
| 22 | class WebBrowserApplet; | 26 | class WebBrowserApplet; |
| 23 | } // namespace Core::Frontend | 27 | } // namespace Core::Frontend |
| 24 | 28 | ||
| 29 | namespace Kernel { | ||
| 30 | class KernelCore; | ||
| 31 | } | ||
| 32 | |||
| 25 | namespace Service::AM { | 33 | namespace Service::AM { |
| 26 | 34 | ||
| 27 | class IStorage; | 35 | class IStorage; |
| @@ -53,7 +61,7 @@ enum class AppletId : u32 { | |||
| 53 | 61 | ||
| 54 | class AppletDataBroker final { | 62 | class AppletDataBroker final { |
| 55 | public: | 63 | public: |
| 56 | AppletDataBroker(); | 64 | explicit AppletDataBroker(Kernel::KernelCore& kernel_); |
| 57 | ~AppletDataBroker(); | 65 | ~AppletDataBroker(); |
| 58 | 66 | ||
| 59 | struct RawChannelData { | 67 | struct RawChannelData { |
| @@ -108,7 +116,7 @@ private: | |||
| 108 | 116 | ||
| 109 | class Applet { | 117 | class Applet { |
| 110 | public: | 118 | public: |
| 111 | Applet(); | 119 | explicit Applet(Kernel::KernelCore& kernel_); |
| 112 | virtual ~Applet(); | 120 | virtual ~Applet(); |
| 113 | 121 | ||
| 114 | virtual void Initialize(); | 122 | virtual void Initialize(); |
| @@ -179,7 +187,7 @@ struct AppletFrontendSet { | |||
| 179 | 187 | ||
| 180 | class AppletManager { | 188 | class AppletManager { |
| 181 | public: | 189 | public: |
| 182 | AppletManager(); | 190 | explicit AppletManager(Core::System& system_); |
| 183 | ~AppletManager(); | 191 | ~AppletManager(); |
| 184 | 192 | ||
| 185 | void SetAppletFrontendSet(AppletFrontendSet set); | 193 | void SetAppletFrontendSet(AppletFrontendSet set); |
| @@ -187,10 +195,11 @@ public: | |||
| 187 | void SetDefaultAppletsIfMissing(); | 195 | void SetDefaultAppletsIfMissing(); |
| 188 | void ClearAll(); | 196 | void ClearAll(); |
| 189 | 197 | ||
| 190 | std::shared_ptr<Applet> GetApplet(AppletId id, u64 current_process_title_id) const; | 198 | std::shared_ptr<Applet> GetApplet(AppletId id) const; |
| 191 | 199 | ||
| 192 | private: | 200 | private: |
| 193 | AppletFrontendSet frontend; | 201 | AppletFrontendSet frontend; |
| 202 | Core::System& system; | ||
| 194 | }; | 203 | }; |
| 195 | 204 | ||
| 196 | } // namespace Applets | 205 | } // 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) { | |||
| 85 | 85 | ||
| 86 | } // Anonymous namespace | 86 | } // Anonymous namespace |
| 87 | 87 | ||
| 88 | Error::Error(const Core::Frontend::ErrorApplet& frontend) : frontend(frontend) {} | 88 | Error::Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_) |
| 89 | : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} | ||
| 89 | 90 | ||
| 90 | Error::~Error() = default; | 91 | Error::~Error() = default; |
| 91 | 92 | ||
| @@ -145,8 +146,8 @@ void Error::Execute() { | |||
| 145 | } | 146 | } |
| 146 | 147 | ||
| 147 | const auto callback = [this] { DisplayCompleted(); }; | 148 | const auto callback = [this] { DisplayCompleted(); }; |
| 148 | const auto title_id = Core::CurrentProcess()->GetTitleID(); | 149 | const auto title_id = system.CurrentProcess()->GetTitleID(); |
| 149 | const auto& reporter{Core::System::GetInstance().GetReporter()}; | 150 | const auto& reporter{system.GetReporter()}; |
| 150 | 151 | ||
| 151 | switch (mode) { | 152 | switch (mode) { |
| 152 | case ErrorAppletMode::ShowError: | 153 | 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 @@ | |||
| 7 | #include "core/hle/result.h" | 7 | #include "core/hle/result.h" |
| 8 | #include "core/hle/service/am/applets/applets.h" | 8 | #include "core/hle/service/am/applets/applets.h" |
| 9 | 9 | ||
| 10 | namespace Core { | ||
| 11 | class System; | ||
| 12 | } | ||
| 13 | |||
| 10 | namespace Service::AM::Applets { | 14 | namespace Service::AM::Applets { |
| 11 | 15 | ||
| 12 | enum class ErrorAppletMode : u8 { | 16 | enum class ErrorAppletMode : u8 { |
| @@ -21,7 +25,7 @@ enum class ErrorAppletMode : u8 { | |||
| 21 | 25 | ||
| 22 | class Error final : public Applet { | 26 | class Error final : public Applet { |
| 23 | public: | 27 | public: |
| 24 | explicit Error(const Core::Frontend::ErrorApplet& frontend); | 28 | explicit Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_); |
| 25 | ~Error() override; | 29 | ~Error() override; |
| 26 | 30 | ||
| 27 | void Initialize() override; | 31 | void Initialize() override; |
| @@ -42,6 +46,7 @@ private: | |||
| 42 | std::unique_ptr<ErrorArguments> args; | 46 | std::unique_ptr<ErrorArguments> args; |
| 43 | 47 | ||
| 44 | bool complete = false; | 48 | bool complete = false; |
| 49 | Core::System& system; | ||
| 45 | }; | 50 | }; |
| 46 | 51 | ||
| 47 | } // namespace Service::AM::Applets | 52 | } // 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) | |||
| 37 | } | 37 | } |
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | Auth::Auth(Core::Frontend::ParentalControlsApplet& frontend) : frontend(frontend) {} | 40 | Auth::Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_) |
| 41 | : Applet{system_.Kernel()}, frontend(frontend_) {} | ||
| 41 | 42 | ||
| 42 | Auth::~Auth() = default; | 43 | Auth::~Auth() = default; |
| 43 | 44 | ||
| @@ -151,7 +152,8 @@ void Auth::AuthFinished(bool successful) { | |||
| 151 | broker.SignalStateChanged(); | 152 | broker.SignalStateChanged(); |
| 152 | } | 153 | } |
| 153 | 154 | ||
| 154 | PhotoViewer::PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend) : frontend(frontend) {} | 155 | PhotoViewer::PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_) |
| 156 | : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} | ||
| 155 | 157 | ||
| 156 | PhotoViewer::~PhotoViewer() = default; | 158 | PhotoViewer::~PhotoViewer() = default; |
| 157 | 159 | ||
| @@ -185,7 +187,7 @@ void PhotoViewer::Execute() { | |||
| 185 | const auto callback = [this] { ViewFinished(); }; | 187 | const auto callback = [this] { ViewFinished(); }; |
| 186 | switch (mode) { | 188 | switch (mode) { |
| 187 | case PhotoViewerAppletMode::CurrentApp: | 189 | case PhotoViewerAppletMode::CurrentApp: |
| 188 | frontend.ShowPhotosForApplication(Core::CurrentProcess()->GetTitleID(), callback); | 190 | frontend.ShowPhotosForApplication(system.CurrentProcess()->GetTitleID(), callback); |
| 189 | break; | 191 | break; |
| 190 | case PhotoViewerAppletMode::AllApps: | 192 | case PhotoViewerAppletMode::AllApps: |
| 191 | frontend.ShowAllPhotos(callback); | 193 | frontend.ShowAllPhotos(callback); |
| @@ -200,7 +202,8 @@ void PhotoViewer::ViewFinished() { | |||
| 200 | broker.SignalStateChanged(); | 202 | broker.SignalStateChanged(); |
| 201 | } | 203 | } |
| 202 | 204 | ||
| 203 | StubApplet::StubApplet(AppletId id) : id(id) {} | 205 | StubApplet::StubApplet(Core::System& system_, AppletId id_) |
| 206 | : Applet{system_.Kernel()}, id(id_), system{system_} {} | ||
| 204 | 207 | ||
| 205 | StubApplet::~StubApplet() = default; | 208 | StubApplet::~StubApplet() = default; |
| 206 | 209 | ||
| @@ -209,7 +212,7 @@ void StubApplet::Initialize() { | |||
| 209 | Applet::Initialize(); | 212 | Applet::Initialize(); |
| 210 | 213 | ||
| 211 | const auto data = broker.PeekDataToAppletForDebug(); | 214 | const auto data = broker.PeekDataToAppletForDebug(); |
| 212 | Core::System::GetInstance().GetReporter().SaveUnimplementedAppletReport( | 215 | system.GetReporter().SaveUnimplementedAppletReport( |
| 213 | static_cast<u32>(id), common_args.arguments_version, common_args.library_version, | 216 | static_cast<u32>(id), common_args.arguments_version, common_args.library_version, |
| 214 | common_args.theme_color, common_args.play_startup_sound, common_args.system_tick, | 217 | common_args.theme_color, common_args.play_startup_sound, common_args.system_tick, |
| 215 | data.normal, data.interactive); | 218 | 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 @@ | |||
| 6 | 6 | ||
| 7 | #include "core/hle/service/am/applets/applets.h" | 7 | #include "core/hle/service/am/applets/applets.h" |
| 8 | 8 | ||
| 9 | namespace Core { | ||
| 10 | class System; | ||
| 11 | } | ||
| 12 | |||
| 9 | namespace Service::AM::Applets { | 13 | namespace Service::AM::Applets { |
| 10 | 14 | ||
| 11 | enum class AuthAppletType : u32 { | 15 | enum class AuthAppletType : u32 { |
| @@ -16,7 +20,7 @@ enum class AuthAppletType : u32 { | |||
| 16 | 20 | ||
| 17 | class Auth final : public Applet { | 21 | class Auth final : public Applet { |
| 18 | public: | 22 | public: |
| 19 | explicit Auth(Core::Frontend::ParentalControlsApplet& frontend); | 23 | explicit Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_); |
| 20 | ~Auth() override; | 24 | ~Auth() override; |
| 21 | 25 | ||
| 22 | void Initialize() override; | 26 | void Initialize() override; |
| @@ -45,7 +49,7 @@ enum class PhotoViewerAppletMode : u8 { | |||
| 45 | 49 | ||
| 46 | class PhotoViewer final : public Applet { | 50 | class PhotoViewer final : public Applet { |
| 47 | public: | 51 | public: |
| 48 | explicit PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend); | 52 | explicit PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_); |
| 49 | ~PhotoViewer() override; | 53 | ~PhotoViewer() override; |
| 50 | 54 | ||
| 51 | void Initialize() override; | 55 | void Initialize() override; |
| @@ -60,11 +64,12 @@ private: | |||
| 60 | const Core::Frontend::PhotoViewerApplet& frontend; | 64 | const Core::Frontend::PhotoViewerApplet& frontend; |
| 61 | bool complete = false; | 65 | bool complete = false; |
| 62 | PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp; | 66 | PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp; |
| 67 | Core::System& system; | ||
| 63 | }; | 68 | }; |
| 64 | 69 | ||
| 65 | class StubApplet final : public Applet { | 70 | class StubApplet final : public Applet { |
| 66 | public: | 71 | public: |
| 67 | explicit StubApplet(AppletId id); | 72 | explicit StubApplet(Core::System& system_, AppletId id_); |
| 68 | ~StubApplet() override; | 73 | ~StubApplet() override; |
| 69 | 74 | ||
| 70 | void Initialize() override; | 75 | void Initialize() override; |
| @@ -76,6 +81,7 @@ public: | |||
| 76 | 81 | ||
| 77 | private: | 82 | private: |
| 78 | AppletId id; | 83 | AppletId id; |
| 84 | Core::System& system; | ||
| 79 | }; | 85 | }; |
| 80 | 86 | ||
| 81 | } // namespace Service::AM::Applets | 87 | } // 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 { | |||
| 15 | 15 | ||
| 16 | constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; | 16 | constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; |
| 17 | 17 | ||
| 18 | ProfileSelect::ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend) | 18 | ProfileSelect::ProfileSelect(Core::System& system_, |
| 19 | : frontend(frontend) {} | 19 | const Core::Frontend::ProfileSelectApplet& frontend_) |
| 20 | : Applet{system_.Kernel()}, frontend(frontend_) {} | ||
| 20 | 21 | ||
| 21 | ProfileSelect::~ProfileSelect() = default; | 22 | ProfileSelect::~ProfileSelect() = default; |
| 22 | 23 | ||
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 @@ | |||
| 11 | #include "core/hle/result.h" | 11 | #include "core/hle/result.h" |
| 12 | #include "core/hle/service/am/applets/applets.h" | 12 | #include "core/hle/service/am/applets/applets.h" |
| 13 | 13 | ||
| 14 | namespace Core { | ||
| 15 | class System; | ||
| 16 | } | ||
| 17 | |||
| 14 | namespace Service::AM::Applets { | 18 | namespace Service::AM::Applets { |
| 15 | 19 | ||
| 16 | struct UserSelectionConfig { | 20 | struct UserSelectionConfig { |
| @@ -29,7 +33,8 @@ static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has inco | |||
| 29 | 33 | ||
| 30 | class ProfileSelect final : public Applet { | 34 | class ProfileSelect final : public Applet { |
| 31 | public: | 35 | public: |
| 32 | explicit ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend); | 36 | explicit ProfileSelect(Core::System& system_, |
| 37 | const Core::Frontend::ProfileSelectApplet& frontend_); | ||
| 33 | ~ProfileSelect() override; | 38 | ~ProfileSelect() override; |
| 34 | 39 | ||
| 35 | void Initialize() override; | 40 | 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( | |||
| 39 | return params; | 39 | return params; |
| 40 | } | 40 | } |
| 41 | 41 | ||
| 42 | SoftwareKeyboard::SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend) | 42 | SoftwareKeyboard::SoftwareKeyboard(Core::System& system_, |
| 43 | : frontend(frontend) {} | 43 | const Core::Frontend::SoftwareKeyboardApplet& frontend_) |
| 44 | : Applet{system_.Kernel()}, frontend(frontend_) {} | ||
| 44 | 45 | ||
| 45 | SoftwareKeyboard::~SoftwareKeyboard() = default; | 46 | SoftwareKeyboard::~SoftwareKeyboard() = default; |
| 46 | 47 | ||
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 @@ | |||
| 16 | 16 | ||
| 17 | union ResultCode; | 17 | union ResultCode; |
| 18 | 18 | ||
| 19 | namespace Core { | ||
| 20 | class System; | ||
| 21 | } | ||
| 22 | |||
| 19 | namespace Service::AM::Applets { | 23 | namespace Service::AM::Applets { |
| 20 | 24 | ||
| 21 | enum class KeysetDisable : u32 { | 25 | enum class KeysetDisable : u32 { |
| @@ -55,7 +59,8 @@ static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect siz | |||
| 55 | 59 | ||
| 56 | class SoftwareKeyboard final : public Applet { | 60 | class SoftwareKeyboard final : public Applet { |
| 57 | public: | 61 | public: |
| 58 | explicit SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend); | 62 | explicit SoftwareKeyboard(Core::System& system_, |
| 63 | const Core::Frontend::SoftwareKeyboardApplet& frontend_); | ||
| 59 | ~SoftwareKeyboard() override; | 64 | ~SoftwareKeyboard() override; |
| 60 | 65 | ||
| 61 | void Initialize() override; | 66 | 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>& | |||
| 190 | return out; | 190 | return out; |
| 191 | } | 191 | } |
| 192 | 192 | ||
| 193 | FileSys::VirtualFile GetApplicationRomFS(u64 title_id, FileSys::ContentRecordType type) { | 193 | FileSys::VirtualFile GetApplicationRomFS(const Core::System& system, u64 title_id, |
| 194 | const auto& installed{Core::System::GetInstance().GetContentProvider()}; | 194 | FileSys::ContentRecordType type) { |
| 195 | const auto& installed{system.GetContentProvider()}; | ||
| 195 | const auto res = installed.GetEntry(title_id, type); | 196 | const auto res = installed.GetEntry(title_id, type); |
| 196 | 197 | ||
| 197 | if (res != nullptr) { | 198 | if (res != nullptr) { |
| @@ -207,10 +208,10 @@ FileSys::VirtualFile GetApplicationRomFS(u64 title_id, FileSys::ContentRecordTyp | |||
| 207 | 208 | ||
| 208 | } // Anonymous namespace | 209 | } // Anonymous namespace |
| 209 | 210 | ||
| 210 | WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id, | 211 | WebBrowser::WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_, |
| 211 | Core::Frontend::ECommerceApplet* frontend_e_commerce) | 212 | Core::Frontend::ECommerceApplet* frontend_e_commerce_) |
| 212 | : frontend(frontend), frontend_e_commerce(frontend_e_commerce), | 213 | : Applet{system_.Kernel()}, frontend(frontend_), |
| 213 | current_process_title_id(current_process_title_id) {} | 214 | frontend_e_commerce(frontend_e_commerce_), system{system_} {} |
| 214 | 215 | ||
| 215 | WebBrowser::~WebBrowser() = default; | 216 | WebBrowser::~WebBrowser() = default; |
| 216 | 217 | ||
| @@ -266,7 +267,7 @@ void WebBrowser::UnpackRomFS() { | |||
| 266 | ASSERT(offline_romfs != nullptr); | 267 | ASSERT(offline_romfs != nullptr); |
| 267 | const auto dir = | 268 | const auto dir = |
| 268 | FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); | 269 | FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); |
| 269 | const auto& vfs{Core::System::GetInstance().GetFilesystem()}; | 270 | const auto& vfs{system.GetFilesystem()}; |
| 270 | const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite); | 271 | const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite); |
| 271 | FileSys::VfsRawCopyD(dir, temp_dir); | 272 | FileSys::VfsRawCopyD(dir, temp_dir); |
| 272 | 273 | ||
| @@ -470,10 +471,10 @@ void WebBrowser::InitializeOffline() { | |||
| 470 | } | 471 | } |
| 471 | 472 | ||
| 472 | if (title_id == 0) { | 473 | if (title_id == 0) { |
| 473 | title_id = current_process_title_id; | 474 | title_id = system.CurrentProcess()->GetTitleID(); |
| 474 | } | 475 | } |
| 475 | 476 | ||
| 476 | offline_romfs = GetApplicationRomFS(title_id, type); | 477 | offline_romfs = GetApplicationRomFS(system, title_id, type); |
| 477 | if (offline_romfs == nullptr) { | 478 | if (offline_romfs == nullptr) { |
| 478 | status = ResultCode(-1); | 479 | status = ResultCode(-1); |
| 479 | LOG_ERROR(Service_AM, "Failed to find offline data for request!"); | 480 | 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 @@ | |||
| 9 | #include "core/hle/service/am/am.h" | 9 | #include "core/hle/service/am/am.h" |
| 10 | #include "core/hle/service/am/applets/applets.h" | 10 | #include "core/hle/service/am/applets/applets.h" |
| 11 | 11 | ||
| 12 | namespace Core { | ||
| 13 | class System; | ||
| 14 | } | ||
| 15 | |||
| 12 | namespace Service::AM::Applets { | 16 | namespace Service::AM::Applets { |
| 13 | 17 | ||
| 14 | enum class ShimKind : u32; | 18 | enum class ShimKind : u32; |
| @@ -17,8 +21,8 @@ enum class WebArgTLVType : u16; | |||
| 17 | 21 | ||
| 18 | class WebBrowser final : public Applet { | 22 | class WebBrowser final : public Applet { |
| 19 | public: | 23 | public: |
| 20 | WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id, | 24 | WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_, |
| 21 | Core::Frontend::ECommerceApplet* frontend_e_commerce = nullptr); | 25 | Core::Frontend::ECommerceApplet* frontend_e_commerce_ = nullptr); |
| 22 | 26 | ||
| 23 | ~WebBrowser() override; | 27 | ~WebBrowser() override; |
| 24 | 28 | ||
| @@ -59,8 +63,6 @@ private: | |||
| 59 | bool unpacked = false; | 63 | bool unpacked = false; |
| 60 | ResultCode status = RESULT_SUCCESS; | 64 | ResultCode status = RESULT_SUCCESS; |
| 61 | 65 | ||
| 62 | u64 current_process_title_id; | ||
| 63 | |||
| 64 | ShimKind kind; | 66 | ShimKind kind; |
| 65 | std::map<WebArgTLVType, std::vector<u8>> args; | 67 | std::map<WebArgTLVType, std::vector<u8>> args; |
| 66 | 68 | ||
| @@ -74,6 +76,8 @@ private: | |||
| 74 | std::optional<u128> user_id; | 76 | std::optional<u128> user_id; |
| 75 | std::optional<bool> shop_full_display; | 77 | std::optional<bool> shop_full_display; |
| 76 | std::string shop_extra_parameter; | 78 | std::string shop_extra_parameter; |
| 79 | |||
| 80 | Core::System& system; | ||
| 77 | }; | 81 | }; |
| 78 | 82 | ||
| 79 | } // namespace Service::AM::Applets | 83 | } // 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: | |||
| 165 | static const FunctionInfo functions[] = { | 165 | static const FunctionInfo functions[] = { |
| 166 | {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, | 166 | {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, |
| 167 | {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, | 167 | {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, |
| 168 | {2, nullptr, "GetAudioDeviceOutputVolume"}, | 168 | {2, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolume"}, |
| 169 | {3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"}, | 169 | {3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"}, |
| 170 | {4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"}, | 170 | {4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"}, |
| 171 | {5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"}, | 171 | {5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"}, |
| 172 | {6, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceNameAuto"}, | 172 | {6, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceNameAuto"}, |
| 173 | {7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"}, | 173 | {7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"}, |
| 174 | {8, nullptr, "GetAudioDeviceOutputVolumeAuto"}, | 174 | {8, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolumeAuto"}, |
| 175 | {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, | 175 | {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, |
| 176 | {11, nullptr, "QueryAudioDeviceInputEvent"}, | 176 | {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"}, |
| 177 | {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, | 177 | {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, |
| 178 | {13, nullptr, "GetAudioSystemMasterVolumeSetting"}, | 178 | {13, nullptr, "GetAudioSystemMasterVolumeSetting"}, |
| 179 | }; | 179 | }; |
| @@ -183,6 +183,10 @@ public: | |||
| 183 | buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic, | 183 | buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic, |
| 184 | "IAudioOutBufferReleasedEvent"); | 184 | "IAudioOutBufferReleasedEvent"); |
| 185 | 185 | ||
| 186 | // Should be similar to audio_output_device_switch_event | ||
| 187 | audio_input_device_switch_event = Kernel::WritableEvent::CreateEventPair( | ||
| 188 | kernel, Kernel::ResetType::Automatic, "IAudioDevice:AudioInputDeviceSwitchedEvent"); | ||
| 189 | |||
| 186 | // Should only be signalled when an audio output device has been changed, example: speaker | 190 | // Should only be signalled when an audio output device has been changed, example: speaker |
| 187 | // to headset | 191 | // to headset |
| 188 | audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair( | 192 | audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair( |
| @@ -246,6 +250,19 @@ private: | |||
| 246 | rb.Push(RESULT_SUCCESS); | 250 | rb.Push(RESULT_SUCCESS); |
| 247 | } | 251 | } |
| 248 | 252 | ||
| 253 | void GetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) { | ||
| 254 | IPC::RequestParser rp{ctx}; | ||
| 255 | |||
| 256 | const auto device_name_buffer = ctx.ReadBuffer(); | ||
| 257 | const std::string name = Common::StringFromBuffer(device_name_buffer); | ||
| 258 | |||
| 259 | LOG_WARNING(Service_Audio, "(STUBBED) called. name={}", name); | ||
| 260 | |||
| 261 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 262 | rb.Push(RESULT_SUCCESS); | ||
| 263 | rb.Push(1.0f); | ||
| 264 | } | ||
| 265 | |||
| 249 | void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { | 266 | void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { |
| 250 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | 267 | LOG_WARNING(Service_Audio, "(STUBBED) called"); |
| 251 | 268 | ||
| @@ -279,6 +296,15 @@ private: | |||
| 279 | rb.Push<u32>(1); | 296 | rb.Push<u32>(1); |
| 280 | } | 297 | } |
| 281 | 298 | ||
| 299 | // Should be similar to QueryAudioDeviceOutputEvent | ||
| 300 | void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) { | ||
| 301 | LOG_WARNING(Service_Audio, "(STUBBED) called"); | ||
| 302 | |||
| 303 | IPC::ResponseBuilder rb{ctx, 2, 1}; | ||
| 304 | rb.Push(RESULT_SUCCESS); | ||
| 305 | rb.PushCopyObjects(audio_input_device_switch_event.readable); | ||
| 306 | } | ||
| 307 | |||
| 282 | void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) { | 308 | void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) { |
| 283 | LOG_DEBUG(Service_Audio, "called"); | 309 | LOG_DEBUG(Service_Audio, "called"); |
| 284 | 310 | ||
| @@ -289,6 +315,7 @@ private: | |||
| 289 | 315 | ||
| 290 | u32_le revision = 0; | 316 | u32_le revision = 0; |
| 291 | Kernel::EventPair buffer_event; | 317 | Kernel::EventPair buffer_event; |
| 318 | Kernel::EventPair audio_input_device_switch_event; | ||
| 292 | Kernel::EventPair audio_output_device_switch_event; | 319 | Kernel::EventPair audio_output_device_switch_event; |
| 293 | 320 | ||
| 294 | }; // namespace Audio | 321 | }; // 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 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include "core/crypto/key_manager.h" | ||
| 6 | #include "core/hle/ipc_helpers.h" | ||
| 5 | #include "core/hle/service/service.h" | 7 | #include "core/hle/service/service.h" |
| 6 | 8 | ||
| 7 | namespace Service::ES { | 9 | namespace Service::ES { |
| 8 | 10 | ||
| 11 | constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::ETicket, 2}; | ||
| 12 | constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3}; | ||
| 13 | |||
| 9 | class ETicket final : public ServiceFramework<ETicket> { | 14 | class ETicket final : public ServiceFramework<ETicket> { |
| 10 | public: | 15 | public: |
| 11 | explicit ETicket() : ServiceFramework{"es"} { | 16 | explicit ETicket() : ServiceFramework{"es"} { |
| 12 | // clang-format off | 17 | // clang-format off |
| 13 | static const FunctionInfo functions[] = { | 18 | static const FunctionInfo functions[] = { |
| 14 | {1, nullptr, "ImportTicket"}, | 19 | {1, &ETicket::ImportTicket, "ImportTicket"}, |
| 15 | {2, nullptr, "ImportTicketCertificateSet"}, | 20 | {2, nullptr, "ImportTicketCertificateSet"}, |
| 16 | {3, nullptr, "DeleteTicket"}, | 21 | {3, nullptr, "DeleteTicket"}, |
| 17 | {4, nullptr, "DeletePersonalizedTicket"}, | 22 | {4, nullptr, "DeletePersonalizedTicket"}, |
| 18 | {5, nullptr, "DeleteAllCommonTicket"}, | 23 | {5, nullptr, "DeleteAllCommonTicket"}, |
| 19 | {6, nullptr, "DeleteAllPersonalizedTicket"}, | 24 | {6, nullptr, "DeleteAllPersonalizedTicket"}, |
| 20 | {7, nullptr, "DeleteAllPersonalizedTicketEx"}, | 25 | {7, nullptr, "DeleteAllPersonalizedTicketEx"}, |
| 21 | {8, nullptr, "GetTitleKey"}, | 26 | {8, &ETicket::GetTitleKey, "GetTitleKey"}, |
| 22 | {9, nullptr, "CountCommonTicket"}, | 27 | {9, &ETicket::CountCommonTicket, "CountCommonTicket"}, |
| 23 | {10, nullptr, "CountPersonalizedTicket"}, | 28 | {10, &ETicket::CountPersonalizedTicket, "CountPersonalizedTicket"}, |
| 24 | {11, nullptr, "ListCommonTicket"}, | 29 | {11, &ETicket::ListCommonTicket, "ListCommonTicket"}, |
| 25 | {12, nullptr, "ListPersonalizedTicket"}, | 30 | {12, &ETicket::ListPersonalizedTicket, "ListPersonalizedTicket"}, |
| 26 | {13, nullptr, "ListMissingPersonalizedTicket"}, | 31 | {13, nullptr, "ListMissingPersonalizedTicket"}, |
| 27 | {14, nullptr, "GetCommonTicketSize"}, | 32 | {14, &ETicket::GetCommonTicketSize, "GetCommonTicketSize"}, |
| 28 | {15, nullptr, "GetPersonalizedTicketSize"}, | 33 | {15, &ETicket::GetPersonalizedTicketSize, "GetPersonalizedTicketSize"}, |
| 29 | {16, nullptr, "GetCommonTicketData"}, | 34 | {16, &ETicket::GetCommonTicketData, "GetCommonTicketData"}, |
| 30 | {17, nullptr, "GetPersonalizedTicketData"}, | 35 | {17, &ETicket::GetPersonalizedTicketData, "GetPersonalizedTicketData"}, |
| 31 | {18, nullptr, "OwnTicket"}, | 36 | {18, nullptr, "OwnTicket"}, |
| 32 | {19, nullptr, "GetTicketInfo"}, | 37 | {19, nullptr, "GetTicketInfo"}, |
| 33 | {20, nullptr, "ListLightTicketInfo"}, | 38 | {20, nullptr, "ListLightTicketInfo"}, |
| @@ -51,7 +56,212 @@ public: | |||
| 51 | }; | 56 | }; |
| 52 | // clang-format on | 57 | // clang-format on |
| 53 | RegisterHandlers(functions); | 58 | RegisterHandlers(functions); |
| 59 | |||
| 60 | keys.PopulateTickets(); | ||
| 61 | keys.SynthesizeTickets(); | ||
| 62 | } | ||
| 63 | |||
| 64 | private: | ||
| 65 | bool CheckRightsId(Kernel::HLERequestContext& ctx, const u128& rights_id) { | ||
| 66 | if (rights_id == u128{}) { | ||
| 67 | LOG_ERROR(Service_ETicket, "The rights ID was invalid!"); | ||
| 68 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 69 | rb.Push(ERROR_INVALID_RIGHTS_ID); | ||
| 70 | return false; | ||
| 71 | } | ||
| 72 | |||
| 73 | return true; | ||
| 74 | } | ||
| 75 | |||
| 76 | void ImportTicket(Kernel::HLERequestContext& ctx) { | ||
| 77 | IPC::RequestParser rp{ctx}; | ||
| 78 | const auto ticket = ctx.ReadBuffer(); | ||
| 79 | const auto cert = ctx.ReadBuffer(1); | ||
| 80 | |||
| 81 | if (ticket.size() < sizeof(Core::Crypto::Ticket)) { | ||
| 82 | LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); | ||
| 83 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 84 | rb.Push(ERROR_INVALID_ARGUMENT); | ||
| 85 | return; | ||
| 86 | } | ||
| 87 | |||
| 88 | Core::Crypto::Ticket raw{}; | ||
| 89 | std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket)); | ||
| 90 | |||
| 91 | if (!keys.AddTicketPersonalized(raw)) { | ||
| 92 | LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); | ||
| 93 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 94 | rb.Push(ERROR_INVALID_ARGUMENT); | ||
| 95 | return; | ||
| 96 | } | ||
| 97 | |||
| 98 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 99 | rb.Push(RESULT_SUCCESS); | ||
| 100 | } | ||
| 101 | |||
| 102 | void GetTitleKey(Kernel::HLERequestContext& ctx) { | ||
| 103 | IPC::RequestParser rp{ctx}; | ||
| 104 | const auto rights_id = rp.PopRaw<u128>(); | ||
| 105 | |||
| 106 | LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); | ||
| 107 | |||
| 108 | if (!CheckRightsId(ctx, rights_id)) | ||
| 109 | return; | ||
| 110 | |||
| 111 | const auto key = | ||
| 112 | keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); | ||
| 113 | |||
| 114 | if (key == Core::Crypto::Key128{}) { | ||
| 115 | LOG_ERROR(Service_ETicket, | ||
| 116 | "The titlekey doesn't exist in the KeyManager or the rights ID was invalid!"); | ||
| 117 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 118 | rb.Push(ERROR_INVALID_RIGHTS_ID); | ||
| 119 | return; | ||
| 120 | } | ||
| 121 | |||
| 122 | ctx.WriteBuffer(key.data(), key.size()); | ||
| 123 | |||
| 124 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 125 | rb.Push(RESULT_SUCCESS); | ||
| 126 | } | ||
| 127 | |||
| 128 | void CountCommonTicket(Kernel::HLERequestContext& ctx) { | ||
| 129 | LOG_DEBUG(Service_ETicket, "called"); | ||
| 130 | |||
| 131 | const auto count = keys.GetCommonTickets().size(); | ||
| 132 | |||
| 133 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 134 | rb.Push(RESULT_SUCCESS); | ||
| 135 | rb.Push<u32>(count); | ||
| 136 | } | ||
| 137 | |||
| 138 | void CountPersonalizedTicket(Kernel::HLERequestContext& ctx) { | ||
| 139 | LOG_DEBUG(Service_ETicket, "called"); | ||
| 140 | |||
| 141 | const auto count = keys.GetPersonalizedTickets().size(); | ||
| 142 | |||
| 143 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 144 | rb.Push(RESULT_SUCCESS); | ||
| 145 | rb.Push<u32>(count); | ||
| 146 | } | ||
| 147 | |||
| 148 | void ListCommonTicket(Kernel::HLERequestContext& ctx) { | ||
| 149 | u32 out_entries; | ||
| 150 | if (keys.GetCommonTickets().empty()) | ||
| 151 | out_entries = 0; | ||
| 152 | else | ||
| 153 | out_entries = ctx.GetWriteBufferSize() / sizeof(u128); | ||
| 154 | |||
| 155 | LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries); | ||
| 156 | |||
| 157 | keys.PopulateTickets(); | ||
| 158 | const auto tickets = keys.GetCommonTickets(); | ||
| 159 | std::vector<u128> ids; | ||
| 160 | std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids), | ||
| 161 | [](const auto& pair) { return pair.first; }); | ||
| 162 | |||
| 163 | out_entries = std::min<u32>(ids.size(), out_entries); | ||
| 164 | ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128)); | ||
| 165 | |||
| 166 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 167 | rb.Push(RESULT_SUCCESS); | ||
| 168 | rb.Push<u32>(out_entries); | ||
| 54 | } | 169 | } |
| 170 | |||
| 171 | void ListPersonalizedTicket(Kernel::HLERequestContext& ctx) { | ||
| 172 | u32 out_entries; | ||
| 173 | if (keys.GetPersonalizedTickets().empty()) | ||
| 174 | out_entries = 0; | ||
| 175 | else | ||
| 176 | out_entries = ctx.GetWriteBufferSize() / sizeof(u128); | ||
| 177 | |||
| 178 | LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries); | ||
| 179 | |||
| 180 | keys.PopulateTickets(); | ||
| 181 | const auto tickets = keys.GetPersonalizedTickets(); | ||
| 182 | std::vector<u128> ids; | ||
| 183 | std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids), | ||
| 184 | [](const auto& pair) { return pair.first; }); | ||
| 185 | |||
| 186 | out_entries = std::min<u32>(ids.size(), out_entries); | ||
| 187 | ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128)); | ||
| 188 | |||
| 189 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 190 | rb.Push(RESULT_SUCCESS); | ||
| 191 | rb.Push<u32>(out_entries); | ||
| 192 | } | ||
| 193 | |||
| 194 | void GetCommonTicketSize(Kernel::HLERequestContext& ctx) { | ||
| 195 | IPC::RequestParser rp{ctx}; | ||
| 196 | const auto rights_id = rp.PopRaw<u128>(); | ||
| 197 | |||
| 198 | LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); | ||
| 199 | |||
| 200 | if (!CheckRightsId(ctx, rights_id)) | ||
| 201 | return; | ||
| 202 | |||
| 203 | const auto ticket = keys.GetCommonTickets().at(rights_id); | ||
| 204 | |||
| 205 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 206 | rb.Push(RESULT_SUCCESS); | ||
| 207 | rb.Push<u64>(ticket.GetSize()); | ||
| 208 | } | ||
| 209 | |||
| 210 | void GetPersonalizedTicketSize(Kernel::HLERequestContext& ctx) { | ||
| 211 | IPC::RequestParser rp{ctx}; | ||
| 212 | const auto rights_id = rp.PopRaw<u128>(); | ||
| 213 | |||
| 214 | LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); | ||
| 215 | |||
| 216 | if (!CheckRightsId(ctx, rights_id)) | ||
| 217 | return; | ||
| 218 | |||
| 219 | const auto ticket = keys.GetPersonalizedTickets().at(rights_id); | ||
| 220 | |||
| 221 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 222 | rb.Push(RESULT_SUCCESS); | ||
| 223 | rb.Push<u64>(ticket.GetSize()); | ||
| 224 | } | ||
| 225 | |||
| 226 | void GetCommonTicketData(Kernel::HLERequestContext& ctx) { | ||
| 227 | IPC::RequestParser rp{ctx}; | ||
| 228 | const auto rights_id = rp.PopRaw<u128>(); | ||
| 229 | |||
| 230 | LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); | ||
| 231 | |||
| 232 | if (!CheckRightsId(ctx, rights_id)) | ||
| 233 | return; | ||
| 234 | |||
| 235 | const auto ticket = keys.GetCommonTickets().at(rights_id); | ||
| 236 | |||
| 237 | const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize()); | ||
| 238 | ctx.WriteBuffer(&ticket, write_size); | ||
| 239 | |||
| 240 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 241 | rb.Push(RESULT_SUCCESS); | ||
| 242 | rb.Push<u64>(write_size); | ||
| 243 | } | ||
| 244 | |||
| 245 | void GetPersonalizedTicketData(Kernel::HLERequestContext& ctx) { | ||
| 246 | IPC::RequestParser rp{ctx}; | ||
| 247 | const auto rights_id = rp.PopRaw<u128>(); | ||
| 248 | |||
| 249 | LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); | ||
| 250 | |||
| 251 | if (!CheckRightsId(ctx, rights_id)) | ||
| 252 | return; | ||
| 253 | |||
| 254 | const auto ticket = keys.GetPersonalizedTickets().at(rights_id); | ||
| 255 | |||
| 256 | const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize()); | ||
| 257 | ctx.WriteBuffer(&ticket, write_size); | ||
| 258 | |||
| 259 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 260 | rb.Push(RESULT_SUCCESS); | ||
| 261 | rb.Push<u64>(write_size); | ||
| 262 | } | ||
| 263 | |||
| 264 | Core::Crypto::KeyManager keys; | ||
| 55 | }; | 265 | }; |
| 56 | 266 | ||
| 57 | void InstallInterfaces(SM::ServiceManager& service_manager) { | 267 | 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 @@ | |||
| 5 | #include <array> | 5 | #include <array> |
| 6 | #include <cstring> | 6 | #include <cstring> |
| 7 | #include <ctime> | 7 | #include <ctime> |
| 8 | #include <fmt/time.h> | 8 | #include <fmt/chrono.h> |
| 9 | #include "common/file_util.h" | 9 | #include "common/file_util.h" |
| 10 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 11 | #include "common/scm_rev.h" | 11 | #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) { | |||
| 636 | return LedPattern{0, 0, 0, 0}; | 636 | return LedPattern{0, 0, 0, 0}; |
| 637 | }; | 637 | }; |
| 638 | } | 638 | } |
| 639 | |||
| 639 | void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { | 640 | void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { |
| 640 | can_controllers_vibrate = can_vibrate; | 641 | can_controllers_vibrate = can_vibrate; |
| 641 | } | 642 | } |
| 642 | 643 | ||
| 644 | bool Controller_NPad::IsVibrationEnabled() const { | ||
| 645 | return can_controllers_vibrate; | ||
| 646 | } | ||
| 647 | |||
| 643 | void Controller_NPad::ClearAllConnectedControllers() { | 648 | void Controller_NPad::ClearAllConnectedControllers() { |
| 644 | for (auto& controller : connected_controllers) { | 649 | for (auto& controller : connected_controllers) { |
| 645 | if (controller.is_connected && controller.type != NPadControllerType::None) { | 650 | if (controller.is_connected && controller.type != NPadControllerType::None) { |
| @@ -648,6 +653,7 @@ void Controller_NPad::ClearAllConnectedControllers() { | |||
| 648 | } | 653 | } |
| 649 | } | 654 | } |
| 650 | } | 655 | } |
| 656 | |||
| 651 | void Controller_NPad::DisconnectAllConnectedControllers() { | 657 | void Controller_NPad::DisconnectAllConnectedControllers() { |
| 652 | std::for_each(connected_controllers.begin(), connected_controllers.end(), | 658 | std::for_each(connected_controllers.begin(), connected_controllers.end(), |
| 653 | [](ControllerHolder& controller) { controller.is_connected = false; }); | 659 | [](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: | |||
| 119 | void DisconnectNPad(u32 npad_id); | 119 | void DisconnectNPad(u32 npad_id); |
| 120 | LedPattern GetLedPattern(u32 npad_id); | 120 | LedPattern GetLedPattern(u32 npad_id); |
| 121 | void SetVibrationEnabled(bool can_vibrate); | 121 | void SetVibrationEnabled(bool can_vibrate); |
| 122 | bool IsVibrationEnabled() const; | ||
| 122 | void ClearAllConnectedControllers(); | 123 | void ClearAllConnectedControllers(); |
| 123 | void DisconnectAllConnectedControllers(); | 124 | void DisconnectAllConnectedControllers(); |
| 124 | void ConnectAllDisconnectedControllers(); | 125 | 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") { | |||
| 216 | {201, &Hid::SendVibrationValue, "SendVibrationValue"}, | 216 | {201, &Hid::SendVibrationValue, "SendVibrationValue"}, |
| 217 | {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"}, | 217 | {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"}, |
| 218 | {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"}, | 218 | {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"}, |
| 219 | {204, nullptr, "PermitVibration"}, | 219 | {204, &Hid::PermitVibration, "PermitVibration"}, |
| 220 | {205, nullptr, "IsVibrationPermitted"}, | 220 | {205, &Hid::IsVibrationPermitted, "IsVibrationPermitted"}, |
| 221 | {206, &Hid::SendVibrationValues, "SendVibrationValues"}, | 221 | {206, &Hid::SendVibrationValues, "SendVibrationValues"}, |
| 222 | {207, nullptr, "SendVibrationGcErmCommand"}, | 222 | {207, nullptr, "SendVibrationGcErmCommand"}, |
| 223 | {208, nullptr, "GetActualVibrationGcErmCommand"}, | 223 | {208, nullptr, "GetActualVibrationGcErmCommand"}, |
| @@ -679,6 +679,27 @@ void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { | |||
| 679 | rb.PushIpcInterface<IActiveVibrationDeviceList>(); | 679 | rb.PushIpcInterface<IActiveVibrationDeviceList>(); |
| 680 | } | 680 | } |
| 681 | 681 | ||
| 682 | void Hid::PermitVibration(Kernel::HLERequestContext& ctx) { | ||
| 683 | IPC::RequestParser rp{ctx}; | ||
| 684 | const auto can_vibrate{rp.Pop<bool>()}; | ||
| 685 | applet_resource->GetController<Controller_NPad>(HidController::NPad) | ||
| 686 | .SetVibrationEnabled(can_vibrate); | ||
| 687 | |||
| 688 | LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate); | ||
| 689 | |||
| 690 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 691 | rb.Push(RESULT_SUCCESS); | ||
| 692 | } | ||
| 693 | |||
| 694 | void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) { | ||
| 695 | LOG_DEBUG(Service_HID, "called"); | ||
| 696 | |||
| 697 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 698 | rb.Push(RESULT_SUCCESS); | ||
| 699 | rb.Push( | ||
| 700 | applet_resource->GetController<Controller_NPad>(HidController::NPad).IsVibrationEnabled()); | ||
| 701 | } | ||
| 702 | |||
| 682 | void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { | 703 | void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { |
| 683 | IPC::RequestParser rp{ctx}; | 704 | IPC::RequestParser rp{ctx}; |
| 684 | const auto applet_resource_user_id{rp.Pop<u64>()}; | 705 | 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: | |||
| 114 | void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); | 114 | void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); |
| 115 | void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx); | 115 | void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx); |
| 116 | void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx); | 116 | void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx); |
| 117 | void PermitVibration(Kernel::HLERequestContext& ctx); | ||
| 118 | void IsVibrationPermitted(Kernel::HLERequestContext& ctx); | ||
| 117 | void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); | 119 | void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); |
| 118 | void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); | 120 | void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); |
| 119 | void StopSixAxisSensor(Kernel::HLERequestContext& ctx); | 121 | void StopSixAxisSensor(Kernel::HLERequestContext& ctx); |
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 131b01d62..8d0353075 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp | |||
| @@ -175,6 +175,10 @@ MiiStoreData ConvertInfoToStoreData(const MiiInfo& info) { | |||
| 175 | } // namespace | 175 | } // namespace |
| 176 | 176 | ||
| 177 | std::ostream& operator<<(std::ostream& os, Source source) { | 177 | std::ostream& operator<<(std::ostream& os, Source source) { |
| 178 | if (static_cast<std::size_t>(source) >= SOURCE_NAMES.size()) { | ||
| 179 | return os << "[UNKNOWN SOURCE]"; | ||
| 180 | } | ||
| 181 | |||
| 178 | os << SOURCE_NAMES.at(static_cast<std::size_t>(source)); | 182 | os << SOURCE_NAMES.at(static_cast<std::size_t>(source)); |
| 179 | return os; | 183 | return os; |
| 180 | } | 184 | } |
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index 76494f0b7..926a1285d 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp | |||
| @@ -37,7 +37,7 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u3 | |||
| 37 | transform, crop_rect}; | 37 | transform, crop_rect}; |
| 38 | 38 | ||
| 39 | system.GetPerfStats().EndGameFrame(); | 39 | system.GetPerfStats().EndGameFrame(); |
| 40 | system.GPU().SwapBuffers(framebuffer); | 40 | system.GPU().SwapBuffers(&framebuffer); |
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | } // namespace Service::Nvidia::Devices | 43 | } // namespace Service::Nvidia::Devices |
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 @@ | |||
| 5 | #include <ctime> | 5 | #include <ctime> |
| 6 | #include <fstream> | 6 | #include <fstream> |
| 7 | 7 | ||
| 8 | #include <fmt/chrono.h> | ||
| 8 | #include <fmt/format.h> | 9 | #include <fmt/format.h> |
| 9 | #include <fmt/time.h> | ||
| 10 | #include <json.hpp> | 10 | #include <json.hpp> |
| 11 | 11 | ||
| 12 | #include "common/file_util.h" | 12 | #include "common/file_util.h" |
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 6a5a4f5c4..f5158d219 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp | |||
| @@ -249,16 +249,10 @@ void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { | |||
| 249 | executing_macro = 0; | 249 | executing_macro = 0; |
| 250 | 250 | ||
| 251 | // Lookup the macro offset | 251 | // Lookup the macro offset |
| 252 | const u32 entry{(method - MacroRegistersStart) >> 1}; | 252 | const u32 entry = ((method - MacroRegistersStart) >> 1) % macro_positions.size(); |
| 253 | const auto& search{macro_offsets.find(entry)}; | ||
| 254 | if (search == macro_offsets.end()) { | ||
| 255 | LOG_CRITICAL(HW_GPU, "macro not found for method 0x{:X}!", method); | ||
| 256 | UNREACHABLE(); | ||
| 257 | return; | ||
| 258 | } | ||
| 259 | 253 | ||
| 260 | // Execute the current macro. | 254 | // Execute the current macro. |
| 261 | macro_interpreter.Execute(search->second, std::move(parameters)); | 255 | macro_interpreter.Execute(macro_positions[entry], std::move(parameters)); |
| 262 | } | 256 | } |
| 263 | 257 | ||
| 264 | void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { | 258 | void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { |
| @@ -421,7 +415,7 @@ void Maxwell3D::ProcessMacroUpload(u32 data) { | |||
| 421 | } | 415 | } |
| 422 | 416 | ||
| 423 | void Maxwell3D::ProcessMacroBind(u32 data) { | 417 | void Maxwell3D::ProcessMacroBind(u32 data) { |
| 424 | macro_offsets[regs.macros.entry] = data; | 418 | macro_positions[regs.macros.entry++] = data; |
| 425 | } | 419 | } |
| 426 | 420 | ||
| 427 | void Maxwell3D::ProcessQueryGet() { | 421 | void Maxwell3D::ProcessQueryGet() { |
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 1ee982b76..0184342a0 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h | |||
| @@ -1270,7 +1270,7 @@ private: | |||
| 1270 | MemoryManager& memory_manager; | 1270 | MemoryManager& memory_manager; |
| 1271 | 1271 | ||
| 1272 | /// Start offsets of each macro in macro_memory | 1272 | /// Start offsets of each macro in macro_memory |
| 1273 | std::unordered_map<u32, u32> macro_offsets; | 1273 | std::array<u32, 0x80> macro_positions = {}; |
| 1274 | 1274 | ||
| 1275 | /// Memory for macro code | 1275 | /// Memory for macro code |
| 1276 | MacroMemory macro_memory; | 1276 | MacroMemory macro_memory; |
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index bc8c2a1c5..c3678b9ea 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h | |||
| @@ -886,6 +886,7 @@ union Instruction { | |||
| 886 | union { | 886 | union { |
| 887 | BitField<0, 3, u64> pred0; | 887 | BitField<0, 3, u64> pred0; |
| 888 | BitField<3, 3, u64> pred3; | 888 | BitField<3, 3, u64> pred3; |
| 889 | BitField<6, 1, u64> neg_b; | ||
| 889 | BitField<7, 1, u64> abs_a; | 890 | BitField<7, 1, u64> abs_a; |
| 890 | BitField<39, 3, u64> pred39; | 891 | BitField<39, 3, u64> pred39; |
| 891 | BitField<42, 1, u64> neg_pred; | 892 | BitField<42, 1, u64> neg_pred; |
| @@ -1019,7 +1020,6 @@ union Instruction { | |||
| 1019 | } iset; | 1020 | } iset; |
| 1020 | 1021 | ||
| 1021 | union { | 1022 | union { |
| 1022 | BitField<41, 2, u64> selector; // i2i and i2f only | ||
| 1023 | BitField<45, 1, u64> negate_a; | 1023 | BitField<45, 1, u64> negate_a; |
| 1024 | BitField<49, 1, u64> abs_a; | 1024 | BitField<49, 1, u64> abs_a; |
| 1025 | BitField<10, 2, Register::Size> src_size; | 1025 | BitField<10, 2, Register::Size> src_size; |
| @@ -1045,6 +1045,13 @@ union Instruction { | |||
| 1045 | } | 1045 | } |
| 1046 | } f2f; | 1046 | } f2f; |
| 1047 | 1047 | ||
| 1048 | union { | ||
| 1049 | BitField<41, 2, u64> selector; | ||
| 1050 | } int_src; | ||
| 1051 | |||
| 1052 | union { | ||
| 1053 | BitField<41, 1, u64> selector; | ||
| 1054 | } float_src; | ||
| 1048 | } conversion; | 1055 | } conversion; |
| 1049 | 1056 | ||
| 1050 | union { | 1057 | union { |
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 8d9db45f5..2c47541cb 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp | |||
| @@ -17,18 +17,6 @@ | |||
| 17 | 17 | ||
| 18 | namespace Tegra { | 18 | namespace Tegra { |
| 19 | 19 | ||
| 20 | u32 FramebufferConfig::BytesPerPixel(PixelFormat format) { | ||
| 21 | switch (format) { | ||
| 22 | case PixelFormat::ABGR8: | ||
| 23 | case PixelFormat::BGRA8: | ||
| 24 | return 4; | ||
| 25 | default: | ||
| 26 | return 4; | ||
| 27 | } | ||
| 28 | |||
| 29 | UNREACHABLE(); | ||
| 30 | } | ||
| 31 | |||
| 32 | GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer, bool is_async) | 20 | GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer, bool is_async) |
| 33 | : system{system}, renderer{renderer}, is_async{is_async} { | 21 | : system{system}, renderer{renderer}, is_async{is_async} { |
| 34 | auto& rasterizer{renderer.Rasterizer()}; | 22 | auto& rasterizer{renderer.Rasterizer()}; |
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 544340ecd..78bc0601a 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h | |||
| @@ -95,14 +95,10 @@ class DebugContext; | |||
| 95 | struct FramebufferConfig { | 95 | struct FramebufferConfig { |
| 96 | enum class PixelFormat : u32 { | 96 | enum class PixelFormat : u32 { |
| 97 | ABGR8 = 1, | 97 | ABGR8 = 1, |
| 98 | RGB565 = 4, | ||
| 98 | BGRA8 = 5, | 99 | BGRA8 = 5, |
| 99 | }; | 100 | }; |
| 100 | 101 | ||
| 101 | /** | ||
| 102 | * Returns the number of bytes per pixel. | ||
| 103 | */ | ||
| 104 | static u32 BytesPerPixel(PixelFormat format); | ||
| 105 | |||
| 106 | VAddr address; | 102 | VAddr address; |
| 107 | u32 offset; | 103 | u32 offset; |
| 108 | u32 width; | 104 | u32 width; |
| @@ -253,8 +249,7 @@ public: | |||
| 253 | virtual void PushGPUEntries(Tegra::CommandList&& entries) = 0; | 249 | virtual void PushGPUEntries(Tegra::CommandList&& entries) = 0; |
| 254 | 250 | ||
| 255 | /// Swap buffers (render frame) | 251 | /// Swap buffers (render frame) |
| 256 | virtual void SwapBuffers( | 252 | virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; |
| 257 | std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) = 0; | ||
| 258 | 253 | ||
| 259 | /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory | 254 | /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory |
| 260 | virtual void FlushRegion(CacheAddr addr, u64 size) = 0; | 255 | virtual void FlushRegion(CacheAddr addr, u64 size) = 0; |
diff --git a/src/video_core/gpu_asynch.cpp b/src/video_core/gpu_asynch.cpp index ea67be831..f2a3a390e 100644 --- a/src/video_core/gpu_asynch.cpp +++ b/src/video_core/gpu_asynch.cpp | |||
| @@ -23,9 +23,8 @@ void GPUAsynch::PushGPUEntries(Tegra::CommandList&& entries) { | |||
| 23 | gpu_thread.SubmitList(std::move(entries)); | 23 | gpu_thread.SubmitList(std::move(entries)); |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | void GPUAsynch::SwapBuffers( | 26 | void GPUAsynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { |
| 27 | std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) { | 27 | gpu_thread.SwapBuffers(framebuffer); |
| 28 | gpu_thread.SwapBuffers(std::move(framebuffer)); | ||
| 29 | } | 28 | } |
| 30 | 29 | ||
| 31 | void GPUAsynch::FlushRegion(CacheAddr addr, u64 size) { | 30 | void GPUAsynch::FlushRegion(CacheAddr addr, u64 size) { |
diff --git a/src/video_core/gpu_asynch.h b/src/video_core/gpu_asynch.h index 36377d677..a12f9bac4 100644 --- a/src/video_core/gpu_asynch.h +++ b/src/video_core/gpu_asynch.h | |||
| @@ -14,15 +14,14 @@ class RendererBase; | |||
| 14 | namespace VideoCommon { | 14 | namespace VideoCommon { |
| 15 | 15 | ||
| 16 | /// Implementation of GPU interface that runs the GPU asynchronously | 16 | /// Implementation of GPU interface that runs the GPU asynchronously |
| 17 | class GPUAsynch : public Tegra::GPU { | 17 | class GPUAsynch final : public Tegra::GPU { |
| 18 | public: | 18 | public: |
| 19 | explicit GPUAsynch(Core::System& system, VideoCore::RendererBase& renderer); | 19 | explicit GPUAsynch(Core::System& system, VideoCore::RendererBase& renderer); |
| 20 | ~GPUAsynch() override; | 20 | ~GPUAsynch() override; |
| 21 | 21 | ||
| 22 | void Start() override; | 22 | void Start() override; |
| 23 | void PushGPUEntries(Tegra::CommandList&& entries) override; | 23 | void PushGPUEntries(Tegra::CommandList&& entries) override; |
| 24 | void SwapBuffers( | 24 | void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; |
| 25 | std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override; | ||
| 26 | void FlushRegion(CacheAddr addr, u64 size) override; | 25 | void FlushRegion(CacheAddr addr, u64 size) override; |
| 27 | void InvalidateRegion(CacheAddr addr, u64 size) override; | 26 | void InvalidateRegion(CacheAddr addr, u64 size) override; |
| 28 | void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; | 27 | void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; |
diff --git a/src/video_core/gpu_synch.cpp b/src/video_core/gpu_synch.cpp index d4ead9c47..d48221077 100644 --- a/src/video_core/gpu_synch.cpp +++ b/src/video_core/gpu_synch.cpp | |||
| @@ -19,9 +19,8 @@ void GPUSynch::PushGPUEntries(Tegra::CommandList&& entries) { | |||
| 19 | dma_pusher->DispatchCalls(); | 19 | dma_pusher->DispatchCalls(); |
| 20 | } | 20 | } |
| 21 | 21 | ||
| 22 | void GPUSynch::SwapBuffers( | 22 | void GPUSynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { |
| 23 | std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) { | 23 | renderer.SwapBuffers(framebuffer); |
| 24 | renderer.SwapBuffers(std::move(framebuffer)); | ||
| 25 | } | 24 | } |
| 26 | 25 | ||
| 27 | void GPUSynch::FlushRegion(CacheAddr addr, u64 size) { | 26 | void GPUSynch::FlushRegion(CacheAddr addr, u64 size) { |
diff --git a/src/video_core/gpu_synch.h b/src/video_core/gpu_synch.h index 07bcc47f1..5eb1c461c 100644 --- a/src/video_core/gpu_synch.h +++ b/src/video_core/gpu_synch.h | |||
| @@ -13,15 +13,14 @@ class RendererBase; | |||
| 13 | namespace VideoCommon { | 13 | namespace VideoCommon { |
| 14 | 14 | ||
| 15 | /// Implementation of GPU interface that runs the GPU synchronously | 15 | /// Implementation of GPU interface that runs the GPU synchronously |
| 16 | class GPUSynch : public Tegra::GPU { | 16 | class GPUSynch final : public Tegra::GPU { |
| 17 | public: | 17 | public: |
| 18 | explicit GPUSynch(Core::System& system, VideoCore::RendererBase& renderer); | 18 | explicit GPUSynch(Core::System& system, VideoCore::RendererBase& renderer); |
| 19 | ~GPUSynch() override; | 19 | ~GPUSynch() override; |
| 20 | 20 | ||
| 21 | void Start() override; | 21 | void Start() override; |
| 22 | void PushGPUEntries(Tegra::CommandList&& entries) override; | 22 | void PushGPUEntries(Tegra::CommandList&& entries) override; |
| 23 | void SwapBuffers( | 23 | void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; |
| 24 | std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override; | ||
| 25 | void FlushRegion(CacheAddr addr, u64 size) override; | 24 | void FlushRegion(CacheAddr addr, u64 size) override; |
| 26 | void InvalidateRegion(CacheAddr addr, u64 size) override; | 25 | void InvalidateRegion(CacheAddr addr, u64 size) override; |
| 27 | void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; | 26 | void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; |
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index b441e92b0..5f039e4fd 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp | |||
| @@ -39,7 +39,7 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p | |||
| 39 | dma_pusher.Push(std::move(submit_list->entries)); | 39 | dma_pusher.Push(std::move(submit_list->entries)); |
| 40 | dma_pusher.DispatchCalls(); | 40 | dma_pusher.DispatchCalls(); |
| 41 | } else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) { | 41 | } else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) { |
| 42 | renderer.SwapBuffers(std::move(data->framebuffer)); | 42 | renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr); |
| 43 | } else if (const auto data = std::get_if<FlushRegionCommand>(&next.data)) { | 43 | } else if (const auto data = std::get_if<FlushRegionCommand>(&next.data)) { |
| 44 | renderer.Rasterizer().FlushRegion(data->addr, data->size); | 44 | renderer.Rasterizer().FlushRegion(data->addr, data->size); |
| 45 | } else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) { | 45 | } else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) { |
| @@ -78,9 +78,9 @@ void ThreadManager::SubmitList(Tegra::CommandList&& entries) { | |||
| 78 | system.CoreTiming().ScheduleEvent(synchronization_ticks, synchronization_event, fence); | 78 | system.CoreTiming().ScheduleEvent(synchronization_ticks, synchronization_event, fence); |
| 79 | } | 79 | } |
| 80 | 80 | ||
| 81 | void ThreadManager::SwapBuffers( | 81 | void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { |
| 82 | std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) { | 82 | PushCommand(SwapBuffersCommand(framebuffer ? *framebuffer |
| 83 | PushCommand(SwapBuffersCommand(std::move(framebuffer))); | 83 | : std::optional<const Tegra::FramebufferConfig>{})); |
| 84 | } | 84 | } |
| 85 | 85 | ||
| 86 | void ThreadManager::FlushRegion(CacheAddr addr, u64 size) { | 86 | void ThreadManager::FlushRegion(CacheAddr addr, u64 size) { |
diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h index 1d9d0c39e..3ae0ec9f3 100644 --- a/src/video_core/gpu_thread.h +++ b/src/video_core/gpu_thread.h | |||
| @@ -110,8 +110,7 @@ public: | |||
| 110 | void SubmitList(Tegra::CommandList&& entries); | 110 | void SubmitList(Tegra::CommandList&& entries); |
| 111 | 111 | ||
| 112 | /// Swap buffers (render frame) | 112 | /// Swap buffers (render frame) |
| 113 | void SwapBuffers( | 113 | void SwapBuffers(const Tegra::FramebufferConfig* framebuffer); |
| 114 | std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer); | ||
| 115 | 114 | ||
| 116 | /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory | 115 | /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory |
| 117 | void FlushRegion(CacheAddr addr, u64 size); | 116 | void FlushRegion(CacheAddr addr, u64 size); |
diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp index 3e91cbc83..084f85e67 100644 --- a/src/video_core/morton.cpp +++ b/src/video_core/morton.cpp | |||
| @@ -25,8 +25,8 @@ static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth | |||
| 25 | 25 | ||
| 26 | // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual | 26 | // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual |
| 27 | // pixel values. | 27 | // pixel values. |
| 28 | const u32 tile_size_x{GetDefaultBlockWidth(format)}; | 28 | constexpr u32 tile_size_x{GetDefaultBlockWidth(format)}; |
| 29 | const u32 tile_size_y{GetDefaultBlockHeight(format)}; | 29 | constexpr u32 tile_size_y{GetDefaultBlockHeight(format)}; |
| 30 | 30 | ||
| 31 | if constexpr (morton_to_linear) { | 31 | if constexpr (morton_to_linear) { |
| 32 | Tegra::Texture::UnswizzleTexture(buffer, addr, tile_size_x, tile_size_y, bytes_per_pixel, | 32 | Tegra::Texture::UnswizzleTexture(buffer, addr, tile_size_x, tile_size_y, bytes_per_pixel, |
| @@ -186,99 +186,6 @@ static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFor | |||
| 186 | return morton_to_linear_fns[static_cast<std::size_t>(format)]; | 186 | return morton_to_linear_fns[static_cast<std::size_t>(format)]; |
| 187 | } | 187 | } |
| 188 | 188 | ||
| 189 | static u32 MortonInterleave128(u32 x, u32 y) { | ||
| 190 | // 128x128 Z-Order coordinate from 2D coordinates | ||
| 191 | static constexpr u32 xlut[] = { | ||
| 192 | 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, | ||
| 193 | 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, | ||
| 194 | 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, | ||
| 195 | 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, | ||
| 196 | 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, | ||
| 197 | 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, | ||
| 198 | 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, | ||
| 199 | 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, | ||
| 200 | 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, | ||
| 201 | 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, | ||
| 202 | 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, | ||
| 203 | 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, | ||
| 204 | 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, | ||
| 205 | 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, | ||
| 206 | 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, | ||
| 207 | 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, | ||
| 208 | 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, | ||
| 209 | 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, | ||
| 210 | 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, | ||
| 211 | 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, | ||
| 212 | 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, | ||
| 213 | 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, | ||
| 214 | 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, | ||
| 215 | 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, | ||
| 216 | 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, | ||
| 217 | 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, | ||
| 218 | 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, | ||
| 219 | 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, | ||
| 220 | 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, | ||
| 221 | 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, | ||
| 222 | 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, | ||
| 223 | 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, | ||
| 224 | 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, | ||
| 225 | 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, | ||
| 226 | 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, | ||
| 227 | }; | ||
| 228 | static constexpr u32 ylut[] = { | ||
| 229 | 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, | ||
| 230 | 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, | ||
| 231 | 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, | ||
| 232 | 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, | ||
| 233 | 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, | ||
| 234 | 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, | ||
| 235 | 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, | ||
| 236 | 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, | ||
| 237 | 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, | ||
| 238 | 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, | ||
| 239 | 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, | ||
| 240 | 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, | ||
| 241 | 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, | ||
| 242 | 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, | ||
| 243 | 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, | ||
| 244 | 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, | ||
| 245 | 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, | ||
| 246 | 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, | ||
| 247 | 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, | ||
| 248 | 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, | ||
| 249 | 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, | ||
| 250 | 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, | ||
| 251 | 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, | ||
| 252 | 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, | ||
| 253 | 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, | ||
| 254 | 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, | ||
| 255 | 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, | ||
| 256 | 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, | ||
| 257 | 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, | ||
| 258 | 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, | ||
| 259 | 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, | ||
| 260 | 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, | ||
| 261 | 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, | ||
| 262 | 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, | ||
| 263 | 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, | ||
| 264 | }; | ||
| 265 | return xlut[x % 128] + ylut[y % 128]; | ||
| 266 | } | ||
| 267 | |||
| 268 | static u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) { | ||
| 269 | // Calculates the offset of the position of the pixel in Morton order | ||
| 270 | // Framebuffer images are split into 128x128 tiles. | ||
| 271 | |||
| 272 | constexpr u32 block_height = 128; | ||
| 273 | const u32 coarse_x = x & ~127; | ||
| 274 | |||
| 275 | const u32 i = MortonInterleave128(x, y); | ||
| 276 | |||
| 277 | const u32 offset = coarse_x * block_height; | ||
| 278 | |||
| 279 | return (i + offset) * bytes_per_pixel; | ||
| 280 | } | ||
| 281 | |||
| 282 | void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stride, | 189 | void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stride, |
| 283 | u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, | 190 | u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, |
| 284 | u8* buffer, u8* addr) { | 191 | u8* buffer, u8* addr) { |
| @@ -286,23 +193,4 @@ void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stri | |||
| 286 | tile_width_spacing, buffer, addr); | 193 | tile_width_spacing, buffer, addr); |
| 287 | } | 194 | } |
| 288 | 195 | ||
| 289 | void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel, | ||
| 290 | u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data) { | ||
| 291 | const bool morton_to_linear = mode == MortonSwizzleMode::MortonToLinear; | ||
| 292 | u8* data_ptrs[2]; | ||
| 293 | for (u32 y = 0; y < height; ++y) { | ||
| 294 | for (u32 x = 0; x < width; ++x) { | ||
| 295 | const u32 coarse_y = y & ~127; | ||
| 296 | const u32 morton_offset = | ||
| 297 | GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; | ||
| 298 | const u32 linear_pixel_index = (x + y * width) * linear_bytes_per_pixel; | ||
| 299 | |||
| 300 | data_ptrs[morton_to_linear ? 1 : 0] = morton_data + morton_offset; | ||
| 301 | data_ptrs[morton_to_linear ? 0 : 1] = &linear_data[linear_pixel_index]; | ||
| 302 | |||
| 303 | std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); | ||
| 304 | } | ||
| 305 | } | ||
| 306 | } | ||
| 307 | |||
| 308 | } // namespace VideoCore | 196 | } // namespace VideoCore |
diff --git a/src/video_core/morton.h b/src/video_core/morton.h index ee5b45555..b714a7e3f 100644 --- a/src/video_core/morton.h +++ b/src/video_core/morton.h | |||
| @@ -15,7 +15,4 @@ void MortonSwizzle(MortonSwizzleMode mode, VideoCore::Surface::PixelFormat forma | |||
| 15 | u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, | 15 | u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, |
| 16 | u8* buffer, u8* addr); | 16 | u8* buffer, u8* addr); |
| 17 | 17 | ||
| 18 | void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel, | ||
| 19 | u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data); | ||
| 20 | |||
| 21 | } // namespace VideoCore | 18 | } // namespace VideoCore |
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 1d54c3723..af1bebc4f 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h | |||
| @@ -36,8 +36,7 @@ public: | |||
| 36 | virtual ~RendererBase(); | 36 | virtual ~RendererBase(); |
| 37 | 37 | ||
| 38 | /// Swap buffers (render frame) | 38 | /// Swap buffers (render frame) |
| 39 | virtual void SwapBuffers( | 39 | virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; |
| 40 | std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) = 0; | ||
| 41 | 40 | ||
| 42 | /// Initialize the renderer | 41 | /// Initialize the renderer |
| 43 | virtual bool Init() = 0; | 42 | virtual bool Init() = 0; |
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 @@ | |||
| 14 | namespace OpenGL { | 14 | namespace OpenGL { |
| 15 | 15 | ||
| 16 | namespace { | 16 | namespace { |
| 17 | |||
| 17 | template <typename T> | 18 | template <typename T> |
| 18 | T GetInteger(GLenum pname) { | 19 | T GetInteger(GLenum pname) { |
| 19 | GLint temporary; | 20 | GLint temporary; |
| 20 | glGetIntegerv(pname, &temporary); | 21 | glGetIntegerv(pname, &temporary); |
| 21 | return static_cast<T>(temporary); | 22 | return static_cast<T>(temporary); |
| 22 | } | 23 | } |
| 24 | |||
| 25 | bool TestProgram(const GLchar* glsl) { | ||
| 26 | const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &glsl)}; | ||
| 27 | GLint link_status; | ||
| 28 | glGetProgramiv(shader, GL_LINK_STATUS, &link_status); | ||
| 29 | glDeleteProgram(shader); | ||
| 30 | return link_status == GL_TRUE; | ||
| 31 | } | ||
| 32 | |||
| 23 | } // Anonymous namespace | 33 | } // Anonymous namespace |
| 24 | 34 | ||
| 25 | Device::Device() { | 35 | Device::Device() { |
| @@ -32,6 +42,11 @@ Device::Device() { | |||
| 32 | has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array; | 42 | has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array; |
| 33 | has_variable_aoffi = TestVariableAoffi(); | 43 | has_variable_aoffi = TestVariableAoffi(); |
| 34 | has_component_indexing_bug = TestComponentIndexingBug(); | 44 | has_component_indexing_bug = TestComponentIndexingBug(); |
| 45 | has_precise_bug = TestPreciseBug(); | ||
| 46 | |||
| 47 | LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi); | ||
| 48 | LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug); | ||
| 49 | LOG_INFO(Render_OpenGL, "Renderer_PreciseBug: {}", has_precise_bug); | ||
| 35 | } | 50 | } |
| 36 | 51 | ||
| 37 | Device::Device(std::nullptr_t) { | 52 | Device::Device(std::nullptr_t) { |
| @@ -42,30 +57,21 @@ Device::Device(std::nullptr_t) { | |||
| 42 | has_vertex_viewport_layer = true; | 57 | has_vertex_viewport_layer = true; |
| 43 | has_variable_aoffi = true; | 58 | has_variable_aoffi = true; |
| 44 | has_component_indexing_bug = false; | 59 | has_component_indexing_bug = false; |
| 60 | has_precise_bug = false; | ||
| 45 | } | 61 | } |
| 46 | 62 | ||
| 47 | bool Device::TestVariableAoffi() { | 63 | bool Device::TestVariableAoffi() { |
| 48 | const GLchar* AOFFI_TEST = R"(#version 430 core | 64 | return TestProgram(R"(#version 430 core |
| 49 | // This is a unit test, please ignore me on apitrace bug reports. | 65 | // This is a unit test, please ignore me on apitrace bug reports. |
| 50 | uniform sampler2D tex; | 66 | uniform sampler2D tex; |
| 51 | uniform ivec2 variable_offset; | 67 | uniform ivec2 variable_offset; |
| 52 | out vec4 output_attribute; | 68 | out vec4 output_attribute; |
| 53 | void main() { | 69 | void main() { |
| 54 | output_attribute = textureOffset(tex, vec2(0), variable_offset); | 70 | output_attribute = textureOffset(tex, vec2(0), variable_offset); |
| 55 | } | 71 | })"); |
| 56 | )"; | ||
| 57 | const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &AOFFI_TEST)}; | ||
| 58 | GLint link_status{}; | ||
| 59 | glGetProgramiv(shader, GL_LINK_STATUS, &link_status); | ||
| 60 | glDeleteProgram(shader); | ||
| 61 | |||
| 62 | const bool supported{link_status == GL_TRUE}; | ||
| 63 | LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", supported); | ||
| 64 | return supported; | ||
| 65 | } | 72 | } |
| 66 | 73 | ||
| 67 | bool Device::TestComponentIndexingBug() { | 74 | bool Device::TestComponentIndexingBug() { |
| 68 | constexpr char log_message[] = "Renderer_ComponentIndexingBug: {}"; | ||
| 69 | const GLchar* COMPONENT_TEST = R"(#version 430 core | 75 | const GLchar* COMPONENT_TEST = R"(#version 430 core |
| 70 | layout (std430, binding = 0) buffer OutputBuffer { | 76 | layout (std430, binding = 0) buffer OutputBuffer { |
| 71 | uint output_value; | 77 | uint output_value; |
| @@ -105,12 +111,21 @@ void main() { | |||
| 105 | GLuint result; | 111 | GLuint result; |
| 106 | glGetNamedBufferSubData(ssbo.handle, 0, sizeof(result), &result); | 112 | glGetNamedBufferSubData(ssbo.handle, 0, sizeof(result), &result); |
| 107 | if (result != values.at(index)) { | 113 | if (result != values.at(index)) { |
| 108 | LOG_INFO(Render_OpenGL, log_message, true); | ||
| 109 | return true; | 114 | return true; |
| 110 | } | 115 | } |
| 111 | } | 116 | } |
| 112 | LOG_INFO(Render_OpenGL, log_message, false); | ||
| 113 | return false; | 117 | return false; |
| 114 | } | 118 | } |
| 115 | 119 | ||
| 120 | bool Device::TestPreciseBug() { | ||
| 121 | return !TestProgram(R"(#version 430 core | ||
| 122 | in vec3 coords; | ||
| 123 | out float out_value; | ||
| 124 | uniform sampler2DShadow tex; | ||
| 125 | void main() { | ||
| 126 | precise float tmp_value = vec4(texture(tex, coords)).x; | ||
| 127 | out_value = tmp_value; | ||
| 128 | })"); | ||
| 129 | } | ||
| 130 | |||
| 116 | } // namespace OpenGL | 131 | } // 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: | |||
| 46 | return has_component_indexing_bug; | 46 | return has_component_indexing_bug; |
| 47 | } | 47 | } |
| 48 | 48 | ||
| 49 | bool HasPreciseBug() const { | ||
| 50 | return has_precise_bug; | ||
| 51 | } | ||
| 52 | |||
| 49 | private: | 53 | private: |
| 50 | static bool TestVariableAoffi(); | 54 | static bool TestVariableAoffi(); |
| 51 | static bool TestComponentIndexingBug(); | 55 | static bool TestComponentIndexingBug(); |
| 56 | static bool TestPreciseBug(); | ||
| 52 | 57 | ||
| 53 | std::size_t uniform_buffer_alignment{}; | 58 | std::size_t uniform_buffer_alignment{}; |
| 54 | std::size_t shader_storage_alignment{}; | 59 | std::size_t shader_storage_alignment{}; |
| @@ -58,6 +63,7 @@ private: | |||
| 58 | bool has_vertex_viewport_layer{}; | 63 | bool has_vertex_viewport_layer{}; |
| 59 | bool has_variable_aoffi{}; | 64 | bool has_variable_aoffi{}; |
| 60 | bool has_component_indexing_bug{}; | 65 | bool has_component_indexing_bug{}; |
| 66 | bool has_precise_bug{}; | ||
| 61 | }; | 67 | }; |
| 62 | 68 | ||
| 63 | } // namespace OpenGL | 69 | } // namespace OpenGL |
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index bb09ecd52..01d89f47d 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp | |||
| @@ -537,8 +537,7 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers( | |||
| 537 | texture_cache.MarkDepthBufferInUse(); | 537 | texture_cache.MarkDepthBufferInUse(); |
| 538 | 538 | ||
| 539 | fbkey.zeta = depth_surface; | 539 | fbkey.zeta = depth_surface; |
| 540 | fbkey.stencil_enable = regs.stencil_enable && | 540 | fbkey.stencil_enable = depth_surface->GetSurfaceParams().type == SurfaceType::DepthStencil; |
| 541 | depth_surface->GetSurfaceParams().type == SurfaceType::DepthStencil; | ||
| 542 | } | 541 | } |
| 543 | 542 | ||
| 544 | texture_cache.GuardRenderTargets(false); | 543 | texture_cache.GuardRenderTargets(false); |
| @@ -577,16 +576,15 @@ void RasterizerOpenGL::ConfigureClearFramebuffer(OpenGLState& current_state, boo | |||
| 577 | if (depth_surface) { | 576 | if (depth_surface) { |
| 578 | const auto& params = depth_surface->GetSurfaceParams(); | 577 | const auto& params = depth_surface->GetSurfaceParams(); |
| 579 | switch (params.type) { | 578 | switch (params.type) { |
| 580 | case VideoCore::Surface::SurfaceType::Depth: { | 579 | case VideoCore::Surface::SurfaceType::Depth: |
| 581 | depth_surface->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER); | 580 | depth_surface->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER); |
| 582 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); | 581 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); |
| 583 | break; | 582 | break; |
| 584 | } | 583 | case VideoCore::Surface::SurfaceType::DepthStencil: |
| 585 | case VideoCore::Surface::SurfaceType::DepthStencil: { | 584 | depth_surface->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_DRAW_FRAMEBUFFER); |
| 586 | depth_surface->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER); | ||
| 587 | break; | 585 | break; |
| 588 | } | 586 | default: |
| 589 | default: { UNIMPLEMENTED(); } | 587 | UNIMPLEMENTED(); |
| 590 | } | 588 | } |
| 591 | } else { | 589 | } else { |
| 592 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, | 590 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, |
| @@ -639,6 +637,7 @@ void RasterizerOpenGL::Clear() { | |||
| 639 | ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!"); | 637 | ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!"); |
| 640 | use_stencil = true; | 638 | use_stencil = true; |
| 641 | clear_state.stencil.test_enabled = true; | 639 | clear_state.stencil.test_enabled = true; |
| 640 | |||
| 642 | if (regs.clear_flags.stencil) { | 641 | if (regs.clear_flags.stencil) { |
| 643 | // Stencil affects the clear so fill it with the used masks | 642 | // Stencil affects the clear so fill it with the used masks |
| 644 | clear_state.stencil.front.test_func = GL_ALWAYS; | 643 | clear_state.stencil.front.test_func = GL_ALWAYS; |
| @@ -1119,9 +1118,12 @@ void RasterizerOpenGL::SyncStencilTestState() { | |||
| 1119 | if (!maxwell3d.dirty.stencil_test) { | 1118 | if (!maxwell3d.dirty.stencil_test) { |
| 1120 | return; | 1119 | return; |
| 1121 | } | 1120 | } |
| 1122 | const auto& regs = maxwell3d.regs; | 1121 | maxwell3d.dirty.stencil_test = false; |
| 1123 | 1122 | ||
| 1123 | const auto& regs = maxwell3d.regs; | ||
| 1124 | state.stencil.test_enabled = regs.stencil_enable != 0; | 1124 | state.stencil.test_enabled = regs.stencil_enable != 0; |
| 1125 | state.MarkDirtyStencilState(); | ||
| 1126 | |||
| 1125 | if (!regs.stencil_enable) { | 1127 | if (!regs.stencil_enable) { |
| 1126 | return; | 1128 | return; |
| 1127 | } | 1129 | } |
| @@ -1150,8 +1152,6 @@ void RasterizerOpenGL::SyncStencilTestState() { | |||
| 1150 | state.stencil.back.action_depth_fail = GL_KEEP; | 1152 | state.stencil.back.action_depth_fail = GL_KEEP; |
| 1151 | state.stencil.back.action_depth_pass = GL_KEEP; | 1153 | state.stencil.back.action_depth_pass = GL_KEEP; |
| 1152 | } | 1154 | } |
| 1153 | state.MarkDirtyStencilState(); | ||
| 1154 | maxwell3d.dirty.stencil_test = false; | ||
| 1155 | } | 1155 | } |
| 1156 | 1156 | ||
| 1157 | void RasterizerOpenGL::SyncColorMask() { | 1157 | void RasterizerOpenGL::SyncColorMask() { |
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, | |||
| 348 | } | 348 | } |
| 349 | 349 | ||
| 350 | std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVariant& variant) { | 350 | std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVariant& variant) { |
| 351 | GLuint handle{}; | 351 | const auto [entry, is_cache_miss] = programs.try_emplace(variant); |
| 352 | if (program_type == ProgramType::Geometry) { | 352 | auto& program = entry->second; |
| 353 | handle = GetGeometryShader(variant); | 353 | if (is_cache_miss) { |
| 354 | } else { | 354 | program = TryLoadProgram(variant); |
| 355 | const auto [entry, is_cache_miss] = programs.try_emplace(variant); | 355 | if (!program) { |
| 356 | auto& program = entry->second; | 356 | program = SpecializeShader(code, entries, program_type, variant); |
| 357 | if (is_cache_miss) { | 357 | disk_cache.SaveUsage(GetUsage(variant)); |
| 358 | program = TryLoadProgram(variant); | ||
| 359 | if (!program) { | ||
| 360 | program = SpecializeShader(code, entries, program_type, variant); | ||
| 361 | disk_cache.SaveUsage(GetUsage(variant)); | ||
| 362 | } | ||
| 363 | |||
| 364 | LabelGLObject(GL_PROGRAM, program->handle, cpu_addr); | ||
| 365 | } | 358 | } |
| 366 | 359 | ||
| 367 | handle = program->handle; | 360 | LabelGLObject(GL_PROGRAM, program->handle, cpu_addr); |
| 368 | } | 361 | } |
| 369 | 362 | ||
| 370 | auto base_bindings = variant.base_bindings; | 363 | auto base_bindings = variant.base_bindings; |
| @@ -375,52 +368,9 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVar | |||
| 375 | base_bindings.gmem += static_cast<u32>(entries.global_memory_entries.size()); | 368 | base_bindings.gmem += static_cast<u32>(entries.global_memory_entries.size()); |
| 376 | base_bindings.sampler += static_cast<u32>(entries.samplers.size()); | 369 | base_bindings.sampler += static_cast<u32>(entries.samplers.size()); |
| 377 | 370 | ||
| 378 | return {handle, base_bindings}; | 371 | return {program->handle, base_bindings}; |
| 379 | } | 372 | } |
| 380 | 373 | ||
| 381 | GLuint CachedShader::GetGeometryShader(const ProgramVariant& variant) { | ||
| 382 | const auto [entry, is_cache_miss] = geometry_programs.try_emplace(variant); | ||
| 383 | auto& programs = entry->second; | ||
| 384 | |||
| 385 | switch (variant.primitive_mode) { | ||
| 386 | case GL_POINTS: | ||
| 387 | return LazyGeometryProgram(programs.points, variant); | ||
| 388 | case GL_LINES: | ||
| 389 | case GL_LINE_STRIP: | ||
| 390 | return LazyGeometryProgram(programs.lines, variant); | ||
| 391 | case GL_LINES_ADJACENCY: | ||
| 392 | case GL_LINE_STRIP_ADJACENCY: | ||
| 393 | return LazyGeometryProgram(programs.lines_adjacency, variant); | ||
| 394 | case GL_TRIANGLES: | ||
| 395 | case GL_TRIANGLE_STRIP: | ||
| 396 | case GL_TRIANGLE_FAN: | ||
| 397 | return LazyGeometryProgram(programs.triangles, variant); | ||
| 398 | case GL_TRIANGLES_ADJACENCY: | ||
| 399 | case GL_TRIANGLE_STRIP_ADJACENCY: | ||
| 400 | return LazyGeometryProgram(programs.triangles_adjacency, variant); | ||
| 401 | default: | ||
| 402 | UNREACHABLE_MSG("Unknown primitive mode."); | ||
| 403 | return LazyGeometryProgram(programs.points, variant); | ||
| 404 | } | ||
| 405 | } | ||
| 406 | |||
| 407 | GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, | ||
| 408 | const ProgramVariant& variant) { | ||
| 409 | if (target_program) { | ||
| 410 | return target_program->handle; | ||
| 411 | } | ||
| 412 | const auto [glsl_name, debug_name, vertices] = GetPrimitiveDescription(variant.primitive_mode); | ||
| 413 | target_program = TryLoadProgram(variant); | ||
| 414 | if (!target_program) { | ||
| 415 | target_program = SpecializeShader(code, entries, program_type, variant); | ||
| 416 | disk_cache.SaveUsage(GetUsage(variant)); | ||
| 417 | } | ||
| 418 | |||
| 419 | LabelGLObject(GL_PROGRAM, target_program->handle, cpu_addr, debug_name); | ||
| 420 | |||
| 421 | return target_program->handle; | ||
| 422 | }; | ||
| 423 | |||
| 424 | CachedProgram CachedShader::TryLoadProgram(const ProgramVariant& variant) const { | 374 | CachedProgram CachedShader::TryLoadProgram(const ProgramVariant& variant) const { |
| 425 | const auto found = precompiled_programs.find(GetUsage(variant)); | 375 | const auto found = precompiled_programs.find(GetUsage(variant)); |
| 426 | if (found == precompiled_programs.end()) { | 376 | 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: | |||
| 86 | explicit CachedShader(const ShaderParameters& params, ProgramType program_type, | 86 | explicit CachedShader(const ShaderParameters& params, ProgramType program_type, |
| 87 | GLShader::ProgramResult result); | 87 | GLShader::ProgramResult result); |
| 88 | 88 | ||
| 89 | // Geometry programs. These are needed because GLSL needs an input topology but it's not | ||
| 90 | // declared by the hardware. Workaround this issue by generating a different shader per input | ||
| 91 | // topology class. | ||
| 92 | struct GeometryPrograms { | ||
| 93 | CachedProgram points; | ||
| 94 | CachedProgram lines; | ||
| 95 | CachedProgram lines_adjacency; | ||
| 96 | CachedProgram triangles; | ||
| 97 | CachedProgram triangles_adjacency; | ||
| 98 | }; | ||
| 99 | |||
| 100 | GLuint GetGeometryShader(const ProgramVariant& variant); | ||
| 101 | |||
| 102 | /// Generates a geometry shader or returns one that already exists. | ||
| 103 | GLuint LazyGeometryProgram(CachedProgram& target_program, const ProgramVariant& variant); | ||
| 104 | |||
| 105 | CachedProgram TryLoadProgram(const ProgramVariant& variant) const; | 89 | CachedProgram TryLoadProgram(const ProgramVariant& variant) const; |
| 106 | 90 | ||
| 107 | ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant) const; | 91 | ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant) const; |
| @@ -117,11 +101,6 @@ private: | |||
| 117 | std::size_t shader_length{}; | 101 | std::size_t shader_length{}; |
| 118 | 102 | ||
| 119 | std::unordered_map<ProgramVariant, CachedProgram> programs; | 103 | std::unordered_map<ProgramVariant, CachedProgram> programs; |
| 120 | std::unordered_map<ProgramVariant, GeometryPrograms> geometry_programs; | ||
| 121 | |||
| 122 | std::unordered_map<u32, GLuint> cbuf_resource_cache; | ||
| 123 | std::unordered_map<u32, GLuint> gmem_resource_cache; | ||
| 124 | std::unordered_map<u32, GLint> uniform_cache; | ||
| 125 | }; | 104 | }; |
| 126 | 105 | ||
| 127 | class ShaderCacheOpenGL final : public RasterizerCache<Shader> { | 106 | 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..a5cc1a86f 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; | |||
| 39 | using Maxwell = Tegra::Engines::Maxwell3D::Regs; | 39 | using Maxwell = Tegra::Engines::Maxwell3D::Regs; |
| 40 | using Operation = const OperationNode&; | 40 | using Operation = const OperationNode&; |
| 41 | 41 | ||
| 42 | enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat }; | 42 | enum class Type { Void, Bool, Bool2, Float, Int, Uint, HalfFloat }; |
| 43 | 43 | ||
| 44 | struct TextureAoffi {}; | 44 | struct TextureAoffi {}; |
| 45 | using TextureArgument = std::pair<Type, Node>; | 45 | using TextureArgument = std::pair<Type, Node>; |
| @@ -48,7 +48,7 @@ using TextureIR = std::variant<TextureAoffi, TextureArgument>; | |||
| 48 | constexpr u32 MAX_CONSTBUFFER_ELEMENTS = | 48 | constexpr u32 MAX_CONSTBUFFER_ELEMENTS = |
| 49 | static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float)); | 49 | static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float)); |
| 50 | 50 | ||
| 51 | class ShaderWriter { | 51 | class ShaderWriter final { |
| 52 | public: | 52 | public: |
| 53 | void AddExpression(std::string_view text) { | 53 | void AddExpression(std::string_view text) { |
| 54 | DEBUG_ASSERT(scope >= 0); | 54 | DEBUG_ASSERT(scope >= 0); |
| @@ -93,9 +93,157 @@ private: | |||
| 93 | u32 temporary_index = 1; | 93 | u32 temporary_index = 1; |
| 94 | }; | 94 | }; |
| 95 | 95 | ||
| 96 | class Expression final { | ||
| 97 | public: | ||
| 98 | Expression(std::string code, Type type) : code{std::move(code)}, type{type} { | ||
| 99 | ASSERT(type != Type::Void); | ||
| 100 | } | ||
| 101 | Expression() : type{Type::Void} {} | ||
| 102 | |||
| 103 | Type GetType() const { | ||
| 104 | return type; | ||
| 105 | } | ||
| 106 | |||
| 107 | std::string GetCode() const { | ||
| 108 | return code; | ||
| 109 | } | ||
| 110 | |||
| 111 | void CheckVoid() const { | ||
| 112 | ASSERT(type == Type::Void); | ||
| 113 | } | ||
| 114 | |||
| 115 | std::string As(Type type) const { | ||
| 116 | switch (type) { | ||
| 117 | case Type::Bool: | ||
| 118 | return AsBool(); | ||
| 119 | case Type::Bool2: | ||
| 120 | return AsBool2(); | ||
| 121 | case Type::Float: | ||
| 122 | return AsFloat(); | ||
| 123 | case Type::Int: | ||
| 124 | return AsInt(); | ||
| 125 | case Type::Uint: | ||
| 126 | return AsUint(); | ||
| 127 | case Type::HalfFloat: | ||
| 128 | return AsHalfFloat(); | ||
| 129 | default: | ||
| 130 | UNREACHABLE_MSG("Invalid type"); | ||
| 131 | return code; | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | std::string AsBool() const { | ||
| 136 | switch (type) { | ||
| 137 | case Type::Bool: | ||
| 138 | return code; | ||
| 139 | default: | ||
| 140 | UNREACHABLE_MSG("Incompatible types"); | ||
| 141 | return code; | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | std::string AsBool2() const { | ||
| 146 | switch (type) { | ||
| 147 | case Type::Bool2: | ||
| 148 | return code; | ||
| 149 | default: | ||
| 150 | UNREACHABLE_MSG("Incompatible types"); | ||
| 151 | return code; | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | std::string AsFloat() const { | ||
| 156 | switch (type) { | ||
| 157 | case Type::Float: | ||
| 158 | return code; | ||
| 159 | case Type::Uint: | ||
| 160 | return fmt::format("utof({})", code); | ||
| 161 | case Type::Int: | ||
| 162 | return fmt::format("itof({})", code); | ||
| 163 | case Type::HalfFloat: | ||
| 164 | return fmt::format("utof(packHalf2x16({}))", code); | ||
| 165 | default: | ||
| 166 | UNREACHABLE_MSG("Incompatible types"); | ||
| 167 | return code; | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | std::string AsInt() const { | ||
| 172 | switch (type) { | ||
| 173 | case Type::Float: | ||
| 174 | return fmt::format("ftoi({})", code); | ||
| 175 | case Type::Uint: | ||
| 176 | return fmt::format("int({})", code); | ||
| 177 | case Type::Int: | ||
| 178 | return code; | ||
| 179 | case Type::HalfFloat: | ||
| 180 | return fmt::format("int(packHalf2x16({}))", code); | ||
| 181 | default: | ||
| 182 | UNREACHABLE_MSG("Incompatible types"); | ||
| 183 | return code; | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | std::string AsUint() const { | ||
| 188 | switch (type) { | ||
| 189 | case Type::Float: | ||
| 190 | return fmt::format("ftou({})", code); | ||
| 191 | case Type::Uint: | ||
| 192 | return code; | ||
| 193 | case Type::Int: | ||
| 194 | return fmt::format("uint({})", code); | ||
| 195 | case Type::HalfFloat: | ||
| 196 | return fmt::format("packHalf2x16({})", code); | ||
| 197 | default: | ||
| 198 | UNREACHABLE_MSG("Incompatible types"); | ||
| 199 | return code; | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | std::string AsHalfFloat() const { | ||
| 204 | switch (type) { | ||
| 205 | case Type::Float: | ||
| 206 | return fmt::format("unpackHalf2x16(ftou({}))", code); | ||
| 207 | case Type::Uint: | ||
| 208 | return fmt::format("unpackHalf2x16({})", code); | ||
| 209 | case Type::Int: | ||
| 210 | return fmt::format("unpackHalf2x16(int({}))", code); | ||
| 211 | case Type::HalfFloat: | ||
| 212 | return code; | ||
| 213 | default: | ||
| 214 | UNREACHABLE_MSG("Incompatible types"); | ||
| 215 | return code; | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | private: | ||
| 220 | std::string code; | ||
| 221 | Type type{}; | ||
| 222 | }; | ||
| 223 | |||
| 224 | constexpr const char* GetTypeString(Type type) { | ||
| 225 | switch (type) { | ||
| 226 | case Type::Bool: | ||
| 227 | return "bool"; | ||
| 228 | case Type::Bool2: | ||
| 229 | return "bvec2"; | ||
| 230 | case Type::Float: | ||
| 231 | return "float"; | ||
| 232 | case Type::Int: | ||
| 233 | return "int"; | ||
| 234 | case Type::Uint: | ||
| 235 | return "uint"; | ||
| 236 | case Type::HalfFloat: | ||
| 237 | return "vec2"; | ||
| 238 | default: | ||
| 239 | UNREACHABLE_MSG("Invalid type"); | ||
| 240 | return "<invalid type>"; | ||
| 241 | } | ||
| 242 | } | ||
| 243 | |||
| 96 | /// Generates code to use for a swizzle operation. | 244 | /// Generates code to use for a swizzle operation. |
| 97 | constexpr const char* GetSwizzle(u32 element) { | 245 | constexpr const char* GetSwizzle(u32 element) { |
| 98 | constexpr std::array<const char*, 4> swizzle = {".x", ".y", ".z", ".w"}; | 246 | constexpr std::array swizzle = {".x", ".y", ".z", ".w"}; |
| 99 | return swizzle.at(element); | 247 | return swizzle.at(element); |
| 100 | } | 248 | } |
| 101 | 249 | ||
| @@ -134,8 +282,8 @@ constexpr bool IsGenericAttribute(Attribute::Index index) { | |||
| 134 | return index >= Attribute::Index::Attribute_0 && index <= Attribute::Index::Attribute_31; | 282 | return index >= Attribute::Index::Attribute_0 && index <= Attribute::Index::Attribute_31; |
| 135 | } | 283 | } |
| 136 | 284 | ||
| 137 | constexpr Attribute::Index ToGenericAttribute(u32 value) { | 285 | constexpr Attribute::Index ToGenericAttribute(u64 value) { |
| 138 | return static_cast<Attribute::Index>(value + static_cast<u32>(Attribute::Index::Attribute_0)); | 286 | return static_cast<Attribute::Index>(value + static_cast<u64>(Attribute::Index::Attribute_0)); |
| 139 | } | 287 | } |
| 140 | 288 | ||
| 141 | u32 GetGenericAttributeIndex(Attribute::Index index) { | 289 | u32 GetGenericAttributeIndex(Attribute::Index index) { |
| @@ -191,7 +339,7 @@ public: | |||
| 191 | 339 | ||
| 192 | // VM's program counter | 340 | // VM's program counter |
| 193 | const auto first_address = ir.GetBasicBlocks().begin()->first; | 341 | const auto first_address = ir.GetBasicBlocks().begin()->first; |
| 194 | code.AddLine("uint jmp_to = {}u;", first_address); | 342 | code.AddLine("uint jmp_to = {}U;", first_address); |
| 195 | 343 | ||
| 196 | // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems | 344 | // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems |
| 197 | // unlikely that shaders will use 20 nested SSYs and PBKs. | 345 | // unlikely that shaders will use 20 nested SSYs and PBKs. |
| @@ -199,7 +347,7 @@ public: | |||
| 199 | constexpr u32 FLOW_STACK_SIZE = 20; | 347 | constexpr u32 FLOW_STACK_SIZE = 20; |
| 200 | for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) { | 348 | for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) { |
| 201 | code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE); | 349 | code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE); |
| 202 | code.AddLine("uint {} = 0u;", FlowStackTopName(stack)); | 350 | code.AddLine("uint {} = 0U;", FlowStackTopName(stack)); |
| 203 | } | 351 | } |
| 204 | } | 352 | } |
| 205 | 353 | ||
| @@ -210,7 +358,7 @@ public: | |||
| 210 | 358 | ||
| 211 | for (const auto& pair : ir.GetBasicBlocks()) { | 359 | for (const auto& pair : ir.GetBasicBlocks()) { |
| 212 | const auto [address, bb] = pair; | 360 | const auto [address, bb] = pair; |
| 213 | code.AddLine("case 0x{:x}u: {{", address); | 361 | code.AddLine("case 0x{:X}U: {{", address); |
| 214 | ++code.scope; | 362 | ++code.scope; |
| 215 | 363 | ||
| 216 | VisitBlock(bb); | 364 | VisitBlock(bb); |
| @@ -322,7 +470,7 @@ private: | |||
| 322 | void DeclareRegisters() { | 470 | void DeclareRegisters() { |
| 323 | const auto& registers = ir.GetRegisters(); | 471 | const auto& registers = ir.GetRegisters(); |
| 324 | for (const u32 gpr : registers) { | 472 | for (const u32 gpr : registers) { |
| 325 | code.AddLine("float {} = 0;", GetRegister(gpr)); | 473 | code.AddLine("float {} = 0.0f;", GetRegister(gpr)); |
| 326 | } | 474 | } |
| 327 | if (!registers.empty()) { | 475 | if (!registers.empty()) { |
| 328 | code.AddNewLine(); | 476 | code.AddNewLine(); |
| @@ -348,7 +496,7 @@ private: | |||
| 348 | return; | 496 | return; |
| 349 | } | 497 | } |
| 350 | const auto element_count = Common::AlignUp(local_memory_size, 4) / 4; | 498 | const auto element_count = Common::AlignUp(local_memory_size, 4) / 4; |
| 351 | code.AddLine("float {}[{}];", GetLocalMemory(), element_count); | 499 | code.AddLine("uint {}[{}];", GetLocalMemory(), element_count); |
| 352 | code.AddNewLine(); | 500 | code.AddNewLine(); |
| 353 | } | 501 | } |
| 354 | 502 | ||
| @@ -371,8 +519,6 @@ private: | |||
| 371 | return "noperspective "; | 519 | return "noperspective "; |
| 372 | default: | 520 | default: |
| 373 | case AttributeUse::Unused: | 521 | case AttributeUse::Unused: |
| 374 | UNREACHABLE_MSG("Unused attribute being fetched"); | ||
| 375 | return {}; | ||
| 376 | UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<u32>(attribute)); | 522 | UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<u32>(attribute)); |
| 377 | return {}; | 523 | return {}; |
| 378 | } | 524 | } |
| @@ -449,7 +595,7 @@ private: | |||
| 449 | const auto [index, size] = entry; | 595 | const auto [index, size] = entry; |
| 450 | code.AddLine("layout (std140, binding = CBUF_BINDING_{}) uniform {} {{", index, | 596 | code.AddLine("layout (std140, binding = CBUF_BINDING_{}) uniform {} {{", index, |
| 451 | GetConstBufferBlock(index)); | 597 | GetConstBufferBlock(index)); |
| 452 | code.AddLine(" vec4 {}[MAX_CONSTBUFFER_ELEMENTS];", GetConstBuffer(index)); | 598 | code.AddLine(" uvec4 {}[{}];", GetConstBuffer(index), MAX_CONSTBUFFER_ELEMENTS); |
| 453 | code.AddLine("}};"); | 599 | code.AddLine("}};"); |
| 454 | code.AddNewLine(); | 600 | code.AddNewLine(); |
| 455 | } | 601 | } |
| @@ -470,7 +616,7 @@ private: | |||
| 470 | 616 | ||
| 471 | code.AddLine("layout (std430, binding = GMEM_BINDING_{}_{}) {} buffer {} {{", | 617 | code.AddLine("layout (std430, binding = GMEM_BINDING_{}_{}) {} buffer {} {{", |
| 472 | base.cbuf_index, base.cbuf_offset, qualifier, GetGlobalMemoryBlock(base)); | 618 | base.cbuf_index, base.cbuf_offset, qualifier, GetGlobalMemoryBlock(base)); |
| 473 | code.AddLine(" float {}[];", GetGlobalMemory(base)); | 619 | code.AddLine(" uint {}[];", GetGlobalMemory(base)); |
| 474 | code.AddLine("}};"); | 620 | code.AddLine("}};"); |
| 475 | code.AddNewLine(); | 621 | code.AddNewLine(); |
| 476 | } | 622 | } |
| @@ -528,7 +674,7 @@ private: | |||
| 528 | if (!ir.HasPhysicalAttributes()) { | 674 | if (!ir.HasPhysicalAttributes()) { |
| 529 | return; | 675 | return; |
| 530 | } | 676 | } |
| 531 | code.AddLine("float readPhysicalAttribute(uint physical_address) {{"); | 677 | code.AddLine("float ReadPhysicalAttribute(uint physical_address) {{"); |
| 532 | ++code.scope; | 678 | ++code.scope; |
| 533 | code.AddLine("switch (physical_address) {{"); | 679 | code.AddLine("switch (physical_address) {{"); |
| 534 | 680 | ||
| @@ -537,15 +683,16 @@ private: | |||
| 537 | for (u32 index = 0; index < num_attributes; ++index) { | 683 | for (u32 index = 0; index < num_attributes; ++index) { |
| 538 | const auto attribute{ToGenericAttribute(index)}; | 684 | const auto attribute{ToGenericAttribute(index)}; |
| 539 | for (u32 element = 0; element < 4; ++element) { | 685 | for (u32 element = 0; element < 4; ++element) { |
| 540 | constexpr u32 generic_base{0x80}; | 686 | constexpr u32 generic_base = 0x80; |
| 541 | constexpr u32 generic_stride{16}; | 687 | constexpr u32 generic_stride = 16; |
| 542 | constexpr u32 element_stride{4}; | 688 | constexpr u32 element_stride = 4; |
| 543 | const u32 address{generic_base + index * generic_stride + element * element_stride}; | 689 | const u32 address{generic_base + index * generic_stride + element * element_stride}; |
| 544 | 690 | ||
| 545 | const bool declared{stage != ProgramType::Fragment || | 691 | const bool declared = stage != ProgramType::Fragment || |
| 546 | header.ps.GetAttributeUse(index) != AttributeUse::Unused}; | 692 | header.ps.GetAttributeUse(index) != AttributeUse::Unused; |
| 547 | const std::string value{declared ? ReadAttribute(attribute, element) : "0"}; | 693 | const std::string value = |
| 548 | code.AddLine("case 0x{:x}: return {};", address, value); | 694 | declared ? ReadAttribute(attribute, element).AsFloat() : "0.0f"; |
| 695 | code.AddLine("case 0x{:X}U: return {};", address, value); | ||
| 549 | } | 696 | } |
| 550 | } | 697 | } |
| 551 | 698 | ||
| @@ -590,13 +737,11 @@ private: | |||
| 590 | 737 | ||
| 591 | void VisitBlock(const NodeBlock& bb) { | 738 | void VisitBlock(const NodeBlock& bb) { |
| 592 | for (const auto& node : bb) { | 739 | for (const auto& node : bb) { |
| 593 | if (const std::string expr = Visit(node); !expr.empty()) { | 740 | Visit(node).CheckVoid(); |
| 594 | code.AddLine(expr); | ||
| 595 | } | ||
| 596 | } | 741 | } |
| 597 | } | 742 | } |
| 598 | 743 | ||
| 599 | std::string Visit(const Node& node) { | 744 | Expression Visit(const Node& node) { |
| 600 | if (const auto operation = std::get_if<OperationNode>(&*node)) { | 745 | if (const auto operation = std::get_if<OperationNode>(&*node)) { |
| 601 | const auto operation_index = static_cast<std::size_t>(operation->GetCode()); | 746 | const auto operation_index = static_cast<std::size_t>(operation->GetCode()); |
| 602 | if (operation_index >= operation_decompilers.size()) { | 747 | if (operation_index >= operation_decompilers.size()) { |
| @@ -614,18 +759,18 @@ private: | |||
| 614 | if (const auto gpr = std::get_if<GprNode>(&*node)) { | 759 | if (const auto gpr = std::get_if<GprNode>(&*node)) { |
| 615 | const u32 index = gpr->GetIndex(); | 760 | const u32 index = gpr->GetIndex(); |
| 616 | if (index == Register::ZeroIndex) { | 761 | if (index == Register::ZeroIndex) { |
| 617 | return "0"; | 762 | return {"0U", Type::Uint}; |
| 618 | } | 763 | } |
| 619 | return GetRegister(index); | 764 | return {GetRegister(index), Type::Float}; |
| 620 | } | 765 | } |
| 621 | 766 | ||
| 622 | if (const auto immediate = std::get_if<ImmediateNode>(&*node)) { | 767 | if (const auto immediate = std::get_if<ImmediateNode>(&*node)) { |
| 623 | const u32 value = immediate->GetValue(); | 768 | const u32 value = immediate->GetValue(); |
| 624 | if (value < 10) { | 769 | if (value < 10) { |
| 625 | // For eyecandy avoid using hex numbers on single digits | 770 | // For eyecandy avoid using hex numbers on single digits |
| 626 | return fmt::format("utof({}u)", immediate->GetValue()); | 771 | return {fmt::format("{}U", immediate->GetValue()), Type::Uint}; |
| 627 | } | 772 | } |
| 628 | return fmt::format("utof(0x{:x}u)", immediate->GetValue()); | 773 | return {fmt::format("0x{:X}U", immediate->GetValue()), Type::Uint}; |
| 629 | } | 774 | } |
| 630 | 775 | ||
| 631 | if (const auto predicate = std::get_if<PredicateNode>(&*node)) { | 776 | if (const auto predicate = std::get_if<PredicateNode>(&*node)) { |
| @@ -640,17 +785,18 @@ private: | |||
| 640 | } | 785 | } |
| 641 | }(); | 786 | }(); |
| 642 | if (predicate->IsNegated()) { | 787 | if (predicate->IsNegated()) { |
| 643 | return fmt::format("!({})", value); | 788 | return {fmt::format("!({})", value), Type::Bool}; |
| 644 | } | 789 | } |
| 645 | return value; | 790 | return {value, Type::Bool}; |
| 646 | } | 791 | } |
| 647 | 792 | ||
| 648 | if (const auto abuf = std::get_if<AbufNode>(&*node)) { | 793 | if (const auto abuf = std::get_if<AbufNode>(&*node)) { |
| 649 | UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ProgramType::Geometry, | 794 | UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ProgramType::Geometry, |
| 650 | "Physical attributes in geometry shaders are not implemented"); | 795 | "Physical attributes in geometry shaders are not implemented"); |
| 651 | if (abuf->IsPhysicalBuffer()) { | 796 | if (abuf->IsPhysicalBuffer()) { |
| 652 | return fmt::format("readPhysicalAttribute(ftou({}))", | 797 | return {fmt::format("ReadPhysicalAttribute({})", |
| 653 | Visit(abuf->GetPhysicalAddress())); | 798 | Visit(abuf->GetPhysicalAddress()).AsUint()), |
| 799 | Type::Float}; | ||
| 654 | } | 800 | } |
| 655 | return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer()); | 801 | return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer()); |
| 656 | } | 802 | } |
| @@ -661,59 +807,64 @@ private: | |||
| 661 | // Direct access | 807 | // Direct access |
| 662 | const u32 offset_imm = immediate->GetValue(); | 808 | const u32 offset_imm = immediate->GetValue(); |
| 663 | ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access"); | 809 | ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access"); |
| 664 | return fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), | 810 | return {fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), |
| 665 | offset_imm / (4 * 4), (offset_imm / 4) % 4); | 811 | offset_imm / (4 * 4), (offset_imm / 4) % 4), |
| 812 | Type::Uint}; | ||
| 666 | } | 813 | } |
| 667 | 814 | ||
| 668 | if (std::holds_alternative<OperationNode>(*offset)) { | 815 | if (std::holds_alternative<OperationNode>(*offset)) { |
| 669 | // Indirect access | 816 | // Indirect access |
| 670 | const std::string final_offset = code.GenerateTemporary(); | 817 | const std::string final_offset = code.GenerateTemporary(); |
| 671 | code.AddLine("uint {} = ftou({}) >> 2;", final_offset, Visit(offset)); | 818 | code.AddLine("uint {} = {} >> 2;", final_offset, Visit(offset).AsUint()); |
| 672 | 819 | ||
| 673 | if (!device.HasComponentIndexingBug()) { | 820 | if (!device.HasComponentIndexingBug()) { |
| 674 | return fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()), | 821 | return {fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()), |
| 675 | final_offset, final_offset); | 822 | final_offset, final_offset), |
| 823 | Type::Uint}; | ||
| 676 | } | 824 | } |
| 677 | 825 | ||
| 678 | // AMD's proprietary GLSL compiler emits ill code for variable component access. | 826 | // AMD's proprietary GLSL compiler emits ill code for variable component access. |
| 679 | // To bypass this driver bug generate 4 ifs, one per each component. | 827 | // To bypass this driver bug generate 4 ifs, one per each component. |
| 680 | const std::string pack = code.GenerateTemporary(); | 828 | const std::string pack = code.GenerateTemporary(); |
| 681 | code.AddLine("vec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()), | 829 | code.AddLine("uvec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()), |
| 682 | final_offset); | 830 | final_offset); |
| 683 | 831 | ||
| 684 | const std::string result = code.GenerateTemporary(); | 832 | const std::string result = code.GenerateTemporary(); |
| 685 | code.AddLine("float {};", result); | 833 | code.AddLine("uint {};", result); |
| 686 | for (u32 swizzle = 0; swizzle < 4; ++swizzle) { | 834 | for (u32 swizzle = 0; swizzle < 4; ++swizzle) { |
| 687 | code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result, | 835 | code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result, |
| 688 | pack, GetSwizzle(swizzle)); | 836 | pack, GetSwizzle(swizzle)); |
| 689 | } | 837 | } |
| 690 | return result; | 838 | return {result, Type::Uint}; |
| 691 | } | 839 | } |
| 692 | 840 | ||
| 693 | UNREACHABLE_MSG("Unmanaged offset node type"); | 841 | UNREACHABLE_MSG("Unmanaged offset node type"); |
| 694 | } | 842 | } |
| 695 | 843 | ||
| 696 | if (const auto gmem = std::get_if<GmemNode>(&*node)) { | 844 | if (const auto gmem = std::get_if<GmemNode>(&*node)) { |
| 697 | const std::string real = Visit(gmem->GetRealAddress()); | 845 | const std::string real = Visit(gmem->GetRealAddress()).AsUint(); |
| 698 | const std::string base = Visit(gmem->GetBaseAddress()); | 846 | const std::string base = Visit(gmem->GetBaseAddress()).AsUint(); |
| 699 | const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); | 847 | const std::string final_offset = fmt::format("({} - {}) >> 2", real, base); |
| 700 | return fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); | 848 | return {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset), |
| 849 | Type::Uint}; | ||
| 701 | } | 850 | } |
| 702 | 851 | ||
| 703 | if (const auto lmem = std::get_if<LmemNode>(&*node)) { | 852 | if (const auto lmem = std::get_if<LmemNode>(&*node)) { |
| 704 | if (stage == ProgramType::Compute) { | 853 | if (stage == ProgramType::Compute) { |
| 705 | LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); | 854 | LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); |
| 706 | } | 855 | } |
| 707 | return fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); | 856 | return { |
| 857 | fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()), | ||
| 858 | Type::Uint}; | ||
| 708 | } | 859 | } |
| 709 | 860 | ||
| 710 | if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) { | 861 | if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) { |
| 711 | return GetInternalFlag(internal_flag->GetFlag()); | 862 | return {GetInternalFlag(internal_flag->GetFlag()), Type::Bool}; |
| 712 | } | 863 | } |
| 713 | 864 | ||
| 714 | if (const auto conditional = std::get_if<ConditionalNode>(&*node)) { | 865 | if (const auto conditional = std::get_if<ConditionalNode>(&*node)) { |
| 715 | // It's invalid to call conditional on nested nodes, use an operation instead | 866 | // It's invalid to call conditional on nested nodes, use an operation instead |
| 716 | code.AddLine("if ({}) {{", Visit(conditional->GetCondition())); | 867 | code.AddLine("if ({}) {{", Visit(conditional->GetCondition()).AsBool()); |
| 717 | ++code.scope; | 868 | ++code.scope; |
| 718 | 869 | ||
| 719 | VisitBlock(conditional->GetCode()); | 870 | VisitBlock(conditional->GetCode()); |
| @@ -724,20 +875,21 @@ private: | |||
| 724 | } | 875 | } |
| 725 | 876 | ||
| 726 | if (const auto comment = std::get_if<CommentNode>(&*node)) { | 877 | if (const auto comment = std::get_if<CommentNode>(&*node)) { |
| 727 | return "// " + comment->GetText(); | 878 | code.AddLine("// " + comment->GetText()); |
| 879 | return {}; | ||
| 728 | } | 880 | } |
| 729 | 881 | ||
| 730 | UNREACHABLE(); | 882 | UNREACHABLE(); |
| 731 | return {}; | 883 | return {}; |
| 732 | } | 884 | } |
| 733 | 885 | ||
| 734 | std::string ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) { | 886 | Expression ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) { |
| 735 | const auto GeometryPass = [&](std::string_view name) { | 887 | const auto GeometryPass = [&](std::string_view name) { |
| 736 | if (stage == ProgramType::Geometry && buffer) { | 888 | if (stage == ProgramType::Geometry && buffer) { |
| 737 | // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games | 889 | // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games |
| 738 | // set an 0x80000000 index for those and the shader fails to build. Find out why | 890 | // set an 0x80000000 index for those and the shader fails to build. Find out why |
| 739 | // this happens and what's its intent. | 891 | // this happens and what's its intent. |
| 740 | return fmt::format("gs_{}[ftou({}) % MAX_VERTEX_INPUT]", name, Visit(buffer)); | 892 | return fmt::format("gs_{}[{} % MAX_VERTEX_INPUT]", name, Visit(buffer).AsUint()); |
| 741 | } | 893 | } |
| 742 | return std::string(name); | 894 | return std::string(name); |
| 743 | }; | 895 | }; |
| @@ -746,25 +898,27 @@ private: | |||
| 746 | case Attribute::Index::Position: | 898 | case Attribute::Index::Position: |
| 747 | switch (stage) { | 899 | switch (stage) { |
| 748 | case ProgramType::Geometry: | 900 | case ProgramType::Geometry: |
| 749 | return fmt::format("gl_in[ftou({})].gl_Position{}", Visit(buffer), | 901 | return {fmt::format("gl_in[{}].gl_Position{}", Visit(buffer).AsUint(), |
| 750 | GetSwizzle(element)); | 902 | GetSwizzle(element)), |
| 903 | Type::Float}; | ||
| 751 | case ProgramType::Fragment: | 904 | case ProgramType::Fragment: |
| 752 | return element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)); | 905 | return {element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)), |
| 906 | Type::Float}; | ||
| 753 | default: | 907 | default: |
| 754 | UNREACHABLE(); | 908 | UNREACHABLE(); |
| 755 | } | 909 | } |
| 756 | case Attribute::Index::PointCoord: | 910 | case Attribute::Index::PointCoord: |
| 757 | switch (element) { | 911 | switch (element) { |
| 758 | case 0: | 912 | case 0: |
| 759 | return "gl_PointCoord.x"; | 913 | return {"gl_PointCoord.x", Type::Float}; |
| 760 | case 1: | 914 | case 1: |
| 761 | return "gl_PointCoord.y"; | 915 | return {"gl_PointCoord.y", Type::Float}; |
| 762 | case 2: | 916 | case 2: |
| 763 | case 3: | 917 | case 3: |
| 764 | return "0"; | 918 | return {"0.0f", Type::Float}; |
| 765 | } | 919 | } |
| 766 | UNREACHABLE(); | 920 | UNREACHABLE(); |
| 767 | return "0"; | 921 | return {"0", Type::Int}; |
| 768 | case Attribute::Index::TessCoordInstanceIDVertexID: | 922 | case Attribute::Index::TessCoordInstanceIDVertexID: |
| 769 | // TODO(Subv): Find out what the values are for the first two elements when inside a | 923 | // TODO(Subv): Find out what the values are for the first two elements when inside a |
| 770 | // vertex shader, and what's the value of the fourth element when inside a Tess Eval | 924 | // vertex shader, and what's the value of the fourth element when inside a Tess Eval |
| @@ -773,44 +927,49 @@ private: | |||
| 773 | switch (element) { | 927 | switch (element) { |
| 774 | case 2: | 928 | case 2: |
| 775 | // Config pack's first value is instance_id. | 929 | // Config pack's first value is instance_id. |
| 776 | return "uintBitsToFloat(config_pack[0])"; | 930 | return {"config_pack[0]", Type::Uint}; |
| 777 | case 3: | 931 | case 3: |
| 778 | return "uintBitsToFloat(gl_VertexID)"; | 932 | return {"gl_VertexID", Type::Int}; |
| 779 | } | 933 | } |
| 780 | UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element); | 934 | UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element); |
| 781 | return "0"; | 935 | return {"0", Type::Int}; |
| 782 | case Attribute::Index::FrontFacing: | 936 | case Attribute::Index::FrontFacing: |
| 783 | // TODO(Subv): Find out what the values are for the other elements. | 937 | // TODO(Subv): Find out what the values are for the other elements. |
| 784 | ASSERT(stage == ProgramType::Fragment); | 938 | ASSERT(stage == ProgramType::Fragment); |
| 785 | switch (element) { | 939 | switch (element) { |
| 786 | case 3: | 940 | case 3: |
| 787 | return "itof(gl_FrontFacing ? -1 : 0)"; | 941 | return {"(gl_FrontFacing ? -1 : 0)", Type::Int}; |
| 788 | } | 942 | } |
| 789 | UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element); | 943 | UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element); |
| 790 | return "0"; | 944 | return {"0", Type::Int}; |
| 791 | default: | 945 | default: |
| 792 | if (IsGenericAttribute(attribute)) { | 946 | if (IsGenericAttribute(attribute)) { |
| 793 | return GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element); | 947 | return {GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element), |
| 948 | Type::Float}; | ||
| 794 | } | 949 | } |
| 795 | break; | 950 | break; |
| 796 | } | 951 | } |
| 797 | UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); | 952 | UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); |
| 798 | return "0"; | 953 | return {"0", Type::Int}; |
| 799 | } | 954 | } |
| 800 | 955 | ||
| 801 | std::string ApplyPrecise(Operation operation, const std::string& value) { | 956 | Expression ApplyPrecise(Operation operation, std::string value, Type type) { |
| 802 | if (!IsPrecise(operation)) { | 957 | if (!IsPrecise(operation)) { |
| 803 | return value; | 958 | return {std::move(value), type}; |
| 804 | } | 959 | } |
| 805 | // There's a bug in NVidia's proprietary drivers that makes precise fail on fragment shaders | 960 | // Old Nvidia drivers have a bug with precise and texture sampling. These are more likely to |
| 806 | const std::string precise = stage != ProgramType::Fragment ? "precise " : ""; | 961 | // be found in fragment shaders, so we disable precise there. There are vertex shaders that |
| 962 | // also fail to build but nobody seems to care about those. | ||
| 963 | // Note: Only bugged drivers will skip precise. | ||
| 964 | const bool disable_precise = device.HasPreciseBug() && stage == ProgramType::Fragment; | ||
| 807 | 965 | ||
| 808 | const std::string temporary = code.GenerateTemporary(); | 966 | std::string temporary = code.GenerateTemporary(); |
| 809 | code.AddLine("{}float {} = {};", precise, temporary, value); | 967 | code.AddLine("{}{} {} = {};", disable_precise ? "" : "precise ", GetTypeString(type), |
| 810 | return temporary; | 968 | temporary, value); |
| 969 | return {std::move(temporary), type}; | ||
| 811 | } | 970 | } |
| 812 | 971 | ||
| 813 | std::string VisitOperand(Operation operation, std::size_t operand_index) { | 972 | Expression VisitOperand(Operation operation, std::size_t operand_index) { |
| 814 | const auto& operand = operation[operand_index]; | 973 | const auto& operand = operation[operand_index]; |
| 815 | const bool parent_precise = IsPrecise(operation); | 974 | const bool parent_precise = IsPrecise(operation); |
| 816 | const bool child_precise = IsPrecise(operand); | 975 | const bool child_precise = IsPrecise(operand); |
| @@ -819,19 +978,16 @@ private: | |||
| 819 | return Visit(operand); | 978 | return Visit(operand); |
| 820 | } | 979 | } |
| 821 | 980 | ||
| 822 | const std::string temporary = code.GenerateTemporary(); | 981 | Expression value = Visit(operand); |
| 823 | code.AddLine("float {} = {};", temporary, Visit(operand)); | 982 | std::string temporary = code.GenerateTemporary(); |
| 824 | return temporary; | 983 | code.AddLine("{} {} = {};", GetTypeString(value.GetType()), temporary, value.GetCode()); |
| 825 | } | 984 | return {std::move(temporary), value.GetType()}; |
| 826 | |||
| 827 | std::string VisitOperand(Operation operation, std::size_t operand_index, Type type) { | ||
| 828 | return CastOperand(VisitOperand(operation, operand_index), type); | ||
| 829 | } | 985 | } |
| 830 | 986 | ||
| 831 | std::optional<std::pair<std::string, bool>> GetOutputAttribute(const AbufNode* abuf) { | 987 | Expression GetOutputAttribute(const AbufNode* abuf) { |
| 832 | switch (const auto attribute = abuf->GetIndex()) { | 988 | switch (const auto attribute = abuf->GetIndex()) { |
| 833 | case Attribute::Index::Position: | 989 | case Attribute::Index::Position: |
| 834 | return std::make_pair("gl_Position"s + GetSwizzle(abuf->GetElement()), false); | 990 | return {"gl_Position"s + GetSwizzle(abuf->GetElement()), Type::Float}; |
| 835 | case Attribute::Index::LayerViewportPointSize: | 991 | case Attribute::Index::LayerViewportPointSize: |
| 836 | switch (abuf->GetElement()) { | 992 | switch (abuf->GetElement()) { |
| 837 | case 0: | 993 | case 0: |
| @@ -841,119 +997,79 @@ private: | |||
| 841 | if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { | 997 | if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { |
| 842 | return {}; | 998 | return {}; |
| 843 | } | 999 | } |
| 844 | return std::make_pair("gl_Layer", true); | 1000 | return {"gl_Layer", Type::Int}; |
| 845 | case 2: | 1001 | case 2: |
| 846 | if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { | 1002 | if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { |
| 847 | return {}; | 1003 | return {}; |
| 848 | } | 1004 | } |
| 849 | return std::make_pair("gl_ViewportIndex", true); | 1005 | return {"gl_ViewportIndex", Type::Int}; |
| 850 | case 3: | 1006 | case 3: |
| 851 | UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader"); | 1007 | UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader"); |
| 852 | return std::make_pair("gl_PointSize", false); | 1008 | return {"gl_PointSize", Type::Float}; |
| 853 | } | 1009 | } |
| 854 | return {}; | 1010 | return {}; |
| 855 | case Attribute::Index::ClipDistances0123: | 1011 | case Attribute::Index::ClipDistances0123: |
| 856 | return std::make_pair(fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), false); | 1012 | return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), Type::Float}; |
| 857 | case Attribute::Index::ClipDistances4567: | 1013 | case Attribute::Index::ClipDistances4567: |
| 858 | return std::make_pair(fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), | 1014 | return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), Type::Float}; |
| 859 | false); | ||
| 860 | default: | 1015 | default: |
| 861 | if (IsGenericAttribute(attribute)) { | 1016 | if (IsGenericAttribute(attribute)) { |
| 862 | return std::make_pair( | 1017 | return {GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()), |
| 863 | GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()), false); | 1018 | Type::Float}; |
| 864 | } | 1019 | } |
| 865 | UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute)); | 1020 | UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute)); |
| 866 | return {}; | 1021 | return {}; |
| 867 | } | 1022 | } |
| 868 | } | 1023 | } |
| 869 | 1024 | ||
| 870 | std::string CastOperand(const std::string& value, Type type) const { | 1025 | Expression GenerateUnary(Operation operation, std::string_view func, Type result_type, |
| 871 | switch (type) { | 1026 | Type type_a) { |
| 872 | case Type::Bool: | 1027 | std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0).As(type_a)); |
| 873 | case Type::Bool2: | 1028 | return ApplyPrecise(operation, std::move(op_str), result_type); |
| 874 | case Type::Float: | ||
| 875 | return value; | ||
| 876 | case Type::Int: | ||
| 877 | return fmt::format("ftoi({})", value); | ||
| 878 | case Type::Uint: | ||
| 879 | return fmt::format("ftou({})", value); | ||
| 880 | case Type::HalfFloat: | ||
| 881 | return fmt::format("toHalf2({})", value); | ||
| 882 | } | ||
| 883 | UNREACHABLE(); | ||
| 884 | return value; | ||
| 885 | } | 1029 | } |
| 886 | 1030 | ||
| 887 | std::string BitwiseCastResult(const std::string& value, Type type, | 1031 | Expression GenerateBinaryInfix(Operation operation, std::string_view func, Type result_type, |
| 888 | bool needs_parenthesis = false) { | 1032 | Type type_a, Type type_b) { |
| 889 | switch (type) { | 1033 | const std::string op_a = VisitOperand(operation, 0).As(type_a); |
| 890 | case Type::Bool: | 1034 | const std::string op_b = VisitOperand(operation, 1).As(type_b); |
| 891 | case Type::Bool2: | 1035 | std::string op_str = fmt::format("({} {} {})", op_a, func, op_b); |
| 892 | case Type::Float: | ||
| 893 | if (needs_parenthesis) { | ||
| 894 | return fmt::format("({})", value); | ||
| 895 | } | ||
| 896 | return value; | ||
| 897 | case Type::Int: | ||
| 898 | return fmt::format("itof({})", value); | ||
| 899 | case Type::Uint: | ||
| 900 | return fmt::format("utof({})", value); | ||
| 901 | case Type::HalfFloat: | ||
| 902 | return fmt::format("fromHalf2({})", value); | ||
| 903 | } | ||
| 904 | UNREACHABLE(); | ||
| 905 | return value; | ||
| 906 | } | ||
| 907 | |||
| 908 | std::string GenerateUnary(Operation operation, const std::string& func, Type result_type, | ||
| 909 | Type type_a, bool needs_parenthesis = true) { | ||
| 910 | const std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0, type_a)); | ||
| 911 | |||
| 912 | return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type, needs_parenthesis)); | ||
| 913 | } | ||
| 914 | |||
| 915 | std::string GenerateBinaryInfix(Operation operation, const std::string& func, Type result_type, | ||
| 916 | Type type_a, Type type_b) { | ||
| 917 | const std::string op_a = VisitOperand(operation, 0, type_a); | ||
| 918 | const std::string op_b = VisitOperand(operation, 1, type_b); | ||
| 919 | const std::string op_str = fmt::format("({} {} {})", op_a, func, op_b); | ||
| 920 | 1036 | ||
| 921 | return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); | 1037 | return ApplyPrecise(operation, std::move(op_str), result_type); |
| 922 | } | 1038 | } |
| 923 | 1039 | ||
| 924 | std::string GenerateBinaryCall(Operation operation, const std::string& func, Type result_type, | 1040 | Expression GenerateBinaryCall(Operation operation, std::string_view func, Type result_type, |
| 925 | Type type_a, Type type_b) { | 1041 | Type type_a, Type type_b) { |
| 926 | const std::string op_a = VisitOperand(operation, 0, type_a); | 1042 | const std::string op_a = VisitOperand(operation, 0).As(type_a); |
| 927 | const std::string op_b = VisitOperand(operation, 1, type_b); | 1043 | const std::string op_b = VisitOperand(operation, 1).As(type_b); |
| 928 | const std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b); | 1044 | std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b); |
| 929 | 1045 | ||
| 930 | return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); | 1046 | return ApplyPrecise(operation, std::move(op_str), result_type); |
| 931 | } | 1047 | } |
| 932 | 1048 | ||
| 933 | std::string GenerateTernary(Operation operation, const std::string& func, Type result_type, | 1049 | Expression GenerateTernary(Operation operation, std::string_view func, Type result_type, |
| 934 | Type type_a, Type type_b, Type type_c) { | 1050 | Type type_a, Type type_b, Type type_c) { |
| 935 | const std::string op_a = VisitOperand(operation, 0, type_a); | 1051 | const std::string op_a = VisitOperand(operation, 0).As(type_a); |
| 936 | const std::string op_b = VisitOperand(operation, 1, type_b); | 1052 | const std::string op_b = VisitOperand(operation, 1).As(type_b); |
| 937 | const std::string op_c = VisitOperand(operation, 2, type_c); | 1053 | const std::string op_c = VisitOperand(operation, 2).As(type_c); |
| 938 | const std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c); | 1054 | std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c); |
| 939 | 1055 | ||
| 940 | return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); | 1056 | return ApplyPrecise(operation, std::move(op_str), result_type); |
| 941 | } | 1057 | } |
| 942 | 1058 | ||
| 943 | std::string GenerateQuaternary(Operation operation, const std::string& func, Type result_type, | 1059 | Expression GenerateQuaternary(Operation operation, const std::string& func, Type result_type, |
| 944 | Type type_a, Type type_b, Type type_c, Type type_d) { | 1060 | Type type_a, Type type_b, Type type_c, Type type_d) { |
| 945 | const std::string op_a = VisitOperand(operation, 0, type_a); | 1061 | const std::string op_a = VisitOperand(operation, 0).As(type_a); |
| 946 | const std::string op_b = VisitOperand(operation, 1, type_b); | 1062 | const std::string op_b = VisitOperand(operation, 1).As(type_b); |
| 947 | const std::string op_c = VisitOperand(operation, 2, type_c); | 1063 | const std::string op_c = VisitOperand(operation, 2).As(type_c); |
| 948 | const std::string op_d = VisitOperand(operation, 3, type_d); | 1064 | const std::string op_d = VisitOperand(operation, 3).As(type_d); |
| 949 | const std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d); | 1065 | std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d); |
| 950 | 1066 | ||
| 951 | return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); | 1067 | return ApplyPrecise(operation, std::move(op_str), result_type); |
| 952 | } | 1068 | } |
| 953 | 1069 | ||
| 954 | std::string GenerateTexture(Operation operation, const std::string& function_suffix, | 1070 | std::string GenerateTexture(Operation operation, const std::string& function_suffix, |
| 955 | const std::vector<TextureIR>& extras) { | 1071 | const std::vector<TextureIR>& extras) { |
| 956 | constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"}; | 1072 | constexpr std::array coord_constructors = {"float", "vec2", "vec3", "vec4"}; |
| 957 | 1073 | ||
| 958 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1074 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 959 | ASSERT(meta); | 1075 | ASSERT(meta); |
| @@ -970,17 +1086,17 @@ private: | |||
| 970 | expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1); | 1086 | expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1); |
| 971 | expr += '('; | 1087 | expr += '('; |
| 972 | for (std::size_t i = 0; i < count; ++i) { | 1088 | for (std::size_t i = 0; i < count; ++i) { |
| 973 | expr += Visit(operation[i]); | 1089 | expr += Visit(operation[i]).AsFloat(); |
| 974 | 1090 | ||
| 975 | const std::size_t next = i + 1; | 1091 | const std::size_t next = i + 1; |
| 976 | if (next < count) | 1092 | if (next < count) |
| 977 | expr += ", "; | 1093 | expr += ", "; |
| 978 | } | 1094 | } |
| 979 | if (has_array) { | 1095 | if (has_array) { |
| 980 | expr += ", float(ftoi(" + Visit(meta->array) + "))"; | 1096 | expr += ", float(" + Visit(meta->array).AsInt() + ')'; |
| 981 | } | 1097 | } |
| 982 | if (has_shadow) { | 1098 | if (has_shadow) { |
| 983 | expr += ", " + Visit(meta->depth_compare); | 1099 | expr += ", " + Visit(meta->depth_compare).AsFloat(); |
| 984 | } | 1100 | } |
| 985 | expr += ')'; | 1101 | expr += ')'; |
| 986 | 1102 | ||
| @@ -1011,11 +1127,11 @@ private: | |||
| 1011 | // required to be constant) | 1127 | // required to be constant) |
| 1012 | expr += std::to_string(static_cast<s32>(immediate->GetValue())); | 1128 | expr += std::to_string(static_cast<s32>(immediate->GetValue())); |
| 1013 | } else { | 1129 | } else { |
| 1014 | expr += fmt::format("ftoi({})", Visit(operand)); | 1130 | expr += Visit(operand).AsInt(); |
| 1015 | } | 1131 | } |
| 1016 | break; | 1132 | break; |
| 1017 | case Type::Float: | 1133 | case Type::Float: |
| 1018 | expr += Visit(operand); | 1134 | expr += Visit(operand).AsFloat(); |
| 1019 | break; | 1135 | break; |
| 1020 | default: { | 1136 | default: { |
| 1021 | const auto type_int = static_cast<u32>(type); | 1137 | const auto type_int = static_cast<u32>(type); |
| @@ -1031,7 +1147,7 @@ private: | |||
| 1031 | if (aoffi.empty()) { | 1147 | if (aoffi.empty()) { |
| 1032 | return {}; | 1148 | return {}; |
| 1033 | } | 1149 | } |
| 1034 | constexpr std::array<const char*, 3> coord_constructors = {"int", "ivec2", "ivec3"}; | 1150 | constexpr std::array coord_constructors = {"int", "ivec2", "ivec3"}; |
| 1035 | std::string expr = ", "; | 1151 | std::string expr = ", "; |
| 1036 | expr += coord_constructors.at(aoffi.size() - 1); | 1152 | expr += coord_constructors.at(aoffi.size() - 1); |
| 1037 | expr += '('; | 1153 | expr += '('; |
| @@ -1044,7 +1160,7 @@ private: | |||
| 1044 | expr += std::to_string(static_cast<s32>(immediate->GetValue())); | 1160 | expr += std::to_string(static_cast<s32>(immediate->GetValue())); |
| 1045 | } else if (device.HasVariableAoffi()) { | 1161 | } else if (device.HasVariableAoffi()) { |
| 1046 | // Avoid using variable AOFFI on unsupported devices. | 1162 | // Avoid using variable AOFFI on unsupported devices. |
| 1047 | expr += fmt::format("ftoi({})", Visit(operand)); | 1163 | expr += Visit(operand).AsInt(); |
| 1048 | } else { | 1164 | } else { |
| 1049 | // Insert 0 on devices not supporting variable AOFFI. | 1165 | // Insert 0 on devices not supporting variable AOFFI. |
| 1050 | expr += '0'; | 1166 | expr += '0'; |
| @@ -1058,328 +1174,314 @@ private: | |||
| 1058 | return expr; | 1174 | return expr; |
| 1059 | } | 1175 | } |
| 1060 | 1176 | ||
| 1061 | std::string Assign(Operation operation) { | 1177 | Expression Assign(Operation operation) { |
| 1062 | const Node& dest = operation[0]; | 1178 | const Node& dest = operation[0]; |
| 1063 | const Node& src = operation[1]; | 1179 | const Node& src = operation[1]; |
| 1064 | 1180 | ||
| 1065 | std::string target; | 1181 | Expression target; |
| 1066 | bool is_integer = false; | ||
| 1067 | |||
| 1068 | if (const auto gpr = std::get_if<GprNode>(&*dest)) { | 1182 | if (const auto gpr = std::get_if<GprNode>(&*dest)) { |
| 1069 | if (gpr->GetIndex() == Register::ZeroIndex) { | 1183 | if (gpr->GetIndex() == Register::ZeroIndex) { |
| 1070 | // Writing to Register::ZeroIndex is a no op | 1184 | // Writing to Register::ZeroIndex is a no op |
| 1071 | return {}; | 1185 | return {}; |
| 1072 | } | 1186 | } |
| 1073 | target = GetRegister(gpr->GetIndex()); | 1187 | target = {GetRegister(gpr->GetIndex()), Type::Float}; |
| 1074 | } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) { | 1188 | } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) { |
| 1075 | UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer()); | 1189 | UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer()); |
| 1076 | const auto result = GetOutputAttribute(abuf); | 1190 | target = GetOutputAttribute(abuf); |
| 1077 | if (!result) { | ||
| 1078 | return {}; | ||
| 1079 | } | ||
| 1080 | target = result->first; | ||
| 1081 | is_integer = result->second; | ||
| 1082 | } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) { | 1191 | } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) { |
| 1083 | if (stage == ProgramType::Compute) { | 1192 | if (stage == ProgramType::Compute) { |
| 1084 | LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); | 1193 | LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); |
| 1085 | } | 1194 | } |
| 1086 | target = fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); | 1195 | target = { |
| 1196 | fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()), | ||
| 1197 | Type::Uint}; | ||
| 1087 | } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) { | 1198 | } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) { |
| 1088 | const std::string real = Visit(gmem->GetRealAddress()); | 1199 | const std::string real = Visit(gmem->GetRealAddress()).AsUint(); |
| 1089 | const std::string base = Visit(gmem->GetBaseAddress()); | 1200 | const std::string base = Visit(gmem->GetBaseAddress()).AsUint(); |
| 1090 | const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); | 1201 | const std::string final_offset = fmt::format("({} - {}) >> 2", real, base); |
| 1091 | target = fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); | 1202 | target = {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset), |
| 1203 | Type::Uint}; | ||
| 1092 | } else { | 1204 | } else { |
| 1093 | UNREACHABLE_MSG("Assign called without a proper target"); | 1205 | UNREACHABLE_MSG("Assign called without a proper target"); |
| 1094 | } | 1206 | } |
| 1095 | 1207 | ||
| 1096 | if (is_integer) { | 1208 | code.AddLine("{} = {};", target.GetCode(), Visit(src).As(target.GetType())); |
| 1097 | code.AddLine("{} = ftoi({});", target, Visit(src)); | ||
| 1098 | } else { | ||
| 1099 | code.AddLine("{} = {};", target, Visit(src)); | ||
| 1100 | } | ||
| 1101 | return {}; | 1209 | return {}; |
| 1102 | } | 1210 | } |
| 1103 | 1211 | ||
| 1104 | template <Type type> | 1212 | template <Type type> |
| 1105 | std::string Add(Operation operation) { | 1213 | Expression Add(Operation operation) { |
| 1106 | return GenerateBinaryInfix(operation, "+", type, type, type); | 1214 | return GenerateBinaryInfix(operation, "+", type, type, type); |
| 1107 | } | 1215 | } |
| 1108 | 1216 | ||
| 1109 | template <Type type> | 1217 | template <Type type> |
| 1110 | std::string Mul(Operation operation) { | 1218 | Expression Mul(Operation operation) { |
| 1111 | return GenerateBinaryInfix(operation, "*", type, type, type); | 1219 | return GenerateBinaryInfix(operation, "*", type, type, type); |
| 1112 | } | 1220 | } |
| 1113 | 1221 | ||
| 1114 | template <Type type> | 1222 | template <Type type> |
| 1115 | std::string Div(Operation operation) { | 1223 | Expression Div(Operation operation) { |
| 1116 | return GenerateBinaryInfix(operation, "/", type, type, type); | 1224 | return GenerateBinaryInfix(operation, "/", type, type, type); |
| 1117 | } | 1225 | } |
| 1118 | 1226 | ||
| 1119 | template <Type type> | 1227 | template <Type type> |
| 1120 | std::string Fma(Operation operation) { | 1228 | Expression Fma(Operation operation) { |
| 1121 | return GenerateTernary(operation, "fma", type, type, type, type); | 1229 | return GenerateTernary(operation, "fma", type, type, type, type); |
| 1122 | } | 1230 | } |
| 1123 | 1231 | ||
| 1124 | template <Type type> | 1232 | template <Type type> |
| 1125 | std::string Negate(Operation operation) { | 1233 | Expression Negate(Operation operation) { |
| 1126 | return GenerateUnary(operation, "-", type, type, true); | 1234 | return GenerateUnary(operation, "-", type, type); |
| 1127 | } | 1235 | } |
| 1128 | 1236 | ||
| 1129 | template <Type type> | 1237 | template <Type type> |
| 1130 | std::string Absolute(Operation operation) { | 1238 | Expression Absolute(Operation operation) { |
| 1131 | return GenerateUnary(operation, "abs", type, type, false); | 1239 | return GenerateUnary(operation, "abs", type, type); |
| 1132 | } | 1240 | } |
| 1133 | 1241 | ||
| 1134 | std::string FClamp(Operation operation) { | 1242 | Expression FClamp(Operation operation) { |
| 1135 | return GenerateTernary(operation, "clamp", Type::Float, Type::Float, Type::Float, | 1243 | return GenerateTernary(operation, "clamp", Type::Float, Type::Float, Type::Float, |
| 1136 | Type::Float); | 1244 | Type::Float); |
| 1137 | } | 1245 | } |
| 1138 | 1246 | ||
| 1139 | std::string FCastHalf0(Operation operation) { | 1247 | Expression FCastHalf0(Operation operation) { |
| 1140 | const std::string op_a = VisitOperand(operation, 0, Type::HalfFloat); | 1248 | return {fmt::format("({})[0]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float}; |
| 1141 | return fmt::format("({})[0]", op_a); | ||
| 1142 | } | 1249 | } |
| 1143 | 1250 | ||
| 1144 | std::string FCastHalf1(Operation operation) { | 1251 | Expression FCastHalf1(Operation operation) { |
| 1145 | const std::string op_a = VisitOperand(operation, 0, Type::HalfFloat); | 1252 | return {fmt::format("({})[1]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float}; |
| 1146 | return fmt::format("({})[1]", op_a); | ||
| 1147 | } | 1253 | } |
| 1148 | 1254 | ||
| 1149 | template <Type type> | 1255 | template <Type type> |
| 1150 | std::string Min(Operation operation) { | 1256 | Expression Min(Operation operation) { |
| 1151 | return GenerateBinaryCall(operation, "min", type, type, type); | 1257 | return GenerateBinaryCall(operation, "min", type, type, type); |
| 1152 | } | 1258 | } |
| 1153 | 1259 | ||
| 1154 | template <Type type> | 1260 | template <Type type> |
| 1155 | std::string Max(Operation operation) { | 1261 | Expression Max(Operation operation) { |
| 1156 | return GenerateBinaryCall(operation, "max", type, type, type); | 1262 | return GenerateBinaryCall(operation, "max", type, type, type); |
| 1157 | } | 1263 | } |
| 1158 | 1264 | ||
| 1159 | std::string Select(Operation operation) { | 1265 | Expression Select(Operation operation) { |
| 1160 | const std::string condition = Visit(operation[0]); | 1266 | const std::string condition = Visit(operation[0]).AsBool(); |
| 1161 | const std::string true_case = Visit(operation[1]); | 1267 | const std::string true_case = Visit(operation[1]).AsUint(); |
| 1162 | const std::string false_case = Visit(operation[2]); | 1268 | const std::string false_case = Visit(operation[2]).AsUint(); |
| 1163 | const std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case); | 1269 | std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case); |
| 1164 | 1270 | ||
| 1165 | return ApplyPrecise(operation, op_str); | 1271 | return ApplyPrecise(operation, std::move(op_str), Type::Uint); |
| 1166 | } | 1272 | } |
| 1167 | 1273 | ||
| 1168 | std::string FCos(Operation operation) { | 1274 | Expression FCos(Operation operation) { |
| 1169 | return GenerateUnary(operation, "cos", Type::Float, Type::Float, false); | 1275 | return GenerateUnary(operation, "cos", Type::Float, Type::Float); |
| 1170 | } | 1276 | } |
| 1171 | 1277 | ||
| 1172 | std::string FSin(Operation operation) { | 1278 | Expression FSin(Operation operation) { |
| 1173 | return GenerateUnary(operation, "sin", Type::Float, Type::Float, false); | 1279 | return GenerateUnary(operation, "sin", Type::Float, Type::Float); |
| 1174 | } | 1280 | } |
| 1175 | 1281 | ||
| 1176 | std::string FExp2(Operation operation) { | 1282 | Expression FExp2(Operation operation) { |
| 1177 | return GenerateUnary(operation, "exp2", Type::Float, Type::Float, false); | 1283 | return GenerateUnary(operation, "exp2", Type::Float, Type::Float); |
| 1178 | } | 1284 | } |
| 1179 | 1285 | ||
| 1180 | std::string FLog2(Operation operation) { | 1286 | Expression FLog2(Operation operation) { |
| 1181 | return GenerateUnary(operation, "log2", Type::Float, Type::Float, false); | 1287 | return GenerateUnary(operation, "log2", Type::Float, Type::Float); |
| 1182 | } | 1288 | } |
| 1183 | 1289 | ||
| 1184 | std::string FInverseSqrt(Operation operation) { | 1290 | Expression FInverseSqrt(Operation operation) { |
| 1185 | return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float, false); | 1291 | return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float); |
| 1186 | } | 1292 | } |
| 1187 | 1293 | ||
| 1188 | std::string FSqrt(Operation operation) { | 1294 | Expression FSqrt(Operation operation) { |
| 1189 | return GenerateUnary(operation, "sqrt", Type::Float, Type::Float, false); | 1295 | return GenerateUnary(operation, "sqrt", Type::Float, Type::Float); |
| 1190 | } | 1296 | } |
| 1191 | 1297 | ||
| 1192 | std::string FRoundEven(Operation operation) { | 1298 | Expression FRoundEven(Operation operation) { |
| 1193 | return GenerateUnary(operation, "roundEven", Type::Float, Type::Float, false); | 1299 | return GenerateUnary(operation, "roundEven", Type::Float, Type::Float); |
| 1194 | } | 1300 | } |
| 1195 | 1301 | ||
| 1196 | std::string FFloor(Operation operation) { | 1302 | Expression FFloor(Operation operation) { |
| 1197 | return GenerateUnary(operation, "floor", Type::Float, Type::Float, false); | 1303 | return GenerateUnary(operation, "floor", Type::Float, Type::Float); |
| 1198 | } | 1304 | } |
| 1199 | 1305 | ||
| 1200 | std::string FCeil(Operation operation) { | 1306 | Expression FCeil(Operation operation) { |
| 1201 | return GenerateUnary(operation, "ceil", Type::Float, Type::Float, false); | 1307 | return GenerateUnary(operation, "ceil", Type::Float, Type::Float); |
| 1202 | } | 1308 | } |
| 1203 | 1309 | ||
| 1204 | std::string FTrunc(Operation operation) { | 1310 | Expression FTrunc(Operation operation) { |
| 1205 | return GenerateUnary(operation, "trunc", Type::Float, Type::Float, false); | 1311 | return GenerateUnary(operation, "trunc", Type::Float, Type::Float); |
| 1206 | } | 1312 | } |
| 1207 | 1313 | ||
| 1208 | template <Type type> | 1314 | template <Type type> |
| 1209 | std::string FCastInteger(Operation operation) { | 1315 | Expression FCastInteger(Operation operation) { |
| 1210 | return GenerateUnary(operation, "float", Type::Float, type, false); | 1316 | return GenerateUnary(operation, "float", Type::Float, type); |
| 1211 | } | 1317 | } |
| 1212 | 1318 | ||
| 1213 | std::string ICastFloat(Operation operation) { | 1319 | Expression ICastFloat(Operation operation) { |
| 1214 | return GenerateUnary(operation, "int", Type::Int, Type::Float, false); | 1320 | return GenerateUnary(operation, "int", Type::Int, Type::Float); |
| 1215 | } | 1321 | } |
| 1216 | 1322 | ||
| 1217 | std::string ICastUnsigned(Operation operation) { | 1323 | Expression ICastUnsigned(Operation operation) { |
| 1218 | return GenerateUnary(operation, "int", Type::Int, Type::Uint, false); | 1324 | return GenerateUnary(operation, "int", Type::Int, Type::Uint); |
| 1219 | } | 1325 | } |
| 1220 | 1326 | ||
| 1221 | template <Type type> | 1327 | template <Type type> |
| 1222 | std::string LogicalShiftLeft(Operation operation) { | 1328 | Expression LogicalShiftLeft(Operation operation) { |
| 1223 | return GenerateBinaryInfix(operation, "<<", type, type, Type::Uint); | 1329 | return GenerateBinaryInfix(operation, "<<", type, type, Type::Uint); |
| 1224 | } | 1330 | } |
| 1225 | 1331 | ||
| 1226 | std::string ILogicalShiftRight(Operation operation) { | 1332 | Expression ILogicalShiftRight(Operation operation) { |
| 1227 | const std::string op_a = VisitOperand(operation, 0, Type::Uint); | 1333 | const std::string op_a = VisitOperand(operation, 0).AsUint(); |
| 1228 | const std::string op_b = VisitOperand(operation, 1, Type::Uint); | 1334 | const std::string op_b = VisitOperand(operation, 1).AsUint(); |
| 1229 | const std::string op_str = fmt::format("int({} >> {})", op_a, op_b); | 1335 | std::string op_str = fmt::format("int({} >> {})", op_a, op_b); |
| 1230 | 1336 | ||
| 1231 | return ApplyPrecise(operation, BitwiseCastResult(op_str, Type::Int)); | 1337 | return ApplyPrecise(operation, std::move(op_str), Type::Int); |
| 1232 | } | 1338 | } |
| 1233 | 1339 | ||
| 1234 | std::string IArithmeticShiftRight(Operation operation) { | 1340 | Expression IArithmeticShiftRight(Operation operation) { |
| 1235 | return GenerateBinaryInfix(operation, ">>", Type::Int, Type::Int, Type::Uint); | 1341 | return GenerateBinaryInfix(operation, ">>", Type::Int, Type::Int, Type::Uint); |
| 1236 | } | 1342 | } |
| 1237 | 1343 | ||
| 1238 | template <Type type> | 1344 | template <Type type> |
| 1239 | std::string BitwiseAnd(Operation operation) { | 1345 | Expression BitwiseAnd(Operation operation) { |
| 1240 | return GenerateBinaryInfix(operation, "&", type, type, type); | 1346 | return GenerateBinaryInfix(operation, "&", type, type, type); |
| 1241 | } | 1347 | } |
| 1242 | 1348 | ||
| 1243 | template <Type type> | 1349 | template <Type type> |
| 1244 | std::string BitwiseOr(Operation operation) { | 1350 | Expression BitwiseOr(Operation operation) { |
| 1245 | return GenerateBinaryInfix(operation, "|", type, type, type); | 1351 | return GenerateBinaryInfix(operation, "|", type, type, type); |
| 1246 | } | 1352 | } |
| 1247 | 1353 | ||
| 1248 | template <Type type> | 1354 | template <Type type> |
| 1249 | std::string BitwiseXor(Operation operation) { | 1355 | Expression BitwiseXor(Operation operation) { |
| 1250 | return GenerateBinaryInfix(operation, "^", type, type, type); | 1356 | return GenerateBinaryInfix(operation, "^", type, type, type); |
| 1251 | } | 1357 | } |
| 1252 | 1358 | ||
| 1253 | template <Type type> | 1359 | template <Type type> |
| 1254 | std::string BitwiseNot(Operation operation) { | 1360 | Expression BitwiseNot(Operation operation) { |
| 1255 | return GenerateUnary(operation, "~", type, type, false); | 1361 | return GenerateUnary(operation, "~", type, type); |
| 1256 | } | 1362 | } |
| 1257 | 1363 | ||
| 1258 | std::string UCastFloat(Operation operation) { | 1364 | Expression UCastFloat(Operation operation) { |
| 1259 | return GenerateUnary(operation, "uint", Type::Uint, Type::Float, false); | 1365 | return GenerateUnary(operation, "uint", Type::Uint, Type::Float); |
| 1260 | } | 1366 | } |
| 1261 | 1367 | ||
| 1262 | std::string UCastSigned(Operation operation) { | 1368 | Expression UCastSigned(Operation operation) { |
| 1263 | return GenerateUnary(operation, "uint", Type::Uint, Type::Int, false); | 1369 | return GenerateUnary(operation, "uint", Type::Uint, Type::Int); |
| 1264 | } | 1370 | } |
| 1265 | 1371 | ||
| 1266 | std::string UShiftRight(Operation operation) { | 1372 | Expression UShiftRight(Operation operation) { |
| 1267 | return GenerateBinaryInfix(operation, ">>", Type::Uint, Type::Uint, Type::Uint); | 1373 | return GenerateBinaryInfix(operation, ">>", Type::Uint, Type::Uint, Type::Uint); |
| 1268 | } | 1374 | } |
| 1269 | 1375 | ||
| 1270 | template <Type type> | 1376 | template <Type type> |
| 1271 | std::string BitfieldInsert(Operation operation) { | 1377 | Expression BitfieldInsert(Operation operation) { |
| 1272 | return GenerateQuaternary(operation, "bitfieldInsert", type, type, type, Type::Int, | 1378 | return GenerateQuaternary(operation, "bitfieldInsert", type, type, type, Type::Int, |
| 1273 | Type::Int); | 1379 | Type::Int); |
| 1274 | } | 1380 | } |
| 1275 | 1381 | ||
| 1276 | template <Type type> | 1382 | template <Type type> |
| 1277 | std::string BitfieldExtract(Operation operation) { | 1383 | Expression BitfieldExtract(Operation operation) { |
| 1278 | return GenerateTernary(operation, "bitfieldExtract", type, type, Type::Int, Type::Int); | 1384 | return GenerateTernary(operation, "bitfieldExtract", type, type, Type::Int, Type::Int); |
| 1279 | } | 1385 | } |
| 1280 | 1386 | ||
| 1281 | template <Type type> | 1387 | template <Type type> |
| 1282 | std::string BitCount(Operation operation) { | 1388 | Expression BitCount(Operation operation) { |
| 1283 | return GenerateUnary(operation, "bitCount", type, type, false); | 1389 | return GenerateUnary(operation, "bitCount", type, type); |
| 1284 | } | 1390 | } |
| 1285 | 1391 | ||
| 1286 | std::string HNegate(Operation operation) { | 1392 | Expression HNegate(Operation operation) { |
| 1287 | const auto GetNegate = [&](std::size_t index) { | 1393 | const auto GetNegate = [&](std::size_t index) { |
| 1288 | return VisitOperand(operation, index, Type::Bool) + " ? -1 : 1"; | 1394 | return VisitOperand(operation, index).AsBool() + " ? -1 : 1"; |
| 1289 | }; | 1395 | }; |
| 1290 | const std::string value = | 1396 | return {fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0).AsHalfFloat(), |
| 1291 | fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0, Type::HalfFloat), | 1397 | GetNegate(1), GetNegate(2)), |
| 1292 | GetNegate(1), GetNegate(2)); | 1398 | Type::HalfFloat}; |
| 1293 | return BitwiseCastResult(value, Type::HalfFloat); | 1399 | } |
| 1294 | } | 1400 | |
| 1295 | 1401 | Expression HClamp(Operation operation) { | |
| 1296 | std::string HClamp(Operation operation) { | 1402 | const std::string value = VisitOperand(operation, 0).AsHalfFloat(); |
| 1297 | const std::string value = VisitOperand(operation, 0, Type::HalfFloat); | 1403 | const std::string min = VisitOperand(operation, 1).AsFloat(); |
| 1298 | const std::string min = VisitOperand(operation, 1, Type::Float); | 1404 | const std::string max = VisitOperand(operation, 2).AsFloat(); |
| 1299 | const std::string max = VisitOperand(operation, 2, Type::Float); | 1405 | std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max); |
| 1300 | const std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max); | 1406 | |
| 1301 | 1407 | return ApplyPrecise(operation, std::move(clamped), Type::HalfFloat); | |
| 1302 | return ApplyPrecise(operation, BitwiseCastResult(clamped, Type::HalfFloat)); | 1408 | } |
| 1303 | } | 1409 | |
| 1304 | 1410 | Expression HCastFloat(Operation operation) { | |
| 1305 | std::string HCastFloat(Operation operation) { | 1411 | return {fmt::format("vec2({})", VisitOperand(operation, 0).AsFloat()), Type::HalfFloat}; |
| 1306 | const std::string op_a = VisitOperand(operation, 0, Type::Float); | 1412 | } |
| 1307 | return fmt::format("fromHalf2(vec2({}, 0.0f))", op_a); | 1413 | |
| 1308 | } | 1414 | Expression HUnpack(Operation operation) { |
| 1309 | 1415 | Expression operand = VisitOperand(operation, 0); | |
| 1310 | std::string HUnpack(Operation operation) { | 1416 | switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) { |
| 1311 | const std::string operand{VisitOperand(operation, 0, Type::HalfFloat)}; | 1417 | case Tegra::Shader::HalfType::H0_H1: |
| 1312 | const auto value = [&]() -> std::string { | 1418 | return operand; |
| 1313 | switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) { | 1419 | case Tegra::Shader::HalfType::F32: |
| 1314 | case Tegra::Shader::HalfType::H0_H1: | 1420 | return {fmt::format("vec2({})", operand.AsFloat()), Type::HalfFloat}; |
| 1315 | return operand; | 1421 | case Tegra::Shader::HalfType::H0_H0: |
| 1316 | case Tegra::Shader::HalfType::F32: | 1422 | return {fmt::format("vec2({}[0])", operand.AsHalfFloat()), Type::HalfFloat}; |
| 1317 | return fmt::format("vec2(fromHalf2({}))", operand); | 1423 | case Tegra::Shader::HalfType::H1_H1: |
| 1318 | case Tegra::Shader::HalfType::H0_H0: | 1424 | return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat}; |
| 1319 | return fmt::format("vec2({}[0])", operand); | 1425 | } |
| 1320 | case Tegra::Shader::HalfType::H1_H1: | ||
| 1321 | return fmt::format("vec2({}[1])", operand); | ||
| 1322 | } | ||
| 1323 | UNREACHABLE(); | ||
| 1324 | return "0"; | ||
| 1325 | }(); | ||
| 1326 | return fmt::format("fromHalf2({})", value); | ||
| 1327 | } | 1426 | } |
| 1328 | 1427 | ||
| 1329 | std::string HMergeF32(Operation operation) { | 1428 | Expression HMergeF32(Operation operation) { |
| 1330 | return fmt::format("float(toHalf2({})[0])", Visit(operation[0])); | 1429 | return {fmt::format("float({}[0])", VisitOperand(operation, 0).AsHalfFloat()), Type::Float}; |
| 1331 | } | 1430 | } |
| 1332 | 1431 | ||
| 1333 | std::string HMergeH0(Operation operation) { | 1432 | Expression HMergeH0(Operation operation) { |
| 1334 | return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[1]), | 1433 | std::string dest = VisitOperand(operation, 0).AsUint(); |
| 1335 | Visit(operation[0])); | 1434 | std::string src = VisitOperand(operation, 1).AsUint(); |
| 1435 | return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", src, dest), Type::Uint}; | ||
| 1336 | } | 1436 | } |
| 1337 | 1437 | ||
| 1338 | std::string HMergeH1(Operation operation) { | 1438 | Expression HMergeH1(Operation operation) { |
| 1339 | return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[0]), | 1439 | std::string dest = VisitOperand(operation, 0).AsUint(); |
| 1340 | Visit(operation[1])); | 1440 | std::string src = VisitOperand(operation, 1).AsUint(); |
| 1441 | return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", dest, src), Type::Uint}; | ||
| 1341 | } | 1442 | } |
| 1342 | 1443 | ||
| 1343 | std::string HPack2(Operation operation) { | 1444 | Expression HPack2(Operation operation) { |
| 1344 | return fmt::format("utof(packHalf2x16(vec2({}, {})))", Visit(operation[0]), | 1445 | return {fmt::format("vec2({}, {})", VisitOperand(operation, 0).AsFloat(), |
| 1345 | Visit(operation[1])); | 1446 | VisitOperand(operation, 1).AsFloat()), |
| 1447 | Type::HalfFloat}; | ||
| 1346 | } | 1448 | } |
| 1347 | 1449 | ||
| 1348 | template <Type type> | 1450 | template <Type type> |
| 1349 | std::string LogicalLessThan(Operation operation) { | 1451 | Expression LogicalLessThan(Operation operation) { |
| 1350 | return GenerateBinaryInfix(operation, "<", Type::Bool, type, type); | 1452 | return GenerateBinaryInfix(operation, "<", Type::Bool, type, type); |
| 1351 | } | 1453 | } |
| 1352 | 1454 | ||
| 1353 | template <Type type> | 1455 | template <Type type> |
| 1354 | std::string LogicalEqual(Operation operation) { | 1456 | Expression LogicalEqual(Operation operation) { |
| 1355 | return GenerateBinaryInfix(operation, "==", Type::Bool, type, type); | 1457 | return GenerateBinaryInfix(operation, "==", Type::Bool, type, type); |
| 1356 | } | 1458 | } |
| 1357 | 1459 | ||
| 1358 | template <Type type> | 1460 | template <Type type> |
| 1359 | std::string LogicalLessEqual(Operation operation) { | 1461 | Expression LogicalLessEqual(Operation operation) { |
| 1360 | return GenerateBinaryInfix(operation, "<=", Type::Bool, type, type); | 1462 | return GenerateBinaryInfix(operation, "<=", Type::Bool, type, type); |
| 1361 | } | 1463 | } |
| 1362 | 1464 | ||
| 1363 | template <Type type> | 1465 | template <Type type> |
| 1364 | std::string LogicalGreaterThan(Operation operation) { | 1466 | Expression LogicalGreaterThan(Operation operation) { |
| 1365 | return GenerateBinaryInfix(operation, ">", Type::Bool, type, type); | 1467 | return GenerateBinaryInfix(operation, ">", Type::Bool, type, type); |
| 1366 | } | 1468 | } |
| 1367 | 1469 | ||
| 1368 | template <Type type> | 1470 | template <Type type> |
| 1369 | std::string LogicalNotEqual(Operation operation) { | 1471 | Expression LogicalNotEqual(Operation operation) { |
| 1370 | return GenerateBinaryInfix(operation, "!=", Type::Bool, type, type); | 1472 | return GenerateBinaryInfix(operation, "!=", Type::Bool, type, type); |
| 1371 | } | 1473 | } |
| 1372 | 1474 | ||
| 1373 | template <Type type> | 1475 | template <Type type> |
| 1374 | std::string LogicalGreaterEqual(Operation operation) { | 1476 | Expression LogicalGreaterEqual(Operation operation) { |
| 1375 | return GenerateBinaryInfix(operation, ">=", Type::Bool, type, type); | 1477 | return GenerateBinaryInfix(operation, ">=", Type::Bool, type, type); |
| 1376 | } | 1478 | } |
| 1377 | 1479 | ||
| 1378 | std::string LogicalFIsNan(Operation operation) { | 1480 | Expression LogicalFIsNan(Operation operation) { |
| 1379 | return GenerateUnary(operation, "isnan", Type::Bool, Type::Float, false); | 1481 | return GenerateUnary(operation, "isnan", Type::Bool, Type::Float); |
| 1380 | } | 1482 | } |
| 1381 | 1483 | ||
| 1382 | std::string LogicalAssign(Operation operation) { | 1484 | Expression LogicalAssign(Operation operation) { |
| 1383 | const Node& dest = operation[0]; | 1485 | const Node& dest = operation[0]; |
| 1384 | const Node& src = operation[1]; | 1486 | const Node& src = operation[1]; |
| 1385 | 1487 | ||
| @@ -1400,78 +1502,80 @@ private: | |||
| 1400 | target = GetInternalFlag(flag->GetFlag()); | 1502 | target = GetInternalFlag(flag->GetFlag()); |
| 1401 | } | 1503 | } |
| 1402 | 1504 | ||
| 1403 | code.AddLine("{} = {};", target, Visit(src)); | 1505 | code.AddLine("{} = {};", target, Visit(src).AsBool()); |
| 1404 | return {}; | 1506 | return {}; |
| 1405 | } | 1507 | } |
| 1406 | 1508 | ||
| 1407 | std::string LogicalAnd(Operation operation) { | 1509 | Expression LogicalAnd(Operation operation) { |
| 1408 | return GenerateBinaryInfix(operation, "&&", Type::Bool, Type::Bool, Type::Bool); | 1510 | return GenerateBinaryInfix(operation, "&&", Type::Bool, Type::Bool, Type::Bool); |
| 1409 | } | 1511 | } |
| 1410 | 1512 | ||
| 1411 | std::string LogicalOr(Operation operation) { | 1513 | Expression LogicalOr(Operation operation) { |
| 1412 | return GenerateBinaryInfix(operation, "||", Type::Bool, Type::Bool, Type::Bool); | 1514 | return GenerateBinaryInfix(operation, "||", Type::Bool, Type::Bool, Type::Bool); |
| 1413 | } | 1515 | } |
| 1414 | 1516 | ||
| 1415 | std::string LogicalXor(Operation operation) { | 1517 | Expression LogicalXor(Operation operation) { |
| 1416 | return GenerateBinaryInfix(operation, "^^", Type::Bool, Type::Bool, Type::Bool); | 1518 | return GenerateBinaryInfix(operation, "^^", Type::Bool, Type::Bool, Type::Bool); |
| 1417 | } | 1519 | } |
| 1418 | 1520 | ||
| 1419 | std::string LogicalNegate(Operation operation) { | 1521 | Expression LogicalNegate(Operation operation) { |
| 1420 | return GenerateUnary(operation, "!", Type::Bool, Type::Bool, false); | 1522 | return GenerateUnary(operation, "!", Type::Bool, Type::Bool); |
| 1421 | } | 1523 | } |
| 1422 | 1524 | ||
| 1423 | std::string LogicalPick2(Operation operation) { | 1525 | Expression LogicalPick2(Operation operation) { |
| 1424 | const std::string pair = VisitOperand(operation, 0, Type::Bool2); | 1526 | return {fmt::format("{}[{}]", VisitOperand(operation, 0).AsBool2(), |
| 1425 | return fmt::format("{}[{}]", pair, VisitOperand(operation, 1, Type::Uint)); | 1527 | VisitOperand(operation, 1).AsUint()), |
| 1528 | Type::Bool}; | ||
| 1426 | } | 1529 | } |
| 1427 | 1530 | ||
| 1428 | std::string LogicalAnd2(Operation operation) { | 1531 | Expression LogicalAnd2(Operation operation) { |
| 1429 | return GenerateUnary(operation, "all", Type::Bool, Type::Bool2); | 1532 | return GenerateUnary(operation, "all", Type::Bool, Type::Bool2); |
| 1430 | } | 1533 | } |
| 1431 | 1534 | ||
| 1432 | template <bool with_nan> | 1535 | template <bool with_nan> |
| 1433 | std::string GenerateHalfComparison(Operation operation, const std::string& compare_op) { | 1536 | Expression GenerateHalfComparison(Operation operation, std::string_view compare_op) { |
| 1434 | const std::string comparison{GenerateBinaryCall(operation, compare_op, Type::Bool2, | 1537 | Expression comparison = GenerateBinaryCall(operation, compare_op, Type::Bool2, |
| 1435 | Type::HalfFloat, Type::HalfFloat)}; | 1538 | Type::HalfFloat, Type::HalfFloat); |
| 1436 | if constexpr (!with_nan) { | 1539 | if constexpr (!with_nan) { |
| 1437 | return comparison; | 1540 | return comparison; |
| 1438 | } | 1541 | } |
| 1439 | return fmt::format("halfFloatNanComparison({}, {}, {})", comparison, | 1542 | return {fmt::format("HalfFloatNanComparison({}, {}, {})", comparison.AsBool2(), |
| 1440 | VisitOperand(operation, 0, Type::HalfFloat), | 1543 | VisitOperand(operation, 0).AsHalfFloat(), |
| 1441 | VisitOperand(operation, 1, Type::HalfFloat)); | 1544 | VisitOperand(operation, 1).AsHalfFloat()), |
| 1545 | Type::Bool2}; | ||
| 1442 | } | 1546 | } |
| 1443 | 1547 | ||
| 1444 | template <bool with_nan> | 1548 | template <bool with_nan> |
| 1445 | std::string Logical2HLessThan(Operation operation) { | 1549 | Expression Logical2HLessThan(Operation operation) { |
| 1446 | return GenerateHalfComparison<with_nan>(operation, "lessThan"); | 1550 | return GenerateHalfComparison<with_nan>(operation, "lessThan"); |
| 1447 | } | 1551 | } |
| 1448 | 1552 | ||
| 1449 | template <bool with_nan> | 1553 | template <bool with_nan> |
| 1450 | std::string Logical2HEqual(Operation operation) { | 1554 | Expression Logical2HEqual(Operation operation) { |
| 1451 | return GenerateHalfComparison<with_nan>(operation, "equal"); | 1555 | return GenerateHalfComparison<with_nan>(operation, "equal"); |
| 1452 | } | 1556 | } |
| 1453 | 1557 | ||
| 1454 | template <bool with_nan> | 1558 | template <bool with_nan> |
| 1455 | std::string Logical2HLessEqual(Operation operation) { | 1559 | Expression Logical2HLessEqual(Operation operation) { |
| 1456 | return GenerateHalfComparison<with_nan>(operation, "lessThanEqual"); | 1560 | return GenerateHalfComparison<with_nan>(operation, "lessThanEqual"); |
| 1457 | } | 1561 | } |
| 1458 | 1562 | ||
| 1459 | template <bool with_nan> | 1563 | template <bool with_nan> |
| 1460 | std::string Logical2HGreaterThan(Operation operation) { | 1564 | Expression Logical2HGreaterThan(Operation operation) { |
| 1461 | return GenerateHalfComparison<with_nan>(operation, "greaterThan"); | 1565 | return GenerateHalfComparison<with_nan>(operation, "greaterThan"); |
| 1462 | } | 1566 | } |
| 1463 | 1567 | ||
| 1464 | template <bool with_nan> | 1568 | template <bool with_nan> |
| 1465 | std::string Logical2HNotEqual(Operation operation) { | 1569 | Expression Logical2HNotEqual(Operation operation) { |
| 1466 | return GenerateHalfComparison<with_nan>(operation, "notEqual"); | 1570 | return GenerateHalfComparison<with_nan>(operation, "notEqual"); |
| 1467 | } | 1571 | } |
| 1468 | 1572 | ||
| 1469 | template <bool with_nan> | 1573 | template <bool with_nan> |
| 1470 | std::string Logical2HGreaterEqual(Operation operation) { | 1574 | Expression Logical2HGreaterEqual(Operation operation) { |
| 1471 | return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual"); | 1575 | return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual"); |
| 1472 | } | 1576 | } |
| 1473 | 1577 | ||
| 1474 | std::string Texture(Operation operation) { | 1578 | Expression Texture(Operation operation) { |
| 1475 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1579 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 1476 | ASSERT(meta); | 1580 | ASSERT(meta); |
| 1477 | 1581 | ||
| @@ -1480,10 +1584,10 @@ private: | |||
| 1480 | if (meta->sampler.IsShadow()) { | 1584 | if (meta->sampler.IsShadow()) { |
| 1481 | expr = "vec4(" + expr + ')'; | 1585 | expr = "vec4(" + expr + ')'; |
| 1482 | } | 1586 | } |
| 1483 | return expr + GetSwizzle(meta->element); | 1587 | return {expr + GetSwizzle(meta->element), Type::Float}; |
| 1484 | } | 1588 | } |
| 1485 | 1589 | ||
| 1486 | std::string TextureLod(Operation operation) { | 1590 | Expression TextureLod(Operation operation) { |
| 1487 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1591 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 1488 | ASSERT(meta); | 1592 | ASSERT(meta); |
| 1489 | 1593 | ||
| @@ -1492,54 +1596,54 @@ private: | |||
| 1492 | if (meta->sampler.IsShadow()) { | 1596 | if (meta->sampler.IsShadow()) { |
| 1493 | expr = "vec4(" + expr + ')'; | 1597 | expr = "vec4(" + expr + ')'; |
| 1494 | } | 1598 | } |
| 1495 | return expr + GetSwizzle(meta->element); | 1599 | return {expr + GetSwizzle(meta->element), Type::Float}; |
| 1496 | } | 1600 | } |
| 1497 | 1601 | ||
| 1498 | std::string TextureGather(Operation operation) { | 1602 | Expression TextureGather(Operation operation) { |
| 1499 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1603 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 1500 | ASSERT(meta); | 1604 | ASSERT(meta); |
| 1501 | 1605 | ||
| 1502 | const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int; | 1606 | const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int; |
| 1503 | return GenerateTexture(operation, "Gather", | 1607 | return {GenerateTexture(operation, "Gather", |
| 1504 | {TextureArgument{type, meta->component}, TextureAoffi{}}) + | 1608 | {TextureArgument{type, meta->component}, TextureAoffi{}}) + |
| 1505 | GetSwizzle(meta->element); | 1609 | GetSwizzle(meta->element), |
| 1610 | Type::Float}; | ||
| 1506 | } | 1611 | } |
| 1507 | 1612 | ||
| 1508 | std::string TextureQueryDimensions(Operation operation) { | 1613 | Expression TextureQueryDimensions(Operation operation) { |
| 1509 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1614 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 1510 | ASSERT(meta); | 1615 | ASSERT(meta); |
| 1511 | 1616 | ||
| 1512 | const std::string sampler = GetSampler(meta->sampler); | 1617 | const std::string sampler = GetSampler(meta->sampler); |
| 1513 | const std::string lod = VisitOperand(operation, 0, Type::Int); | 1618 | const std::string lod = VisitOperand(operation, 0).AsInt(); |
| 1514 | 1619 | ||
| 1515 | switch (meta->element) { | 1620 | switch (meta->element) { |
| 1516 | case 0: | 1621 | case 0: |
| 1517 | case 1: | 1622 | case 1: |
| 1518 | return fmt::format("itof(int(textureSize({}, {}){}))", sampler, lod, | 1623 | return {fmt::format("textureSize({}, {}){}", sampler, lod, GetSwizzle(meta->element)), |
| 1519 | GetSwizzle(meta->element)); | 1624 | Type::Int}; |
| 1520 | case 2: | ||
| 1521 | return "0"; | ||
| 1522 | case 3: | 1625 | case 3: |
| 1523 | return fmt::format("itof(textureQueryLevels({}))", sampler); | 1626 | return {fmt::format("textureQueryLevels({})", sampler), Type::Int}; |
| 1524 | } | 1627 | } |
| 1525 | UNREACHABLE(); | 1628 | UNREACHABLE(); |
| 1526 | return "0"; | 1629 | return {"0", Type::Int}; |
| 1527 | } | 1630 | } |
| 1528 | 1631 | ||
| 1529 | std::string TextureQueryLod(Operation operation) { | 1632 | Expression TextureQueryLod(Operation operation) { |
| 1530 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1633 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 1531 | ASSERT(meta); | 1634 | ASSERT(meta); |
| 1532 | 1635 | ||
| 1533 | if (meta->element < 2) { | 1636 | if (meta->element < 2) { |
| 1534 | return fmt::format("itof(int(({} * vec2(256)){}))", | 1637 | return {fmt::format("int(({} * vec2(256)){})", |
| 1535 | GenerateTexture(operation, "QueryLod", {}), | 1638 | GenerateTexture(operation, "QueryLod", {}), |
| 1536 | GetSwizzle(meta->element)); | 1639 | GetSwizzle(meta->element)), |
| 1640 | Type::Int}; | ||
| 1537 | } | 1641 | } |
| 1538 | return "0"; | 1642 | return {"0", Type::Int}; |
| 1539 | } | 1643 | } |
| 1540 | 1644 | ||
| 1541 | std::string TexelFetch(Operation operation) { | 1645 | Expression TexelFetch(Operation operation) { |
| 1542 | constexpr std::array<const char*, 4> constructors = {"int", "ivec2", "ivec3", "ivec4"}; | 1646 | constexpr std::array constructors = {"int", "ivec2", "ivec3", "ivec4"}; |
| 1543 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1647 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 1544 | ASSERT(meta); | 1648 | ASSERT(meta); |
| 1545 | UNIMPLEMENTED_IF(meta->sampler.IsArray()); | 1649 | UNIMPLEMENTED_IF(meta->sampler.IsArray()); |
| @@ -1552,7 +1656,7 @@ private: | |||
| 1552 | expr += constructors.at(operation.GetOperandsCount() - 1); | 1656 | expr += constructors.at(operation.GetOperandsCount() - 1); |
| 1553 | expr += '('; | 1657 | expr += '('; |
| 1554 | for (std::size_t i = 0; i < count; ++i) { | 1658 | for (std::size_t i = 0; i < count; ++i) { |
| 1555 | expr += VisitOperand(operation, i, Type::Int); | 1659 | expr += VisitOperand(operation, i).AsInt(); |
| 1556 | const std::size_t next = i + 1; | 1660 | const std::size_t next = i + 1; |
| 1557 | if (next == count) | 1661 | if (next == count) |
| 1558 | expr += ')'; | 1662 | expr += ')'; |
| @@ -1565,7 +1669,7 @@ private: | |||
| 1565 | 1669 | ||
| 1566 | if (meta->lod) { | 1670 | if (meta->lod) { |
| 1567 | expr += ", "; | 1671 | expr += ", "; |
| 1568 | expr += CastOperand(Visit(meta->lod), Type::Int); | 1672 | expr += Visit(meta->lod).AsInt(); |
| 1569 | } | 1673 | } |
| 1570 | expr += ')'; | 1674 | expr += ')'; |
| 1571 | expr += GetSwizzle(meta->element); | 1675 | expr += GetSwizzle(meta->element); |
| @@ -1580,11 +1684,11 @@ private: | |||
| 1580 | code.AddLine("float {} = {};", tmp, expr); | 1684 | code.AddLine("float {} = {};", tmp, expr); |
| 1581 | code.AddLine("#endif"); | 1685 | code.AddLine("#endif"); |
| 1582 | 1686 | ||
| 1583 | return tmp; | 1687 | return {tmp, Type::Float}; |
| 1584 | } | 1688 | } |
| 1585 | 1689 | ||
| 1586 | std::string ImageStore(Operation operation) { | 1690 | Expression ImageStore(Operation operation) { |
| 1587 | constexpr std::array<const char*, 4> constructors{"int(", "ivec2(", "ivec3(", "ivec4("}; | 1691 | constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("}; |
| 1588 | const auto meta{std::get<MetaImage>(operation.GetMeta())}; | 1692 | const auto meta{std::get<MetaImage>(operation.GetMeta())}; |
| 1589 | 1693 | ||
| 1590 | std::string expr = "imageStore("; | 1694 | std::string expr = "imageStore("; |
| @@ -1594,7 +1698,7 @@ private: | |||
| 1594 | const std::size_t coords_count{operation.GetOperandsCount()}; | 1698 | const std::size_t coords_count{operation.GetOperandsCount()}; |
| 1595 | expr += constructors.at(coords_count - 1); | 1699 | expr += constructors.at(coords_count - 1); |
| 1596 | for (std::size_t i = 0; i < coords_count; ++i) { | 1700 | for (std::size_t i = 0; i < coords_count; ++i) { |
| 1597 | expr += VisitOperand(operation, i, Type::Int); | 1701 | expr += VisitOperand(operation, i).AsInt(); |
| 1598 | if (i + 1 < coords_count) { | 1702 | if (i + 1 < coords_count) { |
| 1599 | expr += ", "; | 1703 | expr += ", "; |
| 1600 | } | 1704 | } |
| @@ -1605,7 +1709,7 @@ private: | |||
| 1605 | UNIMPLEMENTED_IF(values_count != 4); | 1709 | UNIMPLEMENTED_IF(values_count != 4); |
| 1606 | expr += "vec4("; | 1710 | expr += "vec4("; |
| 1607 | for (std::size_t i = 0; i < values_count; ++i) { | 1711 | for (std::size_t i = 0; i < values_count; ++i) { |
| 1608 | expr += Visit(meta.values.at(i)); | 1712 | expr += Visit(meta.values.at(i)).AsFloat(); |
| 1609 | if (i + 1 < values_count) { | 1713 | if (i + 1 < values_count) { |
| 1610 | expr += ", "; | 1714 | expr += ", "; |
| 1611 | } | 1715 | } |
| @@ -1616,52 +1720,52 @@ private: | |||
| 1616 | return {}; | 1720 | return {}; |
| 1617 | } | 1721 | } |
| 1618 | 1722 | ||
| 1619 | std::string Branch(Operation operation) { | 1723 | Expression Branch(Operation operation) { |
| 1620 | const auto target = std::get_if<ImmediateNode>(&*operation[0]); | 1724 | const auto target = std::get_if<ImmediateNode>(&*operation[0]); |
| 1621 | UNIMPLEMENTED_IF(!target); | 1725 | UNIMPLEMENTED_IF(!target); |
| 1622 | 1726 | ||
| 1623 | code.AddLine("jmp_to = 0x{:x}u;", target->GetValue()); | 1727 | code.AddLine("jmp_to = 0x{:X}U;", target->GetValue()); |
| 1624 | code.AddLine("break;"); | 1728 | code.AddLine("break;"); |
| 1625 | return {}; | 1729 | return {}; |
| 1626 | } | 1730 | } |
| 1627 | 1731 | ||
| 1628 | std::string BranchIndirect(Operation operation) { | 1732 | Expression BranchIndirect(Operation operation) { |
| 1629 | const std::string op_a = VisitOperand(operation, 0, Type::Uint); | 1733 | const std::string op_a = VisitOperand(operation, 0).AsUint(); |
| 1630 | 1734 | ||
| 1631 | code.AddLine("jmp_to = {};", op_a); | 1735 | code.AddLine("jmp_to = {};", op_a); |
| 1632 | code.AddLine("break;"); | 1736 | code.AddLine("break;"); |
| 1633 | return {}; | 1737 | return {}; |
| 1634 | } | 1738 | } |
| 1635 | 1739 | ||
| 1636 | std::string PushFlowStack(Operation operation) { | 1740 | Expression PushFlowStack(Operation operation) { |
| 1637 | const auto stack = std::get<MetaStackClass>(operation.GetMeta()); | 1741 | const auto stack = std::get<MetaStackClass>(operation.GetMeta()); |
| 1638 | const auto target = std::get_if<ImmediateNode>(&*operation[0]); | 1742 | const auto target = std::get_if<ImmediateNode>(&*operation[0]); |
| 1639 | UNIMPLEMENTED_IF(!target); | 1743 | UNIMPLEMENTED_IF(!target); |
| 1640 | 1744 | ||
| 1641 | code.AddLine("{}[{}++] = 0x{:x}u;", FlowStackName(stack), FlowStackTopName(stack), | 1745 | code.AddLine("{}[{}++] = 0x{:X}U;", FlowStackName(stack), FlowStackTopName(stack), |
| 1642 | target->GetValue()); | 1746 | target->GetValue()); |
| 1643 | return {}; | 1747 | return {}; |
| 1644 | } | 1748 | } |
| 1645 | 1749 | ||
| 1646 | std::string PopFlowStack(Operation operation) { | 1750 | Expression PopFlowStack(Operation operation) { |
| 1647 | const auto stack = std::get<MetaStackClass>(operation.GetMeta()); | 1751 | const auto stack = std::get<MetaStackClass>(operation.GetMeta()); |
| 1648 | code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack)); | 1752 | code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack)); |
| 1649 | code.AddLine("break;"); | 1753 | code.AddLine("break;"); |
| 1650 | return {}; | 1754 | return {}; |
| 1651 | } | 1755 | } |
| 1652 | 1756 | ||
| 1653 | std::string Exit(Operation operation) { | 1757 | Expression Exit(Operation operation) { |
| 1654 | if (stage != ProgramType::Fragment) { | 1758 | if (stage != ProgramType::Fragment) { |
| 1655 | code.AddLine("return;"); | 1759 | code.AddLine("return;"); |
| 1656 | return {}; | 1760 | return {}; |
| 1657 | } | 1761 | } |
| 1658 | const auto& used_registers = ir.GetRegisters(); | 1762 | const auto& used_registers = ir.GetRegisters(); |
| 1659 | const auto SafeGetRegister = [&](u32 reg) -> std::string { | 1763 | const auto SafeGetRegister = [&](u32 reg) -> Expression { |
| 1660 | // TODO(Rodrigo): Replace with contains once C++20 releases | 1764 | // TODO(Rodrigo): Replace with contains once C++20 releases |
| 1661 | if (used_registers.find(reg) != used_registers.end()) { | 1765 | if (used_registers.find(reg) != used_registers.end()) { |
| 1662 | return GetRegister(reg); | 1766 | return {GetRegister(reg), Type::Float}; |
| 1663 | } | 1767 | } |
| 1664 | return "0.0f"; | 1768 | return {"0.0f", Type::Float}; |
| 1665 | }; | 1769 | }; |
| 1666 | 1770 | ||
| 1667 | UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented"); | 1771 | UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented"); |
| @@ -1674,7 +1778,7 @@ private: | |||
| 1674 | for (u32 component = 0; component < 4; ++component) { | 1778 | for (u32 component = 0; component < 4; ++component) { |
| 1675 | if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { | 1779 | if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { |
| 1676 | code.AddLine("FragColor{}[{}] = {};", render_target, component, | 1780 | code.AddLine("FragColor{}[{}] = {};", render_target, component, |
| 1677 | SafeGetRegister(current_reg)); | 1781 | SafeGetRegister(current_reg).AsFloat()); |
| 1678 | ++current_reg; | 1782 | ++current_reg; |
| 1679 | } | 1783 | } |
| 1680 | } | 1784 | } |
| @@ -1683,14 +1787,14 @@ private: | |||
| 1683 | if (header.ps.omap.depth) { | 1787 | if (header.ps.omap.depth) { |
| 1684 | // The depth output is always 2 registers after the last color output, and current_reg | 1788 | // The depth output is always 2 registers after the last color output, and current_reg |
| 1685 | // already contains one past the last color register. | 1789 | // already contains one past the last color register. |
| 1686 | code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1)); | 1790 | code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1).AsFloat()); |
| 1687 | } | 1791 | } |
| 1688 | 1792 | ||
| 1689 | code.AddLine("return;"); | 1793 | code.AddLine("return;"); |
| 1690 | return {}; | 1794 | return {}; |
| 1691 | } | 1795 | } |
| 1692 | 1796 | ||
| 1693 | std::string Discard(Operation operation) { | 1797 | Expression Discard(Operation operation) { |
| 1694 | // Enclose "discard" in a conditional, so that GLSL compilation does not complain | 1798 | // Enclose "discard" in a conditional, so that GLSL compilation does not complain |
| 1695 | // about unexecuted instructions that may follow this. | 1799 | // about unexecuted instructions that may follow this. |
| 1696 | code.AddLine("if (true) {{"); | 1800 | code.AddLine("if (true) {{"); |
| @@ -1701,7 +1805,7 @@ private: | |||
| 1701 | return {}; | 1805 | return {}; |
| 1702 | } | 1806 | } |
| 1703 | 1807 | ||
| 1704 | std::string EmitVertex(Operation operation) { | 1808 | Expression EmitVertex(Operation operation) { |
| 1705 | ASSERT_MSG(stage == ProgramType::Geometry, | 1809 | ASSERT_MSG(stage == ProgramType::Geometry, |
| 1706 | "EmitVertex is expected to be used in a geometry shader."); | 1810 | "EmitVertex is expected to be used in a geometry shader."); |
| 1707 | 1811 | ||
| @@ -1712,7 +1816,7 @@ private: | |||
| 1712 | return {}; | 1816 | return {}; |
| 1713 | } | 1817 | } |
| 1714 | 1818 | ||
| 1715 | std::string EndPrimitive(Operation operation) { | 1819 | Expression EndPrimitive(Operation operation) { |
| 1716 | ASSERT_MSG(stage == ProgramType::Geometry, | 1820 | ASSERT_MSG(stage == ProgramType::Geometry, |
| 1717 | "EndPrimitive is expected to be used in a geometry shader."); | 1821 | "EndPrimitive is expected to be used in a geometry shader."); |
| 1718 | 1822 | ||
| @@ -1720,59 +1824,59 @@ private: | |||
| 1720 | return {}; | 1824 | return {}; |
| 1721 | } | 1825 | } |
| 1722 | 1826 | ||
| 1723 | std::string YNegate(Operation operation) { | 1827 | Expression YNegate(Operation operation) { |
| 1724 | // Config pack's third value is Y_NEGATE's state. | 1828 | // Config pack's third value is Y_NEGATE's state. |
| 1725 | return "uintBitsToFloat(config_pack[2])"; | 1829 | return {"config_pack[2]", Type::Uint}; |
| 1726 | } | 1830 | } |
| 1727 | 1831 | ||
| 1728 | template <u32 element> | 1832 | template <u32 element> |
| 1729 | std::string LocalInvocationId(Operation) { | 1833 | Expression LocalInvocationId(Operation) { |
| 1730 | return "utof(gl_LocalInvocationID"s + GetSwizzle(element) + ')'; | 1834 | return {"gl_LocalInvocationID"s + GetSwizzle(element), Type::Uint}; |
| 1731 | } | 1835 | } |
| 1732 | 1836 | ||
| 1733 | template <u32 element> | 1837 | template <u32 element> |
| 1734 | std::string WorkGroupId(Operation) { | 1838 | Expression WorkGroupId(Operation) { |
| 1735 | return "utof(gl_WorkGroupID"s + GetSwizzle(element) + ')'; | 1839 | return {"gl_WorkGroupID"s + GetSwizzle(element), Type::Uint}; |
| 1736 | } | 1840 | } |
| 1737 | 1841 | ||
| 1738 | std::string BallotThread(Operation operation) { | 1842 | Expression BallotThread(Operation operation) { |
| 1739 | const std::string value = VisitOperand(operation, 0, Type::Bool); | 1843 | const std::string value = VisitOperand(operation, 0).AsBool(); |
| 1740 | if (!device.HasWarpIntrinsics()) { | 1844 | if (!device.HasWarpIntrinsics()) { |
| 1741 | LOG_ERROR(Render_OpenGL, | 1845 | LOG_ERROR(Render_OpenGL, |
| 1742 | "Nvidia warp intrinsics are not available and its required by a shader"); | 1846 | "Nvidia warp intrinsics are not available and its required by a shader"); |
| 1743 | // Stub on non-Nvidia devices by simulating all threads voting the same as the active | 1847 | // Stub on non-Nvidia devices by simulating all threads voting the same as the active |
| 1744 | // one. | 1848 | // one. |
| 1745 | return fmt::format("utof({} ? 0xFFFFFFFFU : 0U)", value); | 1849 | return {fmt::format("({} ? 0xFFFFFFFFU : 0U)", value), Type::Uint}; |
| 1746 | } | 1850 | } |
| 1747 | return fmt::format("utof(ballotThreadNV({}))", value); | 1851 | return {fmt::format("ballotThreadNV({})", value), Type::Uint}; |
| 1748 | } | 1852 | } |
| 1749 | 1853 | ||
| 1750 | std::string Vote(Operation operation, const char* func) { | 1854 | Expression Vote(Operation operation, const char* func) { |
| 1751 | const std::string value = VisitOperand(operation, 0, Type::Bool); | 1855 | const std::string value = VisitOperand(operation, 0).AsBool(); |
| 1752 | if (!device.HasWarpIntrinsics()) { | 1856 | if (!device.HasWarpIntrinsics()) { |
| 1753 | LOG_ERROR(Render_OpenGL, | 1857 | LOG_ERROR(Render_OpenGL, |
| 1754 | "Nvidia vote intrinsics are not available and its required by a shader"); | 1858 | "Nvidia vote intrinsics are not available and its required by a shader"); |
| 1755 | // Stub with a warp size of one. | 1859 | // Stub with a warp size of one. |
| 1756 | return value; | 1860 | return {value, Type::Bool}; |
| 1757 | } | 1861 | } |
| 1758 | return fmt::format("{}({})", func, value); | 1862 | return {fmt::format("{}({})", func, value), Type::Bool}; |
| 1759 | } | 1863 | } |
| 1760 | 1864 | ||
| 1761 | std::string VoteAll(Operation operation) { | 1865 | Expression VoteAll(Operation operation) { |
| 1762 | return Vote(operation, "allThreadsNV"); | 1866 | return Vote(operation, "allThreadsNV"); |
| 1763 | } | 1867 | } |
| 1764 | 1868 | ||
| 1765 | std::string VoteAny(Operation operation) { | 1869 | Expression VoteAny(Operation operation) { |
| 1766 | return Vote(operation, "anyThreadNV"); | 1870 | return Vote(operation, "anyThreadNV"); |
| 1767 | } | 1871 | } |
| 1768 | 1872 | ||
| 1769 | std::string VoteEqual(Operation operation) { | 1873 | Expression VoteEqual(Operation operation) { |
| 1770 | if (!device.HasWarpIntrinsics()) { | 1874 | if (!device.HasWarpIntrinsics()) { |
| 1771 | LOG_ERROR(Render_OpenGL, | 1875 | LOG_ERROR(Render_OpenGL, |
| 1772 | "Nvidia vote intrinsics are not available and its required by a shader"); | 1876 | "Nvidia vote intrinsics are not available and its required by a shader"); |
| 1773 | // We must return true here since a stub for a theoretical warp size of 1 will always | 1877 | // We must return true here since a stub for a theoretical warp size of 1 will always |
| 1774 | // return an equal result for all its votes. | 1878 | // return an equal result for all its votes. |
| 1775 | return "true"; | 1879 | return {"true", Type::Bool}; |
| 1776 | } | 1880 | } |
| 1777 | return Vote(operation, "allThreadsEqualNV"); | 1881 | return Vote(operation, "allThreadsEqualNV"); |
| 1778 | } | 1882 | } |
| @@ -1973,8 +2077,8 @@ private: | |||
| 1973 | } | 2077 | } |
| 1974 | 2078 | ||
| 1975 | std::string GetInternalFlag(InternalFlag flag) const { | 2079 | std::string GetInternalFlag(InternalFlag flag) const { |
| 1976 | constexpr std::array<const char*, 4> InternalFlagNames = {"zero_flag", "sign_flag", | 2080 | constexpr std::array InternalFlagNames = {"zero_flag", "sign_flag", "carry_flag", |
| 1977 | "carry_flag", "overflow_flag"}; | 2081 | "overflow_flag"}; |
| 1978 | const auto index = static_cast<u32>(flag); | 2082 | const auto index = static_cast<u32>(flag); |
| 1979 | ASSERT(index < static_cast<u32>(InternalFlag::Amount)); | 2083 | ASSERT(index < static_cast<u32>(InternalFlag::Amount)); |
| 1980 | 2084 | ||
| @@ -2022,24 +2126,16 @@ private: | |||
| 2022 | 2126 | ||
| 2023 | std::string GetCommonDeclarations() { | 2127 | std::string GetCommonDeclarations() { |
| 2024 | return fmt::format( | 2128 | return fmt::format( |
| 2025 | "#define MAX_CONSTBUFFER_ELEMENTS {}\n" | ||
| 2026 | "#define ftoi floatBitsToInt\n" | 2129 | "#define ftoi floatBitsToInt\n" |
| 2027 | "#define ftou floatBitsToUint\n" | 2130 | "#define ftou floatBitsToUint\n" |
| 2028 | "#define itof intBitsToFloat\n" | 2131 | "#define itof intBitsToFloat\n" |
| 2029 | "#define utof uintBitsToFloat\n\n" | 2132 | "#define utof uintBitsToFloat\n\n" |
| 2030 | "float fromHalf2(vec2 pair) {{\n" | 2133 | "bvec2 HalfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{\n" |
| 2031 | " return utof(packHalf2x16(pair));\n" | ||
| 2032 | "}}\n\n" | ||
| 2033 | "vec2 toHalf2(float value) {{\n" | ||
| 2034 | " return unpackHalf2x16(ftou(value));\n" | ||
| 2035 | "}}\n\n" | ||
| 2036 | "bvec2 halfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{\n" | ||
| 2037 | " bvec2 is_nan1 = isnan(pair1);\n" | 2134 | " bvec2 is_nan1 = isnan(pair1);\n" |
| 2038 | " bvec2 is_nan2 = isnan(pair2);\n" | 2135 | " bvec2 is_nan2 = isnan(pair2);\n" |
| 2039 | " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || " | 2136 | " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || " |
| 2040 | "is_nan2.y);\n" | 2137 | "is_nan2.y);\n" |
| 2041 | "}}\n", | 2138 | "}}\n\n"); |
| 2042 | MAX_CONSTBUFFER_ELEMENTS); | ||
| 2043 | } | 2139 | } |
| 2044 | 2140 | ||
| 2045 | ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage, | 2141 | ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage, |
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index a05cef3b9..af9684839 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp | |||
| @@ -101,9 +101,7 @@ RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::Syst | |||
| 101 | 101 | ||
| 102 | RendererOpenGL::~RendererOpenGL() = default; | 102 | RendererOpenGL::~RendererOpenGL() = default; |
| 103 | 103 | ||
| 104 | void RendererOpenGL::SwapBuffers( | 104 | void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { |
| 105 | std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) { | ||
| 106 | |||
| 107 | system.GetPerfStats().EndSystemFrame(); | 105 | system.GetPerfStats().EndSystemFrame(); |
| 108 | 106 | ||
| 109 | // Maintain the rasterizer's state as a priority | 107 | // Maintain the rasterizer's state as a priority |
| @@ -113,9 +111,9 @@ void RendererOpenGL::SwapBuffers( | |||
| 113 | 111 | ||
| 114 | if (framebuffer) { | 112 | if (framebuffer) { |
| 115 | // If framebuffer is provided, reload it from memory to a texture | 113 | // If framebuffer is provided, reload it from memory to a texture |
| 116 | if (screen_info.texture.width != (GLsizei)framebuffer->get().width || | 114 | if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) || |
| 117 | screen_info.texture.height != (GLsizei)framebuffer->get().height || | 115 | screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) || |
| 118 | screen_info.texture.pixel_format != framebuffer->get().pixel_format) { | 116 | screen_info.texture.pixel_format != framebuffer->pixel_format) { |
| 119 | // Reallocate texture if the framebuffer size has changed. | 117 | // Reallocate texture if the framebuffer size has changed. |
| 120 | // This is expected to not happen very often and hence should not be a | 118 | // This is expected to not happen very often and hence should not be a |
| 121 | // performance problem. | 119 | // performance problem. |
| @@ -149,43 +147,43 @@ void RendererOpenGL::SwapBuffers( | |||
| 149 | * Loads framebuffer from emulated memory into the active OpenGL texture. | 147 | * Loads framebuffer from emulated memory into the active OpenGL texture. |
| 150 | */ | 148 | */ |
| 151 | void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) { | 149 | void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) { |
| 152 | const u32 bytes_per_pixel{Tegra::FramebufferConfig::BytesPerPixel(framebuffer.pixel_format)}; | ||
| 153 | const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel}; | ||
| 154 | const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset}; | ||
| 155 | |||
| 156 | // Framebuffer orientation handling | 150 | // Framebuffer orientation handling |
| 157 | framebuffer_transform_flags = framebuffer.transform_flags; | 151 | framebuffer_transform_flags = framebuffer.transform_flags; |
| 158 | framebuffer_crop_rect = framebuffer.crop_rect; | 152 | framebuffer_crop_rect = framebuffer.crop_rect; |
| 159 | 153 | ||
| 160 | // Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default | 154 | const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset}; |
| 161 | // only allows rows to have a memory alignement of 4. | 155 | if (rasterizer->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride)) { |
| 162 | ASSERT(framebuffer.stride % 4 == 0); | 156 | return; |
| 163 | 157 | } | |
| 164 | if (!rasterizer->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride)) { | ||
| 165 | // Reset the screen info's display texture to its own permanent texture | ||
| 166 | screen_info.display_texture = screen_info.texture.resource.handle; | ||
| 167 | |||
| 168 | rasterizer->FlushRegion(ToCacheAddr(Memory::GetPointer(framebuffer_addr)), size_in_bytes); | ||
| 169 | |||
| 170 | constexpr u32 linear_bpp = 4; | ||
| 171 | VideoCore::MortonCopyPixels128(VideoCore::MortonSwizzleMode::MortonToLinear, | ||
| 172 | framebuffer.width, framebuffer.height, bytes_per_pixel, | ||
| 173 | linear_bpp, Memory::GetPointer(framebuffer_addr), | ||
| 174 | gl_framebuffer_data.data()); | ||
| 175 | |||
| 176 | glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride)); | ||
| 177 | 158 | ||
| 178 | // Update existing texture | 159 | // Reset the screen info's display texture to its own permanent texture |
| 179 | // TODO: Test what happens on hardware when you change the framebuffer dimensions so that | 160 | screen_info.display_texture = screen_info.texture.resource.handle; |
| 180 | // they differ from the LCD resolution. | ||
| 181 | // TODO: Applications could theoretically crash yuzu here by specifying too large | ||
| 182 | // framebuffer sizes. We should make sure that this cannot happen. | ||
| 183 | glTextureSubImage2D(screen_info.texture.resource.handle, 0, 0, 0, framebuffer.width, | ||
| 184 | framebuffer.height, screen_info.texture.gl_format, | ||
| 185 | screen_info.texture.gl_type, gl_framebuffer_data.data()); | ||
| 186 | 161 | ||
| 187 | glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); | 162 | const auto pixel_format{ |
| 188 | } | 163 | VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)}; |
| 164 | const u32 bytes_per_pixel{VideoCore::Surface::GetBytesPerPixel(pixel_format)}; | ||
| 165 | const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel}; | ||
| 166 | const auto host_ptr{Memory::GetPointer(framebuffer_addr)}; | ||
| 167 | rasterizer->FlushRegion(ToCacheAddr(host_ptr), size_in_bytes); | ||
| 168 | |||
| 169 | // TODO(Rodrigo): Read this from HLE | ||
| 170 | constexpr u32 block_height_log2 = 4; | ||
| 171 | VideoCore::MortonSwizzle(VideoCore::MortonSwizzleMode::MortonToLinear, pixel_format, | ||
| 172 | framebuffer.stride, block_height_log2, framebuffer.height, 0, 1, 1, | ||
| 173 | gl_framebuffer_data.data(), host_ptr); | ||
| 174 | |||
| 175 | glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride)); | ||
| 176 | |||
| 177 | // Update existing texture | ||
| 178 | // TODO: Test what happens on hardware when you change the framebuffer dimensions so that | ||
| 179 | // they differ from the LCD resolution. | ||
| 180 | // TODO: Applications could theoretically crash yuzu here by specifying too large | ||
| 181 | // framebuffer sizes. We should make sure that this cannot happen. | ||
| 182 | glTextureSubImage2D(screen_info.texture.resource.handle, 0, 0, 0, framebuffer.width, | ||
| 183 | framebuffer.height, screen_info.texture.gl_format, | ||
| 184 | screen_info.texture.gl_type, gl_framebuffer_data.data()); | ||
| 185 | |||
| 186 | glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); | ||
| 189 | } | 187 | } |
| 190 | 188 | ||
| 191 | /** | 189 | /** |
| @@ -276,22 +274,29 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, | |||
| 276 | texture.height = framebuffer.height; | 274 | texture.height = framebuffer.height; |
| 277 | texture.pixel_format = framebuffer.pixel_format; | 275 | texture.pixel_format = framebuffer.pixel_format; |
| 278 | 276 | ||
| 277 | const auto pixel_format{ | ||
| 278 | VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)}; | ||
| 279 | const u32 bytes_per_pixel{VideoCore::Surface::GetBytesPerPixel(pixel_format)}; | ||
| 280 | gl_framebuffer_data.resize(texture.width * texture.height * bytes_per_pixel); | ||
| 281 | |||
| 279 | GLint internal_format; | 282 | GLint internal_format; |
| 280 | switch (framebuffer.pixel_format) { | 283 | switch (framebuffer.pixel_format) { |
| 281 | case Tegra::FramebufferConfig::PixelFormat::ABGR8: | 284 | case Tegra::FramebufferConfig::PixelFormat::ABGR8: |
| 282 | internal_format = GL_RGBA8; | 285 | internal_format = GL_RGBA8; |
| 283 | texture.gl_format = GL_RGBA; | 286 | texture.gl_format = GL_RGBA; |
| 284 | texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; | 287 | texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; |
| 285 | gl_framebuffer_data.resize(texture.width * texture.height * 4); | 288 | break; |
| 289 | case Tegra::FramebufferConfig::PixelFormat::RGB565: | ||
| 290 | internal_format = GL_RGB565; | ||
| 291 | texture.gl_format = GL_RGB; | ||
| 292 | texture.gl_type = GL_UNSIGNED_SHORT_5_6_5; | ||
| 286 | break; | 293 | break; |
| 287 | default: | 294 | default: |
| 288 | internal_format = GL_RGBA8; | 295 | internal_format = GL_RGBA8; |
| 289 | texture.gl_format = GL_RGBA; | 296 | texture.gl_format = GL_RGBA; |
| 290 | texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; | 297 | texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; |
| 291 | gl_framebuffer_data.resize(texture.width * texture.height * 4); | 298 | UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}", |
| 292 | LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer pixel format: {}", | 299 | static_cast<u32>(framebuffer.pixel_format)); |
| 293 | static_cast<u32>(framebuffer.pixel_format)); | ||
| 294 | UNREACHABLE(); | ||
| 295 | } | 300 | } |
| 296 | 301 | ||
| 297 | texture.resource.Release(); | 302 | texture.resource.Release(); |
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 4aebf2321..9bd086368 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h | |||
| @@ -43,14 +43,13 @@ struct ScreenInfo { | |||
| 43 | TextureInfo texture; | 43 | TextureInfo texture; |
| 44 | }; | 44 | }; |
| 45 | 45 | ||
| 46 | class RendererOpenGL : public VideoCore::RendererBase { | 46 | class RendererOpenGL final : public VideoCore::RendererBase { |
| 47 | public: | 47 | public: |
| 48 | explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system); | 48 | explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system); |
| 49 | ~RendererOpenGL() override; | 49 | ~RendererOpenGL() override; |
| 50 | 50 | ||
| 51 | /// Swap buffers (render frame) | 51 | /// Swap buffers (render frame) |
| 52 | void SwapBuffers( | 52 | void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; |
| 53 | std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override; | ||
| 54 | 53 | ||
| 55 | /// Initialize the renderer | 54 | /// Initialize the renderer |
| 56 | bool Init() override; | 55 | bool Init() override; |
diff --git a/src/video_core/shader/decode/conversion.cpp b/src/video_core/shader/decode/conversion.cpp index 8973fbefa..32facd6ba 100644 --- a/src/video_core/shader/decode/conversion.cpp +++ b/src/video_core/shader/decode/conversion.cpp | |||
| @@ -14,6 +14,12 @@ using Tegra::Shader::Instruction; | |||
| 14 | using Tegra::Shader::OpCode; | 14 | using Tegra::Shader::OpCode; |
| 15 | using Tegra::Shader::Register; | 15 | using Tegra::Shader::Register; |
| 16 | 16 | ||
| 17 | namespace { | ||
| 18 | constexpr OperationCode GetFloatSelector(u64 selector) { | ||
| 19 | return selector == 0 ? OperationCode::FCastHalf0 : OperationCode::FCastHalf1; | ||
| 20 | } | ||
| 21 | } // Anonymous namespace | ||
| 22 | |||
| 17 | u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { | 23 | u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { |
| 18 | const Instruction instr = {program_code[pc]}; | 24 | const Instruction instr = {program_code[pc]}; |
| 19 | const auto opcode = OpCode::Decode(instr); | 25 | const auto opcode = OpCode::Decode(instr); |
| @@ -22,7 +28,7 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { | |||
| 22 | case OpCode::Id::I2I_R: | 28 | case OpCode::Id::I2I_R: |
| 23 | case OpCode::Id::I2I_C: | 29 | case OpCode::Id::I2I_C: |
| 24 | case OpCode::Id::I2I_IMM: { | 30 | case OpCode::Id::I2I_IMM: { |
| 25 | UNIMPLEMENTED_IF(instr.conversion.selector); | 31 | UNIMPLEMENTED_IF(instr.conversion.int_src.selector != 0); |
| 26 | UNIMPLEMENTED_IF(instr.conversion.dst_size != Register::Size::Word); | 32 | UNIMPLEMENTED_IF(instr.conversion.dst_size != Register::Size::Word); |
| 27 | UNIMPLEMENTED_IF(instr.alu.saturate_d); | 33 | UNIMPLEMENTED_IF(instr.alu.saturate_d); |
| 28 | 34 | ||
| @@ -57,8 +63,8 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { | |||
| 57 | case OpCode::Id::I2F_R: | 63 | case OpCode::Id::I2F_R: |
| 58 | case OpCode::Id::I2F_C: | 64 | case OpCode::Id::I2F_C: |
| 59 | case OpCode::Id::I2F_IMM: { | 65 | case OpCode::Id::I2F_IMM: { |
| 66 | UNIMPLEMENTED_IF(instr.conversion.int_src.selector != 0); | ||
| 60 | UNIMPLEMENTED_IF(instr.conversion.dst_size == Register::Size::Long); | 67 | UNIMPLEMENTED_IF(instr.conversion.dst_size == Register::Size::Long); |
| 61 | UNIMPLEMENTED_IF(instr.conversion.selector); | ||
| 62 | UNIMPLEMENTED_IF_MSG(instr.generates_cc, | 68 | UNIMPLEMENTED_IF_MSG(instr.generates_cc, |
| 63 | "Condition codes generation in I2F is not implemented"); | 69 | "Condition codes generation in I2F is not implemented"); |
| 64 | 70 | ||
| @@ -113,8 +119,10 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { | |||
| 113 | }(); | 119 | }(); |
| 114 | 120 | ||
| 115 | if (instr.conversion.src_size == Register::Size::Short) { | 121 | if (instr.conversion.src_size == Register::Size::Short) { |
| 116 | // TODO: figure where extract is sey in the encoding | 122 | value = Operation(GetFloatSelector(instr.conversion.float_src.selector), NO_PRECISE, |
| 117 | value = Operation(OperationCode::FCastHalf0, PRECISE, value); | 123 | std::move(value)); |
| 124 | } else { | ||
| 125 | ASSERT(instr.conversion.float_src.selector == 0); | ||
| 118 | } | 126 | } |
| 119 | 127 | ||
| 120 | value = GetOperandAbsNegFloat(value, instr.conversion.abs_a, instr.conversion.negate_a); | 128 | value = GetOperandAbsNegFloat(value, instr.conversion.abs_a, instr.conversion.negate_a); |
| @@ -169,8 +177,10 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { | |||
| 169 | }(); | 177 | }(); |
| 170 | 178 | ||
| 171 | if (instr.conversion.src_size == Register::Size::Short) { | 179 | if (instr.conversion.src_size == Register::Size::Short) { |
| 172 | // TODO: figure where extract is sey in the encoding | 180 | value = Operation(GetFloatSelector(instr.conversion.float_src.selector), NO_PRECISE, |
| 173 | value = Operation(OperationCode::FCastHalf0, PRECISE, value); | 181 | std::move(value)); |
| 182 | } else { | ||
| 183 | ASSERT(instr.conversion.float_src.selector == 0); | ||
| 174 | } | 184 | } |
| 175 | 185 | ||
| 176 | value = GetOperandAbsNegFloat(value, instr.conversion.abs_a, instr.conversion.negate_a); | 186 | value = GetOperandAbsNegFloat(value, instr.conversion.abs_a, instr.conversion.negate_a); |
diff --git a/src/video_core/shader/decode/float_set_predicate.cpp b/src/video_core/shader/decode/float_set_predicate.cpp index 34854fcca..200c2c983 100644 --- a/src/video_core/shader/decode/float_set_predicate.cpp +++ b/src/video_core/shader/decode/float_set_predicate.cpp | |||
| @@ -17,8 +17,8 @@ using Tegra::Shader::Pred; | |||
| 17 | u32 ShaderIR::DecodeFloatSetPredicate(NodeBlock& bb, u32 pc) { | 17 | u32 ShaderIR::DecodeFloatSetPredicate(NodeBlock& bb, u32 pc) { |
| 18 | const Instruction instr = {program_code[pc]}; | 18 | const Instruction instr = {program_code[pc]}; |
| 19 | 19 | ||
| 20 | const Node op_a = GetOperandAbsNegFloat(GetRegister(instr.gpr8), instr.fsetp.abs_a != 0, | 20 | Node op_a = GetOperandAbsNegFloat(GetRegister(instr.gpr8), instr.fsetp.abs_a != 0, |
| 21 | instr.fsetp.neg_a != 0); | 21 | instr.fsetp.neg_a != 0); |
| 22 | Node op_b = [&]() { | 22 | Node op_b = [&]() { |
| 23 | if (instr.is_b_imm) { | 23 | if (instr.is_b_imm) { |
| 24 | return GetImmediate19(instr); | 24 | return GetImmediate19(instr); |
| @@ -28,12 +28,13 @@ u32 ShaderIR::DecodeFloatSetPredicate(NodeBlock& bb, u32 pc) { | |||
| 28 | return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()); | 28 | return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()); |
| 29 | } | 29 | } |
| 30 | }(); | 30 | }(); |
| 31 | op_b = GetOperandAbsNegFloat(op_b, instr.fsetp.abs_b, false); | 31 | op_b = GetOperandAbsNegFloat(std::move(op_b), instr.fsetp.abs_b, instr.fsetp.neg_b); |
| 32 | 32 | ||
| 33 | // We can't use the constant predicate as destination. | 33 | // We can't use the constant predicate as destination. |
| 34 | ASSERT(instr.fsetp.pred3 != static_cast<u64>(Pred::UnusedIndex)); | 34 | ASSERT(instr.fsetp.pred3 != static_cast<u64>(Pred::UnusedIndex)); |
| 35 | 35 | ||
| 36 | const Node predicate = GetPredicateComparisonFloat(instr.fsetp.cond, op_a, op_b); | 36 | const Node predicate = |
| 37 | GetPredicateComparisonFloat(instr.fsetp.cond, std::move(op_a), std::move(op_b)); | ||
| 37 | const Node second_pred = GetPredicate(instr.fsetp.pred39, instr.fsetp.neg_pred != 0); | 38 | const Node second_pred = GetPredicate(instr.fsetp.pred39, instr.fsetp.neg_pred != 0); |
| 38 | 39 | ||
| 39 | const OperationCode combiner = GetPredicateCombiner(instr.fsetp.op); | 40 | const OperationCode combiner = GetPredicateCombiner(instr.fsetp.op); |
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) { | |||
| 42 | cond = instr.hsetp2.reg.cond; | 42 | cond = instr.hsetp2.reg.cond; |
| 43 | h_and = instr.hsetp2.reg.h_and; | 43 | h_and = instr.hsetp2.reg.h_and; |
| 44 | op_b = | 44 | op_b = |
| 45 | UnpackHalfFloat(GetOperandAbsNegHalf(GetRegister(instr.gpr20), instr.hsetp2.reg.abs_b, | 45 | GetOperandAbsNegHalf(UnpackHalfFloat(GetRegister(instr.gpr20), instr.hsetp2.reg.type_b), |
| 46 | instr.hsetp2.reg.negate_b), | 46 | instr.hsetp2.reg.abs_b, instr.hsetp2.reg.negate_b); |
| 47 | instr.hsetp2.reg.type_b); | ||
| 48 | break; | 47 | break; |
| 49 | default: | 48 | default: |
| 50 | UNREACHABLE(); | 49 | UNREACHABLE(); |
| @@ -52,22 +51,22 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) { | |||
| 52 | } | 51 | } |
| 53 | 52 | ||
| 54 | const OperationCode combiner = GetPredicateCombiner(instr.hsetp2.op); | 53 | const OperationCode combiner = GetPredicateCombiner(instr.hsetp2.op); |
| 55 | const Node combined_pred = GetPredicate(instr.hsetp2.pred3, instr.hsetp2.neg_pred); | 54 | const Node combined_pred = GetPredicate(instr.hsetp2.pred39, instr.hsetp2.neg_pred); |
| 56 | 55 | ||
| 57 | const auto Write = [&](u64 dest, Node src) { | 56 | const auto Write = [&](u64 dest, Node src) { |
| 58 | SetPredicate(bb, dest, Operation(combiner, std::move(src), combined_pred)); | 57 | SetPredicate(bb, dest, Operation(combiner, std::move(src), combined_pred)); |
| 59 | }; | 58 | }; |
| 60 | 59 | ||
| 61 | const Node comparison = GetPredicateComparisonHalf(cond, op_a, op_b); | 60 | const Node comparison = GetPredicateComparisonHalf(cond, op_a, op_b); |
| 62 | const u64 first = instr.hsetp2.pred0; | 61 | const u64 first = instr.hsetp2.pred3; |
| 63 | const u64 second = instr.hsetp2.pred39; | 62 | const u64 second = instr.hsetp2.pred0; |
| 64 | if (h_and) { | 63 | if (h_and) { |
| 65 | const Node joined = Operation(OperationCode::LogicalAnd2, comparison); | 64 | Node joined = Operation(OperationCode::LogicalAnd2, comparison); |
| 66 | Write(first, joined); | 65 | Write(first, joined); |
| 67 | Write(second, Operation(OperationCode::LogicalNegate, joined)); | 66 | Write(second, Operation(OperationCode::LogicalNegate, std::move(joined))); |
| 68 | } else { | 67 | } else { |
| 69 | Write(first, Operation(OperationCode::LogicalPick2, comparison, Immediate(0u))); | 68 | Write(first, Operation(OperationCode::LogicalPick2, comparison, Immediate(0U))); |
| 70 | Write(second, Operation(OperationCode::LogicalPick2, comparison, Immediate(1u))); | 69 | Write(second, Operation(OperationCode::LogicalPick2, comparison, Immediate(1U))); |
| 71 | } | 70 | } |
| 72 | 71 | ||
| 73 | return pc; | 72 | return pc; |
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index c50f6354d..4ceb219be 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp | |||
| @@ -445,11 +445,12 @@ PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat | |||
| 445 | switch (format) { | 445 | switch (format) { |
| 446 | case Tegra::FramebufferConfig::PixelFormat::ABGR8: | 446 | case Tegra::FramebufferConfig::PixelFormat::ABGR8: |
| 447 | return PixelFormat::ABGR8U; | 447 | return PixelFormat::ABGR8U; |
| 448 | case Tegra::FramebufferConfig::PixelFormat::RGB565: | ||
| 449 | return PixelFormat::B5G6R5U; | ||
| 448 | case Tegra::FramebufferConfig::PixelFormat::BGRA8: | 450 | case Tegra::FramebufferConfig::PixelFormat::BGRA8: |
| 449 | return PixelFormat::BGRA8; | 451 | return PixelFormat::BGRA8; |
| 450 | default: | 452 | default: |
| 451 | LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); | 453 | UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format)); |
| 452 | UNREACHABLE(); | ||
| 453 | return PixelFormat::ABGR8U; | 454 | return PixelFormat::ABGR8U; |
| 454 | } | 455 | } |
| 455 | } | 456 | } |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 5d0fb3f9f..f594106bf 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -516,10 +516,38 @@ void Config::ReadPathValues() { | |||
| 516 | 516 | ||
| 517 | UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString(); | 517 | UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString(); |
| 518 | UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString(); | 518 | UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString(); |
| 519 | UISettings::values.game_directory_path = | 519 | UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString(); |
| 520 | UISettings::values.game_dir_deprecated = | ||
| 520 | ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString(); | 521 | ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString(); |
| 521 | UISettings::values.game_directory_deepscan = | 522 | UISettings::values.game_dir_deprecated_deepscan = |
| 522 | ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool(); | 523 | ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool(); |
| 524 | const int gamedirs_size = qt_config->beginReadArray(QStringLiteral("gamedirs")); | ||
| 525 | for (int i = 0; i < gamedirs_size; ++i) { | ||
| 526 | qt_config->setArrayIndex(i); | ||
| 527 | UISettings::GameDir game_dir; | ||
| 528 | game_dir.path = ReadSetting(QStringLiteral("path")).toString(); | ||
| 529 | game_dir.deep_scan = ReadSetting(QStringLiteral("deep_scan"), false).toBool(); | ||
| 530 | game_dir.expanded = ReadSetting(QStringLiteral("expanded"), true).toBool(); | ||
| 531 | UISettings::values.game_dirs.append(game_dir); | ||
| 532 | } | ||
| 533 | qt_config->endArray(); | ||
| 534 | // create NAND and SD card directories if empty, these are not removable through the UI, | ||
| 535 | // also carries over old game list settings if present | ||
| 536 | if (UISettings::values.game_dirs.isEmpty()) { | ||
| 537 | UISettings::GameDir game_dir; | ||
| 538 | game_dir.path = QStringLiteral("SDMC"); | ||
| 539 | game_dir.expanded = true; | ||
| 540 | UISettings::values.game_dirs.append(game_dir); | ||
| 541 | game_dir.path = QStringLiteral("UserNAND"); | ||
| 542 | UISettings::values.game_dirs.append(game_dir); | ||
| 543 | game_dir.path = QStringLiteral("SysNAND"); | ||
| 544 | UISettings::values.game_dirs.append(game_dir); | ||
| 545 | if (UISettings::values.game_dir_deprecated != QStringLiteral(".")) { | ||
| 546 | game_dir.path = UISettings::values.game_dir_deprecated; | ||
| 547 | game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan; | ||
| 548 | UISettings::values.game_dirs.append(game_dir); | ||
| 549 | } | ||
| 550 | } | ||
| 523 | UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); | 551 | UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); |
| 524 | 552 | ||
| 525 | qt_config->endGroup(); | 553 | qt_config->endGroup(); |
| @@ -898,10 +926,15 @@ void Config::SavePathValues() { | |||
| 898 | WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path); | 926 | WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path); |
| 899 | WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); | 927 | WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); |
| 900 | WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path); | 928 | WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path); |
| 901 | WriteSetting(QStringLiteral("gameListRootDir"), UISettings::values.game_directory_path, | 929 | qt_config->beginWriteArray(QStringLiteral("gamedirs")); |
| 902 | QStringLiteral(".")); | 930 | for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) { |
| 903 | WriteSetting(QStringLiteral("gameListDeepScan"), UISettings::values.game_directory_deepscan, | 931 | qt_config->setArrayIndex(i); |
| 904 | false); | 932 | const auto& game_dir = UISettings::values.game_dirs[i]; |
| 933 | WriteSetting(QStringLiteral("path"), game_dir.path); | ||
| 934 | WriteSetting(QStringLiteral("deep_scan"), game_dir.deep_scan, false); | ||
| 935 | WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true); | ||
| 936 | } | ||
| 937 | qt_config->endArray(); | ||
| 905 | WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); | 938 | WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); |
| 906 | 939 | ||
| 907 | qt_config->endGroup(); | 940 | 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() { | |||
| 68 | ui->tabWidget->setCurrentIndex(old_index); | 68 | ui->tabWidget->setCurrentIndex(old_index); |
| 69 | } | 69 | } |
| 70 | 70 | ||
| 71 | Q_DECLARE_METATYPE(QList<QWidget*>); | ||
| 72 | |||
| 71 | void ConfigureDialog::PopulateSelectionList() { | 73 | void ConfigureDialog::PopulateSelectionList() { |
| 72 | const std::array<std::pair<QString, QStringList>, 4> items{ | 74 | const std::array<std::pair<QString, QList<QWidget*>>, 4> items{ |
| 73 | {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}}, | 75 | {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}}, |
| 74 | {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}}, | 76 | {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->audioTab}}, |
| 75 | {tr("Graphics"), {tr("Graphics")}}, | 77 | {tr("Graphics"), {ui->graphicsTab}}, |
| 76 | {tr("Controls"), {tr("Input"), tr("Hotkeys")}}}, | 78 | {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}}, |
| 77 | }; | 79 | }; |
| 78 | 80 | ||
| 79 | [[maybe_unused]] const QSignalBlocker blocker(ui->selectorList); | 81 | [[maybe_unused]] const QSignalBlocker blocker(ui->selectorList); |
| @@ -81,7 +83,7 @@ void ConfigureDialog::PopulateSelectionList() { | |||
| 81 | ui->selectorList->clear(); | 83 | ui->selectorList->clear(); |
| 82 | for (const auto& entry : items) { | 84 | for (const auto& entry : items) { |
| 83 | auto* const item = new QListWidgetItem(entry.first); | 85 | auto* const item = new QListWidgetItem(entry.first); |
| 84 | item->setData(Qt::UserRole, entry.second); | 86 | item->setData(Qt::UserRole, QVariant::fromValue(entry.second)); |
| 85 | 87 | ||
| 86 | ui->selectorList->addItem(item); | 88 | ui->selectorList->addItem(item); |
| 87 | } | 89 | } |
| @@ -93,24 +95,26 @@ void ConfigureDialog::UpdateVisibleTabs() { | |||
| 93 | return; | 95 | return; |
| 94 | } | 96 | } |
| 95 | 97 | ||
| 96 | const std::map<QString, QWidget*> widgets = { | 98 | const std::map<QWidget*, QString> widgets = { |
| 97 | {tr("General"), ui->generalTab}, | 99 | {ui->generalTab, tr("General")}, |
| 98 | {tr("System"), ui->systemTab}, | 100 | {ui->systemTab, tr("System")}, |
| 99 | {tr("Profiles"), ui->profileManagerTab}, | 101 | {ui->profileManagerTab, tr("Profiles")}, |
| 100 | {tr("Input"), ui->inputTab}, | 102 | {ui->inputTab, tr("Input")}, |
| 101 | {tr("Hotkeys"), ui->hotkeysTab}, | 103 | {ui->hotkeysTab, tr("Hotkeys")}, |
| 102 | {tr("Graphics"), ui->graphicsTab}, | 104 | {ui->graphicsTab, tr("Graphics")}, |
| 103 | {tr("Audio"), ui->audioTab}, | 105 | {ui->audioTab, tr("Audio")}, |
| 104 | {tr("Debug"), ui->debugTab}, | 106 | {ui->debugTab, tr("Debug")}, |
| 105 | {tr("Web"), ui->webTab}, | 107 | {ui->webTab, tr("Web")}, |
| 106 | {tr("Game List"), ui->gameListTab}, | 108 | {ui->gameListTab, tr("Game List")}, |
| 107 | }; | 109 | }; |
| 108 | 110 | ||
| 109 | [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget); | 111 | [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget); |
| 110 | 112 | ||
| 111 | ui->tabWidget->clear(); | 113 | ui->tabWidget->clear(); |
| 112 | const QStringList tabs = items[0]->data(Qt::UserRole).toStringList(); | 114 | |
| 113 | for (const auto& tab : tabs) { | 115 | const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole)); |
| 114 | ui->tabWidget->addTab(widgets.find(tab)->second, tab); | 116 | |
| 117 | for (const auto tab : tabs) { | ||
| 118 | ui->tabWidget->addTab(tab, widgets.at(tab)); | ||
| 115 | } | 119 | } |
| 116 | } | 120 | } |
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) | |||
| 20 | 20 | ||
| 21 | SetConfiguration(); | 21 | SetConfiguration(); |
| 22 | 22 | ||
| 23 | connect(ui->toggle_deepscan, &QCheckBox::stateChanged, this, | 23 | connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled); |
| 24 | [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); | ||
| 25 | } | 24 | } |
| 26 | 25 | ||
| 27 | ConfigureGeneral::~ConfigureGeneral() = default; | 26 | ConfigureGeneral::~ConfigureGeneral() = default; |
| 28 | 27 | ||
| 29 | void ConfigureGeneral::SetConfiguration() { | 28 | void ConfigureGeneral::SetConfiguration() { |
| 30 | ui->toggle_deepscan->setChecked(UISettings::values.game_directory_deepscan); | ||
| 31 | ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); | 29 | ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); |
| 32 | ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot); | 30 | ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot); |
| 33 | ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); | 31 | ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); |
| 32 | |||
| 33 | ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); | ||
| 34 | ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); | ||
| 35 | ui->frame_limit->setValue(Settings::values.frame_limit); | ||
| 34 | } | 36 | } |
| 35 | 37 | ||
| 36 | void ConfigureGeneral::ApplyConfiguration() { | 38 | void ConfigureGeneral::ApplyConfiguration() { |
| 37 | UISettings::values.game_directory_deepscan = ui->toggle_deepscan->isChecked(); | ||
| 38 | UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); | 39 | UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); |
| 39 | UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); | 40 | UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); |
| 40 | UISettings::values.theme = | 41 | UISettings::values.theme = |
| 41 | ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); | 42 | ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); |
| 43 | |||
| 44 | Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); | ||
| 45 | Settings::values.frame_limit = ui->frame_limit->value(); | ||
| 42 | } | 46 | } |
| 43 | 47 | ||
| 44 | void ConfigureGeneral::changeEvent(QEvent* event) { | 48 | 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 @@ | |||
| 25 | <item> | 25 | <item> |
| 26 | <layout class="QVBoxLayout" name="GeneralVerticalLayout"> | 26 | <layout class="QVBoxLayout" name="GeneralVerticalLayout"> |
| 27 | <item> | 27 | <item> |
| 28 | <widget class="QCheckBox" name="toggle_deepscan"> | 28 | <layout class="QHBoxLayout" name="horizontalLayout_2"> |
| 29 | <property name="text"> | 29 | <item> |
| 30 | <string>Search sub-directories for games</string> | 30 | <widget class="QCheckBox" name="toggle_frame_limit"> |
| 31 | </property> | 31 | <property name="text"> |
| 32 | </widget> | 32 | <string>Limit Speed Percent</string> |
| 33 | </property> | ||
| 34 | </widget> | ||
| 35 | </item> | ||
| 36 | <item> | ||
| 37 | <widget class="QSpinBox" name="frame_limit"> | ||
| 38 | <property name="suffix"> | ||
| 39 | <string>%</string> | ||
| 40 | </property> | ||
| 41 | <property name="minimum"> | ||
| 42 | <number>1</number> | ||
| 43 | </property> | ||
| 44 | <property name="maximum"> | ||
| 45 | <number>9999</number> | ||
| 46 | </property> | ||
| 47 | <property name="value"> | ||
| 48 | <number>100</number> | ||
| 49 | </property> | ||
| 50 | </widget> | ||
| 51 | </item> | ||
| 52 | </layout> | ||
| 33 | </item> | 53 | </item> |
| 34 | <item> | 54 | <item> |
| 35 | <widget class="QCheckBox" name="toggle_check_exit"> | 55 | <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) | |||
| 55 | 55 | ||
| 56 | SetConfiguration(); | 56 | SetConfiguration(); |
| 57 | 57 | ||
| 58 | connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled); | ||
| 59 | connect(ui->bg_button, &QPushButton::clicked, this, [this] { | 58 | connect(ui->bg_button, &QPushButton::clicked, this, [this] { |
| 60 | const QColor new_bg_color = QColorDialog::getColor(bg_color); | 59 | const QColor new_bg_color = QColorDialog::getColor(bg_color); |
| 61 | if (!new_bg_color.isValid()) { | 60 | if (!new_bg_color.isValid()) { |
| @@ -72,9 +71,6 @@ void ConfigureGraphics::SetConfiguration() { | |||
| 72 | 71 | ||
| 73 | ui->resolution_factor_combobox->setCurrentIndex( | 72 | ui->resolution_factor_combobox->setCurrentIndex( |
| 74 | static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); | 73 | static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); |
| 75 | ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); | ||
| 76 | ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); | ||
| 77 | ui->frame_limit->setValue(Settings::values.frame_limit); | ||
| 78 | ui->use_disk_shader_cache->setEnabled(runtime_lock); | 74 | ui->use_disk_shader_cache->setEnabled(runtime_lock); |
| 79 | ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache); | 75 | ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache); |
| 80 | ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); | 76 | ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); |
| @@ -89,8 +85,6 @@ void ConfigureGraphics::SetConfiguration() { | |||
| 89 | void ConfigureGraphics::ApplyConfiguration() { | 85 | void ConfigureGraphics::ApplyConfiguration() { |
| 90 | Settings::values.resolution_factor = | 86 | Settings::values.resolution_factor = |
| 91 | ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); | 87 | ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); |
| 92 | Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); | ||
| 93 | Settings::values.frame_limit = ui->frame_limit->value(); | ||
| 94 | Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked(); | 88 | Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked(); |
| 95 | Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); | 89 | Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); |
| 96 | Settings::values.use_asynchronous_gpu_emulation = | 90 | 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 @@ | |||
| 23 | </property> | 23 | </property> |
| 24 | <layout class="QVBoxLayout" name="verticalLayout_2"> | 24 | <layout class="QVBoxLayout" name="verticalLayout_2"> |
| 25 | <item> | 25 | <item> |
| 26 | <layout class="QHBoxLayout" name="horizontalLayout_2"> | ||
| 27 | <item> | ||
| 28 | <widget class="QCheckBox" name="toggle_frame_limit"> | ||
| 29 | <property name="text"> | ||
| 30 | <string>Limit Speed Percent</string> | ||
| 31 | </property> | ||
| 32 | </widget> | ||
| 33 | </item> | ||
| 34 | <item> | ||
| 35 | <widget class="QSpinBox" name="frame_limit"> | ||
| 36 | <property name="suffix"> | ||
| 37 | <string>%</string> | ||
| 38 | </property> | ||
| 39 | <property name="minimum"> | ||
| 40 | <number>1</number> | ||
| 41 | </property> | ||
| 42 | <property name="maximum"> | ||
| 43 | <number>9999</number> | ||
| 44 | </property> | ||
| 45 | <property name="value"> | ||
| 46 | <number>100</number> | ||
| 47 | </property> | ||
| 48 | </widget> | ||
| 49 | </item> | ||
| 50 | </layout> | ||
| 51 | </item> | ||
| 52 | <item> | ||
| 53 | <widget class="QCheckBox" name="use_disk_shader_cache"> | 26 | <widget class="QCheckBox" name="use_disk_shader_cache"> |
| 54 | <property name="text"> | 27 | <property name="text"> |
| 55 | <string>Use disk shader cache</string> | 28 | <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 | |||
| 301 | }); | 301 | }); |
| 302 | } | 302 | } |
| 303 | connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] { | 303 | connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] { |
| 304 | QMessageBox::information(this, tr("Information"), | 304 | if (QMessageBox::information( |
| 305 | tr("After pressing OK, first move your joystick horizontally, " | 305 | this, tr("Information"), |
| 306 | "and then vertically.")); | 306 | tr("After pressing OK, first move your joystick horizontally, " |
| 307 | HandleClick( | 307 | "and then vertically."), |
| 308 | analog_map_stick[analog_id], | 308 | QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { |
| 309 | [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, | 309 | HandleClick( |
| 310 | InputCommon::Polling::DeviceType::Analog); | 310 | analog_map_stick[analog_id], |
| 311 | [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, | ||
| 312 | InputCommon::Polling::DeviceType::Analog); | ||
| 313 | } | ||
| 311 | }); | 314 | }); |
| 312 | } | 315 | } |
| 313 | 316 | ||
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 | |||
| 34 | return QObject::eventFilter(obj, event); | 34 | return QObject::eventFilter(obj, event); |
| 35 | 35 | ||
| 36 | QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); | 36 | QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); |
| 37 | int rowCount = gamelist->tree_view->model()->rowCount(); | ||
| 38 | QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower(); | 37 | QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower(); |
| 39 | 38 | ||
| 40 | // If the searchfield's text hasn't changed special function keys get checked | 39 | // 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 | |||
| 56 | // If there is only one result launch this game | 55 | // If there is only one result launch this game |
| 57 | case Qt::Key_Return: | 56 | case Qt::Key_Return: |
| 58 | case Qt::Key_Enter: { | 57 | case Qt::Key_Enter: { |
| 59 | QStandardItemModel* item_model = new QStandardItemModel(gamelist->tree_view); | 58 | if (gamelist->search_field->visible == 1) { |
| 60 | QModelIndex root_index = item_model->invisibleRootItem()->index(); | 59 | QString file_path = gamelist->getLastFilterResultItem(); |
| 61 | QStandardItem* child_file; | 60 | |
| 62 | QString file_path; | ||
| 63 | int resultCount = 0; | ||
| 64 | for (int i = 0; i < rowCount; ++i) { | ||
| 65 | if (!gamelist->tree_view->isRowHidden(i, root_index)) { | ||
| 66 | ++resultCount; | ||
| 67 | child_file = gamelist->item_model->item(i, 0); | ||
| 68 | file_path = child_file->data(GameListItemPath::FullPathRole).toString(); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | if (resultCount == 1) { | ||
| 72 | // To avoid loading error dialog loops while confirming them using enter | 61 | // To avoid loading error dialog loops while confirming them using enter |
| 73 | // Also users usually want to run a different game after closing one | 62 | // Also users usually want to run a different game after closing one |
| 74 | gamelist->search_field->edit_filter->clear(); | 63 | gamelist->search_field->edit_filter->clear(); |
| @@ -88,9 +77,31 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve | |||
| 88 | } | 77 | } |
| 89 | 78 | ||
| 90 | void GameListSearchField::setFilterResult(int visible, int total) { | 79 | void GameListSearchField::setFilterResult(int visible, int total) { |
| 80 | this->visible = visible; | ||
| 81 | this->total = total; | ||
| 82 | |||
| 91 | label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); | 83 | label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); |
| 92 | } | 84 | } |
| 93 | 85 | ||
| 86 | QString GameList::getLastFilterResultItem() const { | ||
| 87 | QStandardItem* folder; | ||
| 88 | QStandardItem* child; | ||
| 89 | QString file_path; | ||
| 90 | const int folder_count = item_model->rowCount(); | ||
| 91 | for (int i = 0; i < folder_count; ++i) { | ||
| 92 | folder = item_model->item(i, 0); | ||
| 93 | const QModelIndex folder_index = folder->index(); | ||
| 94 | const int children_count = folder->rowCount(); | ||
| 95 | for (int j = 0; j < children_count; ++j) { | ||
| 96 | if (!tree_view->isRowHidden(j, folder_index)) { | ||
| 97 | child = folder->child(j, 0); | ||
| 98 | file_path = child->data(GameListItemPath::FullPathRole).toString(); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
| 102 | return file_path; | ||
| 103 | } | ||
| 104 | |||
| 94 | void GameListSearchField::clear() { | 105 | void GameListSearchField::clear() { |
| 95 | edit_filter->clear(); | 106 | edit_filter->clear(); |
| 96 | } | 107 | } |
| @@ -147,45 +158,120 @@ static bool ContainsAllWords(const QString& haystack, const QString& userinput) | |||
| 147 | [&haystack](const QString& s) { return haystack.contains(s); }); | 158 | [&haystack](const QString& s) { return haystack.contains(s); }); |
| 148 | } | 159 | } |
| 149 | 160 | ||
| 161 | // Syncs the expanded state of Game Directories with settings to persist across sessions | ||
| 162 | void GameList::onItemExpanded(const QModelIndex& item) { | ||
| 163 | const auto type = item.data(GameListItem::TypeRole).value<GameListItemType>(); | ||
| 164 | if (type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir || | ||
| 165 | type == GameListItemType::UserNandDir || type == GameListItemType::SysNandDir) | ||
| 166 | item.data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded = | ||
| 167 | tree_view->isExpanded(item); | ||
| 168 | } | ||
| 169 | |||
| 150 | // Event in order to filter the gamelist after editing the searchfield | 170 | // Event in order to filter the gamelist after editing the searchfield |
| 151 | void GameList::onTextChanged(const QString& new_text) { | 171 | void GameList::onTextChanged(const QString& new_text) { |
| 152 | const int row_count = tree_view->model()->rowCount(); | 172 | const int folder_count = tree_view->model()->rowCount(); |
| 153 | const QString edit_filter_text = new_text.toLower(); | 173 | QString edit_filter_text = new_text.toLower(); |
| 154 | const QModelIndex root_index = item_model->invisibleRootItem()->index(); | 174 | QStandardItem* folder; |
| 175 | QStandardItem* child; | ||
| 176 | int children_total = 0; | ||
| 177 | QModelIndex root_index = item_model->invisibleRootItem()->index(); | ||
| 155 | 178 | ||
| 156 | // If the searchfield is empty every item is visible | 179 | // If the searchfield is empty every item is visible |
| 157 | // Otherwise the filter gets applied | 180 | // Otherwise the filter gets applied |
| 158 | if (edit_filter_text.isEmpty()) { | 181 | if (edit_filter_text.isEmpty()) { |
| 159 | for (int i = 0; i < row_count; ++i) { | 182 | for (int i = 0; i < folder_count; ++i) { |
| 160 | tree_view->setRowHidden(i, root_index, false); | 183 | folder = item_model->item(i, 0); |
| 184 | const QModelIndex folder_index = folder->index(); | ||
| 185 | const int children_count = folder->rowCount(); | ||
| 186 | for (int j = 0; j < children_count; ++j) { | ||
| 187 | ++children_total; | ||
| 188 | tree_view->setRowHidden(j, folder_index, false); | ||
| 189 | } | ||
| 161 | } | 190 | } |
| 162 | search_field->setFilterResult(row_count, row_count); | 191 | search_field->setFilterResult(children_total, children_total); |
| 163 | } else { | 192 | } else { |
| 164 | int result_count = 0; | 193 | int result_count = 0; |
| 165 | for (int i = 0; i < row_count; ++i) { | 194 | for (int i = 0; i < folder_count; ++i) { |
| 166 | const QStandardItem* child_file = item_model->item(i, 0); | 195 | folder = item_model->item(i, 0); |
| 167 | const QString file_path = | 196 | const QModelIndex folder_index = folder->index(); |
| 168 | child_file->data(GameListItemPath::FullPathRole).toString().toLower(); | 197 | const int children_count = folder->rowCount(); |
| 169 | const QString file_title = | 198 | for (int j = 0; j < children_count; ++j) { |
| 170 | child_file->data(GameListItemPath::TitleRole).toString().toLower(); | 199 | ++children_total; |
| 171 | const QString file_program_id = | 200 | const QStandardItem* child = folder->child(j, 0); |
| 172 | child_file->data(GameListItemPath::ProgramIdRole).toString().toLower(); | 201 | const QString file_path = |
| 173 | 202 | child->data(GameListItemPath::FullPathRole).toString().toLower(); | |
| 174 | // Only items which filename in combination with its title contains all words | 203 | const QString file_title = |
| 175 | // that are in the searchfield will be visible in the gamelist | 204 | child->data(GameListItemPath::TitleRole).toString().toLower(); |
| 176 | // The search is case insensitive because of toLower() | 205 | const QString file_program_id = |
| 177 | // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent | 206 | child->data(GameListItemPath::ProgramIdRole).toString().toLower(); |
| 178 | // multiple conversions of edit_filter_text for each game in the gamelist | 207 | |
| 179 | const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + | 208 | // Only items which filename in combination with its title contains all words |
| 180 | QLatin1Char{' '} + file_title; | 209 | // that are in the searchfield will be visible in the gamelist |
| 181 | if (ContainsAllWords(file_name, edit_filter_text) || | 210 | // The search is case insensitive because of toLower() |
| 182 | (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) { | 211 | // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent |
| 183 | tree_view->setRowHidden(i, root_index, false); | 212 | // multiple conversions of edit_filter_text for each game in the gamelist |
| 184 | ++result_count; | 213 | const QString file_name = |
| 185 | } else { | 214 | file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + |
| 186 | tree_view->setRowHidden(i, root_index, true); | 215 | file_title; |
| 216 | if (ContainsAllWords(file_name, edit_filter_text) || | ||
| 217 | (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) { | ||
| 218 | tree_view->setRowHidden(j, folder_index, false); | ||
| 219 | ++result_count; | ||
| 220 | } else { | ||
| 221 | tree_view->setRowHidden(j, folder_index, true); | ||
| 222 | } | ||
| 223 | search_field->setFilterResult(result_count, children_total); | ||
| 187 | } | 224 | } |
| 188 | search_field->setFilterResult(result_count, row_count); | 225 | } |
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | void GameList::onUpdateThemedIcons() { | ||
| 230 | for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) { | ||
| 231 | QStandardItem* child = item_model->invisibleRootItem()->child(i); | ||
| 232 | |||
| 233 | const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64); | ||
| 234 | switch (child->data(GameListItem::TypeRole).value<GameListItemType>()) { | ||
| 235 | case GameListItemType::SdmcDir: | ||
| 236 | child->setData( | ||
| 237 | QIcon::fromTheme(QStringLiteral("sd_card")) | ||
| 238 | .pixmap(icon_size) | ||
| 239 | .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), | ||
| 240 | Qt::DecorationRole); | ||
| 241 | break; | ||
| 242 | case GameListItemType::UserNandDir: | ||
| 243 | child->setData( | ||
| 244 | QIcon::fromTheme(QStringLiteral("chip")) | ||
| 245 | .pixmap(icon_size) | ||
| 246 | .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), | ||
| 247 | Qt::DecorationRole); | ||
| 248 | break; | ||
| 249 | case GameListItemType::SysNandDir: | ||
| 250 | child->setData( | ||
| 251 | QIcon::fromTheme(QStringLiteral("chip")) | ||
| 252 | .pixmap(icon_size) | ||
| 253 | .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), | ||
| 254 | Qt::DecorationRole); | ||
| 255 | break; | ||
| 256 | case GameListItemType::CustomDir: { | ||
| 257 | const UISettings::GameDir* game_dir = | ||
| 258 | child->data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); | ||
| 259 | const QString icon_name = QFileInfo::exists(game_dir->path) | ||
| 260 | ? QStringLiteral("folder") | ||
| 261 | : QStringLiteral("bad_folder"); | ||
| 262 | child->setData( | ||
| 263 | QIcon::fromTheme(icon_name).pixmap(icon_size).scaled( | ||
| 264 | icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), | ||
| 265 | Qt::DecorationRole); | ||
| 266 | break; | ||
| 267 | } | ||
| 268 | case GameListItemType::AddDir: | ||
| 269 | child->setData( | ||
| 270 | QIcon::fromTheme(QStringLiteral("plus")) | ||
| 271 | .pixmap(icon_size) | ||
| 272 | .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), | ||
| 273 | Qt::DecorationRole); | ||
| 274 | break; | ||
| 189 | } | 275 | } |
| 190 | } | 276 | } |
| 191 | } | 277 | } |
| @@ -214,7 +300,6 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide | |||
| 214 | tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); | 300 | tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); |
| 215 | tree_view->setSortingEnabled(true); | 301 | tree_view->setSortingEnabled(true); |
| 216 | tree_view->setEditTriggers(QHeaderView::NoEditTriggers); | 302 | tree_view->setEditTriggers(QHeaderView::NoEditTriggers); |
| 217 | tree_view->setUniformRowHeights(true); | ||
| 218 | tree_view->setContextMenuPolicy(Qt::CustomContextMenu); | 303 | tree_view->setContextMenuPolicy(Qt::CustomContextMenu); |
| 219 | tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); | 304 | tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); |
| 220 | 305 | ||
| @@ -230,12 +315,16 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide | |||
| 230 | item_model->setHeaderData(COLUMN_FILE_TYPE - 1, Qt::Horizontal, tr("File type")); | 315 | item_model->setHeaderData(COLUMN_FILE_TYPE - 1, Qt::Horizontal, tr("File type")); |
| 231 | item_model->setHeaderData(COLUMN_SIZE - 1, Qt::Horizontal, tr("Size")); | 316 | item_model->setHeaderData(COLUMN_SIZE - 1, Qt::Horizontal, tr("Size")); |
| 232 | } | 317 | } |
| 318 | item_model->setSortRole(GameListItemPath::TitleRole); | ||
| 233 | 319 | ||
| 320 | connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::onUpdateThemedIcons); | ||
| 234 | connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); | 321 | connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); |
| 235 | connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); | 322 | connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); |
| 323 | connect(tree_view, &QTreeView::expanded, this, &GameList::onItemExpanded); | ||
| 324 | connect(tree_view, &QTreeView::collapsed, this, &GameList::onItemExpanded); | ||
| 236 | 325 | ||
| 237 | // We must register all custom types with the Qt Automoc system so that we are able to use it | 326 | // We must register all custom types with the Qt Automoc system so that we are able to use |
| 238 | // with signals/slots. In this case, QList falls under the umbrells of custom types. | 327 | // it with signals/slots. In this case, QList falls under the umbrells of custom types. |
| 239 | qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); | 328 | qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); |
| 240 | 329 | ||
| 241 | layout->setContentsMargins(0, 0, 0, 0); | 330 | layout->setContentsMargins(0, 0, 0, 0); |
| @@ -263,38 +352,68 @@ void GameList::clearFilter() { | |||
| 263 | search_field->clear(); | 352 | search_field->clear(); |
| 264 | } | 353 | } |
| 265 | 354 | ||
| 266 | void GameList::AddEntry(const QList<QStandardItem*>& entry_items) { | 355 | void GameList::AddDirEntry(GameListDir* entry_items) { |
| 267 | item_model->invisibleRootItem()->appendRow(entry_items); | 356 | item_model->invisibleRootItem()->appendRow(entry_items); |
| 357 | tree_view->setExpanded( | ||
| 358 | entry_items->index(), | ||
| 359 | entry_items->data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded); | ||
| 268 | } | 360 | } |
| 269 | 361 | ||
| 270 | void GameList::ValidateEntry(const QModelIndex& item) { | 362 | void GameList::AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent) { |
| 271 | // We don't care about the individual QStandardItem that was selected, but its row. | 363 | parent->appendRow(entry_items); |
| 272 | const int row = item_model->itemFromIndex(item)->row(); | 364 | } |
| 273 | const QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); | ||
| 274 | const QString file_path = child_file->data(GameListItemPath::FullPathRole).toString(); | ||
| 275 | |||
| 276 | if (file_path.isEmpty()) | ||
| 277 | return; | ||
| 278 | |||
| 279 | if (!QFileInfo::exists(file_path)) | ||
| 280 | return; | ||
| 281 | 365 | ||
| 282 | const QFileInfo file_info{file_path}; | 366 | void GameList::ValidateEntry(const QModelIndex& item) { |
| 283 | if (file_info.isDir()) { | 367 | const auto selected = item.sibling(item.row(), 0); |
| 284 | const QDir dir{file_path}; | 368 | |
| 285 | const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files); | 369 | switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) { |
| 286 | if (matching_main.size() == 1) { | 370 | case GameListItemType::Game: { |
| 287 | emit GameChosen(dir.path() + QDir::separator() + matching_main[0]); | 371 | const QString file_path = selected.data(GameListItemPath::FullPathRole).toString(); |
| 372 | if (file_path.isEmpty()) | ||
| 373 | return; | ||
| 374 | const QFileInfo file_info(file_path); | ||
| 375 | if (!file_info.exists()) | ||
| 376 | return; | ||
| 377 | |||
| 378 | if (file_info.isDir()) { | ||
| 379 | const QDir dir{file_path}; | ||
| 380 | const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files); | ||
| 381 | if (matching_main.size() == 1) { | ||
| 382 | emit GameChosen(dir.path() + QDir::separator() + matching_main[0]); | ||
| 383 | } | ||
| 384 | return; | ||
| 288 | } | 385 | } |
| 289 | return; | 386 | |
| 387 | // Users usually want to run a different game after closing one | ||
| 388 | search_field->clear(); | ||
| 389 | emit GameChosen(file_path); | ||
| 390 | break; | ||
| 290 | } | 391 | } |
| 392 | case GameListItemType::AddDir: | ||
| 393 | emit AddDirectory(); | ||
| 394 | break; | ||
| 395 | } | ||
| 396 | } | ||
| 291 | 397 | ||
| 292 | // Users usually want to run a diffrent game after closing one | 398 | bool GameList::isEmpty() const { |
| 293 | search_field->clear(); | 399 | for (int i = 0; i < item_model->rowCount(); i++) { |
| 294 | emit GameChosen(file_path); | 400 | const QStandardItem* child = item_model->invisibleRootItem()->child(i); |
| 401 | const auto type = static_cast<GameListItemType>(child->type()); | ||
| 402 | if (!child->hasChildren() && | ||
| 403 | (type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir || | ||
| 404 | type == GameListItemType::SysNandDir)) { | ||
| 405 | item_model->invisibleRootItem()->removeRow(child->row()); | ||
| 406 | i--; | ||
| 407 | }; | ||
| 408 | } | ||
| 409 | return !item_model->invisibleRootItem()->hasChildren(); | ||
| 295 | } | 410 | } |
| 296 | 411 | ||
| 297 | void GameList::DonePopulating(QStringList watch_list) { | 412 | void GameList::DonePopulating(QStringList watch_list) { |
| 413 | emit ShowList(!isEmpty()); | ||
| 414 | |||
| 415 | item_model->invisibleRootItem()->appendRow(new GameListAddDir()); | ||
| 416 | |||
| 298 | // Clear out the old directories to watch for changes and add the new ones | 417 | // Clear out the old directories to watch for changes and add the new ones |
| 299 | auto watch_dirs = watcher->directories(); | 418 | auto watch_dirs = watcher->directories(); |
| 300 | if (!watch_dirs.isEmpty()) { | 419 | if (!watch_dirs.isEmpty()) { |
| @@ -311,9 +430,13 @@ void GameList::DonePopulating(QStringList watch_list) { | |||
| 311 | QCoreApplication::processEvents(); | 430 | QCoreApplication::processEvents(); |
| 312 | } | 431 | } |
| 313 | tree_view->setEnabled(true); | 432 | tree_view->setEnabled(true); |
| 314 | int rowCount = tree_view->model()->rowCount(); | 433 | const int folder_count = tree_view->model()->rowCount(); |
| 315 | search_field->setFilterResult(rowCount, rowCount); | 434 | int children_total = 0; |
| 316 | if (rowCount > 0) { | 435 | for (int i = 0; i < folder_count; ++i) { |
| 436 | children_total += item_model->item(i, 0)->rowCount(); | ||
| 437 | } | ||
| 438 | search_field->setFilterResult(children_total, children_total); | ||
| 439 | if (children_total > 0) { | ||
| 317 | search_field->setFocus(); | 440 | search_field->setFocus(); |
| 318 | } | 441 | } |
| 319 | } | 442 | } |
| @@ -323,12 +446,27 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { | |||
| 323 | if (!item.isValid()) | 446 | if (!item.isValid()) |
| 324 | return; | 447 | return; |
| 325 | 448 | ||
| 326 | int row = item_model->itemFromIndex(item)->row(); | 449 | const auto selected = item.sibling(item.row(), 0); |
| 327 | QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); | ||
| 328 | u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); | ||
| 329 | std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString(); | ||
| 330 | |||
| 331 | QMenu context_menu; | 450 | QMenu context_menu; |
| 451 | switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) { | ||
| 452 | case GameListItemType::Game: | ||
| 453 | AddGamePopup(context_menu, selected.data(GameListItemPath::ProgramIdRole).toULongLong(), | ||
| 454 | selected.data(GameListItemPath::FullPathRole).toString().toStdString()); | ||
| 455 | break; | ||
| 456 | case GameListItemType::CustomDir: | ||
| 457 | AddPermDirPopup(context_menu, selected); | ||
| 458 | AddCustomDirPopup(context_menu, selected); | ||
| 459 | break; | ||
| 460 | case GameListItemType::SdmcDir: | ||
| 461 | case GameListItemType::UserNandDir: | ||
| 462 | case GameListItemType::SysNandDir: | ||
| 463 | AddPermDirPopup(context_menu, selected); | ||
| 464 | break; | ||
| 465 | } | ||
| 466 | context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); | ||
| 467 | } | ||
| 468 | |||
| 469 | void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) { | ||
| 332 | QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); | 470 | QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); |
| 333 | QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); | 471 | QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); |
| 334 | QAction* open_transferable_shader_cache = | 472 | QAction* open_transferable_shader_cache = |
| @@ -344,19 +482,86 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { | |||
| 344 | auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | 482 | auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); |
| 345 | navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); | 483 | navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); |
| 346 | 484 | ||
| 347 | connect(open_save_location, &QAction::triggered, | 485 | connect(open_save_location, &QAction::triggered, [this, program_id]() { |
| 348 | [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); | 486 | emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); |
| 349 | connect(open_lfs_location, &QAction::triggered, | 487 | }); |
| 350 | [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); }); | 488 | connect(open_lfs_location, &QAction::triggered, [this, program_id]() { |
| 489 | emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); | ||
| 490 | }); | ||
| 351 | connect(open_transferable_shader_cache, &QAction::triggered, | 491 | connect(open_transferable_shader_cache, &QAction::triggered, |
| 352 | [&]() { emit OpenTransferableShaderCacheRequested(program_id); }); | 492 | [this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); }); |
| 353 | connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); }); | 493 | connect(dump_romfs, &QAction::triggered, |
| 354 | connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); }); | 494 | [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); }); |
| 355 | connect(navigate_to_gamedb_entry, &QAction::triggered, | 495 | connect(copy_tid, &QAction::triggered, |
| 356 | [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); | 496 | [this, program_id]() { emit CopyTIDRequested(program_id); }); |
| 357 | connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); }); | 497 | connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { |
| 498 | emit NavigateToGamedbEntryRequested(program_id, compatibility_list); | ||
| 499 | }); | ||
| 500 | connect(properties, &QAction::triggered, | ||
| 501 | [this, path]() { emit OpenPerGameGeneralRequested(path); }); | ||
| 502 | }; | ||
| 503 | |||
| 504 | void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) { | ||
| 505 | UISettings::GameDir& game_dir = | ||
| 506 | *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); | ||
| 507 | |||
| 508 | QAction* deep_scan = context_menu.addAction(tr("Scan Subfolders")); | ||
| 509 | QAction* delete_dir = context_menu.addAction(tr("Remove Game Directory")); | ||
| 510 | |||
| 511 | deep_scan->setCheckable(true); | ||
| 512 | deep_scan->setChecked(game_dir.deep_scan); | ||
| 513 | |||
| 514 | connect(deep_scan, &QAction::triggered, [this, &game_dir] { | ||
| 515 | game_dir.deep_scan = !game_dir.deep_scan; | ||
| 516 | PopulateAsync(UISettings::values.game_dirs); | ||
| 517 | }); | ||
| 518 | connect(delete_dir, &QAction::triggered, [this, &game_dir, selected] { | ||
| 519 | UISettings::values.game_dirs.removeOne(game_dir); | ||
| 520 | item_model->invisibleRootItem()->removeRow(selected.row()); | ||
| 521 | }); | ||
| 522 | } | ||
| 358 | 523 | ||
| 359 | context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); | 524 | void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) { |
| 525 | UISettings::GameDir& game_dir = | ||
| 526 | *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); | ||
| 527 | |||
| 528 | QAction* move_up = context_menu.addAction(tr(u8"\U000025b2 Move Up")); | ||
| 529 | QAction* move_down = context_menu.addAction(tr(u8"\U000025bc Move Down ")); | ||
| 530 | QAction* open_directory_location = context_menu.addAction(tr("Open Directory Location")); | ||
| 531 | |||
| 532 | const int row = selected.row(); | ||
| 533 | |||
| 534 | move_up->setEnabled(row > 0); | ||
| 535 | move_down->setEnabled(row < item_model->rowCount() - 2); | ||
| 536 | |||
| 537 | connect(move_up, &QAction::triggered, [this, selected, row, &game_dir] { | ||
| 538 | // find the indices of the items in settings and swap them | ||
| 539 | std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)], | ||
| 540 | UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf( | ||
| 541 | *selected.sibling(row - 1, 0) | ||
| 542 | .data(GameListDir::GameDirRole) | ||
| 543 | .value<UISettings::GameDir*>())]); | ||
| 544 | // move the treeview items | ||
| 545 | QList<QStandardItem*> item = item_model->takeRow(row); | ||
| 546 | item_model->invisibleRootItem()->insertRow(row - 1, item); | ||
| 547 | tree_view->setExpanded(selected, game_dir.expanded); | ||
| 548 | }); | ||
| 549 | |||
| 550 | connect(move_down, &QAction::triggered, [this, selected, row, &game_dir] { | ||
| 551 | // find the indices of the items in settings and swap them | ||
| 552 | std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)], | ||
| 553 | UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf( | ||
| 554 | *selected.sibling(row + 1, 0) | ||
| 555 | .data(GameListDir::GameDirRole) | ||
| 556 | .value<UISettings::GameDir*>())]); | ||
| 557 | // move the treeview items | ||
| 558 | const QList<QStandardItem*> item = item_model->takeRow(row); | ||
| 559 | item_model->invisibleRootItem()->insertRow(row + 1, item); | ||
| 560 | tree_view->setExpanded(selected, game_dir.expanded); | ||
| 561 | }); | ||
| 562 | |||
| 563 | connect(open_directory_location, &QAction::triggered, | ||
| 564 | [this, game_dir] { emit OpenDirectory(game_dir.path); }); | ||
| 360 | } | 565 | } |
| 361 | 566 | ||
| 362 | void GameList::LoadCompatibilityList() { | 567 | void GameList::LoadCompatibilityList() { |
| @@ -403,14 +608,7 @@ void GameList::LoadCompatibilityList() { | |||
| 403 | } | 608 | } |
| 404 | } | 609 | } |
| 405 | 610 | ||
| 406 | void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { | 611 | void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { |
| 407 | const QFileInfo dir_info{dir_path}; | ||
| 408 | if (!dir_info.exists() || !dir_info.isDir()) { | ||
| 409 | LOG_ERROR(Frontend, "Could not find game list folder at {}", dir_path.toStdString()); | ||
| 410 | search_field->setFilterResult(0, 0); | ||
| 411 | return; | ||
| 412 | } | ||
| 413 | |||
| 414 | tree_view->setEnabled(false); | 612 | tree_view->setEnabled(false); |
| 415 | 613 | ||
| 416 | // Update the columns in case UISettings has changed | 614 | // Update the columns in case UISettings has changed |
| @@ -433,17 +631,19 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { | |||
| 433 | 631 | ||
| 434 | // Delete any rows that might already exist if we're repopulating | 632 | // Delete any rows that might already exist if we're repopulating |
| 435 | item_model->removeRows(0, item_model->rowCount()); | 633 | item_model->removeRows(0, item_model->rowCount()); |
| 634 | search_field->clear(); | ||
| 436 | 635 | ||
| 437 | emit ShouldCancelWorker(); | 636 | emit ShouldCancelWorker(); |
| 438 | 637 | ||
| 439 | GameListWorker* worker = | 638 | GameListWorker* worker = new GameListWorker(vfs, provider, game_dirs, compatibility_list); |
| 440 | new GameListWorker(vfs, provider, dir_path, deep_scan, compatibility_list); | ||
| 441 | 639 | ||
| 442 | connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); | 640 | connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); |
| 641 | connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, | ||
| 642 | Qt::QueuedConnection); | ||
| 443 | connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, | 643 | connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, |
| 444 | Qt::QueuedConnection); | 644 | Qt::QueuedConnection); |
| 445 | // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to cancel | 645 | // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to |
| 446 | // without delay. | 646 | // cancel without delay. |
| 447 | connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel, | 647 | connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel, |
| 448 | Qt::DirectConnection); | 648 | Qt::DirectConnection); |
| 449 | 649 | ||
| @@ -471,10 +671,40 @@ const QStringList GameList::supported_file_extensions = { | |||
| 471 | QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; | 671 | QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; |
| 472 | 672 | ||
| 473 | void GameList::RefreshGameDirectory() { | 673 | void GameList::RefreshGameDirectory() { |
| 474 | if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) { | 674 | if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) { |
| 475 | LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); | 675 | LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); |
| 476 | search_field->clear(); | 676 | PopulateAsync(UISettings::values.game_dirs); |
| 477 | PopulateAsync(UISettings::values.game_directory_path, | ||
| 478 | UISettings::values.game_directory_deepscan); | ||
| 479 | } | 677 | } |
| 480 | } | 678 | } |
| 679 | |||
| 680 | GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} { | ||
| 681 | connect(parent, &GMainWindow::UpdateThemedIcons, this, | ||
| 682 | &GameListPlaceholder::onUpdateThemedIcons); | ||
| 683 | |||
| 684 | layout = new QVBoxLayout; | ||
| 685 | image = new QLabel; | ||
| 686 | text = new QLabel; | ||
| 687 | layout->setAlignment(Qt::AlignCenter); | ||
| 688 | image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); | ||
| 689 | |||
| 690 | text->setText(tr("Double-click to add a new folder to the game list")); | ||
| 691 | QFont font = text->font(); | ||
| 692 | font.setPointSize(20); | ||
| 693 | text->setFont(font); | ||
| 694 | text->setAlignment(Qt::AlignHCenter); | ||
| 695 | image->setAlignment(Qt::AlignHCenter); | ||
| 696 | |||
| 697 | layout->addWidget(image); | ||
| 698 | layout->addWidget(text); | ||
| 699 | setLayout(layout); | ||
| 700 | } | ||
| 701 | |||
| 702 | GameListPlaceholder::~GameListPlaceholder() = default; | ||
| 703 | |||
| 704 | void GameListPlaceholder::onUpdateThemedIcons() { | ||
| 705 | image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); | ||
| 706 | } | ||
| 707 | |||
| 708 | void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) { | ||
| 709 | emit GameListPlaceholder::AddDirectory(); | ||
| 710 | } | ||
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 @@ | |||
| 8 | #include <QHBoxLayout> | 8 | #include <QHBoxLayout> |
| 9 | #include <QLabel> | 9 | #include <QLabel> |
| 10 | #include <QLineEdit> | 10 | #include <QLineEdit> |
| 11 | #include <QList> | ||
| 11 | #include <QModelIndex> | 12 | #include <QModelIndex> |
| 12 | #include <QSettings> | 13 | #include <QSettings> |
| 13 | #include <QStandardItem> | 14 | #include <QStandardItem> |
| @@ -16,13 +17,16 @@ | |||
| 16 | #include <QToolButton> | 17 | #include <QToolButton> |
| 17 | #include <QTreeView> | 18 | #include <QTreeView> |
| 18 | #include <QVBoxLayout> | 19 | #include <QVBoxLayout> |
| 20 | #include <QVector> | ||
| 19 | #include <QWidget> | 21 | #include <QWidget> |
| 20 | 22 | ||
| 21 | #include "common/common_types.h" | 23 | #include "common/common_types.h" |
| 24 | #include "uisettings.h" | ||
| 22 | #include "yuzu/compatibility_list.h" | 25 | #include "yuzu/compatibility_list.h" |
| 23 | 26 | ||
| 24 | class GameListWorker; | 27 | class GameListWorker; |
| 25 | class GameListSearchField; | 28 | class GameListSearchField; |
| 29 | class GameListDir; | ||
| 26 | class GMainWindow; | 30 | class GMainWindow; |
| 27 | 31 | ||
| 28 | namespace FileSys { | 32 | namespace FileSys { |
| @@ -52,12 +56,14 @@ public: | |||
| 52 | FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr); | 56 | FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr); |
| 53 | ~GameList() override; | 57 | ~GameList() override; |
| 54 | 58 | ||
| 59 | QString getLastFilterResultItem() const; | ||
| 55 | void clearFilter(); | 60 | void clearFilter(); |
| 56 | void setFilterFocus(); | 61 | void setFilterFocus(); |
| 57 | void setFilterVisible(bool visibility); | 62 | void setFilterVisible(bool visibility); |
| 63 | bool isEmpty() const; | ||
| 58 | 64 | ||
| 59 | void LoadCompatibilityList(); | 65 | void LoadCompatibilityList(); |
| 60 | void PopulateAsync(const QString& dir_path, bool deep_scan); | 66 | void PopulateAsync(QVector<UISettings::GameDir>& game_dirs); |
| 61 | 67 | ||
| 62 | void SaveInterfaceLayout(); | 68 | void SaveInterfaceLayout(); |
| 63 | void LoadInterfaceLayout(); | 69 | void LoadInterfaceLayout(); |
| @@ -74,19 +80,29 @@ signals: | |||
| 74 | void NavigateToGamedbEntryRequested(u64 program_id, | 80 | void NavigateToGamedbEntryRequested(u64 program_id, |
| 75 | const CompatibilityList& compatibility_list); | 81 | const CompatibilityList& compatibility_list); |
| 76 | void OpenPerGameGeneralRequested(const std::string& file); | 82 | void OpenPerGameGeneralRequested(const std::string& file); |
| 83 | void OpenDirectory(const QString& directory); | ||
| 84 | void AddDirectory(); | ||
| 85 | void ShowList(bool show); | ||
| 77 | 86 | ||
| 78 | private slots: | 87 | private slots: |
| 88 | void onItemExpanded(const QModelIndex& item); | ||
| 79 | void onTextChanged(const QString& new_text); | 89 | void onTextChanged(const QString& new_text); |
| 80 | void onFilterCloseClicked(); | 90 | void onFilterCloseClicked(); |
| 91 | void onUpdateThemedIcons(); | ||
| 81 | 92 | ||
| 82 | private: | 93 | private: |
| 83 | void AddEntry(const QList<QStandardItem*>& entry_items); | 94 | void AddDirEntry(GameListDir* entry_items); |
| 95 | void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent); | ||
| 84 | void ValidateEntry(const QModelIndex& item); | 96 | void ValidateEntry(const QModelIndex& item); |
| 85 | void DonePopulating(QStringList watch_list); | 97 | void DonePopulating(QStringList watch_list); |
| 86 | 98 | ||
| 87 | void PopupContextMenu(const QPoint& menu_location); | ||
| 88 | void RefreshGameDirectory(); | 99 | void RefreshGameDirectory(); |
| 89 | 100 | ||
| 101 | void PopupContextMenu(const QPoint& menu_location); | ||
| 102 | void AddGamePopup(QMenu& context_menu, u64 program_id, std::string path); | ||
| 103 | void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected); | ||
| 104 | void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); | ||
| 105 | |||
| 90 | std::shared_ptr<FileSys::VfsFilesystem> vfs; | 106 | std::shared_ptr<FileSys::VfsFilesystem> vfs; |
| 91 | FileSys::ManualContentProvider* provider; | 107 | FileSys::ManualContentProvider* provider; |
| 92 | GameListSearchField* search_field; | 108 | GameListSearchField* search_field; |
| @@ -102,3 +118,24 @@ private: | |||
| 102 | }; | 118 | }; |
| 103 | 119 | ||
| 104 | Q_DECLARE_METATYPE(GameListOpenTarget); | 120 | Q_DECLARE_METATYPE(GameListOpenTarget); |
| 121 | |||
| 122 | class GameListPlaceholder : public QWidget { | ||
| 123 | Q_OBJECT | ||
| 124 | public: | ||
| 125 | explicit GameListPlaceholder(GMainWindow* parent = nullptr); | ||
| 126 | ~GameListPlaceholder(); | ||
| 127 | |||
| 128 | signals: | ||
| 129 | void AddDirectory(); | ||
| 130 | |||
| 131 | private slots: | ||
| 132 | void onUpdateThemedIcons(); | ||
| 133 | |||
| 134 | protected: | ||
| 135 | void mouseDoubleClickEvent(QMouseEvent* event) override; | ||
| 136 | |||
| 137 | private: | ||
| 138 | QVBoxLayout* layout = nullptr; | ||
| 139 | QLabel* image = nullptr; | ||
| 140 | QLabel* text = nullptr; | ||
| 141 | }; | ||
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 @@ | |||
| 10 | #include <utility> | 10 | #include <utility> |
| 11 | 11 | ||
| 12 | #include <QCoreApplication> | 12 | #include <QCoreApplication> |
| 13 | #include <QFileInfo> | ||
| 13 | #include <QImage> | 14 | #include <QImage> |
| 14 | #include <QObject> | 15 | #include <QObject> |
| 15 | #include <QStandardItem> | 16 | #include <QStandardItem> |
| @@ -22,6 +23,17 @@ | |||
| 22 | #include "yuzu/uisettings.h" | 23 | #include "yuzu/uisettings.h" |
| 23 | #include "yuzu/util/util.h" | 24 | #include "yuzu/util/util.h" |
| 24 | 25 | ||
| 26 | enum class GameListItemType { | ||
| 27 | Game = QStandardItem::UserType + 1, | ||
| 28 | CustomDir = QStandardItem::UserType + 2, | ||
| 29 | SdmcDir = QStandardItem::UserType + 3, | ||
| 30 | UserNandDir = QStandardItem::UserType + 4, | ||
| 31 | SysNandDir = QStandardItem::UserType + 5, | ||
| 32 | AddDir = QStandardItem::UserType + 6 | ||
| 33 | }; | ||
| 34 | |||
| 35 | Q_DECLARE_METATYPE(GameListItemType); | ||
| 36 | |||
| 25 | /** | 37 | /** |
| 26 | * Gets the default icon (for games without valid title metadata) | 38 | * Gets the default icon (for games without valid title metadata) |
| 27 | * @param size The desired width and height of the default icon. | 39 | * @param size The desired width and height of the default icon. |
| @@ -36,8 +48,13 @@ static QPixmap GetDefaultIcon(u32 size) { | |||
| 36 | class GameListItem : public QStandardItem { | 48 | class GameListItem : public QStandardItem { |
| 37 | 49 | ||
| 38 | public: | 50 | public: |
| 51 | // used to access type from item index | ||
| 52 | static const int TypeRole = Qt::UserRole + 1; | ||
| 53 | static const int SortRole = Qt::UserRole + 2; | ||
| 39 | GameListItem() = default; | 54 | GameListItem() = default; |
| 40 | explicit GameListItem(const QString& string) : QStandardItem(string) {} | 55 | GameListItem(const QString& string) : QStandardItem(string) { |
| 56 | setData(string, SortRole); | ||
| 57 | } | ||
| 41 | }; | 58 | }; |
| 42 | 59 | ||
| 43 | /** | 60 | /** |
| @@ -48,14 +65,15 @@ public: | |||
| 48 | */ | 65 | */ |
| 49 | class GameListItemPath : public GameListItem { | 66 | class GameListItemPath : public GameListItem { |
| 50 | public: | 67 | public: |
| 51 | static const int FullPathRole = Qt::UserRole + 1; | 68 | static const int TitleRole = SortRole; |
| 52 | static const int TitleRole = Qt::UserRole + 2; | 69 | static const int FullPathRole = SortRole + 1; |
| 53 | static const int ProgramIdRole = Qt::UserRole + 3; | 70 | static const int ProgramIdRole = SortRole + 2; |
| 54 | static const int FileTypeRole = Qt::UserRole + 4; | 71 | static const int FileTypeRole = SortRole + 3; |
| 55 | 72 | ||
| 56 | GameListItemPath() = default; | 73 | GameListItemPath() = default; |
| 57 | GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data, | 74 | GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data, |
| 58 | const QString& game_name, const QString& game_type, u64 program_id) { | 75 | const QString& game_name, const QString& game_type, u64 program_id) { |
| 76 | setData(type(), TypeRole); | ||
| 59 | setData(game_path, FullPathRole); | 77 | setData(game_path, FullPathRole); |
| 60 | setData(game_name, TitleRole); | 78 | setData(game_name, TitleRole); |
| 61 | setData(qulonglong(program_id), ProgramIdRole); | 79 | setData(qulonglong(program_id), ProgramIdRole); |
| @@ -72,6 +90,10 @@ public: | |||
| 72 | setData(picture, Qt::DecorationRole); | 90 | setData(picture, Qt::DecorationRole); |
| 73 | } | 91 | } |
| 74 | 92 | ||
| 93 | int type() const override { | ||
| 94 | return static_cast<int>(GameListItemType::Game); | ||
| 95 | } | ||
| 96 | |||
| 75 | QVariant data(int role) const override { | 97 | QVariant data(int role) const override { |
| 76 | if (role == Qt::DisplayRole) { | 98 | if (role == Qt::DisplayRole) { |
| 77 | std::string filename; | 99 | std::string filename; |
| @@ -103,9 +125,11 @@ public: | |||
| 103 | class GameListItemCompat : public GameListItem { | 125 | class GameListItemCompat : public GameListItem { |
| 104 | Q_DECLARE_TR_FUNCTIONS(GameListItemCompat) | 126 | Q_DECLARE_TR_FUNCTIONS(GameListItemCompat) |
| 105 | public: | 127 | public: |
| 106 | static const int CompatNumberRole = Qt::UserRole + 1; | 128 | static const int CompatNumberRole = SortRole; |
| 107 | GameListItemCompat() = default; | 129 | GameListItemCompat() = default; |
| 108 | explicit GameListItemCompat(const QString& compatibility) { | 130 | explicit GameListItemCompat(const QString& compatibility) { |
| 131 | setData(type(), TypeRole); | ||
| 132 | |||
| 109 | struct CompatStatus { | 133 | struct CompatStatus { |
| 110 | QString color; | 134 | QString color; |
| 111 | const char* text; | 135 | const char* text; |
| @@ -135,6 +159,10 @@ public: | |||
| 135 | setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); | 159 | setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); |
| 136 | } | 160 | } |
| 137 | 161 | ||
| 162 | int type() const override { | ||
| 163 | return static_cast<int>(GameListItemType::Game); | ||
| 164 | } | ||
| 165 | |||
| 138 | bool operator<(const QStandardItem& other) const override { | 166 | bool operator<(const QStandardItem& other) const override { |
| 139 | return data(CompatNumberRole) < other.data(CompatNumberRole); | 167 | return data(CompatNumberRole) < other.data(CompatNumberRole); |
| 140 | } | 168 | } |
| @@ -146,12 +174,12 @@ public: | |||
| 146 | * human-readable string representation will be displayed to the user. | 174 | * human-readable string representation will be displayed to the user. |
| 147 | */ | 175 | */ |
| 148 | class GameListItemSize : public GameListItem { | 176 | class GameListItemSize : public GameListItem { |
| 149 | |||
| 150 | public: | 177 | public: |
| 151 | static const int SizeRole = Qt::UserRole + 1; | 178 | static const int SizeRole = SortRole; |
| 152 | 179 | ||
| 153 | GameListItemSize() = default; | 180 | GameListItemSize() = default; |
| 154 | explicit GameListItemSize(const qulonglong size_bytes) { | 181 | explicit GameListItemSize(const qulonglong size_bytes) { |
| 182 | setData(type(), TypeRole); | ||
| 155 | setData(size_bytes, SizeRole); | 183 | setData(size_bytes, SizeRole); |
| 156 | } | 184 | } |
| 157 | 185 | ||
| @@ -167,6 +195,10 @@ public: | |||
| 167 | } | 195 | } |
| 168 | } | 196 | } |
| 169 | 197 | ||
| 198 | int type() const override { | ||
| 199 | return static_cast<int>(GameListItemType::Game); | ||
| 200 | } | ||
| 201 | |||
| 170 | /** | 202 | /** |
| 171 | * This operator is, in practice, only used by the TreeView sorting systems. | 203 | * This operator is, in practice, only used by the TreeView sorting systems. |
| 172 | * Override it so that it will correctly sort by numerical value instead of by string | 204 | * Override it so that it will correctly sort by numerical value instead of by string |
| @@ -177,6 +209,82 @@ public: | |||
| 177 | } | 209 | } |
| 178 | }; | 210 | }; |
| 179 | 211 | ||
| 212 | class GameListDir : public GameListItem { | ||
| 213 | public: | ||
| 214 | static const int GameDirRole = Qt::UserRole + 2; | ||
| 215 | |||
| 216 | explicit GameListDir(UISettings::GameDir& directory, | ||
| 217 | GameListItemType dir_type = GameListItemType::CustomDir) | ||
| 218 | : dir_type{dir_type} { | ||
| 219 | setData(type(), TypeRole); | ||
| 220 | |||
| 221 | UISettings::GameDir* game_dir = &directory; | ||
| 222 | setData(QVariant::fromValue(game_dir), GameDirRole); | ||
| 223 | |||
| 224 | const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64); | ||
| 225 | switch (dir_type) { | ||
| 226 | case GameListItemType::SdmcDir: | ||
| 227 | setData( | ||
| 228 | QIcon::fromTheme(QStringLiteral("sd_card")) | ||
| 229 | .pixmap(icon_size) | ||
| 230 | .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), | ||
| 231 | Qt::DecorationRole); | ||
| 232 | setData(QObject::tr("Installed SD Titles"), Qt::DisplayRole); | ||
| 233 | break; | ||
| 234 | case GameListItemType::UserNandDir: | ||
| 235 | setData( | ||
| 236 | QIcon::fromTheme(QStringLiteral("chip")) | ||
| 237 | .pixmap(icon_size) | ||
| 238 | .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), | ||
| 239 | Qt::DecorationRole); | ||
| 240 | setData(QObject::tr("Installed NAND Titles"), Qt::DisplayRole); | ||
| 241 | break; | ||
| 242 | case GameListItemType::SysNandDir: | ||
| 243 | setData( | ||
| 244 | QIcon::fromTheme(QStringLiteral("chip")) | ||
| 245 | .pixmap(icon_size) | ||
| 246 | .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), | ||
| 247 | Qt::DecorationRole); | ||
| 248 | setData(QObject::tr("System Titles"), Qt::DisplayRole); | ||
| 249 | break; | ||
| 250 | case GameListItemType::CustomDir: | ||
| 251 | const QString icon_name = QFileInfo::exists(game_dir->path) | ||
| 252 | ? QStringLiteral("folder") | ||
| 253 | : QStringLiteral("bad_folder"); | ||
| 254 | setData(QIcon::fromTheme(icon_name).pixmap(icon_size).scaled( | ||
| 255 | icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), | ||
| 256 | Qt::DecorationRole); | ||
| 257 | setData(game_dir->path, Qt::DisplayRole); | ||
| 258 | break; | ||
| 259 | }; | ||
| 260 | }; | ||
| 261 | |||
| 262 | int type() const override { | ||
| 263 | return static_cast<int>(dir_type); | ||
| 264 | } | ||
| 265 | |||
| 266 | private: | ||
| 267 | GameListItemType dir_type; | ||
| 268 | }; | ||
| 269 | |||
| 270 | class GameListAddDir : public GameListItem { | ||
| 271 | public: | ||
| 272 | explicit GameListAddDir() { | ||
| 273 | setData(type(), TypeRole); | ||
| 274 | |||
| 275 | const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64); | ||
| 276 | setData(QIcon::fromTheme(QStringLiteral("plus")) | ||
| 277 | .pixmap(icon_size) | ||
| 278 | .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), | ||
| 279 | Qt::DecorationRole); | ||
| 280 | setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole); | ||
| 281 | } | ||
| 282 | |||
| 283 | int type() const override { | ||
| 284 | return static_cast<int>(GameListItemType::AddDir); | ||
| 285 | } | ||
| 286 | }; | ||
| 287 | |||
| 180 | class GameList; | 288 | class GameList; |
| 181 | class QHBoxLayout; | 289 | class QHBoxLayout; |
| 182 | class QTreeView; | 290 | class QTreeView; |
| @@ -208,6 +316,9 @@ private: | |||
| 208 | // EventFilter in order to process systemkeys while editing the searchfield | 316 | // EventFilter in order to process systemkeys while editing the searchfield |
| 209 | bool eventFilter(QObject* obj, QEvent* event) override; | 317 | bool eventFilter(QObject* obj, QEvent* event) override; |
| 210 | }; | 318 | }; |
| 319 | int visible; | ||
| 320 | int total; | ||
| 321 | |||
| 211 | QHBoxLayout* layout_filter = nullptr; | 322 | QHBoxLayout* layout_filter = nullptr; |
| 212 | QTreeView* tree_view = nullptr; | 323 | QTreeView* tree_view = nullptr; |
| 213 | QLabel* label_filter = nullptr; | 324 | 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 | |||
| 223 | } // Anonymous namespace | 223 | } // Anonymous namespace |
| 224 | 224 | ||
| 225 | GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, | 225 | GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, |
| 226 | FileSys::ManualContentProvider* provider, QString dir_path, | 226 | FileSys::ManualContentProvider* provider, |
| 227 | bool deep_scan, const CompatibilityList& compatibility_list) | 227 | QVector<UISettings::GameDir>& game_dirs, |
| 228 | : vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan), | 228 | const CompatibilityList& compatibility_list) |
| 229 | : vfs(std::move(vfs)), provider(provider), game_dirs(game_dirs), | ||
| 229 | compatibility_list(compatibility_list) {} | 230 | compatibility_list(compatibility_list) {} |
| 230 | 231 | ||
| 231 | GameListWorker::~GameListWorker() = default; | 232 | GameListWorker::~GameListWorker() = default; |
| 232 | 233 | ||
| 233 | void GameListWorker::AddTitlesToGameList() { | 234 | void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { |
| 234 | const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>( | 235 | using namespace FileSys; |
| 235 | Core::System::GetInstance().GetContentProvider()); | 236 | |
| 236 | const auto installed_games = cache.ListEntriesFilterOrigin( | 237 | const auto& cache = |
| 237 | std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program); | 238 | dynamic_cast<ContentProviderUnion&>(Core::System::GetInstance().GetContentProvider()); |
| 239 | |||
| 240 | std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> installed_games; | ||
| 241 | installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application, | ||
| 242 | ContentRecordType::Program); | ||
| 243 | |||
| 244 | if (parent_dir->type() == static_cast<int>(GameListItemType::SdmcDir)) { | ||
| 245 | installed_games = cache.ListEntriesFilterOrigin( | ||
| 246 | ContentProviderUnionSlot::SDMC, TitleType::Application, ContentRecordType::Program); | ||
| 247 | } else if (parent_dir->type() == static_cast<int>(GameListItemType::UserNandDir)) { | ||
| 248 | installed_games = cache.ListEntriesFilterOrigin( | ||
| 249 | ContentProviderUnionSlot::UserNAND, TitleType::Application, ContentRecordType::Program); | ||
| 250 | } else if (parent_dir->type() == static_cast<int>(GameListItemType::SysNandDir)) { | ||
| 251 | installed_games = cache.ListEntriesFilterOrigin( | ||
| 252 | ContentProviderUnionSlot::SysNAND, TitleType::Application, ContentRecordType::Program); | ||
| 253 | } | ||
| 238 | 254 | ||
| 239 | for (const auto& [slot, game] : installed_games) { | 255 | for (const auto& [slot, game] : installed_games) { |
| 240 | if (slot == FileSys::ContentProviderUnionSlot::FrontendManual) | 256 | if (slot == ContentProviderUnionSlot::FrontendManual) |
| 241 | continue; | 257 | continue; |
| 242 | 258 | ||
| 243 | const auto file = cache.GetEntryUnparsed(game.title_id, game.type); | 259 | const auto file = cache.GetEntryUnparsed(game.title_id, game.type); |
| @@ -250,21 +266,22 @@ void GameListWorker::AddTitlesToGameList() { | |||
| 250 | u64 program_id = 0; | 266 | u64 program_id = 0; |
| 251 | loader->ReadProgramId(program_id); | 267 | loader->ReadProgramId(program_id); |
| 252 | 268 | ||
| 253 | const FileSys::PatchManager patch{program_id}; | 269 | const PatchManager patch{program_id}; |
| 254 | const auto control = cache.GetEntry(game.title_id, FileSys::ContentRecordType::Control); | 270 | const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control); |
| 255 | if (control != nullptr) | 271 | if (control != nullptr) |
| 256 | GetMetadataFromControlNCA(patch, *control, icon, name); | 272 | GetMetadataFromControlNCA(patch, *control, icon, name); |
| 257 | 273 | ||
| 258 | emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id, | 274 | emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id, |
| 259 | compatibility_list, patch)); | 275 | compatibility_list, patch), |
| 276 | parent_dir); | ||
| 260 | } | 277 | } |
| 261 | } | 278 | } |
| 262 | 279 | ||
| 263 | void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, | 280 | void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, |
| 264 | unsigned int recursion) { | 281 | unsigned int recursion, GameListDir* parent_dir) { |
| 265 | const auto callback = [this, target, recursion](u64* num_entries_out, | 282 | const auto callback = [this, target, recursion, |
| 266 | const std::string& directory, | 283 | parent_dir](u64* num_entries_out, const std::string& directory, |
| 267 | const std::string& virtual_name) -> bool { | 284 | const std::string& virtual_name) -> bool { |
| 268 | if (stop_processing) { | 285 | if (stop_processing) { |
| 269 | // Breaks the callback loop. | 286 | // Breaks the callback loop. |
| 270 | return false; | 287 | return false; |
| @@ -317,11 +334,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 317 | const FileSys::PatchManager patch{program_id}; | 334 | const FileSys::PatchManager patch{program_id}; |
| 318 | 335 | ||
| 319 | emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, | 336 | emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, |
| 320 | compatibility_list, patch)); | 337 | compatibility_list, patch), |
| 338 | parent_dir); | ||
| 321 | } | 339 | } |
| 322 | } else if (is_dir && recursion > 0) { | 340 | } else if (is_dir && recursion > 0) { |
| 323 | watch_list.append(QString::fromStdString(physical_name)); | 341 | watch_list.append(QString::fromStdString(physical_name)); |
| 324 | ScanFileSystem(target, physical_name, recursion - 1); | 342 | ScanFileSystem(target, physical_name, recursion - 1, parent_dir); |
| 325 | } | 343 | } |
| 326 | 344 | ||
| 327 | return true; | 345 | return true; |
| @@ -332,12 +350,32 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 332 | 350 | ||
| 333 | void GameListWorker::run() { | 351 | void GameListWorker::run() { |
| 334 | stop_processing = false; | 352 | stop_processing = false; |
| 335 | watch_list.append(dir_path); | 353 | |
| 336 | provider->ClearAllEntries(); | 354 | for (UISettings::GameDir& game_dir : game_dirs) { |
| 337 | ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(), | 355 | if (game_dir.path == QStringLiteral("SDMC")) { |
| 338 | deep_scan ? 256 : 0); | 356 | auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); |
| 339 | AddTitlesToGameList(); | 357 | emit DirEntryReady({game_list_dir}); |
| 340 | ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0); | 358 | AddTitlesToGameList(game_list_dir); |
| 359 | } else if (game_dir.path == QStringLiteral("UserNAND")) { | ||
| 360 | auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); | ||
| 361 | emit DirEntryReady({game_list_dir}); | ||
| 362 | AddTitlesToGameList(game_list_dir); | ||
| 363 | } else if (game_dir.path == QStringLiteral("SysNAND")) { | ||
| 364 | auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); | ||
| 365 | emit DirEntryReady({game_list_dir}); | ||
| 366 | AddTitlesToGameList(game_list_dir); | ||
| 367 | } else { | ||
| 368 | watch_list.append(game_dir.path); | ||
| 369 | auto* const game_list_dir = new GameListDir(game_dir); | ||
| 370 | emit DirEntryReady({game_list_dir}); | ||
| 371 | provider->ClearAllEntries(); | ||
| 372 | ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 2, | ||
| 373 | game_list_dir); | ||
| 374 | ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), | ||
| 375 | game_dir.deep_scan ? 256 : 0, game_list_dir); | ||
| 376 | } | ||
| 377 | }; | ||
| 378 | |||
| 341 | emit Finished(watch_list); | 379 | emit Finished(watch_list); |
| 342 | } | 380 | } |
| 343 | 381 | ||
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 @@ | |||
| 14 | #include <QObject> | 14 | #include <QObject> |
| 15 | #include <QRunnable> | 15 | #include <QRunnable> |
| 16 | #include <QString> | 16 | #include <QString> |
| 17 | #include <QVector> | ||
| 17 | 18 | ||
| 18 | #include "common/common_types.h" | 19 | #include "common/common_types.h" |
| 19 | #include "yuzu/compatibility_list.h" | 20 | #include "yuzu/compatibility_list.h" |
| @@ -33,9 +34,10 @@ class GameListWorker : public QObject, public QRunnable { | |||
| 33 | Q_OBJECT | 34 | Q_OBJECT |
| 34 | 35 | ||
| 35 | public: | 36 | public: |
| 36 | GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, | 37 | explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, |
| 37 | FileSys::ManualContentProvider* provider, QString dir_path, bool deep_scan, | 38 | FileSys::ManualContentProvider* provider, |
| 38 | const CompatibilityList& compatibility_list); | 39 | QVector<UISettings::GameDir>& game_dirs, |
| 40 | const CompatibilityList& compatibility_list); | ||
| 39 | ~GameListWorker() override; | 41 | ~GameListWorker() override; |
| 40 | 42 | ||
| 41 | /// Starts the processing of directory tree information. | 43 | /// Starts the processing of directory tree information. |
| @@ -48,31 +50,33 @@ signals: | |||
| 48 | /** | 50 | /** |
| 49 | * The `EntryReady` signal is emitted once an entry has been prepared and is ready | 51 | * The `EntryReady` signal is emitted once an entry has been prepared and is ready |
| 50 | * to be added to the game list. | 52 | * to be added to the game list. |
| 51 | * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. | 53 | * @param entry_items a list with `QStandardItem`s that make up the columns of the new |
| 54 | * entry. | ||
| 52 | */ | 55 | */ |
| 53 | void EntryReady(QList<QStandardItem*> entry_items); | 56 | void DirEntryReady(GameListDir* entry_items); |
| 57 | void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir); | ||
| 54 | 58 | ||
| 55 | /** | 59 | /** |
| 56 | * After the worker has traversed the game directory looking for entries, this signal is emitted | 60 | * After the worker has traversed the game directory looking for entries, this signal is |
| 57 | * with a list of folders that should be watched for changes as well. | 61 | * emitted with a list of folders that should be watched for changes as well. |
| 58 | */ | 62 | */ |
| 59 | void Finished(QStringList watch_list); | 63 | void Finished(QStringList watch_list); |
| 60 | 64 | ||
| 61 | private: | 65 | private: |
| 62 | void AddTitlesToGameList(); | 66 | void AddTitlesToGameList(GameListDir* parent_dir); |
| 63 | 67 | ||
| 64 | enum class ScanTarget { | 68 | enum class ScanTarget { |
| 65 | FillManualContentProvider, | 69 | FillManualContentProvider, |
| 66 | PopulateGameList, | 70 | PopulateGameList, |
| 67 | }; | 71 | }; |
| 68 | 72 | ||
| 69 | void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0); | 73 | void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion, |
| 74 | GameListDir* parent_dir); | ||
| 70 | 75 | ||
| 71 | std::shared_ptr<FileSys::VfsFilesystem> vfs; | 76 | std::shared_ptr<FileSys::VfsFilesystem> vfs; |
| 72 | FileSys::ManualContentProvider* provider; | 77 | FileSys::ManualContentProvider* provider; |
| 73 | QStringList watch_list; | 78 | QStringList watch_list; |
| 74 | QString dir_path; | ||
| 75 | bool deep_scan; | ||
| 76 | const CompatibilityList& compatibility_list; | 79 | const CompatibilityList& compatibility_list; |
| 80 | QVector<UISettings::GameDir>& game_dirs; | ||
| 77 | std::atomic_bool stop_processing; | 81 | std::atomic_bool stop_processing; |
| 78 | }; | 82 | }; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index a7c656fdb..8304c6517 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -6,6 +6,9 @@ | |||
| 6 | #include <clocale> | 6 | #include <clocale> |
| 7 | #include <memory> | 7 | #include <memory> |
| 8 | #include <thread> | 8 | #include <thread> |
| 9 | #ifdef __APPLE__ | ||
| 10 | #include <unistd.h> // for chdir | ||
| 11 | #endif | ||
| 9 | 12 | ||
| 10 | // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. | 13 | // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. |
| 11 | #include "applets/error.h" | 14 | #include "applets/error.h" |
| @@ -119,6 +122,7 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); | |||
| 119 | #endif | 122 | #endif |
| 120 | 123 | ||
| 121 | #ifdef _WIN32 | 124 | #ifdef _WIN32 |
| 125 | #include <windows.h> | ||
| 122 | extern "C" { | 126 | extern "C" { |
| 123 | // tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable | 127 | // tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable |
| 124 | // graphics | 128 | // graphics |
| @@ -215,8 +219,7 @@ GMainWindow::GMainWindow() | |||
| 215 | OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); | 219 | OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); |
| 216 | 220 | ||
| 217 | game_list->LoadCompatibilityList(); | 221 | game_list->LoadCompatibilityList(); |
| 218 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 222 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 219 | UISettings::values.game_directory_deepscan); | ||
| 220 | 223 | ||
| 221 | // Show one-time "callout" messages to the user | 224 | // Show one-time "callout" messages to the user |
| 222 | ShowTelemetryCallout(); | 225 | ShowTelemetryCallout(); |
| @@ -426,6 +429,10 @@ void GMainWindow::InitializeWidgets() { | |||
| 426 | game_list = new GameList(vfs, provider.get(), this); | 429 | game_list = new GameList(vfs, provider.get(), this); |
| 427 | ui.horizontalLayout->addWidget(game_list); | 430 | ui.horizontalLayout->addWidget(game_list); |
| 428 | 431 | ||
| 432 | game_list_placeholder = new GameListPlaceholder(this); | ||
| 433 | ui.horizontalLayout->addWidget(game_list_placeholder); | ||
| 434 | game_list_placeholder->setVisible(false); | ||
| 435 | |||
| 429 | loading_screen = new LoadingScreen(this); | 436 | loading_screen = new LoadingScreen(this); |
| 430 | loading_screen->hide(); | 437 | loading_screen->hide(); |
| 431 | ui.horizontalLayout->addWidget(loading_screen); | 438 | ui.horizontalLayout->addWidget(loading_screen); |
| @@ -659,6 +666,7 @@ void GMainWindow::RestoreUIState() { | |||
| 659 | 666 | ||
| 660 | void GMainWindow::ConnectWidgetEvents() { | 667 | void GMainWindow::ConnectWidgetEvents() { |
| 661 | connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); | 668 | connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); |
| 669 | connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory); | ||
| 662 | connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); | 670 | connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); |
| 663 | connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, | 671 | connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, |
| 664 | &GMainWindow::OnTransferableShaderCacheOpenFile); | 672 | &GMainWindow::OnTransferableShaderCacheOpenFile); |
| @@ -666,6 +674,11 @@ void GMainWindow::ConnectWidgetEvents() { | |||
| 666 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); | 674 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); |
| 667 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, | 675 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, |
| 668 | &GMainWindow::OnGameListNavigateToGamedbEntry); | 676 | &GMainWindow::OnGameListNavigateToGamedbEntry); |
| 677 | connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory); | ||
| 678 | connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this, | ||
| 679 | &GMainWindow::OnGameListAddDirectory); | ||
| 680 | connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList); | ||
| 681 | |||
| 669 | connect(game_list, &GameList::OpenPerGameGeneralRequested, this, | 682 | connect(game_list, &GameList::OpenPerGameGeneralRequested, this, |
| 670 | &GMainWindow::OnGameListOpenPerGameProperties); | 683 | &GMainWindow::OnGameListOpenPerGameProperties); |
| 671 | 684 | ||
| @@ -683,8 +696,6 @@ void GMainWindow::ConnectMenuEvents() { | |||
| 683 | connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); | 696 | connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); |
| 684 | connect(ui.action_Install_File_NAND, &QAction::triggered, this, | 697 | connect(ui.action_Install_File_NAND, &QAction::triggered, this, |
| 685 | &GMainWindow::OnMenuInstallToNAND); | 698 | &GMainWindow::OnMenuInstallToNAND); |
| 686 | connect(ui.action_Select_Game_List_Root, &QAction::triggered, this, | ||
| 687 | &GMainWindow::OnMenuSelectGameListRoot); | ||
| 688 | connect(ui.action_Select_NAND_Directory, &QAction::triggered, this, | 699 | connect(ui.action_Select_NAND_Directory, &QAction::triggered, this, |
| 689 | [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); }); | 700 | [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); }); |
| 690 | connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this, | 701 | connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this, |
| @@ -747,6 +758,18 @@ void GMainWindow::OnDisplayTitleBars(bool show) { | |||
| 747 | } | 758 | } |
| 748 | } | 759 | } |
| 749 | 760 | ||
| 761 | void GMainWindow::PreventOSSleep() { | ||
| 762 | #ifdef _WIN32 | ||
| 763 | SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED); | ||
| 764 | #endif | ||
| 765 | } | ||
| 766 | |||
| 767 | void GMainWindow::AllowOSSleep() { | ||
| 768 | #ifdef _WIN32 | ||
| 769 | SetThreadExecutionState(ES_CONTINUOUS); | ||
| 770 | #endif | ||
| 771 | } | ||
| 772 | |||
| 750 | QStringList GMainWindow::GetUnsupportedGLExtensions() { | 773 | QStringList GMainWindow::GetUnsupportedGLExtensions() { |
| 751 | QStringList unsupported_ext; | 774 | QStringList unsupported_ext; |
| 752 | 775 | ||
| @@ -937,6 +960,7 @@ void GMainWindow::BootGame(const QString& filename) { | |||
| 937 | // Update the GUI | 960 | // Update the GUI |
| 938 | if (ui.action_Single_Window_Mode->isChecked()) { | 961 | if (ui.action_Single_Window_Mode->isChecked()) { |
| 939 | game_list->hide(); | 962 | game_list->hide(); |
| 963 | game_list_placeholder->hide(); | ||
| 940 | } | 964 | } |
| 941 | status_bar_update_timer.start(2000); | 965 | status_bar_update_timer.start(2000); |
| 942 | 966 | ||
| @@ -966,6 +990,8 @@ void GMainWindow::BootGame(const QString& filename) { | |||
| 966 | } | 990 | } |
| 967 | 991 | ||
| 968 | void GMainWindow::ShutdownGame() { | 992 | void GMainWindow::ShutdownGame() { |
| 993 | AllowOSSleep(); | ||
| 994 | |||
| 969 | discord_rpc->Pause(); | 995 | discord_rpc->Pause(); |
| 970 | emu_thread->RequestStop(); | 996 | emu_thread->RequestStop(); |
| 971 | 997 | ||
| @@ -992,7 +1018,10 @@ void GMainWindow::ShutdownGame() { | |||
| 992 | render_window->hide(); | 1018 | render_window->hide(); |
| 993 | loading_screen->hide(); | 1019 | loading_screen->hide(); |
| 994 | loading_screen->Clear(); | 1020 | loading_screen->Clear(); |
| 995 | game_list->show(); | 1021 | if (game_list->isEmpty()) |
| 1022 | game_list_placeholder->show(); | ||
| 1023 | else | ||
| 1024 | game_list->show(); | ||
| 996 | game_list->setFilterFocus(); | 1025 | game_list->setFilterFocus(); |
| 997 | 1026 | ||
| 998 | UpdateWindowTitle(); | 1027 | UpdateWindowTitle(); |
| @@ -1283,6 +1312,47 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, | |||
| 1283 | QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); | 1312 | QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); |
| 1284 | } | 1313 | } |
| 1285 | 1314 | ||
| 1315 | void GMainWindow::OnGameListOpenDirectory(const QString& directory) { | ||
| 1316 | QString path; | ||
| 1317 | if (directory == QStringLiteral("SDMC")) { | ||
| 1318 | path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + | ||
| 1319 | "Nintendo/Contents/registered"); | ||
| 1320 | } else if (directory == QStringLiteral("UserNAND")) { | ||
| 1321 | path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + | ||
| 1322 | "user/Contents/registered"); | ||
| 1323 | } else if (directory == QStringLiteral("SysNAND")) { | ||
| 1324 | path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + | ||
| 1325 | "system/Contents/registered"); | ||
| 1326 | } else { | ||
| 1327 | path = directory; | ||
| 1328 | } | ||
| 1329 | if (!QFileInfo::exists(path)) { | ||
| 1330 | QMessageBox::critical(this, tr("Error Opening %1").arg(path), tr("Folder does not exist!")); | ||
| 1331 | return; | ||
| 1332 | } | ||
| 1333 | QDesktopServices::openUrl(QUrl::fromLocalFile(path)); | ||
| 1334 | } | ||
| 1335 | |||
| 1336 | void GMainWindow::OnGameListAddDirectory() { | ||
| 1337 | const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); | ||
| 1338 | if (dir_path.isEmpty()) | ||
| 1339 | return; | ||
| 1340 | UISettings::GameDir game_dir{dir_path, false, true}; | ||
| 1341 | if (!UISettings::values.game_dirs.contains(game_dir)) { | ||
| 1342 | UISettings::values.game_dirs.append(game_dir); | ||
| 1343 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 1344 | } else { | ||
| 1345 | LOG_WARNING(Frontend, "Selected directory is already in the game list"); | ||
| 1346 | } | ||
| 1347 | } | ||
| 1348 | |||
| 1349 | void GMainWindow::OnGameListShowList(bool show) { | ||
| 1350 | if (emulation_running && ui.action_Single_Window_Mode->isChecked()) | ||
| 1351 | return; | ||
| 1352 | game_list->setVisible(show); | ||
| 1353 | game_list_placeholder->setVisible(!show); | ||
| 1354 | }; | ||
| 1355 | |||
| 1286 | void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { | 1356 | void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { |
| 1287 | u64 title_id{}; | 1357 | u64 title_id{}; |
| 1288 | const auto v_file = Core::GetGameFileFromPath(vfs, file); | 1358 | const auto v_file = Core::GetGameFileFromPath(vfs, file); |
| @@ -1301,8 +1371,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { | |||
| 1301 | 1371 | ||
| 1302 | const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); | 1372 | const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); |
| 1303 | if (reload) { | 1373 | if (reload) { |
| 1304 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 1374 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1305 | UISettings::values.game_directory_deepscan); | ||
| 1306 | } | 1375 | } |
| 1307 | 1376 | ||
| 1308 | config->Save(); | 1377 | config->Save(); |
| @@ -1392,8 +1461,7 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 1392 | const auto success = [this]() { | 1461 | const auto success = [this]() { |
| 1393 | QMessageBox::information(this, tr("Successfully Installed"), | 1462 | QMessageBox::information(this, tr("Successfully Installed"), |
| 1394 | tr("The file was successfully installed.")); | 1463 | tr("The file was successfully installed.")); |
| 1395 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 1464 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1396 | UISettings::values.game_directory_deepscan); | ||
| 1397 | FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + | 1465 | FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + |
| 1398 | DIR_SEP + "game_list"); | 1466 | DIR_SEP + "game_list"); |
| 1399 | }; | 1467 | }; |
| @@ -1518,14 +1586,6 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 1518 | } | 1586 | } |
| 1519 | } | 1587 | } |
| 1520 | 1588 | ||
| 1521 | void GMainWindow::OnMenuSelectGameListRoot() { | ||
| 1522 | QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); | ||
| 1523 | if (!dir_path.isEmpty()) { | ||
| 1524 | UISettings::values.game_directory_path = dir_path; | ||
| 1525 | game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan); | ||
| 1526 | } | ||
| 1527 | } | ||
| 1528 | |||
| 1529 | void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) { | 1589 | void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) { |
| 1530 | const auto res = QMessageBox::information( | 1590 | const auto res = QMessageBox::information( |
| 1531 | this, tr("Changing Emulated Directory"), | 1591 | this, tr("Changing Emulated Directory"), |
| @@ -1544,8 +1604,7 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) | |||
| 1544 | : FileUtil::UserPath::NANDDir, | 1604 | : FileUtil::UserPath::NANDDir, |
| 1545 | dir_path.toStdString()); | 1605 | dir_path.toStdString()); |
| 1546 | Service::FileSystem::CreateFactories(*vfs); | 1606 | Service::FileSystem::CreateFactories(*vfs); |
| 1547 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 1607 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1548 | UISettings::values.game_directory_deepscan); | ||
| 1549 | } | 1608 | } |
| 1550 | } | 1609 | } |
| 1551 | 1610 | ||
| @@ -1567,6 +1626,8 @@ void GMainWindow::OnMenuRecentFile() { | |||
| 1567 | } | 1626 | } |
| 1568 | 1627 | ||
| 1569 | void GMainWindow::OnStartGame() { | 1628 | void GMainWindow::OnStartGame() { |
| 1629 | PreventOSSleep(); | ||
| 1630 | |||
| 1570 | emu_thread->SetRunning(true); | 1631 | emu_thread->SetRunning(true); |
| 1571 | 1632 | ||
| 1572 | qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>( | 1633 | qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>( |
| @@ -1598,6 +1659,8 @@ void GMainWindow::OnPauseGame() { | |||
| 1598 | ui.action_Pause->setEnabled(false); | 1659 | ui.action_Pause->setEnabled(false); |
| 1599 | ui.action_Stop->setEnabled(true); | 1660 | ui.action_Stop->setEnabled(true); |
| 1600 | ui.action_Capture_Screenshot->setEnabled(false); | 1661 | ui.action_Capture_Screenshot->setEnabled(false); |
| 1662 | |||
| 1663 | AllowOSSleep(); | ||
| 1601 | } | 1664 | } |
| 1602 | 1665 | ||
| 1603 | void GMainWindow::OnStopGame() { | 1666 | void GMainWindow::OnStopGame() { |
| @@ -1705,11 +1768,11 @@ void GMainWindow::OnConfigure() { | |||
| 1705 | if (UISettings::values.enable_discord_presence != old_discord_presence) { | 1768 | if (UISettings::values.enable_discord_presence != old_discord_presence) { |
| 1706 | SetDiscordEnabled(UISettings::values.enable_discord_presence); | 1769 | SetDiscordEnabled(UISettings::values.enable_discord_presence); |
| 1707 | } | 1770 | } |
| 1771 | emit UpdateThemedIcons(); | ||
| 1708 | 1772 | ||
| 1709 | const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); | 1773 | const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); |
| 1710 | if (reload) { | 1774 | if (reload) { |
| 1711 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 1775 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1712 | UISettings::values.game_directory_deepscan); | ||
| 1713 | } | 1776 | } |
| 1714 | 1777 | ||
| 1715 | config->Save(); | 1778 | config->Save(); |
| @@ -1973,8 +2036,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { | |||
| 1973 | Service::FileSystem::CreateFactories(*vfs); | 2036 | Service::FileSystem::CreateFactories(*vfs); |
| 1974 | 2037 | ||
| 1975 | if (behavior == ReinitializeKeyBehavior::Warning) { | 2038 | if (behavior == ReinitializeKeyBehavior::Warning) { |
| 1976 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 2039 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1977 | UISettings::values.game_directory_deepscan); | ||
| 1978 | } | 2040 | } |
| 1979 | } | 2041 | } |
| 1980 | 2042 | ||
| @@ -2139,7 +2201,6 @@ void GMainWindow::UpdateUITheme() { | |||
| 2139 | } | 2201 | } |
| 2140 | 2202 | ||
| 2141 | QIcon::setThemeSearchPaths(theme_paths); | 2203 | QIcon::setThemeSearchPaths(theme_paths); |
| 2142 | emit UpdateThemedIcons(); | ||
| 2143 | } | 2204 | } |
| 2144 | 2205 | ||
| 2145 | void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { | 2206 | void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { |
| @@ -2168,6 +2229,14 @@ int main(int argc, char* argv[]) { | |||
| 2168 | QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); | 2229 | QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); |
| 2169 | QCoreApplication::setApplicationName(QStringLiteral("yuzu")); | 2230 | QCoreApplication::setApplicationName(QStringLiteral("yuzu")); |
| 2170 | 2231 | ||
| 2232 | #ifdef __APPLE__ | ||
| 2233 | // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/". | ||
| 2234 | // But since we require the working directory to be the executable path for the location of the | ||
| 2235 | // user folder in the Qt Frontend, we need to cd into that working directory | ||
| 2236 | const std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; | ||
| 2237 | chdir(bin_path.c_str()); | ||
| 2238 | #endif | ||
| 2239 | |||
| 2171 | // Enables the core to make the qt created contexts current on std::threads | 2240 | // Enables the core to make the qt created contexts current on std::threads |
| 2172 | QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); | 2241 | QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); |
| 2173 | QApplication app(argc, argv); | 2242 | QApplication app(argc, argv); |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 1137bbc7a..7d16188cb 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -30,6 +30,7 @@ class ProfilerWidget; | |||
| 30 | class QLabel; | 30 | class QLabel; |
| 31 | class WaitTreeWidget; | 31 | class WaitTreeWidget; |
| 32 | enum class GameListOpenTarget; | 32 | enum class GameListOpenTarget; |
| 33 | class GameListPlaceholder; | ||
| 33 | 34 | ||
| 34 | namespace Core::Frontend { | 35 | namespace Core::Frontend { |
| 35 | struct SoftwareKeyboardParameters; | 36 | struct SoftwareKeyboardParameters; |
| @@ -130,6 +131,9 @@ private: | |||
| 130 | void ConnectWidgetEvents(); | 131 | void ConnectWidgetEvents(); |
| 131 | void ConnectMenuEvents(); | 132 | void ConnectMenuEvents(); |
| 132 | 133 | ||
| 134 | void PreventOSSleep(); | ||
| 135 | void AllowOSSleep(); | ||
| 136 | |||
| 133 | QStringList GetUnsupportedGLExtensions(); | 137 | QStringList GetUnsupportedGLExtensions(); |
| 134 | bool LoadROM(const QString& filename); | 138 | bool LoadROM(const QString& filename); |
| 135 | void BootGame(const QString& filename); | 139 | void BootGame(const QString& filename); |
| @@ -183,12 +187,13 @@ private slots: | |||
| 183 | void OnGameListCopyTID(u64 program_id); | 187 | void OnGameListCopyTID(u64 program_id); |
| 184 | void OnGameListNavigateToGamedbEntry(u64 program_id, | 188 | void OnGameListNavigateToGamedbEntry(u64 program_id, |
| 185 | const CompatibilityList& compatibility_list); | 189 | const CompatibilityList& compatibility_list); |
| 190 | void OnGameListOpenDirectory(const QString& directory); | ||
| 191 | void OnGameListAddDirectory(); | ||
| 192 | void OnGameListShowList(bool show); | ||
| 186 | void OnGameListOpenPerGameProperties(const std::string& file); | 193 | void OnGameListOpenPerGameProperties(const std::string& file); |
| 187 | void OnMenuLoadFile(); | 194 | void OnMenuLoadFile(); |
| 188 | void OnMenuLoadFolder(); | 195 | void OnMenuLoadFolder(); |
| 189 | void OnMenuInstallToNAND(); | 196 | void OnMenuInstallToNAND(); |
| 190 | /// Called whenever a user selects the "File->Select Game List Root" menu item | ||
| 191 | void OnMenuSelectGameListRoot(); | ||
| 192 | /// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card | 197 | /// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card |
| 193 | void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target); | 198 | void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target); |
| 194 | void OnMenuRecentFile(); | 199 | void OnMenuRecentFile(); |
| @@ -220,6 +225,8 @@ private: | |||
| 220 | GameList* game_list; | 225 | GameList* game_list; |
| 221 | LoadingScreen* loading_screen; | 226 | LoadingScreen* loading_screen; |
| 222 | 227 | ||
| 228 | GameListPlaceholder* game_list_placeholder; | ||
| 229 | |||
| 223 | // Status bar elements | 230 | // Status bar elements |
| 224 | QLabel* message_label = nullptr; | 231 | QLabel* message_label = nullptr; |
| 225 | QLabel* emu_speed_label = nullptr; | 232 | 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 @@ | |||
| 62 | <addaction name="action_Load_File"/> | 62 | <addaction name="action_Load_File"/> |
| 63 | <addaction name="action_Load_Folder"/> | 63 | <addaction name="action_Load_Folder"/> |
| 64 | <addaction name="separator"/> | 64 | <addaction name="separator"/> |
| 65 | <addaction name="action_Select_Game_List_Root"/> | ||
| 66 | <addaction name="menu_recent_files"/> | 65 | <addaction name="menu_recent_files"/> |
| 67 | <addaction name="separator"/> | 66 | <addaction name="separator"/> |
| 68 | <addaction name="action_Select_NAND_Directory"/> | 67 | <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 @@ | |||
| 8 | #include <atomic> | 8 | #include <atomic> |
| 9 | #include <vector> | 9 | #include <vector> |
| 10 | #include <QByteArray> | 10 | #include <QByteArray> |
| 11 | #include <QMetaType> | ||
| 11 | #include <QString> | 12 | #include <QString> |
| 12 | #include <QStringList> | 13 | #include <QStringList> |
| 14 | #include <QVector> | ||
| 13 | #include "common/common_types.h" | 15 | #include "common/common_types.h" |
| 14 | 16 | ||
| 15 | namespace UISettings { | 17 | namespace UISettings { |
| @@ -25,6 +27,18 @@ struct Shortcut { | |||
| 25 | using Themes = std::array<std::pair<const char*, const char*>, 2>; | 27 | using Themes = std::array<std::pair<const char*, const char*>, 2>; |
| 26 | extern const Themes themes; | 28 | extern const Themes themes; |
| 27 | 29 | ||
| 30 | struct GameDir { | ||
| 31 | QString path; | ||
| 32 | bool deep_scan; | ||
| 33 | bool expanded; | ||
| 34 | bool operator==(const GameDir& rhs) const { | ||
| 35 | return path == rhs.path; | ||
| 36 | }; | ||
| 37 | bool operator!=(const GameDir& rhs) const { | ||
| 38 | return !operator==(rhs); | ||
| 39 | }; | ||
| 40 | }; | ||
| 41 | |||
| 28 | struct Values { | 42 | struct Values { |
| 29 | QByteArray geometry; | 43 | QByteArray geometry; |
| 30 | QByteArray state; | 44 | QByteArray state; |
| @@ -55,8 +69,9 @@ struct Values { | |||
| 55 | QString roms_path; | 69 | QString roms_path; |
| 56 | QString symbols_path; | 70 | QString symbols_path; |
| 57 | QString screenshot_path; | 71 | QString screenshot_path; |
| 58 | QString game_directory_path; | 72 | QString game_dir_deprecated; |
| 59 | bool game_directory_deepscan; | 73 | bool game_dir_deprecated_deepscan; |
| 74 | QVector<UISettings::GameDir> game_dirs; | ||
| 60 | QStringList recent_files; | 75 | QStringList recent_files; |
| 61 | 76 | ||
| 62 | QString theme; | 77 | QString theme; |
| @@ -84,3 +99,5 @@ struct Values { | |||
| 84 | 99 | ||
| 85 | extern Values values; | 100 | extern Values values; |
| 86 | } // namespace UISettings | 101 | } // namespace UISettings |
| 102 | |||
| 103 | Q_DECLARE_METATYPE(UISettings::GameDir*); | ||