diff options
Diffstat (limited to 'src')
79 files changed, 2704 insertions, 1221 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/loader/nro.cpp b/src/core/loader/nro.cpp index e92e2e06e..3a5361fdd 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp | |||
| @@ -258,6 +258,15 @@ ResultStatus AppLoader_NRO::ReadTitle(std::string& title) { | |||
| 258 | return ResultStatus::Success; | 258 | return ResultStatus::Success; |
| 259 | } | 259 | } |
| 260 | 260 | ||
| 261 | ResultStatus AppLoader_NRO::ReadControlData(FileSys::NACP& control) { | ||
| 262 | if (nacp == nullptr) { | ||
| 263 | return ResultStatus::ErrorNoControl; | ||
| 264 | } | ||
| 265 | |||
| 266 | control = *nacp; | ||
| 267 | return ResultStatus::Success; | ||
| 268 | } | ||
| 269 | |||
| 261 | bool AppLoader_NRO::IsRomFSUpdatable() const { | 270 | bool AppLoader_NRO::IsRomFSUpdatable() const { |
| 262 | return false; | 271 | return false; |
| 263 | } | 272 | } |
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h index 1ffdae805..71811bc29 100644 --- a/src/core/loader/nro.h +++ b/src/core/loader/nro.h | |||
| @@ -43,6 +43,7 @@ public: | |||
| 43 | ResultStatus ReadProgramId(u64& out_program_id) override; | 43 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 44 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | 44 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |
| 45 | ResultStatus ReadTitle(std::string& title) override; | 45 | ResultStatus ReadTitle(std::string& title) override; |
| 46 | ResultStatus ReadControlData(FileSys::NACP& control) override; | ||
| 46 | bool IsRomFSUpdatable() const override; | 47 | bool IsRomFSUpdatable() const override; |
| 47 | 48 | ||
| 48 | private: | 49 | private: |
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index 5d4c3e6ea..cfe0771e2 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp | |||
| @@ -5,8 +5,8 @@ | |||
| 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/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp index 08586d33c..63d449135 100644 --- a/src/video_core/engines/kepler_compute.cpp +++ b/src/video_core/engines/kepler_compute.cpp | |||
| @@ -2,6 +2,7 @@ | |||
| 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 <bitset> | ||
| 5 | #include "common/assert.h" | 6 | #include "common/assert.h" |
| 6 | #include "common/logging/log.h" | 7 | #include "common/logging/log.h" |
| 7 | #include "core/core.h" | 8 | #include "core/core.h" |
| @@ -49,6 +50,33 @@ void KeplerCompute::CallMethod(const GPU::MethodCall& method_call) { | |||
| 49 | } | 50 | } |
| 50 | } | 51 | } |
| 51 | 52 | ||
| 53 | Tegra::Texture::FullTextureInfo KeplerCompute::GetTexture(std::size_t offset) const { | ||
| 54 | const std::bitset<8> cbuf_mask = launch_description.const_buffer_enable_mask.Value(); | ||
| 55 | ASSERT(cbuf_mask[regs.tex_cb_index]); | ||
| 56 | |||
| 57 | const auto& texinfo = launch_description.const_buffer_config[regs.tex_cb_index]; | ||
| 58 | ASSERT(texinfo.Address() != 0); | ||
| 59 | |||
| 60 | const GPUVAddr address = texinfo.Address() + offset * sizeof(Texture::TextureHandle); | ||
| 61 | ASSERT(address < texinfo.Address() + texinfo.size); | ||
| 62 | |||
| 63 | const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(address)}; | ||
| 64 | return GetTextureInfo(tex_handle, offset); | ||
| 65 | } | ||
| 66 | |||
| 67 | Texture::FullTextureInfo KeplerCompute::GetTextureInfo(const Texture::TextureHandle tex_handle, | ||
| 68 | std::size_t offset) const { | ||
| 69 | return Texture::FullTextureInfo{static_cast<u32>(offset), GetTICEntry(tex_handle.tic_id), | ||
| 70 | GetTSCEntry(tex_handle.tsc_id)}; | ||
| 71 | } | ||
| 72 | |||
| 73 | u32 KeplerCompute::AccessConstBuffer32(u64 const_buffer, u64 offset) const { | ||
| 74 | const auto& buffer = launch_description.const_buffer_config[const_buffer]; | ||
| 75 | u32 result; | ||
| 76 | std::memcpy(&result, memory_manager.GetPointer(buffer.Address() + offset), sizeof(u32)); | ||
| 77 | return result; | ||
| 78 | } | ||
| 79 | |||
| 52 | void KeplerCompute::ProcessLaunch() { | 80 | void KeplerCompute::ProcessLaunch() { |
| 53 | const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address(); | 81 | const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address(); |
| 54 | memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description, | 82 | memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description, |
| @@ -60,4 +88,29 @@ void KeplerCompute::ProcessLaunch() { | |||
| 60 | rasterizer.DispatchCompute(code_addr); | 88 | rasterizer.DispatchCompute(code_addr); |
| 61 | } | 89 | } |
| 62 | 90 | ||
| 91 | Texture::TICEntry KeplerCompute::GetTICEntry(u32 tic_index) const { | ||
| 92 | const GPUVAddr tic_address_gpu{regs.tic.Address() + tic_index * sizeof(Texture::TICEntry)}; | ||
| 93 | |||
| 94 | Texture::TICEntry tic_entry; | ||
| 95 | memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry)); | ||
| 96 | |||
| 97 | const auto r_type{tic_entry.r_type.Value()}; | ||
| 98 | const auto g_type{tic_entry.g_type.Value()}; | ||
| 99 | const auto b_type{tic_entry.b_type.Value()}; | ||
| 100 | const auto a_type{tic_entry.a_type.Value()}; | ||
| 101 | |||
| 102 | // TODO(Subv): Different data types for separate components are not supported | ||
| 103 | DEBUG_ASSERT(r_type == g_type && r_type == b_type && r_type == a_type); | ||
| 104 | |||
| 105 | return tic_entry; | ||
| 106 | } | ||
| 107 | |||
| 108 | Texture::TSCEntry KeplerCompute::GetTSCEntry(u32 tsc_index) const { | ||
| 109 | const GPUVAddr tsc_address_gpu{regs.tsc.Address() + tsc_index * sizeof(Texture::TSCEntry)}; | ||
| 110 | |||
| 111 | Texture::TSCEntry tsc_entry; | ||
| 112 | memory_manager.ReadBlockUnsafe(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry)); | ||
| 113 | return tsc_entry; | ||
| 114 | } | ||
| 115 | |||
| 63 | } // namespace Tegra::Engines | 116 | } // namespace Tegra::Engines |
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h index 6a3309a2c..90cf650d2 100644 --- a/src/video_core/engines/kepler_compute.h +++ b/src/video_core/engines/kepler_compute.h | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | #include "common/common_types.h" | 12 | #include "common/common_types.h" |
| 13 | #include "video_core/engines/engine_upload.h" | 13 | #include "video_core/engines/engine_upload.h" |
| 14 | #include "video_core/gpu.h" | 14 | #include "video_core/gpu.h" |
| 15 | #include "video_core/textures/texture.h" | ||
| 15 | 16 | ||
| 16 | namespace Core { | 17 | namespace Core { |
| 17 | class System; | 18 | class System; |
| @@ -111,7 +112,7 @@ public: | |||
| 111 | 112 | ||
| 112 | INSERT_PADDING_WORDS(0x3FE); | 113 | INSERT_PADDING_WORDS(0x3FE); |
| 113 | 114 | ||
| 114 | u32 texture_const_buffer_index; | 115 | u32 tex_cb_index; |
| 115 | 116 | ||
| 116 | INSERT_PADDING_WORDS(0x374); | 117 | INSERT_PADDING_WORDS(0x374); |
| 117 | }; | 118 | }; |
| @@ -149,7 +150,7 @@ public: | |||
| 149 | union { | 150 | union { |
| 150 | BitField<0, 8, u32> const_buffer_enable_mask; | 151 | BitField<0, 8, u32> const_buffer_enable_mask; |
| 151 | BitField<29, 2, u32> cache_layout; | 152 | BitField<29, 2, u32> cache_layout; |
| 152 | } memory_config; | 153 | }; |
| 153 | 154 | ||
| 154 | INSERT_PADDING_WORDS(0x8); | 155 | INSERT_PADDING_WORDS(0x8); |
| 155 | 156 | ||
| @@ -194,6 +195,14 @@ public: | |||
| 194 | /// Write the value to the register identified by method. | 195 | /// Write the value to the register identified by method. |
| 195 | void CallMethod(const GPU::MethodCall& method_call); | 196 | void CallMethod(const GPU::MethodCall& method_call); |
| 196 | 197 | ||
| 198 | Tegra::Texture::FullTextureInfo GetTexture(std::size_t offset) const; | ||
| 199 | |||
| 200 | /// Given a Texture Handle, returns the TSC and TIC entries. | ||
| 201 | Texture::FullTextureInfo GetTextureInfo(const Texture::TextureHandle tex_handle, | ||
| 202 | std::size_t offset) const; | ||
| 203 | |||
| 204 | u32 AccessConstBuffer32(u64 const_buffer, u64 offset) const; | ||
| 205 | |||
| 197 | private: | 206 | private: |
| 198 | Core::System& system; | 207 | Core::System& system; |
| 199 | VideoCore::RasterizerInterface& rasterizer; | 208 | VideoCore::RasterizerInterface& rasterizer; |
| @@ -201,6 +210,12 @@ private: | |||
| 201 | Upload::State upload_state; | 210 | Upload::State upload_state; |
| 202 | 211 | ||
| 203 | void ProcessLaunch(); | 212 | void ProcessLaunch(); |
| 213 | |||
| 214 | /// Retrieves information about a specific TIC entry from the TIC buffer. | ||
| 215 | Texture::TICEntry GetTICEntry(u32 tic_index) const; | ||
| 216 | |||
| 217 | /// Retrieves information about a specific TSC entry from the TSC buffer. | ||
| 218 | Texture::TSCEntry GetTSCEntry(u32 tsc_index) const; | ||
| 204 | }; | 219 | }; |
| 205 | 220 | ||
| 206 | #define ASSERT_REG_POSITION(field_name, position) \ | 221 | #define ASSERT_REG_POSITION(field_name, position) \ |
| @@ -218,12 +233,12 @@ ASSERT_REG_POSITION(launch, 0xAF); | |||
| 218 | ASSERT_REG_POSITION(tsc, 0x557); | 233 | ASSERT_REG_POSITION(tsc, 0x557); |
| 219 | ASSERT_REG_POSITION(tic, 0x55D); | 234 | ASSERT_REG_POSITION(tic, 0x55D); |
| 220 | ASSERT_REG_POSITION(code_loc, 0x582); | 235 | ASSERT_REG_POSITION(code_loc, 0x582); |
| 221 | ASSERT_REG_POSITION(texture_const_buffer_index, 0x982); | 236 | ASSERT_REG_POSITION(tex_cb_index, 0x982); |
| 222 | ASSERT_LAUNCH_PARAM_POSITION(program_start, 0x8); | 237 | ASSERT_LAUNCH_PARAM_POSITION(program_start, 0x8); |
| 223 | ASSERT_LAUNCH_PARAM_POSITION(grid_dim_x, 0xC); | 238 | ASSERT_LAUNCH_PARAM_POSITION(grid_dim_x, 0xC); |
| 224 | ASSERT_LAUNCH_PARAM_POSITION(shared_alloc, 0x11); | 239 | ASSERT_LAUNCH_PARAM_POSITION(shared_alloc, 0x11); |
| 225 | ASSERT_LAUNCH_PARAM_POSITION(block_dim_x, 0x12); | 240 | ASSERT_LAUNCH_PARAM_POSITION(block_dim_x, 0x12); |
| 226 | ASSERT_LAUNCH_PARAM_POSITION(memory_config, 0x14); | 241 | ASSERT_LAUNCH_PARAM_POSITION(const_buffer_enable_mask, 0x14); |
| 227 | ASSERT_LAUNCH_PARAM_POSITION(const_buffer_config, 0x1D); | 242 | ASSERT_LAUNCH_PARAM_POSITION(const_buffer_config, 0x1D); |
| 228 | 243 | ||
| 229 | #undef ASSERT_REG_POSITION | 244 | #undef ASSERT_REG_POSITION |
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index f5158d219..c8c92757a 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp | |||
| @@ -244,7 +244,7 @@ void Maxwell3D::InitDirtySettings() { | |||
| 244 | dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_clamp)] = polygon_offset_dirty_reg; | 244 | dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_clamp)] = polygon_offset_dirty_reg; |
| 245 | } | 245 | } |
| 246 | 246 | ||
| 247 | void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { | 247 | void Maxwell3D::CallMacroMethod(u32 method, std::size_t num_parameters, const u32* parameters) { |
| 248 | // Reset the current macro. | 248 | // Reset the current macro. |
| 249 | executing_macro = 0; | 249 | executing_macro = 0; |
| 250 | 250 | ||
| @@ -252,7 +252,7 @@ void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { | |||
| 252 | const u32 entry = ((method - MacroRegistersStart) >> 1) % macro_positions.size(); | 252 | const u32 entry = ((method - MacroRegistersStart) >> 1) % macro_positions.size(); |
| 253 | 253 | ||
| 254 | // Execute the current macro. | 254 | // Execute the current macro. |
| 255 | macro_interpreter.Execute(macro_positions[entry], std::move(parameters)); | 255 | macro_interpreter.Execute(macro_positions[entry], num_parameters, parameters); |
| 256 | } | 256 | } |
| 257 | 257 | ||
| 258 | void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { | 258 | void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { |
| @@ -289,7 +289,8 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { | |||
| 289 | 289 | ||
| 290 | // Call the macro when there are no more parameters in the command buffer | 290 | // Call the macro when there are no more parameters in the command buffer |
| 291 | if (method_call.IsLastCall()) { | 291 | if (method_call.IsLastCall()) { |
| 292 | CallMacroMethod(executing_macro, std::move(macro_params)); | 292 | CallMacroMethod(executing_macro, macro_params.size(), macro_params.data()); |
| 293 | macro_params.clear(); | ||
| 293 | } | 294 | } |
| 294 | return; | 295 | return; |
| 295 | } | 296 | } |
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 0184342a0..f67a5389f 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h | |||
| @@ -62,6 +62,7 @@ public: | |||
| 62 | static constexpr std::size_t NumVertexAttributes = 32; | 62 | static constexpr std::size_t NumVertexAttributes = 32; |
| 63 | static constexpr std::size_t NumVaryings = 31; | 63 | static constexpr std::size_t NumVaryings = 31; |
| 64 | static constexpr std::size_t NumTextureSamplers = 32; | 64 | static constexpr std::size_t NumTextureSamplers = 32; |
| 65 | static constexpr std::size_t NumImages = 8; // TODO(Rodrigo): Investigate this number | ||
| 65 | static constexpr std::size_t NumClipDistances = 8; | 66 | static constexpr std::size_t NumClipDistances = 8; |
| 66 | static constexpr std::size_t MaxShaderProgram = 6; | 67 | static constexpr std::size_t MaxShaderProgram = 6; |
| 67 | static constexpr std::size_t MaxShaderStage = 5; | 68 | static constexpr std::size_t MaxShaderStage = 5; |
| @@ -1307,9 +1308,10 @@ private: | |||
| 1307 | /** | 1308 | /** |
| 1308 | * Call a macro on this engine. | 1309 | * Call a macro on this engine. |
| 1309 | * @param method Method to call | 1310 | * @param method Method to call |
| 1311 | * @param num_parameters Number of arguments | ||
| 1310 | * @param parameters Arguments to the method call | 1312 | * @param parameters Arguments to the method call |
| 1311 | */ | 1313 | */ |
| 1312 | void CallMacroMethod(u32 method, std::vector<u32> parameters); | 1314 | void CallMacroMethod(u32 method, std::size_t num_parameters, const u32* parameters); |
| 1313 | 1315 | ||
| 1314 | /// Handles writes to the macro uploading register. | 1316 | /// Handles writes to the macro uploading register. |
| 1315 | void ProcessMacroUpload(u32 data); | 1317 | void ProcessMacroUpload(u32 data); |
diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp index 9f59a2dc1..4e1cb98db 100644 --- a/src/video_core/macro_interpreter.cpp +++ b/src/video_core/macro_interpreter.cpp | |||
| @@ -14,11 +14,18 @@ namespace Tegra { | |||
| 14 | 14 | ||
| 15 | MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {} | 15 | MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {} |
| 16 | 16 | ||
| 17 | void MacroInterpreter::Execute(u32 offset, std::vector<u32> parameters) { | 17 | void MacroInterpreter::Execute(u32 offset, std::size_t num_parameters, const u32* parameters) { |
| 18 | MICROPROFILE_SCOPE(MacroInterp); | 18 | MICROPROFILE_SCOPE(MacroInterp); |
| 19 | Reset(); | 19 | Reset(); |
| 20 | |||
| 20 | registers[1] = parameters[0]; | 21 | registers[1] = parameters[0]; |
| 21 | this->parameters = std::move(parameters); | 22 | |
| 23 | if (num_parameters > parameters_capacity) { | ||
| 24 | parameters_capacity = num_parameters; | ||
| 25 | this->parameters = std::make_unique<u32[]>(num_parameters); | ||
| 26 | } | ||
| 27 | std::memcpy(this->parameters.get(), parameters, num_parameters * sizeof(u32)); | ||
| 28 | this->num_parameters = num_parameters; | ||
| 22 | 29 | ||
| 23 | // Execute the code until we hit an exit condition. | 30 | // Execute the code until we hit an exit condition. |
| 24 | bool keep_executing = true; | 31 | bool keep_executing = true; |
| @@ -27,7 +34,7 @@ void MacroInterpreter::Execute(u32 offset, std::vector<u32> parameters) { | |||
| 27 | } | 34 | } |
| 28 | 35 | ||
| 29 | // Assert the the macro used all the input parameters | 36 | // Assert the the macro used all the input parameters |
| 30 | ASSERT(next_parameter_index == this->parameters.size()); | 37 | ASSERT(next_parameter_index == num_parameters); |
| 31 | } | 38 | } |
| 32 | 39 | ||
| 33 | void MacroInterpreter::Reset() { | 40 | void MacroInterpreter::Reset() { |
| @@ -35,7 +42,7 @@ void MacroInterpreter::Reset() { | |||
| 35 | pc = 0; | 42 | pc = 0; |
| 36 | delayed_pc = {}; | 43 | delayed_pc = {}; |
| 37 | method_address.raw = 0; | 44 | method_address.raw = 0; |
| 38 | parameters.clear(); | 45 | num_parameters = 0; |
| 39 | // The next parameter index starts at 1, because $r1 already has the value of the first | 46 | // The next parameter index starts at 1, because $r1 already has the value of the first |
| 40 | // parameter. | 47 | // parameter. |
| 41 | next_parameter_index = 1; | 48 | next_parameter_index = 1; |
| @@ -229,7 +236,8 @@ void MacroInterpreter::ProcessResult(ResultOperation operation, u32 reg, u32 res | |||
| 229 | } | 236 | } |
| 230 | 237 | ||
| 231 | u32 MacroInterpreter::FetchParameter() { | 238 | u32 MacroInterpreter::FetchParameter() { |
| 232 | return parameters.at(next_parameter_index++); | 239 | ASSERT(next_parameter_index < num_parameters); |
| 240 | return parameters[next_parameter_index++]; | ||
| 233 | } | 241 | } |
| 234 | 242 | ||
| 235 | u32 MacroInterpreter::GetRegister(u32 register_id) const { | 243 | u32 MacroInterpreter::GetRegister(u32 register_id) const { |
diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h index cde360288..76b6a895b 100644 --- a/src/video_core/macro_interpreter.h +++ b/src/video_core/macro_interpreter.h | |||
| @@ -25,7 +25,7 @@ public: | |||
| 25 | * @param offset Offset to start execution at. | 25 | * @param offset Offset to start execution at. |
| 26 | * @param parameters The parameters of the macro. | 26 | * @param parameters The parameters of the macro. |
| 27 | */ | 27 | */ |
| 28 | void Execute(u32 offset, std::vector<u32> parameters); | 28 | void Execute(u32 offset, std::size_t num_parameters, const u32* parameters); |
| 29 | 29 | ||
| 30 | private: | 30 | private: |
| 31 | enum class Operation : u32 { | 31 | enum class Operation : u32 { |
| @@ -162,10 +162,12 @@ private: | |||
| 162 | MethodAddress method_address = {}; | 162 | MethodAddress method_address = {}; |
| 163 | 163 | ||
| 164 | /// Input parameters of the current macro. | 164 | /// Input parameters of the current macro. |
| 165 | std::vector<u32> parameters; | 165 | std::unique_ptr<u32[]> parameters; |
| 166 | std::size_t num_parameters = 0; | ||
| 167 | std::size_t parameters_capacity = 0; | ||
| 166 | /// Index of the next parameter that will be fetched by the 'parm' instruction. | 168 | /// Index of the next parameter that will be fetched by the 'parm' instruction. |
| 167 | u32 next_parameter_index = 0; | 169 | u32 next_parameter_index = 0; |
| 168 | 170 | ||
| 169 | bool carry_flag{}; | 171 | bool carry_flag = false; |
| 170 | }; | 172 | }; |
| 171 | } // namespace Tegra | 173 | } // namespace Tegra |
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index 03d434b28..4f59a87b4 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp | |||
| @@ -14,12 +14,22 @@ | |||
| 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..4e266cdad 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp | |||
| @@ -331,7 +331,7 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { | |||
| 331 | const auto stage_enum = static_cast<Maxwell::ShaderStage>(stage); | 331 | const auto stage_enum = static_cast<Maxwell::ShaderStage>(stage); |
| 332 | SetupDrawConstBuffers(stage_enum, shader); | 332 | SetupDrawConstBuffers(stage_enum, shader); |
| 333 | SetupDrawGlobalMemory(stage_enum, shader); | 333 | SetupDrawGlobalMemory(stage_enum, shader); |
| 334 | const auto texture_buffer_usage{SetupTextures(stage_enum, shader, base_bindings)}; | 334 | const auto texture_buffer_usage{SetupDrawTextures(stage_enum, shader, base_bindings)}; |
| 335 | 335 | ||
| 336 | const ProgramVariant variant{base_bindings, primitive_mode, texture_buffer_usage}; | 336 | const ProgramVariant variant{base_bindings, primitive_mode, texture_buffer_usage}; |
| 337 | const auto [program_handle, next_bindings] = shader->GetProgramHandle(variant); | 337 | const auto [program_handle, next_bindings] = shader->GetProgramHandle(variant); |
| @@ -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; |
| @@ -802,7 +801,11 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { | |||
| 802 | } | 801 | } |
| 803 | 802 | ||
| 804 | auto kernel = shader_cache.GetComputeKernel(code_addr); | 803 | auto kernel = shader_cache.GetComputeKernel(code_addr); |
| 805 | const auto [program, next_bindings] = kernel->GetProgramHandle({}); | 804 | ProgramVariant variant; |
| 805 | variant.texture_buffer_usage = SetupComputeTextures(kernel); | ||
| 806 | SetupComputeImages(kernel); | ||
| 807 | |||
| 808 | const auto [program, next_bindings] = kernel->GetProgramHandle(variant); | ||
| 806 | state.draw.shader_program = program; | 809 | state.draw.shader_program = program; |
| 807 | state.draw.program_pipeline = 0; | 810 | state.draw.program_pipeline = 0; |
| 808 | 811 | ||
| @@ -817,13 +820,13 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { | |||
| 817 | SetupComputeConstBuffers(kernel); | 820 | SetupComputeConstBuffers(kernel); |
| 818 | SetupComputeGlobalMemory(kernel); | 821 | SetupComputeGlobalMemory(kernel); |
| 819 | 822 | ||
| 820 | // TODO(Rodrigo): Bind images and samplers | ||
| 821 | |||
| 822 | buffer_cache.Unmap(); | 823 | buffer_cache.Unmap(); |
| 823 | 824 | ||
| 824 | bind_ubo_pushbuffer.Bind(); | 825 | bind_ubo_pushbuffer.Bind(); |
| 825 | bind_ssbo_pushbuffer.Bind(); | 826 | bind_ssbo_pushbuffer.Bind(); |
| 826 | 827 | ||
| 828 | state.ApplyTextures(); | ||
| 829 | state.ApplyImages(); | ||
| 827 | state.ApplyShaderProgram(); | 830 | state.ApplyShaderProgram(); |
| 828 | state.ApplyProgramPipeline(); | 831 | state.ApplyProgramPipeline(); |
| 829 | 832 | ||
| @@ -923,7 +926,7 @@ void RasterizerOpenGL::SetupComputeConstBuffers(const Shader& kernel) { | |||
| 923 | const auto& launch_desc = system.GPU().KeplerCompute().launch_description; | 926 | const auto& launch_desc = system.GPU().KeplerCompute().launch_description; |
| 924 | for (const auto& entry : kernel->GetShaderEntries().const_buffers) { | 927 | for (const auto& entry : kernel->GetShaderEntries().const_buffers) { |
| 925 | const auto& config = launch_desc.const_buffer_config[entry.GetIndex()]; | 928 | const auto& config = launch_desc.const_buffer_config[entry.GetIndex()]; |
| 926 | const std::bitset<8> mask = launch_desc.memory_config.const_buffer_enable_mask.Value(); | 929 | const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value(); |
| 927 | Tegra::Engines::ConstBufferInfo buffer; | 930 | Tegra::Engines::ConstBufferInfo buffer; |
| 928 | buffer.address = config.Address(); | 931 | buffer.address = config.Address(); |
| 929 | buffer.size = config.size; | 932 | buffer.size = config.size; |
| @@ -982,53 +985,125 @@ void RasterizerOpenGL::SetupGlobalMemory(const GLShader::GlobalMemoryEntry& entr | |||
| 982 | bind_ssbo_pushbuffer.Push(ssbo, buffer_offset, static_cast<GLsizeiptr>(size)); | 985 | bind_ssbo_pushbuffer.Push(ssbo, buffer_offset, static_cast<GLsizeiptr>(size)); |
| 983 | } | 986 | } |
| 984 | 987 | ||
| 985 | TextureBufferUsage RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader, | 988 | TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stage, |
| 986 | BaseBindings base_bindings) { | 989 | const Shader& shader, |
| 990 | BaseBindings base_bindings) { | ||
| 987 | MICROPROFILE_SCOPE(OpenGL_Texture); | 991 | MICROPROFILE_SCOPE(OpenGL_Texture); |
| 988 | const auto& gpu = system.GPU(); | 992 | const auto& gpu = system.GPU(); |
| 989 | const auto& maxwell3d = gpu.Maxwell3D(); | 993 | const auto& maxwell3d = gpu.Maxwell3D(); |
| 990 | const auto& entries = shader->GetShaderEntries().samplers; | 994 | const auto& entries = shader->GetShaderEntries().samplers; |
| 991 | 995 | ||
| 992 | ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.texture_units), | 996 | ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.textures), |
| 993 | "Exceeded the number of active textures."); | 997 | "Exceeded the number of active textures."); |
| 994 | 998 | ||
| 995 | TextureBufferUsage texture_buffer_usage{0}; | 999 | TextureBufferUsage texture_buffer_usage{0}; |
| 996 | 1000 | ||
| 997 | for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { | 1001 | for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { |
| 998 | const auto& entry = entries[bindpoint]; | 1002 | const auto& entry = entries[bindpoint]; |
| 999 | Tegra::Texture::FullTextureInfo texture; | 1003 | const auto texture = [&]() { |
| 1000 | if (entry.IsBindless()) { | 1004 | if (!entry.IsBindless()) { |
| 1005 | return maxwell3d.GetStageTexture(stage, entry.GetOffset()); | ||
| 1006 | } | ||
| 1001 | const auto cbuf = entry.GetBindlessCBuf(); | 1007 | const auto cbuf = entry.GetBindlessCBuf(); |
| 1002 | Tegra::Texture::TextureHandle tex_handle; | 1008 | Tegra::Texture::TextureHandle tex_handle; |
| 1003 | tex_handle.raw = maxwell3d.AccessConstBuffer32(stage, cbuf.first, cbuf.second); | 1009 | tex_handle.raw = maxwell3d.AccessConstBuffer32(stage, cbuf.first, cbuf.second); |
| 1004 | texture = maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset()); | 1010 | return maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset()); |
| 1005 | } else { | 1011 | }(); |
| 1006 | texture = maxwell3d.GetStageTexture(stage, entry.GetOffset()); | 1012 | |
| 1013 | if (SetupTexture(base_bindings.sampler + bindpoint, texture, entry)) { | ||
| 1014 | texture_buffer_usage.set(bindpoint); | ||
| 1007 | } | 1015 | } |
| 1008 | const u32 current_bindpoint = base_bindings.sampler + bindpoint; | 1016 | } |
| 1009 | 1017 | ||
| 1010 | auto& unit{state.texture_units[current_bindpoint]}; | 1018 | return texture_buffer_usage; |
| 1011 | unit.sampler = sampler_cache.GetSampler(texture.tsc); | 1019 | } |
| 1012 | 1020 | ||
| 1013 | if (const auto view{texture_cache.GetTextureSurface(texture, entry)}; view) { | 1021 | TextureBufferUsage RasterizerOpenGL::SetupComputeTextures(const Shader& kernel) { |
| 1014 | if (view->GetSurfaceParams().IsBuffer()) { | 1022 | MICROPROFILE_SCOPE(OpenGL_Texture); |
| 1015 | // Record that this texture is a texture buffer. | 1023 | const auto& compute = system.GPU().KeplerCompute(); |
| 1016 | texture_buffer_usage.set(bindpoint); | 1024 | const auto& entries = kernel->GetShaderEntries().samplers; |
| 1017 | } else { | 1025 | |
| 1018 | // Apply swizzle to textures that are not buffers. | 1026 | ASSERT_MSG(entries.size() <= std::size(state.textures), |
| 1019 | view->ApplySwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source, | 1027 | "Exceeded the number of active textures."); |
| 1020 | texture.tic.w_source); | 1028 | |
| 1029 | TextureBufferUsage texture_buffer_usage{0}; | ||
| 1030 | |||
| 1031 | for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { | ||
| 1032 | const auto& entry = entries[bindpoint]; | ||
| 1033 | const auto texture = [&]() { | ||
| 1034 | if (!entry.IsBindless()) { | ||
| 1035 | return compute.GetTexture(entry.GetOffset()); | ||
| 1021 | } | 1036 | } |
| 1022 | state.texture_units[current_bindpoint].texture = view->GetTexture(); | 1037 | const auto cbuf = entry.GetBindlessCBuf(); |
| 1023 | } else { | 1038 | Tegra::Texture::TextureHandle tex_handle; |
| 1024 | // Can occur when texture addr is null or its memory is unmapped/invalid | 1039 | tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second); |
| 1025 | unit.texture = 0; | 1040 | return compute.GetTextureInfo(tex_handle, entry.GetOffset()); |
| 1041 | }(); | ||
| 1042 | |||
| 1043 | if (SetupTexture(bindpoint, texture, entry)) { | ||
| 1044 | texture_buffer_usage.set(bindpoint); | ||
| 1026 | } | 1045 | } |
| 1027 | } | 1046 | } |
| 1028 | 1047 | ||
| 1029 | return texture_buffer_usage; | 1048 | return texture_buffer_usage; |
| 1030 | } | 1049 | } |
| 1031 | 1050 | ||
| 1051 | bool RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, | ||
| 1052 | const GLShader::SamplerEntry& entry) { | ||
| 1053 | state.samplers[binding] = sampler_cache.GetSampler(texture.tsc); | ||
| 1054 | |||
| 1055 | const auto view = texture_cache.GetTextureSurface(texture.tic, entry); | ||
| 1056 | if (!view) { | ||
| 1057 | // Can occur when texture addr is null or its memory is unmapped/invalid | ||
| 1058 | state.textures[binding] = 0; | ||
| 1059 | return false; | ||
| 1060 | } | ||
| 1061 | state.textures[binding] = view->GetTexture(); | ||
| 1062 | |||
| 1063 | if (view->GetSurfaceParams().IsBuffer()) { | ||
| 1064 | return true; | ||
| 1065 | } | ||
| 1066 | |||
| 1067 | // Apply swizzle to textures that are not buffers. | ||
| 1068 | view->ApplySwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source, | ||
| 1069 | texture.tic.w_source); | ||
| 1070 | return false; | ||
| 1071 | } | ||
| 1072 | |||
| 1073 | void RasterizerOpenGL::SetupComputeImages(const Shader& shader) { | ||
| 1074 | const auto& compute = system.GPU().KeplerCompute(); | ||
| 1075 | const auto& entries = shader->GetShaderEntries().images; | ||
| 1076 | for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { | ||
| 1077 | const auto& entry = entries[bindpoint]; | ||
| 1078 | const auto tic = [&]() { | ||
| 1079 | if (!entry.IsBindless()) { | ||
| 1080 | return compute.GetTexture(entry.GetOffset()).tic; | ||
| 1081 | } | ||
| 1082 | const auto cbuf = entry.GetBindlessCBuf(); | ||
| 1083 | Tegra::Texture::TextureHandle tex_handle; | ||
| 1084 | tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second); | ||
| 1085 | return compute.GetTextureInfo(tex_handle, entry.GetOffset()).tic; | ||
| 1086 | }(); | ||
| 1087 | SetupImage(bindpoint, tic, entry); | ||
| 1088 | } | ||
| 1089 | } | ||
| 1090 | |||
| 1091 | void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, | ||
| 1092 | const GLShader::ImageEntry& entry) { | ||
| 1093 | const auto view = texture_cache.GetImageSurface(tic, entry); | ||
| 1094 | if (!view) { | ||
| 1095 | state.images[binding] = 0; | ||
| 1096 | return; | ||
| 1097 | } | ||
| 1098 | if (!tic.IsBuffer()) { | ||
| 1099 | view->ApplySwizzle(tic.x_source, tic.y_source, tic.z_source, tic.w_source); | ||
| 1100 | } | ||
| 1101 | if (entry.IsWritten()) { | ||
| 1102 | view->MarkAsModified(texture_cache.Tick()); | ||
| 1103 | } | ||
| 1104 | state.images[binding] = view->GetTexture(); | ||
| 1105 | } | ||
| 1106 | |||
| 1032 | void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { | 1107 | void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { |
| 1033 | const auto& regs = system.GPU().Maxwell3D().regs; | 1108 | const auto& regs = system.GPU().Maxwell3D().regs; |
| 1034 | const bool geometry_shaders_enabled = | 1109 | const bool geometry_shaders_enabled = |
| @@ -1119,9 +1194,12 @@ void RasterizerOpenGL::SyncStencilTestState() { | |||
| 1119 | if (!maxwell3d.dirty.stencil_test) { | 1194 | if (!maxwell3d.dirty.stencil_test) { |
| 1120 | return; | 1195 | return; |
| 1121 | } | 1196 | } |
| 1122 | const auto& regs = maxwell3d.regs; | 1197 | maxwell3d.dirty.stencil_test = false; |
| 1123 | 1198 | ||
| 1199 | const auto& regs = maxwell3d.regs; | ||
| 1124 | state.stencil.test_enabled = regs.stencil_enable != 0; | 1200 | state.stencil.test_enabled = regs.stencil_enable != 0; |
| 1201 | state.MarkDirtyStencilState(); | ||
| 1202 | |||
| 1125 | if (!regs.stencil_enable) { | 1203 | if (!regs.stencil_enable) { |
| 1126 | return; | 1204 | return; |
| 1127 | } | 1205 | } |
| @@ -1150,8 +1228,6 @@ void RasterizerOpenGL::SyncStencilTestState() { | |||
| 1150 | state.stencil.back.action_depth_fail = GL_KEEP; | 1228 | state.stencil.back.action_depth_fail = GL_KEEP; |
| 1151 | state.stencil.back.action_depth_pass = GL_KEEP; | 1229 | state.stencil.back.action_depth_pass = GL_KEEP; |
| 1152 | } | 1230 | } |
| 1153 | state.MarkDirtyStencilState(); | ||
| 1154 | maxwell3d.dirty.stencil_test = false; | ||
| 1155 | } | 1231 | } |
| 1156 | 1232 | ||
| 1157 | void RasterizerOpenGL::SyncColorMask() { | 1233 | void RasterizerOpenGL::SyncColorMask() { |
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 9d20a4fbf..eada752e0 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h | |||
| @@ -32,6 +32,7 @@ | |||
| 32 | #include "video_core/renderer_opengl/gl_state.h" | 32 | #include "video_core/renderer_opengl/gl_state.h" |
| 33 | #include "video_core/renderer_opengl/gl_texture_cache.h" | 33 | #include "video_core/renderer_opengl/gl_texture_cache.h" |
| 34 | #include "video_core/renderer_opengl/utils.h" | 34 | #include "video_core/renderer_opengl/utils.h" |
| 35 | #include "video_core/textures/texture.h" | ||
| 35 | 36 | ||
| 36 | namespace Core { | 37 | namespace Core { |
| 37 | class System; | 38 | class System; |
| @@ -137,8 +138,22 @@ private: | |||
| 137 | 138 | ||
| 138 | /// Configures the current textures to use for the draw command. Returns shaders texture buffer | 139 | /// Configures the current textures to use for the draw command. Returns shaders texture buffer |
| 139 | /// usage. | 140 | /// usage. |
| 140 | TextureBufferUsage SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, | 141 | TextureBufferUsage SetupDrawTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, |
| 141 | const Shader& shader, BaseBindings base_bindings); | 142 | const Shader& shader, BaseBindings base_bindings); |
| 143 | |||
| 144 | /// Configures the textures used in a compute shader. Returns texture buffer usage. | ||
| 145 | TextureBufferUsage SetupComputeTextures(const Shader& kernel); | ||
| 146 | |||
| 147 | /// Configures a texture. Returns true when the texture is a texture buffer. | ||
| 148 | bool SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, | ||
| 149 | const GLShader::SamplerEntry& entry); | ||
| 150 | |||
| 151 | /// Configures images in a compute shader. | ||
| 152 | void SetupComputeImages(const Shader& shader); | ||
| 153 | |||
| 154 | /// Configures an image. | ||
| 155 | void SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, | ||
| 156 | const GLShader::ImageEntry& entry); | ||
| 142 | 157 | ||
| 143 | /// Syncs the viewport and depth range to match the guest state | 158 | /// Syncs the viewport and depth range to match the guest state |
| 144 | void SyncViewport(OpenGLState& current_state); | 159 | void SyncViewport(OpenGLState& current_state); |
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index cf6a5cddf..909ccb82c 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp | |||
| @@ -348,23 +348,16 @@ Shader CachedShader::CreateKernelFromCache(const ShaderParameters& params, | |||
| 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..6edb2ca38 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); |
| @@ -241,11 +389,10 @@ public: | |||
| 241 | for (const auto& sampler : ir.GetSamplers()) { | 389 | for (const auto& sampler : ir.GetSamplers()) { |
| 242 | entries.samplers.emplace_back(sampler); | 390 | entries.samplers.emplace_back(sampler); |
| 243 | } | 391 | } |
| 244 | for (const auto& image : ir.GetImages()) { | 392 | for (const auto& [offset, image] : ir.GetImages()) { |
| 245 | entries.images.emplace_back(image); | 393 | entries.images.emplace_back(image); |
| 246 | } | 394 | } |
| 247 | for (const auto& gmem_pair : ir.GetGlobalMemory()) { | 395 | for (const auto& [base, usage] : ir.GetGlobalMemory()) { |
| 248 | const auto& [base, usage] = gmem_pair; | ||
| 249 | entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset, | 396 | entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset, |
| 250 | usage.is_read, usage.is_written); | 397 | usage.is_read, usage.is_written); |
| 251 | } | 398 | } |
| @@ -322,7 +469,7 @@ private: | |||
| 322 | void DeclareRegisters() { | 469 | void DeclareRegisters() { |
| 323 | const auto& registers = ir.GetRegisters(); | 470 | const auto& registers = ir.GetRegisters(); |
| 324 | for (const u32 gpr : registers) { | 471 | for (const u32 gpr : registers) { |
| 325 | code.AddLine("float {} = 0;", GetRegister(gpr)); | 472 | code.AddLine("float {} = 0.0f;", GetRegister(gpr)); |
| 326 | } | 473 | } |
| 327 | if (!registers.empty()) { | 474 | if (!registers.empty()) { |
| 328 | code.AddNewLine(); | 475 | code.AddNewLine(); |
| @@ -348,7 +495,7 @@ private: | |||
| 348 | return; | 495 | return; |
| 349 | } | 496 | } |
| 350 | const auto element_count = Common::AlignUp(local_memory_size, 4) / 4; | 497 | const auto element_count = Common::AlignUp(local_memory_size, 4) / 4; |
| 351 | code.AddLine("float {}[{}];", GetLocalMemory(), element_count); | 498 | code.AddLine("uint {}[{}];", GetLocalMemory(), element_count); |
| 352 | code.AddNewLine(); | 499 | code.AddNewLine(); |
| 353 | } | 500 | } |
| 354 | 501 | ||
| @@ -371,8 +518,6 @@ private: | |||
| 371 | return "noperspective "; | 518 | return "noperspective "; |
| 372 | default: | 519 | default: |
| 373 | case AttributeUse::Unused: | 520 | case AttributeUse::Unused: |
| 374 | UNREACHABLE_MSG("Unused attribute being fetched"); | ||
| 375 | return {}; | ||
| 376 | UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<u32>(attribute)); | 521 | UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<u32>(attribute)); |
| 377 | return {}; | 522 | return {}; |
| 378 | } | 523 | } |
| @@ -449,7 +594,7 @@ private: | |||
| 449 | const auto [index, size] = entry; | 594 | const auto [index, size] = entry; |
| 450 | code.AddLine("layout (std140, binding = CBUF_BINDING_{}) uniform {} {{", index, | 595 | code.AddLine("layout (std140, binding = CBUF_BINDING_{}) uniform {} {{", index, |
| 451 | GetConstBufferBlock(index)); | 596 | GetConstBufferBlock(index)); |
| 452 | code.AddLine(" vec4 {}[MAX_CONSTBUFFER_ELEMENTS];", GetConstBuffer(index)); | 597 | code.AddLine(" uvec4 {}[{}];", GetConstBuffer(index), MAX_CONSTBUFFER_ELEMENTS); |
| 453 | code.AddLine("}};"); | 598 | code.AddLine("}};"); |
| 454 | code.AddNewLine(); | 599 | code.AddNewLine(); |
| 455 | } | 600 | } |
| @@ -470,7 +615,7 @@ private: | |||
| 470 | 615 | ||
| 471 | code.AddLine("layout (std430, binding = GMEM_BINDING_{}_{}) {} buffer {} {{", | 616 | code.AddLine("layout (std430, binding = GMEM_BINDING_{}_{}) {} buffer {} {{", |
| 472 | base.cbuf_index, base.cbuf_offset, qualifier, GetGlobalMemoryBlock(base)); | 617 | base.cbuf_index, base.cbuf_offset, qualifier, GetGlobalMemoryBlock(base)); |
| 473 | code.AddLine(" float {}[];", GetGlobalMemory(base)); | 618 | code.AddLine(" uint {}[];", GetGlobalMemory(base)); |
| 474 | code.AddLine("}};"); | 619 | code.AddLine("}};"); |
| 475 | code.AddNewLine(); | 620 | code.AddNewLine(); |
| 476 | } | 621 | } |
| @@ -528,7 +673,7 @@ private: | |||
| 528 | if (!ir.HasPhysicalAttributes()) { | 673 | if (!ir.HasPhysicalAttributes()) { |
| 529 | return; | 674 | return; |
| 530 | } | 675 | } |
| 531 | code.AddLine("float readPhysicalAttribute(uint physical_address) {{"); | 676 | code.AddLine("float ReadPhysicalAttribute(uint physical_address) {{"); |
| 532 | ++code.scope; | 677 | ++code.scope; |
| 533 | code.AddLine("switch (physical_address) {{"); | 678 | code.AddLine("switch (physical_address) {{"); |
| 534 | 679 | ||
| @@ -537,15 +682,16 @@ private: | |||
| 537 | for (u32 index = 0; index < num_attributes; ++index) { | 682 | for (u32 index = 0; index < num_attributes; ++index) { |
| 538 | const auto attribute{ToGenericAttribute(index)}; | 683 | const auto attribute{ToGenericAttribute(index)}; |
| 539 | for (u32 element = 0; element < 4; ++element) { | 684 | for (u32 element = 0; element < 4; ++element) { |
| 540 | constexpr u32 generic_base{0x80}; | 685 | constexpr u32 generic_base = 0x80; |
| 541 | constexpr u32 generic_stride{16}; | 686 | constexpr u32 generic_stride = 16; |
| 542 | constexpr u32 element_stride{4}; | 687 | constexpr u32 element_stride = 4; |
| 543 | const u32 address{generic_base + index * generic_stride + element * element_stride}; | 688 | const u32 address{generic_base + index * generic_stride + element * element_stride}; |
| 544 | 689 | ||
| 545 | const bool declared{stage != ProgramType::Fragment || | 690 | const bool declared = stage != ProgramType::Fragment || |
| 546 | header.ps.GetAttributeUse(index) != AttributeUse::Unused}; | 691 | header.ps.GetAttributeUse(index) != AttributeUse::Unused; |
| 547 | const std::string value{declared ? ReadAttribute(attribute, element) : "0"}; | 692 | const std::string value = |
| 548 | code.AddLine("case 0x{:x}: return {};", address, value); | 693 | declared ? ReadAttribute(attribute, element).AsFloat() : "0.0f"; |
| 694 | code.AddLine("case 0x{:X}U: return {};", address, value); | ||
| 549 | } | 695 | } |
| 550 | } | 696 | } |
| 551 | 697 | ||
| @@ -559,7 +705,7 @@ private: | |||
| 559 | 705 | ||
| 560 | void DeclareImages() { | 706 | void DeclareImages() { |
| 561 | const auto& images{ir.GetImages()}; | 707 | const auto& images{ir.GetImages()}; |
| 562 | for (const auto& image : images) { | 708 | for (const auto& [offset, image] : images) { |
| 563 | const std::string image_type = [&]() { | 709 | const std::string image_type = [&]() { |
| 564 | switch (image.GetType()) { | 710 | switch (image.GetType()) { |
| 565 | case Tegra::Shader::ImageType::Texture1D: | 711 | case Tegra::Shader::ImageType::Texture1D: |
| @@ -579,9 +725,16 @@ private: | |||
| 579 | return "image1D"; | 725 | return "image1D"; |
| 580 | } | 726 | } |
| 581 | }(); | 727 | }(); |
| 582 | code.AddLine("layout (binding = IMAGE_BINDING_{}) coherent volatile writeonly uniform " | 728 | std::string qualifier = "coherent volatile"; |
| 729 | if (image.IsRead() && !image.IsWritten()) { | ||
| 730 | qualifier += " readonly"; | ||
| 731 | } else if (image.IsWritten() && !image.IsRead()) { | ||
| 732 | qualifier += " writeonly"; | ||
| 733 | } | ||
| 734 | |||
| 735 | code.AddLine("layout (binding = IMAGE_BINDING_{}) {} uniform " | ||
| 583 | "{} {};", | 736 | "{} {};", |
| 584 | image.GetIndex(), image_type, GetImage(image)); | 737 | image.GetIndex(), qualifier, image_type, GetImage(image)); |
| 585 | } | 738 | } |
| 586 | if (!images.empty()) { | 739 | if (!images.empty()) { |
| 587 | code.AddNewLine(); | 740 | code.AddNewLine(); |
| @@ -590,13 +743,11 @@ private: | |||
| 590 | 743 | ||
| 591 | void VisitBlock(const NodeBlock& bb) { | 744 | void VisitBlock(const NodeBlock& bb) { |
| 592 | for (const auto& node : bb) { | 745 | for (const auto& node : bb) { |
| 593 | if (const std::string expr = Visit(node); !expr.empty()) { | 746 | Visit(node).CheckVoid(); |
| 594 | code.AddLine(expr); | ||
| 595 | } | ||
| 596 | } | 747 | } |
| 597 | } | 748 | } |
| 598 | 749 | ||
| 599 | std::string Visit(const Node& node) { | 750 | Expression Visit(const Node& node) { |
| 600 | if (const auto operation = std::get_if<OperationNode>(&*node)) { | 751 | if (const auto operation = std::get_if<OperationNode>(&*node)) { |
| 601 | const auto operation_index = static_cast<std::size_t>(operation->GetCode()); | 752 | const auto operation_index = static_cast<std::size_t>(operation->GetCode()); |
| 602 | if (operation_index >= operation_decompilers.size()) { | 753 | if (operation_index >= operation_decompilers.size()) { |
| @@ -614,18 +765,18 @@ private: | |||
| 614 | if (const auto gpr = std::get_if<GprNode>(&*node)) { | 765 | if (const auto gpr = std::get_if<GprNode>(&*node)) { |
| 615 | const u32 index = gpr->GetIndex(); | 766 | const u32 index = gpr->GetIndex(); |
| 616 | if (index == Register::ZeroIndex) { | 767 | if (index == Register::ZeroIndex) { |
| 617 | return "0"; | 768 | return {"0U", Type::Uint}; |
| 618 | } | 769 | } |
| 619 | return GetRegister(index); | 770 | return {GetRegister(index), Type::Float}; |
| 620 | } | 771 | } |
| 621 | 772 | ||
| 622 | if (const auto immediate = std::get_if<ImmediateNode>(&*node)) { | 773 | if (const auto immediate = std::get_if<ImmediateNode>(&*node)) { |
| 623 | const u32 value = immediate->GetValue(); | 774 | const u32 value = immediate->GetValue(); |
| 624 | if (value < 10) { | 775 | if (value < 10) { |
| 625 | // For eyecandy avoid using hex numbers on single digits | 776 | // For eyecandy avoid using hex numbers on single digits |
| 626 | return fmt::format("utof({}u)", immediate->GetValue()); | 777 | return {fmt::format("{}U", immediate->GetValue()), Type::Uint}; |
| 627 | } | 778 | } |
| 628 | return fmt::format("utof(0x{:x}u)", immediate->GetValue()); | 779 | return {fmt::format("0x{:X}U", immediate->GetValue()), Type::Uint}; |
| 629 | } | 780 | } |
| 630 | 781 | ||
| 631 | if (const auto predicate = std::get_if<PredicateNode>(&*node)) { | 782 | if (const auto predicate = std::get_if<PredicateNode>(&*node)) { |
| @@ -640,17 +791,18 @@ private: | |||
| 640 | } | 791 | } |
| 641 | }(); | 792 | }(); |
| 642 | if (predicate->IsNegated()) { | 793 | if (predicate->IsNegated()) { |
| 643 | return fmt::format("!({})", value); | 794 | return {fmt::format("!({})", value), Type::Bool}; |
| 644 | } | 795 | } |
| 645 | return value; | 796 | return {value, Type::Bool}; |
| 646 | } | 797 | } |
| 647 | 798 | ||
| 648 | if (const auto abuf = std::get_if<AbufNode>(&*node)) { | 799 | if (const auto abuf = std::get_if<AbufNode>(&*node)) { |
| 649 | UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ProgramType::Geometry, | 800 | UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ProgramType::Geometry, |
| 650 | "Physical attributes in geometry shaders are not implemented"); | 801 | "Physical attributes in geometry shaders are not implemented"); |
| 651 | if (abuf->IsPhysicalBuffer()) { | 802 | if (abuf->IsPhysicalBuffer()) { |
| 652 | return fmt::format("readPhysicalAttribute(ftou({}))", | 803 | return {fmt::format("ReadPhysicalAttribute({})", |
| 653 | Visit(abuf->GetPhysicalAddress())); | 804 | Visit(abuf->GetPhysicalAddress()).AsUint()), |
| 805 | Type::Float}; | ||
| 654 | } | 806 | } |
| 655 | return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer()); | 807 | return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer()); |
| 656 | } | 808 | } |
| @@ -661,59 +813,64 @@ private: | |||
| 661 | // Direct access | 813 | // Direct access |
| 662 | const u32 offset_imm = immediate->GetValue(); | 814 | const u32 offset_imm = immediate->GetValue(); |
| 663 | ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access"); | 815 | ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access"); |
| 664 | return fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), | 816 | return {fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), |
| 665 | offset_imm / (4 * 4), (offset_imm / 4) % 4); | 817 | offset_imm / (4 * 4), (offset_imm / 4) % 4), |
| 818 | Type::Uint}; | ||
| 666 | } | 819 | } |
| 667 | 820 | ||
| 668 | if (std::holds_alternative<OperationNode>(*offset)) { | 821 | if (std::holds_alternative<OperationNode>(*offset)) { |
| 669 | // Indirect access | 822 | // Indirect access |
| 670 | const std::string final_offset = code.GenerateTemporary(); | 823 | const std::string final_offset = code.GenerateTemporary(); |
| 671 | code.AddLine("uint {} = ftou({}) >> 2;", final_offset, Visit(offset)); | 824 | code.AddLine("uint {} = {} >> 2;", final_offset, Visit(offset).AsUint()); |
| 672 | 825 | ||
| 673 | if (!device.HasComponentIndexingBug()) { | 826 | if (!device.HasComponentIndexingBug()) { |
| 674 | return fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()), | 827 | return {fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()), |
| 675 | final_offset, final_offset); | 828 | final_offset, final_offset), |
| 829 | Type::Uint}; | ||
| 676 | } | 830 | } |
| 677 | 831 | ||
| 678 | // AMD's proprietary GLSL compiler emits ill code for variable component access. | 832 | // 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. | 833 | // To bypass this driver bug generate 4 ifs, one per each component. |
| 680 | const std::string pack = code.GenerateTemporary(); | 834 | const std::string pack = code.GenerateTemporary(); |
| 681 | code.AddLine("vec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()), | 835 | code.AddLine("uvec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()), |
| 682 | final_offset); | 836 | final_offset); |
| 683 | 837 | ||
| 684 | const std::string result = code.GenerateTemporary(); | 838 | const std::string result = code.GenerateTemporary(); |
| 685 | code.AddLine("float {};", result); | 839 | code.AddLine("uint {};", result); |
| 686 | for (u32 swizzle = 0; swizzle < 4; ++swizzle) { | 840 | for (u32 swizzle = 0; swizzle < 4; ++swizzle) { |
| 687 | code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result, | 841 | code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result, |
| 688 | pack, GetSwizzle(swizzle)); | 842 | pack, GetSwizzle(swizzle)); |
| 689 | } | 843 | } |
| 690 | return result; | 844 | return {result, Type::Uint}; |
| 691 | } | 845 | } |
| 692 | 846 | ||
| 693 | UNREACHABLE_MSG("Unmanaged offset node type"); | 847 | UNREACHABLE_MSG("Unmanaged offset node type"); |
| 694 | } | 848 | } |
| 695 | 849 | ||
| 696 | if (const auto gmem = std::get_if<GmemNode>(&*node)) { | 850 | if (const auto gmem = std::get_if<GmemNode>(&*node)) { |
| 697 | const std::string real = Visit(gmem->GetRealAddress()); | 851 | const std::string real = Visit(gmem->GetRealAddress()).AsUint(); |
| 698 | const std::string base = Visit(gmem->GetBaseAddress()); | 852 | const std::string base = Visit(gmem->GetBaseAddress()).AsUint(); |
| 699 | const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); | 853 | const std::string final_offset = fmt::format("({} - {}) >> 2", real, base); |
| 700 | return fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); | 854 | return {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset), |
| 855 | Type::Uint}; | ||
| 701 | } | 856 | } |
| 702 | 857 | ||
| 703 | if (const auto lmem = std::get_if<LmemNode>(&*node)) { | 858 | if (const auto lmem = std::get_if<LmemNode>(&*node)) { |
| 704 | if (stage == ProgramType::Compute) { | 859 | if (stage == ProgramType::Compute) { |
| 705 | LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); | 860 | LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); |
| 706 | } | 861 | } |
| 707 | return fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); | 862 | return { |
| 863 | fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()), | ||
| 864 | Type::Uint}; | ||
| 708 | } | 865 | } |
| 709 | 866 | ||
| 710 | if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) { | 867 | if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) { |
| 711 | return GetInternalFlag(internal_flag->GetFlag()); | 868 | return {GetInternalFlag(internal_flag->GetFlag()), Type::Bool}; |
| 712 | } | 869 | } |
| 713 | 870 | ||
| 714 | if (const auto conditional = std::get_if<ConditionalNode>(&*node)) { | 871 | if (const auto conditional = std::get_if<ConditionalNode>(&*node)) { |
| 715 | // It's invalid to call conditional on nested nodes, use an operation instead | 872 | // It's invalid to call conditional on nested nodes, use an operation instead |
| 716 | code.AddLine("if ({}) {{", Visit(conditional->GetCondition())); | 873 | code.AddLine("if ({}) {{", Visit(conditional->GetCondition()).AsBool()); |
| 717 | ++code.scope; | 874 | ++code.scope; |
| 718 | 875 | ||
| 719 | VisitBlock(conditional->GetCode()); | 876 | VisitBlock(conditional->GetCode()); |
| @@ -724,20 +881,21 @@ private: | |||
| 724 | } | 881 | } |
| 725 | 882 | ||
| 726 | if (const auto comment = std::get_if<CommentNode>(&*node)) { | 883 | if (const auto comment = std::get_if<CommentNode>(&*node)) { |
| 727 | return "// " + comment->GetText(); | 884 | code.AddLine("// " + comment->GetText()); |
| 885 | return {}; | ||
| 728 | } | 886 | } |
| 729 | 887 | ||
| 730 | UNREACHABLE(); | 888 | UNREACHABLE(); |
| 731 | return {}; | 889 | return {}; |
| 732 | } | 890 | } |
| 733 | 891 | ||
| 734 | std::string ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) { | 892 | Expression ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) { |
| 735 | const auto GeometryPass = [&](std::string_view name) { | 893 | const auto GeometryPass = [&](std::string_view name) { |
| 736 | if (stage == ProgramType::Geometry && buffer) { | 894 | if (stage == ProgramType::Geometry && buffer) { |
| 737 | // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games | 895 | // 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 | 896 | // set an 0x80000000 index for those and the shader fails to build. Find out why |
| 739 | // this happens and what's its intent. | 897 | // this happens and what's its intent. |
| 740 | return fmt::format("gs_{}[ftou({}) % MAX_VERTEX_INPUT]", name, Visit(buffer)); | 898 | return fmt::format("gs_{}[{} % MAX_VERTEX_INPUT]", name, Visit(buffer).AsUint()); |
| 741 | } | 899 | } |
| 742 | return std::string(name); | 900 | return std::string(name); |
| 743 | }; | 901 | }; |
| @@ -746,25 +904,27 @@ private: | |||
| 746 | case Attribute::Index::Position: | 904 | case Attribute::Index::Position: |
| 747 | switch (stage) { | 905 | switch (stage) { |
| 748 | case ProgramType::Geometry: | 906 | case ProgramType::Geometry: |
| 749 | return fmt::format("gl_in[ftou({})].gl_Position{}", Visit(buffer), | 907 | return {fmt::format("gl_in[{}].gl_Position{}", Visit(buffer).AsUint(), |
| 750 | GetSwizzle(element)); | 908 | GetSwizzle(element)), |
| 909 | Type::Float}; | ||
| 751 | case ProgramType::Fragment: | 910 | case ProgramType::Fragment: |
| 752 | return element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)); | 911 | return {element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)), |
| 912 | Type::Float}; | ||
| 753 | default: | 913 | default: |
| 754 | UNREACHABLE(); | 914 | UNREACHABLE(); |
| 755 | } | 915 | } |
| 756 | case Attribute::Index::PointCoord: | 916 | case Attribute::Index::PointCoord: |
| 757 | switch (element) { | 917 | switch (element) { |
| 758 | case 0: | 918 | case 0: |
| 759 | return "gl_PointCoord.x"; | 919 | return {"gl_PointCoord.x", Type::Float}; |
| 760 | case 1: | 920 | case 1: |
| 761 | return "gl_PointCoord.y"; | 921 | return {"gl_PointCoord.y", Type::Float}; |
| 762 | case 2: | 922 | case 2: |
| 763 | case 3: | 923 | case 3: |
| 764 | return "0"; | 924 | return {"0.0f", Type::Float}; |
| 765 | } | 925 | } |
| 766 | UNREACHABLE(); | 926 | UNREACHABLE(); |
| 767 | return "0"; | 927 | return {"0", Type::Int}; |
| 768 | case Attribute::Index::TessCoordInstanceIDVertexID: | 928 | case Attribute::Index::TessCoordInstanceIDVertexID: |
| 769 | // TODO(Subv): Find out what the values are for the first two elements when inside a | 929 | // 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 | 930 | // vertex shader, and what's the value of the fourth element when inside a Tess Eval |
| @@ -773,44 +933,49 @@ private: | |||
| 773 | switch (element) { | 933 | switch (element) { |
| 774 | case 2: | 934 | case 2: |
| 775 | // Config pack's first value is instance_id. | 935 | // Config pack's first value is instance_id. |
| 776 | return "uintBitsToFloat(config_pack[0])"; | 936 | return {"config_pack[0]", Type::Uint}; |
| 777 | case 3: | 937 | case 3: |
| 778 | return "uintBitsToFloat(gl_VertexID)"; | 938 | return {"gl_VertexID", Type::Int}; |
| 779 | } | 939 | } |
| 780 | UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element); | 940 | UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element); |
| 781 | return "0"; | 941 | return {"0", Type::Int}; |
| 782 | case Attribute::Index::FrontFacing: | 942 | case Attribute::Index::FrontFacing: |
| 783 | // TODO(Subv): Find out what the values are for the other elements. | 943 | // TODO(Subv): Find out what the values are for the other elements. |
| 784 | ASSERT(stage == ProgramType::Fragment); | 944 | ASSERT(stage == ProgramType::Fragment); |
| 785 | switch (element) { | 945 | switch (element) { |
| 786 | case 3: | 946 | case 3: |
| 787 | return "itof(gl_FrontFacing ? -1 : 0)"; | 947 | return {"(gl_FrontFacing ? -1 : 0)", Type::Int}; |
| 788 | } | 948 | } |
| 789 | UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element); | 949 | UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element); |
| 790 | return "0"; | 950 | return {"0", Type::Int}; |
| 791 | default: | 951 | default: |
| 792 | if (IsGenericAttribute(attribute)) { | 952 | if (IsGenericAttribute(attribute)) { |
| 793 | return GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element); | 953 | return {GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element), |
| 954 | Type::Float}; | ||
| 794 | } | 955 | } |
| 795 | break; | 956 | break; |
| 796 | } | 957 | } |
| 797 | UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); | 958 | UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); |
| 798 | return "0"; | 959 | return {"0", Type::Int}; |
| 799 | } | 960 | } |
| 800 | 961 | ||
| 801 | std::string ApplyPrecise(Operation operation, const std::string& value) { | 962 | Expression ApplyPrecise(Operation operation, std::string value, Type type) { |
| 802 | if (!IsPrecise(operation)) { | 963 | if (!IsPrecise(operation)) { |
| 803 | return value; | 964 | return {std::move(value), type}; |
| 804 | } | 965 | } |
| 805 | // There's a bug in NVidia's proprietary drivers that makes precise fail on fragment shaders | 966 | // 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 " : ""; | 967 | // be found in fragment shaders, so we disable precise there. There are vertex shaders that |
| 968 | // also fail to build but nobody seems to care about those. | ||
| 969 | // Note: Only bugged drivers will skip precise. | ||
| 970 | const bool disable_precise = device.HasPreciseBug() && stage == ProgramType::Fragment; | ||
| 807 | 971 | ||
| 808 | const std::string temporary = code.GenerateTemporary(); | 972 | std::string temporary = code.GenerateTemporary(); |
| 809 | code.AddLine("{}float {} = {};", precise, temporary, value); | 973 | code.AddLine("{}{} {} = {};", disable_precise ? "" : "precise ", GetTypeString(type), |
| 810 | return temporary; | 974 | temporary, value); |
| 975 | return {std::move(temporary), type}; | ||
| 811 | } | 976 | } |
| 812 | 977 | ||
| 813 | std::string VisitOperand(Operation operation, std::size_t operand_index) { | 978 | Expression VisitOperand(Operation operation, std::size_t operand_index) { |
| 814 | const auto& operand = operation[operand_index]; | 979 | const auto& operand = operation[operand_index]; |
| 815 | const bool parent_precise = IsPrecise(operation); | 980 | const bool parent_precise = IsPrecise(operation); |
| 816 | const bool child_precise = IsPrecise(operand); | 981 | const bool child_precise = IsPrecise(operand); |
| @@ -819,19 +984,16 @@ private: | |||
| 819 | return Visit(operand); | 984 | return Visit(operand); |
| 820 | } | 985 | } |
| 821 | 986 | ||
| 822 | const std::string temporary = code.GenerateTemporary(); | 987 | Expression value = Visit(operand); |
| 823 | code.AddLine("float {} = {};", temporary, Visit(operand)); | 988 | std::string temporary = code.GenerateTemporary(); |
| 824 | return temporary; | 989 | code.AddLine("{} {} = {};", GetTypeString(value.GetType()), temporary, value.GetCode()); |
| 825 | } | 990 | 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 | } | 991 | } |
| 830 | 992 | ||
| 831 | std::optional<std::pair<std::string, bool>> GetOutputAttribute(const AbufNode* abuf) { | 993 | Expression GetOutputAttribute(const AbufNode* abuf) { |
| 832 | switch (const auto attribute = abuf->GetIndex()) { | 994 | switch (const auto attribute = abuf->GetIndex()) { |
| 833 | case Attribute::Index::Position: | 995 | case Attribute::Index::Position: |
| 834 | return std::make_pair("gl_Position"s + GetSwizzle(abuf->GetElement()), false); | 996 | return {"gl_Position"s + GetSwizzle(abuf->GetElement()), Type::Float}; |
| 835 | case Attribute::Index::LayerViewportPointSize: | 997 | case Attribute::Index::LayerViewportPointSize: |
| 836 | switch (abuf->GetElement()) { | 998 | switch (abuf->GetElement()) { |
| 837 | case 0: | 999 | case 0: |
| @@ -841,119 +1003,79 @@ private: | |||
| 841 | if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { | 1003 | if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { |
| 842 | return {}; | 1004 | return {}; |
| 843 | } | 1005 | } |
| 844 | return std::make_pair("gl_Layer", true); | 1006 | return {"gl_Layer", Type::Int}; |
| 845 | case 2: | 1007 | case 2: |
| 846 | if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { | 1008 | if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { |
| 847 | return {}; | 1009 | return {}; |
| 848 | } | 1010 | } |
| 849 | return std::make_pair("gl_ViewportIndex", true); | 1011 | return {"gl_ViewportIndex", Type::Int}; |
| 850 | case 3: | 1012 | case 3: |
| 851 | UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader"); | 1013 | UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader"); |
| 852 | return std::make_pair("gl_PointSize", false); | 1014 | return {"gl_PointSize", Type::Float}; |
| 853 | } | 1015 | } |
| 854 | return {}; | 1016 | return {}; |
| 855 | case Attribute::Index::ClipDistances0123: | 1017 | case Attribute::Index::ClipDistances0123: |
| 856 | return std::make_pair(fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), false); | 1018 | return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), Type::Float}; |
| 857 | case Attribute::Index::ClipDistances4567: | 1019 | case Attribute::Index::ClipDistances4567: |
| 858 | return std::make_pair(fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), | 1020 | return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), Type::Float}; |
| 859 | false); | ||
| 860 | default: | 1021 | default: |
| 861 | if (IsGenericAttribute(attribute)) { | 1022 | if (IsGenericAttribute(attribute)) { |
| 862 | return std::make_pair( | 1023 | return {GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()), |
| 863 | GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()), false); | 1024 | Type::Float}; |
| 864 | } | 1025 | } |
| 865 | UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute)); | 1026 | UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute)); |
| 866 | return {}; | 1027 | return {}; |
| 867 | } | 1028 | } |
| 868 | } | 1029 | } |
| 869 | 1030 | ||
| 870 | std::string CastOperand(const std::string& value, Type type) const { | 1031 | Expression GenerateUnary(Operation operation, std::string_view func, Type result_type, |
| 871 | switch (type) { | 1032 | Type type_a) { |
| 872 | case Type::Bool: | 1033 | std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0).As(type_a)); |
| 873 | case Type::Bool2: | 1034 | 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 | } | 1035 | } |
| 886 | 1036 | ||
| 887 | std::string BitwiseCastResult(const std::string& value, Type type, | 1037 | Expression GenerateBinaryInfix(Operation operation, std::string_view func, Type result_type, |
| 888 | bool needs_parenthesis = false) { | 1038 | Type type_a, Type type_b) { |
| 889 | switch (type) { | 1039 | const std::string op_a = VisitOperand(operation, 0).As(type_a); |
| 890 | case Type::Bool: | 1040 | const std::string op_b = VisitOperand(operation, 1).As(type_b); |
| 891 | case Type::Bool2: | 1041 | 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 | 1042 | ||
| 921 | return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); | 1043 | return ApplyPrecise(operation, std::move(op_str), result_type); |
| 922 | } | 1044 | } |
| 923 | 1045 | ||
| 924 | std::string GenerateBinaryCall(Operation operation, const std::string& func, Type result_type, | 1046 | Expression GenerateBinaryCall(Operation operation, std::string_view func, Type result_type, |
| 925 | Type type_a, Type type_b) { | 1047 | Type type_a, Type type_b) { |
| 926 | const std::string op_a = VisitOperand(operation, 0, type_a); | 1048 | const std::string op_a = VisitOperand(operation, 0).As(type_a); |
| 927 | const std::string op_b = VisitOperand(operation, 1, type_b); | 1049 | const std::string op_b = VisitOperand(operation, 1).As(type_b); |
| 928 | const std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b); | 1050 | std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b); |
| 929 | 1051 | ||
| 930 | return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); | 1052 | return ApplyPrecise(operation, std::move(op_str), result_type); |
| 931 | } | 1053 | } |
| 932 | 1054 | ||
| 933 | std::string GenerateTernary(Operation operation, const std::string& func, Type result_type, | 1055 | Expression GenerateTernary(Operation operation, std::string_view func, Type result_type, |
| 934 | Type type_a, Type type_b, Type type_c) { | 1056 | Type type_a, Type type_b, Type type_c) { |
| 935 | const std::string op_a = VisitOperand(operation, 0, type_a); | 1057 | const std::string op_a = VisitOperand(operation, 0).As(type_a); |
| 936 | const std::string op_b = VisitOperand(operation, 1, type_b); | 1058 | const std::string op_b = VisitOperand(operation, 1).As(type_b); |
| 937 | const std::string op_c = VisitOperand(operation, 2, type_c); | 1059 | 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); | 1060 | std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c); |
| 939 | 1061 | ||
| 940 | return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); | 1062 | return ApplyPrecise(operation, std::move(op_str), result_type); |
| 941 | } | 1063 | } |
| 942 | 1064 | ||
| 943 | std::string GenerateQuaternary(Operation operation, const std::string& func, Type result_type, | 1065 | Expression GenerateQuaternary(Operation operation, const std::string& func, Type result_type, |
| 944 | Type type_a, Type type_b, Type type_c, Type type_d) { | 1066 | Type type_a, Type type_b, Type type_c, Type type_d) { |
| 945 | const std::string op_a = VisitOperand(operation, 0, type_a); | 1067 | const std::string op_a = VisitOperand(operation, 0).As(type_a); |
| 946 | const std::string op_b = VisitOperand(operation, 1, type_b); | 1068 | const std::string op_b = VisitOperand(operation, 1).As(type_b); |
| 947 | const std::string op_c = VisitOperand(operation, 2, type_c); | 1069 | const std::string op_c = VisitOperand(operation, 2).As(type_c); |
| 948 | const std::string op_d = VisitOperand(operation, 3, type_d); | 1070 | 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); | 1071 | std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d); |
| 950 | 1072 | ||
| 951 | return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); | 1073 | return ApplyPrecise(operation, std::move(op_str), result_type); |
| 952 | } | 1074 | } |
| 953 | 1075 | ||
| 954 | std::string GenerateTexture(Operation operation, const std::string& function_suffix, | 1076 | std::string GenerateTexture(Operation operation, const std::string& function_suffix, |
| 955 | const std::vector<TextureIR>& extras) { | 1077 | const std::vector<TextureIR>& extras) { |
| 956 | constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"}; | 1078 | constexpr std::array coord_constructors = {"float", "vec2", "vec3", "vec4"}; |
| 957 | 1079 | ||
| 958 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1080 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 959 | ASSERT(meta); | 1081 | ASSERT(meta); |
| @@ -970,17 +1092,17 @@ private: | |||
| 970 | expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1); | 1092 | expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1); |
| 971 | expr += '('; | 1093 | expr += '('; |
| 972 | for (std::size_t i = 0; i < count; ++i) { | 1094 | for (std::size_t i = 0; i < count; ++i) { |
| 973 | expr += Visit(operation[i]); | 1095 | expr += Visit(operation[i]).AsFloat(); |
| 974 | 1096 | ||
| 975 | const std::size_t next = i + 1; | 1097 | const std::size_t next = i + 1; |
| 976 | if (next < count) | 1098 | if (next < count) |
| 977 | expr += ", "; | 1099 | expr += ", "; |
| 978 | } | 1100 | } |
| 979 | if (has_array) { | 1101 | if (has_array) { |
| 980 | expr += ", float(ftoi(" + Visit(meta->array) + "))"; | 1102 | expr += ", float(" + Visit(meta->array).AsInt() + ')'; |
| 981 | } | 1103 | } |
| 982 | if (has_shadow) { | 1104 | if (has_shadow) { |
| 983 | expr += ", " + Visit(meta->depth_compare); | 1105 | expr += ", " + Visit(meta->depth_compare).AsFloat(); |
| 984 | } | 1106 | } |
| 985 | expr += ')'; | 1107 | expr += ')'; |
| 986 | 1108 | ||
| @@ -1011,11 +1133,11 @@ private: | |||
| 1011 | // required to be constant) | 1133 | // required to be constant) |
| 1012 | expr += std::to_string(static_cast<s32>(immediate->GetValue())); | 1134 | expr += std::to_string(static_cast<s32>(immediate->GetValue())); |
| 1013 | } else { | 1135 | } else { |
| 1014 | expr += fmt::format("ftoi({})", Visit(operand)); | 1136 | expr += Visit(operand).AsInt(); |
| 1015 | } | 1137 | } |
| 1016 | break; | 1138 | break; |
| 1017 | case Type::Float: | 1139 | case Type::Float: |
| 1018 | expr += Visit(operand); | 1140 | expr += Visit(operand).AsFloat(); |
| 1019 | break; | 1141 | break; |
| 1020 | default: { | 1142 | default: { |
| 1021 | const auto type_int = static_cast<u32>(type); | 1143 | const auto type_int = static_cast<u32>(type); |
| @@ -1031,7 +1153,7 @@ private: | |||
| 1031 | if (aoffi.empty()) { | 1153 | if (aoffi.empty()) { |
| 1032 | return {}; | 1154 | return {}; |
| 1033 | } | 1155 | } |
| 1034 | constexpr std::array<const char*, 3> coord_constructors = {"int", "ivec2", "ivec3"}; | 1156 | constexpr std::array coord_constructors = {"int", "ivec2", "ivec3"}; |
| 1035 | std::string expr = ", "; | 1157 | std::string expr = ", "; |
| 1036 | expr += coord_constructors.at(aoffi.size() - 1); | 1158 | expr += coord_constructors.at(aoffi.size() - 1); |
| 1037 | expr += '('; | 1159 | expr += '('; |
| @@ -1044,7 +1166,7 @@ private: | |||
| 1044 | expr += std::to_string(static_cast<s32>(immediate->GetValue())); | 1166 | expr += std::to_string(static_cast<s32>(immediate->GetValue())); |
| 1045 | } else if (device.HasVariableAoffi()) { | 1167 | } else if (device.HasVariableAoffi()) { |
| 1046 | // Avoid using variable AOFFI on unsupported devices. | 1168 | // Avoid using variable AOFFI on unsupported devices. |
| 1047 | expr += fmt::format("ftoi({})", Visit(operand)); | 1169 | expr += Visit(operand).AsInt(); |
| 1048 | } else { | 1170 | } else { |
| 1049 | // Insert 0 on devices not supporting variable AOFFI. | 1171 | // Insert 0 on devices not supporting variable AOFFI. |
| 1050 | expr += '0'; | 1172 | expr += '0'; |
| @@ -1058,328 +1180,314 @@ private: | |||
| 1058 | return expr; | 1180 | return expr; |
| 1059 | } | 1181 | } |
| 1060 | 1182 | ||
| 1061 | std::string Assign(Operation operation) { | 1183 | Expression Assign(Operation operation) { |
| 1062 | const Node& dest = operation[0]; | 1184 | const Node& dest = operation[0]; |
| 1063 | const Node& src = operation[1]; | 1185 | const Node& src = operation[1]; |
| 1064 | 1186 | ||
| 1065 | std::string target; | 1187 | Expression target; |
| 1066 | bool is_integer = false; | ||
| 1067 | |||
| 1068 | if (const auto gpr = std::get_if<GprNode>(&*dest)) { | 1188 | if (const auto gpr = std::get_if<GprNode>(&*dest)) { |
| 1069 | if (gpr->GetIndex() == Register::ZeroIndex) { | 1189 | if (gpr->GetIndex() == Register::ZeroIndex) { |
| 1070 | // Writing to Register::ZeroIndex is a no op | 1190 | // Writing to Register::ZeroIndex is a no op |
| 1071 | return {}; | 1191 | return {}; |
| 1072 | } | 1192 | } |
| 1073 | target = GetRegister(gpr->GetIndex()); | 1193 | target = {GetRegister(gpr->GetIndex()), Type::Float}; |
| 1074 | } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) { | 1194 | } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) { |
| 1075 | UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer()); | 1195 | UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer()); |
| 1076 | const auto result = GetOutputAttribute(abuf); | 1196 | 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)) { | 1197 | } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) { |
| 1083 | if (stage == ProgramType::Compute) { | 1198 | if (stage == ProgramType::Compute) { |
| 1084 | LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); | 1199 | LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); |
| 1085 | } | 1200 | } |
| 1086 | target = fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); | 1201 | target = { |
| 1202 | fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()), | ||
| 1203 | Type::Uint}; | ||
| 1087 | } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) { | 1204 | } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) { |
| 1088 | const std::string real = Visit(gmem->GetRealAddress()); | 1205 | const std::string real = Visit(gmem->GetRealAddress()).AsUint(); |
| 1089 | const std::string base = Visit(gmem->GetBaseAddress()); | 1206 | const std::string base = Visit(gmem->GetBaseAddress()).AsUint(); |
| 1090 | const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); | 1207 | const std::string final_offset = fmt::format("({} - {}) >> 2", real, base); |
| 1091 | target = fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); | 1208 | target = {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset), |
| 1209 | Type::Uint}; | ||
| 1092 | } else { | 1210 | } else { |
| 1093 | UNREACHABLE_MSG("Assign called without a proper target"); | 1211 | UNREACHABLE_MSG("Assign called without a proper target"); |
| 1094 | } | 1212 | } |
| 1095 | 1213 | ||
| 1096 | if (is_integer) { | 1214 | 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 {}; | 1215 | return {}; |
| 1102 | } | 1216 | } |
| 1103 | 1217 | ||
| 1104 | template <Type type> | 1218 | template <Type type> |
| 1105 | std::string Add(Operation operation) { | 1219 | Expression Add(Operation operation) { |
| 1106 | return GenerateBinaryInfix(operation, "+", type, type, type); | 1220 | return GenerateBinaryInfix(operation, "+", type, type, type); |
| 1107 | } | 1221 | } |
| 1108 | 1222 | ||
| 1109 | template <Type type> | 1223 | template <Type type> |
| 1110 | std::string Mul(Operation operation) { | 1224 | Expression Mul(Operation operation) { |
| 1111 | return GenerateBinaryInfix(operation, "*", type, type, type); | 1225 | return GenerateBinaryInfix(operation, "*", type, type, type); |
| 1112 | } | 1226 | } |
| 1113 | 1227 | ||
| 1114 | template <Type type> | 1228 | template <Type type> |
| 1115 | std::string Div(Operation operation) { | 1229 | Expression Div(Operation operation) { |
| 1116 | return GenerateBinaryInfix(operation, "/", type, type, type); | 1230 | return GenerateBinaryInfix(operation, "/", type, type, type); |
| 1117 | } | 1231 | } |
| 1118 | 1232 | ||
| 1119 | template <Type type> | 1233 | template <Type type> |
| 1120 | std::string Fma(Operation operation) { | 1234 | Expression Fma(Operation operation) { |
| 1121 | return GenerateTernary(operation, "fma", type, type, type, type); | 1235 | return GenerateTernary(operation, "fma", type, type, type, type); |
| 1122 | } | 1236 | } |
| 1123 | 1237 | ||
| 1124 | template <Type type> | 1238 | template <Type type> |
| 1125 | std::string Negate(Operation operation) { | 1239 | Expression Negate(Operation operation) { |
| 1126 | return GenerateUnary(operation, "-", type, type, true); | 1240 | return GenerateUnary(operation, "-", type, type); |
| 1127 | } | 1241 | } |
| 1128 | 1242 | ||
| 1129 | template <Type type> | 1243 | template <Type type> |
| 1130 | std::string Absolute(Operation operation) { | 1244 | Expression Absolute(Operation operation) { |
| 1131 | return GenerateUnary(operation, "abs", type, type, false); | 1245 | return GenerateUnary(operation, "abs", type, type); |
| 1132 | } | 1246 | } |
| 1133 | 1247 | ||
| 1134 | std::string FClamp(Operation operation) { | 1248 | Expression FClamp(Operation operation) { |
| 1135 | return GenerateTernary(operation, "clamp", Type::Float, Type::Float, Type::Float, | 1249 | return GenerateTernary(operation, "clamp", Type::Float, Type::Float, Type::Float, |
| 1136 | Type::Float); | 1250 | Type::Float); |
| 1137 | } | 1251 | } |
| 1138 | 1252 | ||
| 1139 | std::string FCastHalf0(Operation operation) { | 1253 | Expression FCastHalf0(Operation operation) { |
| 1140 | const std::string op_a = VisitOperand(operation, 0, Type::HalfFloat); | 1254 | return {fmt::format("({})[0]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float}; |
| 1141 | return fmt::format("({})[0]", op_a); | ||
| 1142 | } | 1255 | } |
| 1143 | 1256 | ||
| 1144 | std::string FCastHalf1(Operation operation) { | 1257 | Expression FCastHalf1(Operation operation) { |
| 1145 | const std::string op_a = VisitOperand(operation, 0, Type::HalfFloat); | 1258 | return {fmt::format("({})[1]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float}; |
| 1146 | return fmt::format("({})[1]", op_a); | ||
| 1147 | } | 1259 | } |
| 1148 | 1260 | ||
| 1149 | template <Type type> | 1261 | template <Type type> |
| 1150 | std::string Min(Operation operation) { | 1262 | Expression Min(Operation operation) { |
| 1151 | return GenerateBinaryCall(operation, "min", type, type, type); | 1263 | return GenerateBinaryCall(operation, "min", type, type, type); |
| 1152 | } | 1264 | } |
| 1153 | 1265 | ||
| 1154 | template <Type type> | 1266 | template <Type type> |
| 1155 | std::string Max(Operation operation) { | 1267 | Expression Max(Operation operation) { |
| 1156 | return GenerateBinaryCall(operation, "max", type, type, type); | 1268 | return GenerateBinaryCall(operation, "max", type, type, type); |
| 1157 | } | 1269 | } |
| 1158 | 1270 | ||
| 1159 | std::string Select(Operation operation) { | 1271 | Expression Select(Operation operation) { |
| 1160 | const std::string condition = Visit(operation[0]); | 1272 | const std::string condition = Visit(operation[0]).AsBool(); |
| 1161 | const std::string true_case = Visit(operation[1]); | 1273 | const std::string true_case = Visit(operation[1]).AsUint(); |
| 1162 | const std::string false_case = Visit(operation[2]); | 1274 | const std::string false_case = Visit(operation[2]).AsUint(); |
| 1163 | const std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case); | 1275 | std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case); |
| 1164 | 1276 | ||
| 1165 | return ApplyPrecise(operation, op_str); | 1277 | return ApplyPrecise(operation, std::move(op_str), Type::Uint); |
| 1166 | } | 1278 | } |
| 1167 | 1279 | ||
| 1168 | std::string FCos(Operation operation) { | 1280 | Expression FCos(Operation operation) { |
| 1169 | return GenerateUnary(operation, "cos", Type::Float, Type::Float, false); | 1281 | return GenerateUnary(operation, "cos", Type::Float, Type::Float); |
| 1170 | } | 1282 | } |
| 1171 | 1283 | ||
| 1172 | std::string FSin(Operation operation) { | 1284 | Expression FSin(Operation operation) { |
| 1173 | return GenerateUnary(operation, "sin", Type::Float, Type::Float, false); | 1285 | return GenerateUnary(operation, "sin", Type::Float, Type::Float); |
| 1174 | } | 1286 | } |
| 1175 | 1287 | ||
| 1176 | std::string FExp2(Operation operation) { | 1288 | Expression FExp2(Operation operation) { |
| 1177 | return GenerateUnary(operation, "exp2", Type::Float, Type::Float, false); | 1289 | return GenerateUnary(operation, "exp2", Type::Float, Type::Float); |
| 1178 | } | 1290 | } |
| 1179 | 1291 | ||
| 1180 | std::string FLog2(Operation operation) { | 1292 | Expression FLog2(Operation operation) { |
| 1181 | return GenerateUnary(operation, "log2", Type::Float, Type::Float, false); | 1293 | return GenerateUnary(operation, "log2", Type::Float, Type::Float); |
| 1182 | } | 1294 | } |
| 1183 | 1295 | ||
| 1184 | std::string FInverseSqrt(Operation operation) { | 1296 | Expression FInverseSqrt(Operation operation) { |
| 1185 | return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float, false); | 1297 | return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float); |
| 1186 | } | 1298 | } |
| 1187 | 1299 | ||
| 1188 | std::string FSqrt(Operation operation) { | 1300 | Expression FSqrt(Operation operation) { |
| 1189 | return GenerateUnary(operation, "sqrt", Type::Float, Type::Float, false); | 1301 | return GenerateUnary(operation, "sqrt", Type::Float, Type::Float); |
| 1190 | } | 1302 | } |
| 1191 | 1303 | ||
| 1192 | std::string FRoundEven(Operation operation) { | 1304 | Expression FRoundEven(Operation operation) { |
| 1193 | return GenerateUnary(operation, "roundEven", Type::Float, Type::Float, false); | 1305 | return GenerateUnary(operation, "roundEven", Type::Float, Type::Float); |
| 1194 | } | 1306 | } |
| 1195 | 1307 | ||
| 1196 | std::string FFloor(Operation operation) { | 1308 | Expression FFloor(Operation operation) { |
| 1197 | return GenerateUnary(operation, "floor", Type::Float, Type::Float, false); | 1309 | return GenerateUnary(operation, "floor", Type::Float, Type::Float); |
| 1198 | } | 1310 | } |
| 1199 | 1311 | ||
| 1200 | std::string FCeil(Operation operation) { | 1312 | Expression FCeil(Operation operation) { |
| 1201 | return GenerateUnary(operation, "ceil", Type::Float, Type::Float, false); | 1313 | return GenerateUnary(operation, "ceil", Type::Float, Type::Float); |
| 1202 | } | 1314 | } |
| 1203 | 1315 | ||
| 1204 | std::string FTrunc(Operation operation) { | 1316 | Expression FTrunc(Operation operation) { |
| 1205 | return GenerateUnary(operation, "trunc", Type::Float, Type::Float, false); | 1317 | return GenerateUnary(operation, "trunc", Type::Float, Type::Float); |
| 1206 | } | 1318 | } |
| 1207 | 1319 | ||
| 1208 | template <Type type> | 1320 | template <Type type> |
| 1209 | std::string FCastInteger(Operation operation) { | 1321 | Expression FCastInteger(Operation operation) { |
| 1210 | return GenerateUnary(operation, "float", Type::Float, type, false); | 1322 | return GenerateUnary(operation, "float", Type::Float, type); |
| 1211 | } | 1323 | } |
| 1212 | 1324 | ||
| 1213 | std::string ICastFloat(Operation operation) { | 1325 | Expression ICastFloat(Operation operation) { |
| 1214 | return GenerateUnary(operation, "int", Type::Int, Type::Float, false); | 1326 | return GenerateUnary(operation, "int", Type::Int, Type::Float); |
| 1215 | } | 1327 | } |
| 1216 | 1328 | ||
| 1217 | std::string ICastUnsigned(Operation operation) { | 1329 | Expression ICastUnsigned(Operation operation) { |
| 1218 | return GenerateUnary(operation, "int", Type::Int, Type::Uint, false); | 1330 | return GenerateUnary(operation, "int", Type::Int, Type::Uint); |
| 1219 | } | 1331 | } |
| 1220 | 1332 | ||
| 1221 | template <Type type> | 1333 | template <Type type> |
| 1222 | std::string LogicalShiftLeft(Operation operation) { | 1334 | Expression LogicalShiftLeft(Operation operation) { |
| 1223 | return GenerateBinaryInfix(operation, "<<", type, type, Type::Uint); | 1335 | return GenerateBinaryInfix(operation, "<<", type, type, Type::Uint); |
| 1224 | } | 1336 | } |
| 1225 | 1337 | ||
| 1226 | std::string ILogicalShiftRight(Operation operation) { | 1338 | Expression ILogicalShiftRight(Operation operation) { |
| 1227 | const std::string op_a = VisitOperand(operation, 0, Type::Uint); | 1339 | const std::string op_a = VisitOperand(operation, 0).AsUint(); |
| 1228 | const std::string op_b = VisitOperand(operation, 1, Type::Uint); | 1340 | const std::string op_b = VisitOperand(operation, 1).AsUint(); |
| 1229 | const std::string op_str = fmt::format("int({} >> {})", op_a, op_b); | 1341 | std::string op_str = fmt::format("int({} >> {})", op_a, op_b); |
| 1230 | 1342 | ||
| 1231 | return ApplyPrecise(operation, BitwiseCastResult(op_str, Type::Int)); | 1343 | return ApplyPrecise(operation, std::move(op_str), Type::Int); |
| 1232 | } | 1344 | } |
| 1233 | 1345 | ||
| 1234 | std::string IArithmeticShiftRight(Operation operation) { | 1346 | Expression IArithmeticShiftRight(Operation operation) { |
| 1235 | return GenerateBinaryInfix(operation, ">>", Type::Int, Type::Int, Type::Uint); | 1347 | return GenerateBinaryInfix(operation, ">>", Type::Int, Type::Int, Type::Uint); |
| 1236 | } | 1348 | } |
| 1237 | 1349 | ||
| 1238 | template <Type type> | 1350 | template <Type type> |
| 1239 | std::string BitwiseAnd(Operation operation) { | 1351 | Expression BitwiseAnd(Operation operation) { |
| 1240 | return GenerateBinaryInfix(operation, "&", type, type, type); | 1352 | return GenerateBinaryInfix(operation, "&", type, type, type); |
| 1241 | } | 1353 | } |
| 1242 | 1354 | ||
| 1243 | template <Type type> | 1355 | template <Type type> |
| 1244 | std::string BitwiseOr(Operation operation) { | 1356 | Expression BitwiseOr(Operation operation) { |
| 1245 | return GenerateBinaryInfix(operation, "|", type, type, type); | 1357 | return GenerateBinaryInfix(operation, "|", type, type, type); |
| 1246 | } | 1358 | } |
| 1247 | 1359 | ||
| 1248 | template <Type type> | 1360 | template <Type type> |
| 1249 | std::string BitwiseXor(Operation operation) { | 1361 | Expression BitwiseXor(Operation operation) { |
| 1250 | return GenerateBinaryInfix(operation, "^", type, type, type); | 1362 | return GenerateBinaryInfix(operation, "^", type, type, type); |
| 1251 | } | 1363 | } |
| 1252 | 1364 | ||
| 1253 | template <Type type> | 1365 | template <Type type> |
| 1254 | std::string BitwiseNot(Operation operation) { | 1366 | Expression BitwiseNot(Operation operation) { |
| 1255 | return GenerateUnary(operation, "~", type, type, false); | 1367 | return GenerateUnary(operation, "~", type, type); |
| 1256 | } | 1368 | } |
| 1257 | 1369 | ||
| 1258 | std::string UCastFloat(Operation operation) { | 1370 | Expression UCastFloat(Operation operation) { |
| 1259 | return GenerateUnary(operation, "uint", Type::Uint, Type::Float, false); | 1371 | return GenerateUnary(operation, "uint", Type::Uint, Type::Float); |
| 1260 | } | 1372 | } |
| 1261 | 1373 | ||
| 1262 | std::string UCastSigned(Operation operation) { | 1374 | Expression UCastSigned(Operation operation) { |
| 1263 | return GenerateUnary(operation, "uint", Type::Uint, Type::Int, false); | 1375 | return GenerateUnary(operation, "uint", Type::Uint, Type::Int); |
| 1264 | } | 1376 | } |
| 1265 | 1377 | ||
| 1266 | std::string UShiftRight(Operation operation) { | 1378 | Expression UShiftRight(Operation operation) { |
| 1267 | return GenerateBinaryInfix(operation, ">>", Type::Uint, Type::Uint, Type::Uint); | 1379 | return GenerateBinaryInfix(operation, ">>", Type::Uint, Type::Uint, Type::Uint); |
| 1268 | } | 1380 | } |
| 1269 | 1381 | ||
| 1270 | template <Type type> | 1382 | template <Type type> |
| 1271 | std::string BitfieldInsert(Operation operation) { | 1383 | Expression BitfieldInsert(Operation operation) { |
| 1272 | return GenerateQuaternary(operation, "bitfieldInsert", type, type, type, Type::Int, | 1384 | return GenerateQuaternary(operation, "bitfieldInsert", type, type, type, Type::Int, |
| 1273 | Type::Int); | 1385 | Type::Int); |
| 1274 | } | 1386 | } |
| 1275 | 1387 | ||
| 1276 | template <Type type> | 1388 | template <Type type> |
| 1277 | std::string BitfieldExtract(Operation operation) { | 1389 | Expression BitfieldExtract(Operation operation) { |
| 1278 | return GenerateTernary(operation, "bitfieldExtract", type, type, Type::Int, Type::Int); | 1390 | return GenerateTernary(operation, "bitfieldExtract", type, type, Type::Int, Type::Int); |
| 1279 | } | 1391 | } |
| 1280 | 1392 | ||
| 1281 | template <Type type> | 1393 | template <Type type> |
| 1282 | std::string BitCount(Operation operation) { | 1394 | Expression BitCount(Operation operation) { |
| 1283 | return GenerateUnary(operation, "bitCount", type, type, false); | 1395 | return GenerateUnary(operation, "bitCount", type, type); |
| 1284 | } | 1396 | } |
| 1285 | 1397 | ||
| 1286 | std::string HNegate(Operation operation) { | 1398 | Expression HNegate(Operation operation) { |
| 1287 | const auto GetNegate = [&](std::size_t index) { | 1399 | const auto GetNegate = [&](std::size_t index) { |
| 1288 | return VisitOperand(operation, index, Type::Bool) + " ? -1 : 1"; | 1400 | return VisitOperand(operation, index).AsBool() + " ? -1 : 1"; |
| 1289 | }; | 1401 | }; |
| 1290 | const std::string value = | 1402 | return {fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0).AsHalfFloat(), |
| 1291 | fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0, Type::HalfFloat), | 1403 | GetNegate(1), GetNegate(2)), |
| 1292 | GetNegate(1), GetNegate(2)); | 1404 | Type::HalfFloat}; |
| 1293 | return BitwiseCastResult(value, Type::HalfFloat); | 1405 | } |
| 1294 | } | 1406 | |
| 1295 | 1407 | Expression HClamp(Operation operation) { | |
| 1296 | std::string HClamp(Operation operation) { | 1408 | const std::string value = VisitOperand(operation, 0).AsHalfFloat(); |
| 1297 | const std::string value = VisitOperand(operation, 0, Type::HalfFloat); | 1409 | const std::string min = VisitOperand(operation, 1).AsFloat(); |
| 1298 | const std::string min = VisitOperand(operation, 1, Type::Float); | 1410 | const std::string max = VisitOperand(operation, 2).AsFloat(); |
| 1299 | const std::string max = VisitOperand(operation, 2, Type::Float); | 1411 | std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max); |
| 1300 | const std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max); | 1412 | |
| 1301 | 1413 | return ApplyPrecise(operation, std::move(clamped), Type::HalfFloat); | |
| 1302 | return ApplyPrecise(operation, BitwiseCastResult(clamped, Type::HalfFloat)); | 1414 | } |
| 1303 | } | 1415 | |
| 1304 | 1416 | Expression HCastFloat(Operation operation) { | |
| 1305 | std::string HCastFloat(Operation operation) { | 1417 | return {fmt::format("vec2({})", VisitOperand(operation, 0).AsFloat()), Type::HalfFloat}; |
| 1306 | const std::string op_a = VisitOperand(operation, 0, Type::Float); | 1418 | } |
| 1307 | return fmt::format("fromHalf2(vec2({}, 0.0f))", op_a); | 1419 | |
| 1308 | } | 1420 | Expression HUnpack(Operation operation) { |
| 1309 | 1421 | Expression operand = VisitOperand(operation, 0); | |
| 1310 | std::string HUnpack(Operation operation) { | 1422 | switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) { |
| 1311 | const std::string operand{VisitOperand(operation, 0, Type::HalfFloat)}; | 1423 | case Tegra::Shader::HalfType::H0_H1: |
| 1312 | const auto value = [&]() -> std::string { | 1424 | return operand; |
| 1313 | switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) { | 1425 | case Tegra::Shader::HalfType::F32: |
| 1314 | case Tegra::Shader::HalfType::H0_H1: | 1426 | return {fmt::format("vec2({})", operand.AsFloat()), Type::HalfFloat}; |
| 1315 | return operand; | 1427 | case Tegra::Shader::HalfType::H0_H0: |
| 1316 | case Tegra::Shader::HalfType::F32: | 1428 | return {fmt::format("vec2({}[0])", operand.AsHalfFloat()), Type::HalfFloat}; |
| 1317 | return fmt::format("vec2(fromHalf2({}))", operand); | 1429 | case Tegra::Shader::HalfType::H1_H1: |
| 1318 | case Tegra::Shader::HalfType::H0_H0: | 1430 | return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat}; |
| 1319 | return fmt::format("vec2({}[0])", operand); | 1431 | } |
| 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 | } | 1432 | } |
| 1328 | 1433 | ||
| 1329 | std::string HMergeF32(Operation operation) { | 1434 | Expression HMergeF32(Operation operation) { |
| 1330 | return fmt::format("float(toHalf2({})[0])", Visit(operation[0])); | 1435 | return {fmt::format("float({}[0])", VisitOperand(operation, 0).AsHalfFloat()), Type::Float}; |
| 1331 | } | 1436 | } |
| 1332 | 1437 | ||
| 1333 | std::string HMergeH0(Operation operation) { | 1438 | Expression HMergeH0(Operation operation) { |
| 1334 | return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[1]), | 1439 | std::string dest = VisitOperand(operation, 0).AsUint(); |
| 1335 | Visit(operation[0])); | 1440 | std::string src = VisitOperand(operation, 1).AsUint(); |
| 1441 | return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", src, dest), Type::Uint}; | ||
| 1336 | } | 1442 | } |
| 1337 | 1443 | ||
| 1338 | std::string HMergeH1(Operation operation) { | 1444 | Expression HMergeH1(Operation operation) { |
| 1339 | return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[0]), | 1445 | std::string dest = VisitOperand(operation, 0).AsUint(); |
| 1340 | Visit(operation[1])); | 1446 | std::string src = VisitOperand(operation, 1).AsUint(); |
| 1447 | return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", dest, src), Type::Uint}; | ||
| 1341 | } | 1448 | } |
| 1342 | 1449 | ||
| 1343 | std::string HPack2(Operation operation) { | 1450 | Expression HPack2(Operation operation) { |
| 1344 | return fmt::format("utof(packHalf2x16(vec2({}, {})))", Visit(operation[0]), | 1451 | return {fmt::format("vec2({}, {})", VisitOperand(operation, 0).AsFloat(), |
| 1345 | Visit(operation[1])); | 1452 | VisitOperand(operation, 1).AsFloat()), |
| 1453 | Type::HalfFloat}; | ||
| 1346 | } | 1454 | } |
| 1347 | 1455 | ||
| 1348 | template <Type type> | 1456 | template <Type type> |
| 1349 | std::string LogicalLessThan(Operation operation) { | 1457 | Expression LogicalLessThan(Operation operation) { |
| 1350 | return GenerateBinaryInfix(operation, "<", Type::Bool, type, type); | 1458 | return GenerateBinaryInfix(operation, "<", Type::Bool, type, type); |
| 1351 | } | 1459 | } |
| 1352 | 1460 | ||
| 1353 | template <Type type> | 1461 | template <Type type> |
| 1354 | std::string LogicalEqual(Operation operation) { | 1462 | Expression LogicalEqual(Operation operation) { |
| 1355 | return GenerateBinaryInfix(operation, "==", Type::Bool, type, type); | 1463 | return GenerateBinaryInfix(operation, "==", Type::Bool, type, type); |
| 1356 | } | 1464 | } |
| 1357 | 1465 | ||
| 1358 | template <Type type> | 1466 | template <Type type> |
| 1359 | std::string LogicalLessEqual(Operation operation) { | 1467 | Expression LogicalLessEqual(Operation operation) { |
| 1360 | return GenerateBinaryInfix(operation, "<=", Type::Bool, type, type); | 1468 | return GenerateBinaryInfix(operation, "<=", Type::Bool, type, type); |
| 1361 | } | 1469 | } |
| 1362 | 1470 | ||
| 1363 | template <Type type> | 1471 | template <Type type> |
| 1364 | std::string LogicalGreaterThan(Operation operation) { | 1472 | Expression LogicalGreaterThan(Operation operation) { |
| 1365 | return GenerateBinaryInfix(operation, ">", Type::Bool, type, type); | 1473 | return GenerateBinaryInfix(operation, ">", Type::Bool, type, type); |
| 1366 | } | 1474 | } |
| 1367 | 1475 | ||
| 1368 | template <Type type> | 1476 | template <Type type> |
| 1369 | std::string LogicalNotEqual(Operation operation) { | 1477 | Expression LogicalNotEqual(Operation operation) { |
| 1370 | return GenerateBinaryInfix(operation, "!=", Type::Bool, type, type); | 1478 | return GenerateBinaryInfix(operation, "!=", Type::Bool, type, type); |
| 1371 | } | 1479 | } |
| 1372 | 1480 | ||
| 1373 | template <Type type> | 1481 | template <Type type> |
| 1374 | std::string LogicalGreaterEqual(Operation operation) { | 1482 | Expression LogicalGreaterEqual(Operation operation) { |
| 1375 | return GenerateBinaryInfix(operation, ">=", Type::Bool, type, type); | 1483 | return GenerateBinaryInfix(operation, ">=", Type::Bool, type, type); |
| 1376 | } | 1484 | } |
| 1377 | 1485 | ||
| 1378 | std::string LogicalFIsNan(Operation operation) { | 1486 | Expression LogicalFIsNan(Operation operation) { |
| 1379 | return GenerateUnary(operation, "isnan", Type::Bool, Type::Float, false); | 1487 | return GenerateUnary(operation, "isnan", Type::Bool, Type::Float); |
| 1380 | } | 1488 | } |
| 1381 | 1489 | ||
| 1382 | std::string LogicalAssign(Operation operation) { | 1490 | Expression LogicalAssign(Operation operation) { |
| 1383 | const Node& dest = operation[0]; | 1491 | const Node& dest = operation[0]; |
| 1384 | const Node& src = operation[1]; | 1492 | const Node& src = operation[1]; |
| 1385 | 1493 | ||
| @@ -1400,78 +1508,80 @@ private: | |||
| 1400 | target = GetInternalFlag(flag->GetFlag()); | 1508 | target = GetInternalFlag(flag->GetFlag()); |
| 1401 | } | 1509 | } |
| 1402 | 1510 | ||
| 1403 | code.AddLine("{} = {};", target, Visit(src)); | 1511 | code.AddLine("{} = {};", target, Visit(src).AsBool()); |
| 1404 | return {}; | 1512 | return {}; |
| 1405 | } | 1513 | } |
| 1406 | 1514 | ||
| 1407 | std::string LogicalAnd(Operation operation) { | 1515 | Expression LogicalAnd(Operation operation) { |
| 1408 | return GenerateBinaryInfix(operation, "&&", Type::Bool, Type::Bool, Type::Bool); | 1516 | return GenerateBinaryInfix(operation, "&&", Type::Bool, Type::Bool, Type::Bool); |
| 1409 | } | 1517 | } |
| 1410 | 1518 | ||
| 1411 | std::string LogicalOr(Operation operation) { | 1519 | Expression LogicalOr(Operation operation) { |
| 1412 | return GenerateBinaryInfix(operation, "||", Type::Bool, Type::Bool, Type::Bool); | 1520 | return GenerateBinaryInfix(operation, "||", Type::Bool, Type::Bool, Type::Bool); |
| 1413 | } | 1521 | } |
| 1414 | 1522 | ||
| 1415 | std::string LogicalXor(Operation operation) { | 1523 | Expression LogicalXor(Operation operation) { |
| 1416 | return GenerateBinaryInfix(operation, "^^", Type::Bool, Type::Bool, Type::Bool); | 1524 | return GenerateBinaryInfix(operation, "^^", Type::Bool, Type::Bool, Type::Bool); |
| 1417 | } | 1525 | } |
| 1418 | 1526 | ||
| 1419 | std::string LogicalNegate(Operation operation) { | 1527 | Expression LogicalNegate(Operation operation) { |
| 1420 | return GenerateUnary(operation, "!", Type::Bool, Type::Bool, false); | 1528 | return GenerateUnary(operation, "!", Type::Bool, Type::Bool); |
| 1421 | } | 1529 | } |
| 1422 | 1530 | ||
| 1423 | std::string LogicalPick2(Operation operation) { | 1531 | Expression LogicalPick2(Operation operation) { |
| 1424 | const std::string pair = VisitOperand(operation, 0, Type::Bool2); | 1532 | return {fmt::format("{}[{}]", VisitOperand(operation, 0).AsBool2(), |
| 1425 | return fmt::format("{}[{}]", pair, VisitOperand(operation, 1, Type::Uint)); | 1533 | VisitOperand(operation, 1).AsUint()), |
| 1534 | Type::Bool}; | ||
| 1426 | } | 1535 | } |
| 1427 | 1536 | ||
| 1428 | std::string LogicalAnd2(Operation operation) { | 1537 | Expression LogicalAnd2(Operation operation) { |
| 1429 | return GenerateUnary(operation, "all", Type::Bool, Type::Bool2); | 1538 | return GenerateUnary(operation, "all", Type::Bool, Type::Bool2); |
| 1430 | } | 1539 | } |
| 1431 | 1540 | ||
| 1432 | template <bool with_nan> | 1541 | template <bool with_nan> |
| 1433 | std::string GenerateHalfComparison(Operation operation, const std::string& compare_op) { | 1542 | Expression GenerateHalfComparison(Operation operation, std::string_view compare_op) { |
| 1434 | const std::string comparison{GenerateBinaryCall(operation, compare_op, Type::Bool2, | 1543 | Expression comparison = GenerateBinaryCall(operation, compare_op, Type::Bool2, |
| 1435 | Type::HalfFloat, Type::HalfFloat)}; | 1544 | Type::HalfFloat, Type::HalfFloat); |
| 1436 | if constexpr (!with_nan) { | 1545 | if constexpr (!with_nan) { |
| 1437 | return comparison; | 1546 | return comparison; |
| 1438 | } | 1547 | } |
| 1439 | return fmt::format("halfFloatNanComparison({}, {}, {})", comparison, | 1548 | return {fmt::format("HalfFloatNanComparison({}, {}, {})", comparison.AsBool2(), |
| 1440 | VisitOperand(operation, 0, Type::HalfFloat), | 1549 | VisitOperand(operation, 0).AsHalfFloat(), |
| 1441 | VisitOperand(operation, 1, Type::HalfFloat)); | 1550 | VisitOperand(operation, 1).AsHalfFloat()), |
| 1551 | Type::Bool2}; | ||
| 1442 | } | 1552 | } |
| 1443 | 1553 | ||
| 1444 | template <bool with_nan> | 1554 | template <bool with_nan> |
| 1445 | std::string Logical2HLessThan(Operation operation) { | 1555 | Expression Logical2HLessThan(Operation operation) { |
| 1446 | return GenerateHalfComparison<with_nan>(operation, "lessThan"); | 1556 | return GenerateHalfComparison<with_nan>(operation, "lessThan"); |
| 1447 | } | 1557 | } |
| 1448 | 1558 | ||
| 1449 | template <bool with_nan> | 1559 | template <bool with_nan> |
| 1450 | std::string Logical2HEqual(Operation operation) { | 1560 | Expression Logical2HEqual(Operation operation) { |
| 1451 | return GenerateHalfComparison<with_nan>(operation, "equal"); | 1561 | return GenerateHalfComparison<with_nan>(operation, "equal"); |
| 1452 | } | 1562 | } |
| 1453 | 1563 | ||
| 1454 | template <bool with_nan> | 1564 | template <bool with_nan> |
| 1455 | std::string Logical2HLessEqual(Operation operation) { | 1565 | Expression Logical2HLessEqual(Operation operation) { |
| 1456 | return GenerateHalfComparison<with_nan>(operation, "lessThanEqual"); | 1566 | return GenerateHalfComparison<with_nan>(operation, "lessThanEqual"); |
| 1457 | } | 1567 | } |
| 1458 | 1568 | ||
| 1459 | template <bool with_nan> | 1569 | template <bool with_nan> |
| 1460 | std::string Logical2HGreaterThan(Operation operation) { | 1570 | Expression Logical2HGreaterThan(Operation operation) { |
| 1461 | return GenerateHalfComparison<with_nan>(operation, "greaterThan"); | 1571 | return GenerateHalfComparison<with_nan>(operation, "greaterThan"); |
| 1462 | } | 1572 | } |
| 1463 | 1573 | ||
| 1464 | template <bool with_nan> | 1574 | template <bool with_nan> |
| 1465 | std::string Logical2HNotEqual(Operation operation) { | 1575 | Expression Logical2HNotEqual(Operation operation) { |
| 1466 | return GenerateHalfComparison<with_nan>(operation, "notEqual"); | 1576 | return GenerateHalfComparison<with_nan>(operation, "notEqual"); |
| 1467 | } | 1577 | } |
| 1468 | 1578 | ||
| 1469 | template <bool with_nan> | 1579 | template <bool with_nan> |
| 1470 | std::string Logical2HGreaterEqual(Operation operation) { | 1580 | Expression Logical2HGreaterEqual(Operation operation) { |
| 1471 | return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual"); | 1581 | return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual"); |
| 1472 | } | 1582 | } |
| 1473 | 1583 | ||
| 1474 | std::string Texture(Operation operation) { | 1584 | Expression Texture(Operation operation) { |
| 1475 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1585 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 1476 | ASSERT(meta); | 1586 | ASSERT(meta); |
| 1477 | 1587 | ||
| @@ -1480,10 +1590,10 @@ private: | |||
| 1480 | if (meta->sampler.IsShadow()) { | 1590 | if (meta->sampler.IsShadow()) { |
| 1481 | expr = "vec4(" + expr + ')'; | 1591 | expr = "vec4(" + expr + ')'; |
| 1482 | } | 1592 | } |
| 1483 | return expr + GetSwizzle(meta->element); | 1593 | return {expr + GetSwizzle(meta->element), Type::Float}; |
| 1484 | } | 1594 | } |
| 1485 | 1595 | ||
| 1486 | std::string TextureLod(Operation operation) { | 1596 | Expression TextureLod(Operation operation) { |
| 1487 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1597 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 1488 | ASSERT(meta); | 1598 | ASSERT(meta); |
| 1489 | 1599 | ||
| @@ -1492,54 +1602,54 @@ private: | |||
| 1492 | if (meta->sampler.IsShadow()) { | 1602 | if (meta->sampler.IsShadow()) { |
| 1493 | expr = "vec4(" + expr + ')'; | 1603 | expr = "vec4(" + expr + ')'; |
| 1494 | } | 1604 | } |
| 1495 | return expr + GetSwizzle(meta->element); | 1605 | return {expr + GetSwizzle(meta->element), Type::Float}; |
| 1496 | } | 1606 | } |
| 1497 | 1607 | ||
| 1498 | std::string TextureGather(Operation operation) { | 1608 | Expression TextureGather(Operation operation) { |
| 1499 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1609 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 1500 | ASSERT(meta); | 1610 | ASSERT(meta); |
| 1501 | 1611 | ||
| 1502 | const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int; | 1612 | const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int; |
| 1503 | return GenerateTexture(operation, "Gather", | 1613 | return {GenerateTexture(operation, "Gather", |
| 1504 | {TextureArgument{type, meta->component}, TextureAoffi{}}) + | 1614 | {TextureArgument{type, meta->component}, TextureAoffi{}}) + |
| 1505 | GetSwizzle(meta->element); | 1615 | GetSwizzle(meta->element), |
| 1616 | Type::Float}; | ||
| 1506 | } | 1617 | } |
| 1507 | 1618 | ||
| 1508 | std::string TextureQueryDimensions(Operation operation) { | 1619 | Expression TextureQueryDimensions(Operation operation) { |
| 1509 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1620 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 1510 | ASSERT(meta); | 1621 | ASSERT(meta); |
| 1511 | 1622 | ||
| 1512 | const std::string sampler = GetSampler(meta->sampler); | 1623 | const std::string sampler = GetSampler(meta->sampler); |
| 1513 | const std::string lod = VisitOperand(operation, 0, Type::Int); | 1624 | const std::string lod = VisitOperand(operation, 0).AsInt(); |
| 1514 | 1625 | ||
| 1515 | switch (meta->element) { | 1626 | switch (meta->element) { |
| 1516 | case 0: | 1627 | case 0: |
| 1517 | case 1: | 1628 | case 1: |
| 1518 | return fmt::format("itof(int(textureSize({}, {}){}))", sampler, lod, | 1629 | return {fmt::format("textureSize({}, {}){}", sampler, lod, GetSwizzle(meta->element)), |
| 1519 | GetSwizzle(meta->element)); | 1630 | Type::Int}; |
| 1520 | case 2: | ||
| 1521 | return "0"; | ||
| 1522 | case 3: | 1631 | case 3: |
| 1523 | return fmt::format("itof(textureQueryLevels({}))", sampler); | 1632 | return {fmt::format("textureQueryLevels({})", sampler), Type::Int}; |
| 1524 | } | 1633 | } |
| 1525 | UNREACHABLE(); | 1634 | UNREACHABLE(); |
| 1526 | return "0"; | 1635 | return {"0", Type::Int}; |
| 1527 | } | 1636 | } |
| 1528 | 1637 | ||
| 1529 | std::string TextureQueryLod(Operation operation) { | 1638 | Expression TextureQueryLod(Operation operation) { |
| 1530 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1639 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 1531 | ASSERT(meta); | 1640 | ASSERT(meta); |
| 1532 | 1641 | ||
| 1533 | if (meta->element < 2) { | 1642 | if (meta->element < 2) { |
| 1534 | return fmt::format("itof(int(({} * vec2(256)){}))", | 1643 | return {fmt::format("int(({} * vec2(256)){})", |
| 1535 | GenerateTexture(operation, "QueryLod", {}), | 1644 | GenerateTexture(operation, "QueryLod", {}), |
| 1536 | GetSwizzle(meta->element)); | 1645 | GetSwizzle(meta->element)), |
| 1646 | Type::Int}; | ||
| 1537 | } | 1647 | } |
| 1538 | return "0"; | 1648 | return {"0", Type::Int}; |
| 1539 | } | 1649 | } |
| 1540 | 1650 | ||
| 1541 | std::string TexelFetch(Operation operation) { | 1651 | Expression TexelFetch(Operation operation) { |
| 1542 | constexpr std::array<const char*, 4> constructors = {"int", "ivec2", "ivec3", "ivec4"}; | 1652 | constexpr std::array constructors = {"int", "ivec2", "ivec3", "ivec4"}; |
| 1543 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); | 1653 | const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); |
| 1544 | ASSERT(meta); | 1654 | ASSERT(meta); |
| 1545 | UNIMPLEMENTED_IF(meta->sampler.IsArray()); | 1655 | UNIMPLEMENTED_IF(meta->sampler.IsArray()); |
| @@ -1552,7 +1662,7 @@ private: | |||
| 1552 | expr += constructors.at(operation.GetOperandsCount() - 1); | 1662 | expr += constructors.at(operation.GetOperandsCount() - 1); |
| 1553 | expr += '('; | 1663 | expr += '('; |
| 1554 | for (std::size_t i = 0; i < count; ++i) { | 1664 | for (std::size_t i = 0; i < count; ++i) { |
| 1555 | expr += VisitOperand(operation, i, Type::Int); | 1665 | expr += VisitOperand(operation, i).AsInt(); |
| 1556 | const std::size_t next = i + 1; | 1666 | const std::size_t next = i + 1; |
| 1557 | if (next == count) | 1667 | if (next == count) |
| 1558 | expr += ')'; | 1668 | expr += ')'; |
| @@ -1565,7 +1675,7 @@ private: | |||
| 1565 | 1675 | ||
| 1566 | if (meta->lod) { | 1676 | if (meta->lod) { |
| 1567 | expr += ", "; | 1677 | expr += ", "; |
| 1568 | expr += CastOperand(Visit(meta->lod), Type::Int); | 1678 | expr += Visit(meta->lod).AsInt(); |
| 1569 | } | 1679 | } |
| 1570 | expr += ')'; | 1680 | expr += ')'; |
| 1571 | expr += GetSwizzle(meta->element); | 1681 | expr += GetSwizzle(meta->element); |
| @@ -1580,11 +1690,11 @@ private: | |||
| 1580 | code.AddLine("float {} = {};", tmp, expr); | 1690 | code.AddLine("float {} = {};", tmp, expr); |
| 1581 | code.AddLine("#endif"); | 1691 | code.AddLine("#endif"); |
| 1582 | 1692 | ||
| 1583 | return tmp; | 1693 | return {tmp, Type::Float}; |
| 1584 | } | 1694 | } |
| 1585 | 1695 | ||
| 1586 | std::string ImageStore(Operation operation) { | 1696 | Expression ImageStore(Operation operation) { |
| 1587 | constexpr std::array<const char*, 4> constructors{"int(", "ivec2(", "ivec3(", "ivec4("}; | 1697 | constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("}; |
| 1588 | const auto meta{std::get<MetaImage>(operation.GetMeta())}; | 1698 | const auto meta{std::get<MetaImage>(operation.GetMeta())}; |
| 1589 | 1699 | ||
| 1590 | std::string expr = "imageStore("; | 1700 | std::string expr = "imageStore("; |
| @@ -1594,7 +1704,7 @@ private: | |||
| 1594 | const std::size_t coords_count{operation.GetOperandsCount()}; | 1704 | const std::size_t coords_count{operation.GetOperandsCount()}; |
| 1595 | expr += constructors.at(coords_count - 1); | 1705 | expr += constructors.at(coords_count - 1); |
| 1596 | for (std::size_t i = 0; i < coords_count; ++i) { | 1706 | for (std::size_t i = 0; i < coords_count; ++i) { |
| 1597 | expr += VisitOperand(operation, i, Type::Int); | 1707 | expr += VisitOperand(operation, i).AsInt(); |
| 1598 | if (i + 1 < coords_count) { | 1708 | if (i + 1 < coords_count) { |
| 1599 | expr += ", "; | 1709 | expr += ", "; |
| 1600 | } | 1710 | } |
| @@ -1605,7 +1715,7 @@ private: | |||
| 1605 | UNIMPLEMENTED_IF(values_count != 4); | 1715 | UNIMPLEMENTED_IF(values_count != 4); |
| 1606 | expr += "vec4("; | 1716 | expr += "vec4("; |
| 1607 | for (std::size_t i = 0; i < values_count; ++i) { | 1717 | for (std::size_t i = 0; i < values_count; ++i) { |
| 1608 | expr += Visit(meta.values.at(i)); | 1718 | expr += Visit(meta.values.at(i)).AsFloat(); |
| 1609 | if (i + 1 < values_count) { | 1719 | if (i + 1 < values_count) { |
| 1610 | expr += ", "; | 1720 | expr += ", "; |
| 1611 | } | 1721 | } |
| @@ -1616,52 +1726,52 @@ private: | |||
| 1616 | return {}; | 1726 | return {}; |
| 1617 | } | 1727 | } |
| 1618 | 1728 | ||
| 1619 | std::string Branch(Operation operation) { | 1729 | Expression Branch(Operation operation) { |
| 1620 | const auto target = std::get_if<ImmediateNode>(&*operation[0]); | 1730 | const auto target = std::get_if<ImmediateNode>(&*operation[0]); |
| 1621 | UNIMPLEMENTED_IF(!target); | 1731 | UNIMPLEMENTED_IF(!target); |
| 1622 | 1732 | ||
| 1623 | code.AddLine("jmp_to = 0x{:x}u;", target->GetValue()); | 1733 | code.AddLine("jmp_to = 0x{:X}U;", target->GetValue()); |
| 1624 | code.AddLine("break;"); | 1734 | code.AddLine("break;"); |
| 1625 | return {}; | 1735 | return {}; |
| 1626 | } | 1736 | } |
| 1627 | 1737 | ||
| 1628 | std::string BranchIndirect(Operation operation) { | 1738 | Expression BranchIndirect(Operation operation) { |
| 1629 | const std::string op_a = VisitOperand(operation, 0, Type::Uint); | 1739 | const std::string op_a = VisitOperand(operation, 0).AsUint(); |
| 1630 | 1740 | ||
| 1631 | code.AddLine("jmp_to = {};", op_a); | 1741 | code.AddLine("jmp_to = {};", op_a); |
| 1632 | code.AddLine("break;"); | 1742 | code.AddLine("break;"); |
| 1633 | return {}; | 1743 | return {}; |
| 1634 | } | 1744 | } |
| 1635 | 1745 | ||
| 1636 | std::string PushFlowStack(Operation operation) { | 1746 | Expression PushFlowStack(Operation operation) { |
| 1637 | const auto stack = std::get<MetaStackClass>(operation.GetMeta()); | 1747 | const auto stack = std::get<MetaStackClass>(operation.GetMeta()); |
| 1638 | const auto target = std::get_if<ImmediateNode>(&*operation[0]); | 1748 | const auto target = std::get_if<ImmediateNode>(&*operation[0]); |
| 1639 | UNIMPLEMENTED_IF(!target); | 1749 | UNIMPLEMENTED_IF(!target); |
| 1640 | 1750 | ||
| 1641 | code.AddLine("{}[{}++] = 0x{:x}u;", FlowStackName(stack), FlowStackTopName(stack), | 1751 | code.AddLine("{}[{}++] = 0x{:X}U;", FlowStackName(stack), FlowStackTopName(stack), |
| 1642 | target->GetValue()); | 1752 | target->GetValue()); |
| 1643 | return {}; | 1753 | return {}; |
| 1644 | } | 1754 | } |
| 1645 | 1755 | ||
| 1646 | std::string PopFlowStack(Operation operation) { | 1756 | Expression PopFlowStack(Operation operation) { |
| 1647 | const auto stack = std::get<MetaStackClass>(operation.GetMeta()); | 1757 | const auto stack = std::get<MetaStackClass>(operation.GetMeta()); |
| 1648 | code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack)); | 1758 | code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack)); |
| 1649 | code.AddLine("break;"); | 1759 | code.AddLine("break;"); |
| 1650 | return {}; | 1760 | return {}; |
| 1651 | } | 1761 | } |
| 1652 | 1762 | ||
| 1653 | std::string Exit(Operation operation) { | 1763 | Expression Exit(Operation operation) { |
| 1654 | if (stage != ProgramType::Fragment) { | 1764 | if (stage != ProgramType::Fragment) { |
| 1655 | code.AddLine("return;"); | 1765 | code.AddLine("return;"); |
| 1656 | return {}; | 1766 | return {}; |
| 1657 | } | 1767 | } |
| 1658 | const auto& used_registers = ir.GetRegisters(); | 1768 | const auto& used_registers = ir.GetRegisters(); |
| 1659 | const auto SafeGetRegister = [&](u32 reg) -> std::string { | 1769 | const auto SafeGetRegister = [&](u32 reg) -> Expression { |
| 1660 | // TODO(Rodrigo): Replace with contains once C++20 releases | 1770 | // TODO(Rodrigo): Replace with contains once C++20 releases |
| 1661 | if (used_registers.find(reg) != used_registers.end()) { | 1771 | if (used_registers.find(reg) != used_registers.end()) { |
| 1662 | return GetRegister(reg); | 1772 | return {GetRegister(reg), Type::Float}; |
| 1663 | } | 1773 | } |
| 1664 | return "0.0f"; | 1774 | return {"0.0f", Type::Float}; |
| 1665 | }; | 1775 | }; |
| 1666 | 1776 | ||
| 1667 | UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented"); | 1777 | UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented"); |
| @@ -1674,7 +1784,7 @@ private: | |||
| 1674 | for (u32 component = 0; component < 4; ++component) { | 1784 | for (u32 component = 0; component < 4; ++component) { |
| 1675 | if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { | 1785 | if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { |
| 1676 | code.AddLine("FragColor{}[{}] = {};", render_target, component, | 1786 | code.AddLine("FragColor{}[{}] = {};", render_target, component, |
| 1677 | SafeGetRegister(current_reg)); | 1787 | SafeGetRegister(current_reg).AsFloat()); |
| 1678 | ++current_reg; | 1788 | ++current_reg; |
| 1679 | } | 1789 | } |
| 1680 | } | 1790 | } |
| @@ -1683,14 +1793,14 @@ private: | |||
| 1683 | if (header.ps.omap.depth) { | 1793 | if (header.ps.omap.depth) { |
| 1684 | // The depth output is always 2 registers after the last color output, and current_reg | 1794 | // The depth output is always 2 registers after the last color output, and current_reg |
| 1685 | // already contains one past the last color register. | 1795 | // already contains one past the last color register. |
| 1686 | code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1)); | 1796 | code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1).AsFloat()); |
| 1687 | } | 1797 | } |
| 1688 | 1798 | ||
| 1689 | code.AddLine("return;"); | 1799 | code.AddLine("return;"); |
| 1690 | return {}; | 1800 | return {}; |
| 1691 | } | 1801 | } |
| 1692 | 1802 | ||
| 1693 | std::string Discard(Operation operation) { | 1803 | Expression Discard(Operation operation) { |
| 1694 | // Enclose "discard" in a conditional, so that GLSL compilation does not complain | 1804 | // Enclose "discard" in a conditional, so that GLSL compilation does not complain |
| 1695 | // about unexecuted instructions that may follow this. | 1805 | // about unexecuted instructions that may follow this. |
| 1696 | code.AddLine("if (true) {{"); | 1806 | code.AddLine("if (true) {{"); |
| @@ -1701,7 +1811,7 @@ private: | |||
| 1701 | return {}; | 1811 | return {}; |
| 1702 | } | 1812 | } |
| 1703 | 1813 | ||
| 1704 | std::string EmitVertex(Operation operation) { | 1814 | Expression EmitVertex(Operation operation) { |
| 1705 | ASSERT_MSG(stage == ProgramType::Geometry, | 1815 | ASSERT_MSG(stage == ProgramType::Geometry, |
| 1706 | "EmitVertex is expected to be used in a geometry shader."); | 1816 | "EmitVertex is expected to be used in a geometry shader."); |
| 1707 | 1817 | ||
| @@ -1712,7 +1822,7 @@ private: | |||
| 1712 | return {}; | 1822 | return {}; |
| 1713 | } | 1823 | } |
| 1714 | 1824 | ||
| 1715 | std::string EndPrimitive(Operation operation) { | 1825 | Expression EndPrimitive(Operation operation) { |
| 1716 | ASSERT_MSG(stage == ProgramType::Geometry, | 1826 | ASSERT_MSG(stage == ProgramType::Geometry, |
| 1717 | "EndPrimitive is expected to be used in a geometry shader."); | 1827 | "EndPrimitive is expected to be used in a geometry shader."); |
| 1718 | 1828 | ||
| @@ -1720,59 +1830,59 @@ private: | |||
| 1720 | return {}; | 1830 | return {}; |
| 1721 | } | 1831 | } |
| 1722 | 1832 | ||
| 1723 | std::string YNegate(Operation operation) { | 1833 | Expression YNegate(Operation operation) { |
| 1724 | // Config pack's third value is Y_NEGATE's state. | 1834 | // Config pack's third value is Y_NEGATE's state. |
| 1725 | return "uintBitsToFloat(config_pack[2])"; | 1835 | return {"config_pack[2]", Type::Uint}; |
| 1726 | } | 1836 | } |
| 1727 | 1837 | ||
| 1728 | template <u32 element> | 1838 | template <u32 element> |
| 1729 | std::string LocalInvocationId(Operation) { | 1839 | Expression LocalInvocationId(Operation) { |
| 1730 | return "utof(gl_LocalInvocationID"s + GetSwizzle(element) + ')'; | 1840 | return {"gl_LocalInvocationID"s + GetSwizzle(element), Type::Uint}; |
| 1731 | } | 1841 | } |
| 1732 | 1842 | ||
| 1733 | template <u32 element> | 1843 | template <u32 element> |
| 1734 | std::string WorkGroupId(Operation) { | 1844 | Expression WorkGroupId(Operation) { |
| 1735 | return "utof(gl_WorkGroupID"s + GetSwizzle(element) + ')'; | 1845 | return {"gl_WorkGroupID"s + GetSwizzle(element), Type::Uint}; |
| 1736 | } | 1846 | } |
| 1737 | 1847 | ||
| 1738 | std::string BallotThread(Operation operation) { | 1848 | Expression BallotThread(Operation operation) { |
| 1739 | const std::string value = VisitOperand(operation, 0, Type::Bool); | 1849 | const std::string value = VisitOperand(operation, 0).AsBool(); |
| 1740 | if (!device.HasWarpIntrinsics()) { | 1850 | if (!device.HasWarpIntrinsics()) { |
| 1741 | LOG_ERROR(Render_OpenGL, | 1851 | LOG_ERROR(Render_OpenGL, |
| 1742 | "Nvidia warp intrinsics are not available and its required by a shader"); | 1852 | "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 | 1853 | // Stub on non-Nvidia devices by simulating all threads voting the same as the active |
| 1744 | // one. | 1854 | // one. |
| 1745 | return fmt::format("utof({} ? 0xFFFFFFFFU : 0U)", value); | 1855 | return {fmt::format("({} ? 0xFFFFFFFFU : 0U)", value), Type::Uint}; |
| 1746 | } | 1856 | } |
| 1747 | return fmt::format("utof(ballotThreadNV({}))", value); | 1857 | return {fmt::format("ballotThreadNV({})", value), Type::Uint}; |
| 1748 | } | 1858 | } |
| 1749 | 1859 | ||
| 1750 | std::string Vote(Operation operation, const char* func) { | 1860 | Expression Vote(Operation operation, const char* func) { |
| 1751 | const std::string value = VisitOperand(operation, 0, Type::Bool); | 1861 | const std::string value = VisitOperand(operation, 0).AsBool(); |
| 1752 | if (!device.HasWarpIntrinsics()) { | 1862 | if (!device.HasWarpIntrinsics()) { |
| 1753 | LOG_ERROR(Render_OpenGL, | 1863 | LOG_ERROR(Render_OpenGL, |
| 1754 | "Nvidia vote intrinsics are not available and its required by a shader"); | 1864 | "Nvidia vote intrinsics are not available and its required by a shader"); |
| 1755 | // Stub with a warp size of one. | 1865 | // Stub with a warp size of one. |
| 1756 | return value; | 1866 | return {value, Type::Bool}; |
| 1757 | } | 1867 | } |
| 1758 | return fmt::format("{}({})", func, value); | 1868 | return {fmt::format("{}({})", func, value), Type::Bool}; |
| 1759 | } | 1869 | } |
| 1760 | 1870 | ||
| 1761 | std::string VoteAll(Operation operation) { | 1871 | Expression VoteAll(Operation operation) { |
| 1762 | return Vote(operation, "allThreadsNV"); | 1872 | return Vote(operation, "allThreadsNV"); |
| 1763 | } | 1873 | } |
| 1764 | 1874 | ||
| 1765 | std::string VoteAny(Operation operation) { | 1875 | Expression VoteAny(Operation operation) { |
| 1766 | return Vote(operation, "anyThreadNV"); | 1876 | return Vote(operation, "anyThreadNV"); |
| 1767 | } | 1877 | } |
| 1768 | 1878 | ||
| 1769 | std::string VoteEqual(Operation operation) { | 1879 | Expression VoteEqual(Operation operation) { |
| 1770 | if (!device.HasWarpIntrinsics()) { | 1880 | if (!device.HasWarpIntrinsics()) { |
| 1771 | LOG_ERROR(Render_OpenGL, | 1881 | LOG_ERROR(Render_OpenGL, |
| 1772 | "Nvidia vote intrinsics are not available and its required by a shader"); | 1882 | "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 | 1883 | // 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. | 1884 | // return an equal result for all its votes. |
| 1775 | return "true"; | 1885 | return {"true", Type::Bool}; |
| 1776 | } | 1886 | } |
| 1777 | return Vote(operation, "allThreadsEqualNV"); | 1887 | return Vote(operation, "allThreadsEqualNV"); |
| 1778 | } | 1888 | } |
| @@ -1973,8 +2083,8 @@ private: | |||
| 1973 | } | 2083 | } |
| 1974 | 2084 | ||
| 1975 | std::string GetInternalFlag(InternalFlag flag) const { | 2085 | std::string GetInternalFlag(InternalFlag flag) const { |
| 1976 | constexpr std::array<const char*, 4> InternalFlagNames = {"zero_flag", "sign_flag", | 2086 | constexpr std::array InternalFlagNames = {"zero_flag", "sign_flag", "carry_flag", |
| 1977 | "carry_flag", "overflow_flag"}; | 2087 | "overflow_flag"}; |
| 1978 | const auto index = static_cast<u32>(flag); | 2088 | const auto index = static_cast<u32>(flag); |
| 1979 | ASSERT(index < static_cast<u32>(InternalFlag::Amount)); | 2089 | ASSERT(index < static_cast<u32>(InternalFlag::Amount)); |
| 1980 | 2090 | ||
| @@ -2022,24 +2132,16 @@ private: | |||
| 2022 | 2132 | ||
| 2023 | std::string GetCommonDeclarations() { | 2133 | std::string GetCommonDeclarations() { |
| 2024 | return fmt::format( | 2134 | return fmt::format( |
| 2025 | "#define MAX_CONSTBUFFER_ELEMENTS {}\n" | ||
| 2026 | "#define ftoi floatBitsToInt\n" | 2135 | "#define ftoi floatBitsToInt\n" |
| 2027 | "#define ftou floatBitsToUint\n" | 2136 | "#define ftou floatBitsToUint\n" |
| 2028 | "#define itof intBitsToFloat\n" | 2137 | "#define itof intBitsToFloat\n" |
| 2029 | "#define utof uintBitsToFloat\n\n" | 2138 | "#define utof uintBitsToFloat\n\n" |
| 2030 | "float fromHalf2(vec2 pair) {{\n" | 2139 | "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" | 2140 | " bvec2 is_nan1 = isnan(pair1);\n" |
| 2038 | " bvec2 is_nan2 = isnan(pair2);\n" | 2141 | " bvec2 is_nan2 = isnan(pair2);\n" |
| 2039 | " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || " | 2142 | " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || " |
| 2040 | "is_nan2.y);\n" | 2143 | "is_nan2.y);\n" |
| 2041 | "}}\n", | 2144 | "}}\n\n"); |
| 2042 | MAX_CONSTBUFFER_ELEMENTS); | ||
| 2043 | } | 2145 | } |
| 2044 | 2146 | ||
| 2045 | ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage, | 2147 | ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage, |
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp index 969fe9ced..5450feedf 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp | |||
| @@ -341,13 +341,16 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn | |||
| 341 | u64 index{}; | 341 | u64 index{}; |
| 342 | u32 type{}; | 342 | u32 type{}; |
| 343 | u8 is_bindless{}; | 343 | u8 is_bindless{}; |
| 344 | u8 is_read{}; | ||
| 345 | u8 is_written{}; | ||
| 344 | if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) || | 346 | if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) || |
| 345 | !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless)) { | 347 | !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) || |
| 348 | !LoadObjectFromPrecompiled(is_read) || !LoadObjectFromPrecompiled(is_written)) { | ||
| 346 | return {}; | 349 | return {}; |
| 347 | } | 350 | } |
| 348 | entry.entries.images.emplace_back( | 351 | entry.entries.images.emplace_back(static_cast<u64>(offset), static_cast<std::size_t>(index), |
| 349 | static_cast<std::size_t>(offset), static_cast<std::size_t>(index), | 352 | static_cast<Tegra::Shader::ImageType>(type), |
| 350 | static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0); | 353 | is_bindless != 0, is_written != 0, is_read != 0); |
| 351 | } | 354 | } |
| 352 | 355 | ||
| 353 | u32 global_memory_count{}; | 356 | u32 global_memory_count{}; |
| @@ -429,7 +432,9 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std: | |||
| 429 | if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) || | 432 | if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) || |
| 430 | !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) || | 433 | !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) || |
| 431 | !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) || | 434 | !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) || |
| 432 | !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0))) { | 435 | !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0)) || |
| 436 | !SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) || | ||
| 437 | !SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0))) { | ||
| 433 | return false; | 438 | return false; |
| 434 | } | 439 | } |
| 435 | } | 440 | } |
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index f4777d0b0..6eabf4fac 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp | |||
| @@ -34,6 +34,25 @@ bool UpdateTie(T1 current_value, const T2 new_value) { | |||
| 34 | return changed; | 34 | return changed; |
| 35 | } | 35 | } |
| 36 | 36 | ||
| 37 | template <typename T> | ||
| 38 | std::optional<std::pair<GLuint, GLsizei>> UpdateArray(T& current_values, const T& new_values) { | ||
| 39 | std::optional<std::size_t> first; | ||
| 40 | std::size_t last; | ||
| 41 | for (std::size_t i = 0; i < std::size(current_values); ++i) { | ||
| 42 | if (!UpdateValue(current_values[i], new_values[i])) { | ||
| 43 | continue; | ||
| 44 | } | ||
| 45 | if (!first) { | ||
| 46 | first = i; | ||
| 47 | } | ||
| 48 | last = i; | ||
| 49 | } | ||
| 50 | if (!first) { | ||
| 51 | return std::nullopt; | ||
| 52 | } | ||
| 53 | return std::make_pair(static_cast<GLuint>(*first), static_cast<GLsizei>(last - *first + 1)); | ||
| 54 | } | ||
| 55 | |||
| 37 | void Enable(GLenum cap, bool enable) { | 56 | void Enable(GLenum cap, bool enable) { |
| 38 | if (enable) { | 57 | if (enable) { |
| 39 | glEnable(cap); | 58 | glEnable(cap); |
| @@ -134,10 +153,6 @@ OpenGLState::OpenGLState() { | |||
| 134 | logic_op.enabled = false; | 153 | logic_op.enabled = false; |
| 135 | logic_op.operation = GL_COPY; | 154 | logic_op.operation = GL_COPY; |
| 136 | 155 | ||
| 137 | for (auto& texture_unit : texture_units) { | ||
| 138 | texture_unit.Reset(); | ||
| 139 | } | ||
| 140 | |||
| 141 | draw.read_framebuffer = 0; | 156 | draw.read_framebuffer = 0; |
| 142 | draw.draw_framebuffer = 0; | 157 | draw.draw_framebuffer = 0; |
| 143 | draw.vertex_array = 0; | 158 | draw.vertex_array = 0; |
| @@ -496,52 +511,20 @@ void OpenGLState::ApplyAlphaTest() const { | |||
| 496 | } | 511 | } |
| 497 | 512 | ||
| 498 | void OpenGLState::ApplyTextures() const { | 513 | void OpenGLState::ApplyTextures() const { |
| 499 | bool has_delta{}; | 514 | if (const auto update = UpdateArray(cur_state.textures, textures)) { |
| 500 | std::size_t first{}; | 515 | glBindTextures(update->first, update->second, textures.data() + update->first); |
| 501 | std::size_t last{}; | ||
| 502 | std::array<GLuint, Maxwell::NumTextureSamplers> textures; | ||
| 503 | |||
| 504 | for (std::size_t i = 0; i < std::size(texture_units); ++i) { | ||
| 505 | const auto& texture_unit = texture_units[i]; | ||
| 506 | auto& cur_state_texture_unit = cur_state.texture_units[i]; | ||
| 507 | textures[i] = texture_unit.texture; | ||
| 508 | if (cur_state_texture_unit.texture == textures[i]) { | ||
| 509 | continue; | ||
| 510 | } | ||
| 511 | cur_state_texture_unit.texture = textures[i]; | ||
| 512 | if (!has_delta) { | ||
| 513 | first = i; | ||
| 514 | has_delta = true; | ||
| 515 | } | ||
| 516 | last = i; | ||
| 517 | } | ||
| 518 | if (has_delta) { | ||
| 519 | glBindTextures(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1), | ||
| 520 | textures.data() + first); | ||
| 521 | } | 516 | } |
| 522 | } | 517 | } |
| 523 | 518 | ||
| 524 | void OpenGLState::ApplySamplers() const { | 519 | void OpenGLState::ApplySamplers() const { |
| 525 | bool has_delta{}; | 520 | if (const auto update = UpdateArray(cur_state.samplers, samplers)) { |
| 526 | std::size_t first{}; | 521 | glBindSamplers(update->first, update->second, samplers.data() + update->first); |
| 527 | std::size_t last{}; | ||
| 528 | std::array<GLuint, Maxwell::NumTextureSamplers> samplers; | ||
| 529 | |||
| 530 | for (std::size_t i = 0; i < std::size(samplers); ++i) { | ||
| 531 | samplers[i] = texture_units[i].sampler; | ||
| 532 | if (cur_state.texture_units[i].sampler == texture_units[i].sampler) { | ||
| 533 | continue; | ||
| 534 | } | ||
| 535 | cur_state.texture_units[i].sampler = texture_units[i].sampler; | ||
| 536 | if (!has_delta) { | ||
| 537 | first = i; | ||
| 538 | has_delta = true; | ||
| 539 | } | ||
| 540 | last = i; | ||
| 541 | } | 522 | } |
| 542 | if (has_delta) { | 523 | } |
| 543 | glBindSamplers(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1), | 524 | |
| 544 | samplers.data() + first); | 525 | void OpenGLState::ApplyImages() const { |
| 526 | if (const auto update = UpdateArray(cur_state.images, images)) { | ||
| 527 | glBindImageTextures(update->first, update->second, images.data() + update->first); | ||
| 545 | } | 528 | } |
| 546 | } | 529 | } |
| 547 | 530 | ||
| @@ -576,6 +559,7 @@ void OpenGLState::Apply() { | |||
| 576 | ApplyLogicOp(); | 559 | ApplyLogicOp(); |
| 577 | ApplyTextures(); | 560 | ApplyTextures(); |
| 578 | ApplySamplers(); | 561 | ApplySamplers(); |
| 562 | ApplyImages(); | ||
| 579 | if (dirty.polygon_offset) { | 563 | if (dirty.polygon_offset) { |
| 580 | ApplyPolygonOffset(); | 564 | ApplyPolygonOffset(); |
| 581 | dirty.polygon_offset = false; | 565 | dirty.polygon_offset = false; |
| @@ -606,18 +590,18 @@ void OpenGLState::EmulateViewportWithScissor() { | |||
| 606 | } | 590 | } |
| 607 | 591 | ||
| 608 | OpenGLState& OpenGLState::UnbindTexture(GLuint handle) { | 592 | OpenGLState& OpenGLState::UnbindTexture(GLuint handle) { |
| 609 | for (auto& unit : texture_units) { | 593 | for (auto& texture : textures) { |
| 610 | if (unit.texture == handle) { | 594 | if (texture == handle) { |
| 611 | unit.Unbind(); | 595 | texture = 0; |
| 612 | } | 596 | } |
| 613 | } | 597 | } |
| 614 | return *this; | 598 | return *this; |
| 615 | } | 599 | } |
| 616 | 600 | ||
| 617 | OpenGLState& OpenGLState::ResetSampler(GLuint handle) { | 601 | OpenGLState& OpenGLState::ResetSampler(GLuint handle) { |
| 618 | for (auto& unit : texture_units) { | 602 | for (auto& sampler : samplers) { |
| 619 | if (unit.sampler == handle) { | 603 | if (sampler == handle) { |
| 620 | unit.sampler = 0; | 604 | sampler = 0; |
| 621 | } | 605 | } |
| 622 | } | 606 | } |
| 623 | return *this; | 607 | return *this; |
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index fdf9a8a12..949b13051 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h | |||
| @@ -118,21 +118,9 @@ public: | |||
| 118 | GLenum operation; | 118 | GLenum operation; |
| 119 | } logic_op; | 119 | } logic_op; |
| 120 | 120 | ||
| 121 | // 3 texture units - one for each that is used in PICA fragment shader emulation | 121 | std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> textures{}; |
| 122 | struct TextureUnit { | 122 | std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> samplers{}; |
| 123 | GLuint texture; // GL_TEXTURE_BINDING_2D | 123 | std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumImages> images{}; |
| 124 | GLuint sampler; // GL_SAMPLER_BINDING | ||
| 125 | |||
| 126 | void Unbind() { | ||
| 127 | texture = 0; | ||
| 128 | } | ||
| 129 | |||
| 130 | void Reset() { | ||
| 131 | Unbind(); | ||
| 132 | sampler = 0; | ||
| 133 | } | ||
| 134 | }; | ||
| 135 | std::array<TextureUnit, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_units; | ||
| 136 | 124 | ||
| 137 | struct { | 125 | struct { |
| 138 | GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING | 126 | GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING |
| @@ -220,6 +208,7 @@ public: | |||
| 220 | void ApplyLogicOp() const; | 208 | void ApplyLogicOp() const; |
| 221 | void ApplyTextures() const; | 209 | void ApplyTextures() const; |
| 222 | void ApplySamplers() const; | 210 | void ApplySamplers() const; |
| 211 | void ApplyImages() const; | ||
| 223 | void ApplyDepthClamp() const; | 212 | void ApplyDepthClamp() const; |
| 224 | void ApplyPolygonOffset() const; | 213 | void ApplyPolygonOffset() const; |
| 225 | void ApplyAlphaTest() const; | 214 | void ApplyAlphaTest() const; |
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index 21324488a..8e13ab38b 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h | |||
| @@ -78,6 +78,17 @@ public: | |||
| 78 | /// Attaches this texture view to the current bound GL_DRAW_FRAMEBUFFER | 78 | /// Attaches this texture view to the current bound GL_DRAW_FRAMEBUFFER |
| 79 | void Attach(GLenum attachment, GLenum target) const; | 79 | void Attach(GLenum attachment, GLenum target) const; |
| 80 | 80 | ||
| 81 | void ApplySwizzle(Tegra::Texture::SwizzleSource x_source, | ||
| 82 | Tegra::Texture::SwizzleSource y_source, | ||
| 83 | Tegra::Texture::SwizzleSource z_source, | ||
| 84 | Tegra::Texture::SwizzleSource w_source); | ||
| 85 | |||
| 86 | void DecorateViewName(GPUVAddr gpu_addr, std::string prefix); | ||
| 87 | |||
| 88 | void MarkAsModified(u64 tick) { | ||
| 89 | surface.MarkAsModified(true, tick); | ||
| 90 | } | ||
| 91 | |||
| 81 | GLuint GetTexture() const { | 92 | GLuint GetTexture() const { |
| 82 | if (is_proxy) { | 93 | if (is_proxy) { |
| 83 | return surface.GetTexture(); | 94 | return surface.GetTexture(); |
| @@ -89,13 +100,6 @@ public: | |||
| 89 | return surface.GetSurfaceParams(); | 100 | return surface.GetSurfaceParams(); |
| 90 | } | 101 | } |
| 91 | 102 | ||
| 92 | void ApplySwizzle(Tegra::Texture::SwizzleSource x_source, | ||
| 93 | Tegra::Texture::SwizzleSource y_source, | ||
| 94 | Tegra::Texture::SwizzleSource z_source, | ||
| 95 | Tegra::Texture::SwizzleSource w_source); | ||
| 96 | |||
| 97 | void DecorateViewName(GPUVAddr gpu_addr, std::string prefix); | ||
| 98 | |||
| 99 | private: | 103 | private: |
| 100 | u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, | 104 | u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, |
| 101 | Tegra::Texture::SwizzleSource y_source, | 105 | Tegra::Texture::SwizzleSource y_source, |
| @@ -111,8 +115,8 @@ private: | |||
| 111 | GLenum target{}; | 115 | GLenum target{}; |
| 112 | 116 | ||
| 113 | OGLTextureView texture_view; | 117 | OGLTextureView texture_view; |
| 114 | u32 swizzle; | 118 | u32 swizzle{}; |
| 115 | bool is_proxy; | 119 | bool is_proxy{}; |
| 116 | }; | 120 | }; |
| 117 | 121 | ||
| 118 | class TextureCacheOpenGL final : public TextureCacheBase { | 122 | class TextureCacheOpenGL final : public TextureCacheBase { |
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index af9684839..839178152 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp | |||
| @@ -342,7 +342,7 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, | |||
| 342 | ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v), | 342 | ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v), |
| 343 | }}; | 343 | }}; |
| 344 | 344 | ||
| 345 | state.texture_units[0].texture = screen_info.display_texture; | 345 | state.textures[0] = screen_info.display_texture; |
| 346 | // Workaround brigthness problems in SMO by enabling sRGB in the final output | 346 | // Workaround brigthness problems in SMO by enabling sRGB in the final output |
| 347 | // if it has been used in the frame. Needed because of this bug in QT: QTBUG-50987 | 347 | // if it has been used in the frame. Needed because of this bug in QT: QTBUG-50987 |
| 348 | state.framebuffer_srgb.enabled = OpenGLState::GetsRGBUsed(); | 348 | state.framebuffer_srgb.enabled = OpenGLState::GetsRGBUsed(); |
| @@ -352,7 +352,7 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, | |||
| 352 | glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | 352 | glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| 353 | // Restore default state | 353 | // Restore default state |
| 354 | state.framebuffer_srgb.enabled = false; | 354 | state.framebuffer_srgb.enabled = false; |
| 355 | state.texture_units[0].texture = 0; | 355 | state.textures[0] = 0; |
| 356 | state.AllDirty(); | 356 | state.AllDirty(); |
| 357 | state.Apply(); | 357 | state.Apply(); |
| 358 | // Clear sRGB state for the next frame | 358 | // Clear sRGB state for the next frame |
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/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp index 77151a24b..008109a99 100644 --- a/src/video_core/shader/decode/image.cpp +++ b/src/video_core/shader/decode/image.cpp | |||
| @@ -61,56 +61,54 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { | |||
| 61 | } | 61 | } |
| 62 | 62 | ||
| 63 | const auto type{instr.sust.image_type}; | 63 | const auto type{instr.sust.image_type}; |
| 64 | const auto& image{instr.sust.is_immediate ? GetImage(instr.image, type) | 64 | auto& image{instr.sust.is_immediate ? GetImage(instr.image, type) |
| 65 | : GetBindlessImage(instr.gpr39, type)}; | 65 | : GetBindlessImage(instr.gpr39, type)}; |
| 66 | image.MarkWrite(); | ||
| 67 | |||
| 66 | MetaImage meta{image, values}; | 68 | MetaImage meta{image, values}; |
| 67 | const Node store{Operation(OperationCode::ImageStore, meta, std::move(coords))}; | 69 | const Node store{Operation(OperationCode::ImageStore, meta, std::move(coords))}; |
| 68 | bb.push_back(store); | 70 | bb.push_back(store); |
| 69 | break; | 71 | break; |
| 70 | } | 72 | } |
| 71 | default: | 73 | default: |
| 72 | UNIMPLEMENTED_MSG("Unhandled conversion instruction: {}", opcode->get().GetName()); | 74 | UNIMPLEMENTED_MSG("Unhandled image instruction: {}", opcode->get().GetName()); |
| 73 | } | 75 | } |
| 74 | 76 | ||
| 75 | return pc; | 77 | return pc; |
| 76 | } | 78 | } |
| 77 | 79 | ||
| 78 | const Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { | 80 | Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { |
| 79 | const auto offset{static_cast<std::size_t>(image.index.Value())}; | 81 | const auto offset{static_cast<u64>(image.index.Value())}; |
| 80 | 82 | ||
| 81 | // If this image has already been used, return the existing mapping. | 83 | // If this image has already been used, return the existing mapping. |
| 82 | const auto itr{std::find_if(used_images.begin(), used_images.end(), | 84 | const auto it = used_images.find(offset); |
| 83 | [=](const Image& entry) { return entry.GetOffset() == offset; })}; | 85 | if (it != used_images.end()) { |
| 84 | if (itr != used_images.end()) { | 86 | ASSERT(it->second.GetType() == type); |
| 85 | ASSERT(itr->GetType() == type); | 87 | return it->second; |
| 86 | return *itr; | ||
| 87 | } | 88 | } |
| 88 | 89 | ||
| 89 | // Otherwise create a new mapping for this image. | 90 | // Otherwise create a new mapping for this image. |
| 90 | const std::size_t next_index{used_images.size()}; | 91 | const std::size_t next_index{used_images.size()}; |
| 91 | const Image entry{offset, next_index, type}; | 92 | return used_images.emplace(offset, Image{offset, next_index, type}).first->second; |
| 92 | return *used_images.emplace(entry).first; | ||
| 93 | } | 93 | } |
| 94 | 94 | ||
| 95 | const Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, | 95 | Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) { |
| 96 | Tegra::Shader::ImageType type) { | ||
| 97 | const Node image_register{GetRegister(reg)}; | 96 | const Node image_register{GetRegister(reg)}; |
| 98 | const auto [base_image, cbuf_index, cbuf_offset]{ | 97 | const auto [base_image, cbuf_index, cbuf_offset]{ |
| 99 | TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))}; | 98 | TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))}; |
| 100 | const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)}; | 99 | const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)}; |
| 101 | 100 | ||
| 102 | // If this image has already been used, return the existing mapping. | 101 | // If this image has already been used, return the existing mapping. |
| 103 | const auto itr{std::find_if(used_images.begin(), used_images.end(), | 102 | const auto it = used_images.find(cbuf_key); |
| 104 | [=](const Image& entry) { return entry.GetOffset() == cbuf_key; })}; | 103 | if (it != used_images.end()) { |
| 105 | if (itr != used_images.end()) { | 104 | ASSERT(it->second.GetType() == type); |
| 106 | ASSERT(itr->GetType() == type); | 105 | return it->second; |
| 107 | return *itr; | ||
| 108 | } | 106 | } |
| 109 | 107 | ||
| 110 | // Otherwise create a new mapping for this image. | 108 | // Otherwise create a new mapping for this image. |
| 111 | const std::size_t next_index{used_images.size()}; | 109 | const std::size_t next_index{used_images.size()}; |
| 112 | const Image entry{cbuf_index, cbuf_offset, next_index, type}; | 110 | return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type}) |
| 113 | return *used_images.emplace(entry).first; | 111 | .first->second; |
| 114 | } | 112 | } |
| 115 | 113 | ||
| 116 | } // namespace VideoCommon::Shader | 114 | } // namespace VideoCommon::Shader |
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h index 5db9313c4..b29aedce8 100644 --- a/src/video_core/shader/node.h +++ b/src/video_core/shader/node.h | |||
| @@ -273,46 +273,64 @@ private: | |||
| 273 | bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not. | 273 | bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not. |
| 274 | }; | 274 | }; |
| 275 | 275 | ||
| 276 | class Image { | 276 | class Image final { |
| 277 | public: | 277 | public: |
| 278 | explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type) | 278 | constexpr explicit Image(u64 offset, std::size_t index, Tegra::Shader::ImageType type) |
| 279 | : offset{offset}, index{index}, type{type}, is_bindless{false} {} | 279 | : offset{offset}, index{index}, type{type}, is_bindless{false} {} |
| 280 | 280 | ||
| 281 | explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, | 281 | constexpr explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, |
| 282 | Tegra::Shader::ImageType type) | 282 | Tegra::Shader::ImageType type) |
| 283 | : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type}, | 283 | : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type}, |
| 284 | is_bindless{true} {} | 284 | is_bindless{true} {} |
| 285 | 285 | ||
| 286 | explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, | 286 | constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, |
| 287 | bool is_bindless) | 287 | bool is_bindless, bool is_written, bool is_read) |
| 288 | : offset{offset}, index{index}, type{type}, is_bindless{is_bindless} {} | 288 | : offset{offset}, index{index}, type{type}, is_bindless{is_bindless}, |
| 289 | is_written{is_written}, is_read{is_read} {} | ||
| 289 | 290 | ||
| 290 | std::size_t GetOffset() const { | 291 | void MarkRead() { |
| 292 | is_read = true; | ||
| 293 | } | ||
| 294 | |||
| 295 | void MarkWrite() { | ||
| 296 | is_written = true; | ||
| 297 | } | ||
| 298 | |||
| 299 | constexpr std::size_t GetOffset() const { | ||
| 291 | return offset; | 300 | return offset; |
| 292 | } | 301 | } |
| 293 | 302 | ||
| 294 | std::size_t GetIndex() const { | 303 | constexpr std::size_t GetIndex() const { |
| 295 | return index; | 304 | return index; |
| 296 | } | 305 | } |
| 297 | 306 | ||
| 298 | Tegra::Shader::ImageType GetType() const { | 307 | constexpr Tegra::Shader::ImageType GetType() const { |
| 299 | return type; | 308 | return type; |
| 300 | } | 309 | } |
| 301 | 310 | ||
| 302 | bool IsBindless() const { | 311 | constexpr bool IsBindless() const { |
| 303 | return is_bindless; | 312 | return is_bindless; |
| 304 | } | 313 | } |
| 305 | 314 | ||
| 306 | bool operator<(const Image& rhs) const { | 315 | constexpr bool IsRead() const { |
| 307 | return std::tie(offset, index, type, is_bindless) < | 316 | return is_read; |
| 308 | std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless); | 317 | } |
| 318 | |||
| 319 | constexpr bool IsWritten() const { | ||
| 320 | return is_written; | ||
| 321 | } | ||
| 322 | |||
| 323 | constexpr std::pair<u32, u32> GetBindlessCBuf() const { | ||
| 324 | return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)}; | ||
| 309 | } | 325 | } |
| 310 | 326 | ||
| 311 | private: | 327 | private: |
| 312 | std::size_t offset{}; | 328 | u64 offset{}; |
| 313 | std::size_t index{}; | 329 | std::size_t index{}; |
| 314 | Tegra::Shader::ImageType type{}; | 330 | Tegra::Shader::ImageType type{}; |
| 315 | bool is_bindless{}; | 331 | bool is_bindless{}; |
| 332 | bool is_read{}; | ||
| 333 | bool is_written{}; | ||
| 316 | }; | 334 | }; |
| 317 | 335 | ||
| 318 | struct GlobalMemoryBase { | 336 | struct GlobalMemoryBase { |
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index bcc9b79b6..0f891eace 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h | |||
| @@ -95,7 +95,7 @@ public: | |||
| 95 | return used_samplers; | 95 | return used_samplers; |
| 96 | } | 96 | } |
| 97 | 97 | ||
| 98 | const std::set<Image>& GetImages() const { | 98 | const std::map<u64, Image>& GetImages() const { |
| 99 | return used_images; | 99 | return used_images; |
| 100 | } | 100 | } |
| 101 | 101 | ||
| @@ -272,10 +272,10 @@ private: | |||
| 272 | bool is_shadow); | 272 | bool is_shadow); |
| 273 | 273 | ||
| 274 | /// Accesses an image. | 274 | /// Accesses an image. |
| 275 | const Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type); | 275 | Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type); |
| 276 | 276 | ||
| 277 | /// Access a bindless image sampler. | 277 | /// Access a bindless image sampler. |
| 278 | const Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type); | 278 | Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type); |
| 279 | 279 | ||
| 280 | /// Extracts a sequence of bits from a node | 280 | /// Extracts a sequence of bits from a node |
| 281 | Node BitfieldExtract(Node value, u32 offset, u32 bits); | 281 | Node BitfieldExtract(Node value, u32 offset, u32 bits); |
| @@ -356,7 +356,7 @@ private: | |||
| 356 | std::set<Tegra::Shader::Attribute::Index> used_output_attributes; | 356 | std::set<Tegra::Shader::Attribute::Index> used_output_attributes; |
| 357 | std::map<u32, ConstBuffer> used_cbufs; | 357 | std::map<u32, ConstBuffer> used_cbufs; |
| 358 | std::set<Sampler> used_samplers; | 358 | std::set<Sampler> used_samplers; |
| 359 | std::set<Image> used_images; | 359 | std::map<u64, Image> used_images; |
| 360 | std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{}; | 360 | std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{}; |
| 361 | std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory; | 361 | std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory; |
| 362 | bool uses_layer{}; | 362 | bool uses_layer{}; |
diff --git a/src/video_core/texture_cache/surface_base.h b/src/video_core/texture_cache/surface_base.h index bcce8d863..5e497e49f 100644 --- a/src/video_core/texture_cache/surface_base.h +++ b/src/video_core/texture_cache/surface_base.h | |||
| @@ -195,18 +195,18 @@ public: | |||
| 195 | 195 | ||
| 196 | virtual void DownloadTexture(std::vector<u8>& staging_buffer) = 0; | 196 | virtual void DownloadTexture(std::vector<u8>& staging_buffer) = 0; |
| 197 | 197 | ||
| 198 | void MarkAsModified(const bool is_modified_, const u64 tick) { | 198 | void MarkAsModified(bool is_modified_, u64 tick) { |
| 199 | is_modified = is_modified_ || is_target; | 199 | is_modified = is_modified_ || is_target; |
| 200 | modification_tick = tick; | 200 | modification_tick = tick; |
| 201 | } | 201 | } |
| 202 | 202 | ||
| 203 | void MarkAsRenderTarget(const bool is_target, const u32 index) { | 203 | void MarkAsRenderTarget(bool is_target_, u32 index_) { |
| 204 | this->is_target = is_target; | 204 | is_target = is_target_; |
| 205 | this->index = index; | 205 | index = index_; |
| 206 | } | 206 | } |
| 207 | 207 | ||
| 208 | void MarkAsPicked(const bool is_picked) { | 208 | void MarkAsPicked(bool is_picked_) { |
| 209 | this->is_picked = is_picked; | 209 | is_picked = is_picked_; |
| 210 | } | 210 | } |
| 211 | 211 | ||
| 212 | bool IsModified() const { | 212 | bool IsModified() const { |
diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp index fd5472451..1e4d3fb79 100644 --- a/src/video_core/texture_cache/surface_params.cpp +++ b/src/video_core/texture_cache/surface_params.cpp | |||
| @@ -24,55 +24,62 @@ using VideoCore::Surface::SurfaceTarget; | |||
| 24 | using VideoCore::Surface::SurfaceTargetFromTextureType; | 24 | using VideoCore::Surface::SurfaceTargetFromTextureType; |
| 25 | using VideoCore::Surface::SurfaceType; | 25 | using VideoCore::Surface::SurfaceType; |
| 26 | 26 | ||
| 27 | SurfaceTarget TextureType2SurfaceTarget(Tegra::Shader::TextureType type, bool is_array) { | 27 | namespace { |
| 28 | |||
| 29 | SurfaceTarget TextureTypeToSurfaceTarget(Tegra::Shader::TextureType type, bool is_array) { | ||
| 28 | switch (type) { | 30 | switch (type) { |
| 29 | case Tegra::Shader::TextureType::Texture1D: { | 31 | case Tegra::Shader::TextureType::Texture1D: |
| 30 | if (is_array) | 32 | return is_array ? SurfaceTarget::Texture1DArray : SurfaceTarget::Texture1D; |
| 31 | return SurfaceTarget::Texture1DArray; | 33 | case Tegra::Shader::TextureType::Texture2D: |
| 32 | else | 34 | return is_array ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D; |
| 33 | return SurfaceTarget::Texture1D; | 35 | case Tegra::Shader::TextureType::Texture3D: |
| 34 | } | ||
| 35 | case Tegra::Shader::TextureType::Texture2D: { | ||
| 36 | if (is_array) | ||
| 37 | return SurfaceTarget::Texture2DArray; | ||
| 38 | else | ||
| 39 | return SurfaceTarget::Texture2D; | ||
| 40 | } | ||
| 41 | case Tegra::Shader::TextureType::Texture3D: { | ||
| 42 | ASSERT(!is_array); | 36 | ASSERT(!is_array); |
| 43 | return SurfaceTarget::Texture3D; | 37 | return SurfaceTarget::Texture3D; |
| 44 | } | 38 | case Tegra::Shader::TextureType::TextureCube: |
| 45 | case Tegra::Shader::TextureType::TextureCube: { | 39 | return is_array ? SurfaceTarget::TextureCubeArray : SurfaceTarget::TextureCubemap; |
| 46 | if (is_array) | 40 | default: |
| 47 | return SurfaceTarget::TextureCubeArray; | ||
| 48 | else | ||
| 49 | return SurfaceTarget::TextureCubemap; | ||
| 50 | } | ||
| 51 | default: { | ||
| 52 | UNREACHABLE(); | 41 | UNREACHABLE(); |
| 53 | return SurfaceTarget::Texture2D; | 42 | return SurfaceTarget::Texture2D; |
| 54 | } | 43 | } |
| 44 | } | ||
| 45 | |||
| 46 | SurfaceTarget ImageTypeToSurfaceTarget(Tegra::Shader::ImageType type) { | ||
| 47 | switch (type) { | ||
| 48 | case Tegra::Shader::ImageType::Texture1D: | ||
| 49 | return SurfaceTarget::Texture1D; | ||
| 50 | case Tegra::Shader::ImageType::TextureBuffer: | ||
| 51 | return SurfaceTarget::TextureBuffer; | ||
| 52 | case Tegra::Shader::ImageType::Texture1DArray: | ||
| 53 | return SurfaceTarget::Texture1DArray; | ||
| 54 | case Tegra::Shader::ImageType::Texture2D: | ||
| 55 | return SurfaceTarget::Texture2D; | ||
| 56 | case Tegra::Shader::ImageType::Texture2DArray: | ||
| 57 | return SurfaceTarget::Texture2DArray; | ||
| 58 | case Tegra::Shader::ImageType::Texture3D: | ||
| 59 | return SurfaceTarget::Texture3D; | ||
| 60 | default: | ||
| 61 | UNREACHABLE(); | ||
| 62 | return SurfaceTarget::Texture2D; | ||
| 55 | } | 63 | } |
| 56 | } | 64 | } |
| 57 | 65 | ||
| 58 | namespace { | ||
| 59 | constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { | 66 | constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { |
| 60 | return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); | 67 | return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); |
| 61 | } | 68 | } |
| 69 | |||
| 62 | } // Anonymous namespace | 70 | } // Anonymous namespace |
| 63 | 71 | ||
| 64 | SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, | 72 | SurfaceParams SurfaceParams::CreateForTexture(const Tegra::Texture::TICEntry& tic, |
| 65 | const Tegra::Texture::FullTextureInfo& config, | ||
| 66 | const VideoCommon::Shader::Sampler& entry) { | 73 | const VideoCommon::Shader::Sampler& entry) { |
| 67 | SurfaceParams params; | 74 | SurfaceParams params; |
| 68 | params.is_tiled = config.tic.IsTiled(); | 75 | params.is_tiled = tic.IsTiled(); |
| 69 | params.srgb_conversion = config.tic.IsSrgbConversionEnabled(); | 76 | params.srgb_conversion = tic.IsSrgbConversionEnabled(); |
| 70 | params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, | 77 | params.block_width = params.is_tiled ? tic.BlockWidth() : 0, |
| 71 | params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, | 78 | params.block_height = params.is_tiled ? tic.BlockHeight() : 0, |
| 72 | params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, | 79 | params.block_depth = params.is_tiled ? tic.BlockDepth() : 0, |
| 73 | params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; | 80 | params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1; |
| 74 | params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), | 81 | params.pixel_format = |
| 75 | params.srgb_conversion); | 82 | PixelFormatFromTextureFormat(tic.format, tic.r_type.Value(), params.srgb_conversion); |
| 76 | params.type = GetFormatType(params.pixel_format); | 83 | params.type = GetFormatType(params.pixel_format); |
| 77 | if (entry.IsShadow() && params.type == SurfaceType::ColorTexture) { | 84 | if (entry.IsShadow() && params.type == SurfaceType::ColorTexture) { |
| 78 | switch (params.pixel_format) { | 85 | switch (params.pixel_format) { |
| @@ -92,31 +99,72 @@ SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, | |||
| 92 | } | 99 | } |
| 93 | params.type = GetFormatType(params.pixel_format); | 100 | params.type = GetFormatType(params.pixel_format); |
| 94 | } | 101 | } |
| 95 | params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); | 102 | params.component_type = ComponentTypeFromTexture(tic.r_type.Value()); |
| 96 | params.type = GetFormatType(params.pixel_format); | 103 | params.type = GetFormatType(params.pixel_format); |
| 97 | // TODO: on 1DBuffer we should use the tic info. | 104 | // TODO: on 1DBuffer we should use the tic info. |
| 98 | if (!config.tic.IsBuffer()) { | 105 | if (tic.IsBuffer()) { |
| 99 | params.target = TextureType2SurfaceTarget(entry.GetType(), entry.IsArray()); | 106 | params.target = SurfaceTarget::TextureBuffer; |
| 100 | params.width = config.tic.Width(); | 107 | params.width = tic.Width(); |
| 101 | params.height = config.tic.Height(); | 108 | params.pitch = params.width * params.GetBytesPerPixel(); |
| 102 | params.depth = config.tic.Depth(); | 109 | params.height = 1; |
| 103 | params.pitch = params.is_tiled ? 0 : config.tic.Pitch(); | 110 | params.depth = 1; |
| 111 | params.num_levels = 1; | ||
| 112 | params.emulated_levels = 1; | ||
| 113 | params.is_layered = false; | ||
| 114 | } else { | ||
| 115 | params.target = TextureTypeToSurfaceTarget(entry.GetType(), entry.IsArray()); | ||
| 116 | params.width = tic.Width(); | ||
| 117 | params.height = tic.Height(); | ||
| 118 | params.depth = tic.Depth(); | ||
| 119 | params.pitch = params.is_tiled ? 0 : tic.Pitch(); | ||
| 104 | if (params.target == SurfaceTarget::TextureCubemap || | 120 | if (params.target == SurfaceTarget::TextureCubemap || |
| 105 | params.target == SurfaceTarget::TextureCubeArray) { | 121 | params.target == SurfaceTarget::TextureCubeArray) { |
| 106 | params.depth *= 6; | 122 | params.depth *= 6; |
| 107 | } | 123 | } |
| 108 | params.num_levels = config.tic.max_mip_level + 1; | 124 | params.num_levels = tic.max_mip_level + 1; |
| 109 | params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); | 125 | params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); |
| 110 | params.is_layered = params.IsLayered(); | 126 | params.is_layered = params.IsLayered(); |
| 111 | } else { | 127 | } |
| 128 | return params; | ||
| 129 | } | ||
| 130 | |||
| 131 | SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic, | ||
| 132 | const VideoCommon::Shader::Image& entry) { | ||
| 133 | SurfaceParams params; | ||
| 134 | params.is_tiled = tic.IsTiled(); | ||
| 135 | params.srgb_conversion = tic.IsSrgbConversionEnabled(); | ||
| 136 | params.block_width = params.is_tiled ? tic.BlockWidth() : 0, | ||
| 137 | params.block_height = params.is_tiled ? tic.BlockHeight() : 0, | ||
| 138 | params.block_depth = params.is_tiled ? tic.BlockDepth() : 0, | ||
| 139 | params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1; | ||
| 140 | params.pixel_format = | ||
| 141 | PixelFormatFromTextureFormat(tic.format, tic.r_type.Value(), params.srgb_conversion); | ||
| 142 | params.type = GetFormatType(params.pixel_format); | ||
| 143 | params.component_type = ComponentTypeFromTexture(tic.r_type.Value()); | ||
| 144 | params.type = GetFormatType(params.pixel_format); | ||
| 145 | params.target = ImageTypeToSurfaceTarget(entry.GetType()); | ||
| 146 | // TODO: on 1DBuffer we should use the tic info. | ||
| 147 | if (tic.IsBuffer()) { | ||
| 112 | params.target = SurfaceTarget::TextureBuffer; | 148 | params.target = SurfaceTarget::TextureBuffer; |
| 113 | params.width = config.tic.Width(); | 149 | params.width = tic.Width(); |
| 114 | params.pitch = params.width * params.GetBytesPerPixel(); | 150 | params.pitch = params.width * params.GetBytesPerPixel(); |
| 115 | params.height = 1; | 151 | params.height = 1; |
| 116 | params.depth = 1; | 152 | params.depth = 1; |
| 117 | params.num_levels = 1; | 153 | params.num_levels = 1; |
| 118 | params.emulated_levels = 1; | 154 | params.emulated_levels = 1; |
| 119 | params.is_layered = false; | 155 | params.is_layered = false; |
| 156 | } else { | ||
| 157 | params.width = tic.Width(); | ||
| 158 | params.height = tic.Height(); | ||
| 159 | params.depth = tic.Depth(); | ||
| 160 | params.pitch = params.is_tiled ? 0 : tic.Pitch(); | ||
| 161 | if (params.target == SurfaceTarget::TextureCubemap || | ||
| 162 | params.target == SurfaceTarget::TextureCubeArray) { | ||
| 163 | params.depth *= 6; | ||
| 164 | } | ||
| 165 | params.num_levels = tic.max_mip_level + 1; | ||
| 166 | params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); | ||
| 167 | params.is_layered = params.IsLayered(); | ||
| 120 | } | 168 | } |
| 121 | return params; | 169 | return params; |
| 122 | } | 170 | } |
diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h index e7ef66ee2..c58e7f8a4 100644 --- a/src/video_core/texture_cache/surface_params.h +++ b/src/video_core/texture_cache/surface_params.h | |||
| @@ -4,8 +4,6 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <map> | ||
| 8 | |||
| 9 | #include "common/alignment.h" | 7 | #include "common/alignment.h" |
| 10 | #include "common/bit_util.h" | 8 | #include "common/bit_util.h" |
| 11 | #include "common/cityhash.h" | 9 | #include "common/cityhash.h" |
| @@ -23,10 +21,13 @@ using VideoCore::Surface::SurfaceCompression; | |||
| 23 | class SurfaceParams { | 21 | class SurfaceParams { |
| 24 | public: | 22 | public: |
| 25 | /// Creates SurfaceCachedParams from a texture configuration. | 23 | /// Creates SurfaceCachedParams from a texture configuration. |
| 26 | static SurfaceParams CreateForTexture(Core::System& system, | 24 | static SurfaceParams CreateForTexture(const Tegra::Texture::TICEntry& tic, |
| 27 | const Tegra::Texture::FullTextureInfo& config, | ||
| 28 | const VideoCommon::Shader::Sampler& entry); | 25 | const VideoCommon::Shader::Sampler& entry); |
| 29 | 26 | ||
| 27 | /// Creates SurfaceCachedParams from an image configuration. | ||
| 28 | static SurfaceParams CreateForImage(const Tegra::Texture::TICEntry& tic, | ||
| 29 | const VideoCommon::Shader::Image& entry); | ||
| 30 | |||
| 30 | /// Creates SurfaceCachedParams for a depth buffer configuration. | 31 | /// Creates SurfaceCachedParams for a depth buffer configuration. |
| 31 | static SurfaceParams CreateForDepthBuffer( | 32 | static SurfaceParams CreateForDepthBuffer( |
| 32 | Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, | 33 | Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, |
diff --git a/src/video_core/texture_cache/surface_view.cpp b/src/video_core/texture_cache/surface_view.cpp index 467696a4c..57a1f5803 100644 --- a/src/video_core/texture_cache/surface_view.cpp +++ b/src/video_core/texture_cache/surface_view.cpp | |||
| @@ -10,7 +10,7 @@ | |||
| 10 | namespace VideoCommon { | 10 | namespace VideoCommon { |
| 11 | 11 | ||
| 12 | std::size_t ViewParams::Hash() const { | 12 | std::size_t ViewParams::Hash() const { |
| 13 | return static_cast<std::size_t>(base_layer) ^ static_cast<std::size_t>(num_layers << 16) ^ | 13 | return static_cast<std::size_t>(base_layer) ^ (static_cast<std::size_t>(num_layers) << 16) ^ |
| 14 | (static_cast<std::size_t>(base_level) << 24) ^ | 14 | (static_cast<std::size_t>(base_level) << 24) ^ |
| 15 | (static_cast<std::size_t>(num_levels) << 32) ^ (static_cast<std::size_t>(target) << 36); | 15 | (static_cast<std::size_t>(num_levels) << 32) ^ (static_cast<std::size_t>(target) << 36); |
| 16 | } | 16 | } |
diff --git a/src/video_core/texture_cache/surface_view.h b/src/video_core/texture_cache/surface_view.h index 04ca5639b..b17fd11a9 100644 --- a/src/video_core/texture_cache/surface_view.h +++ b/src/video_core/texture_cache/surface_view.h | |||
| @@ -13,8 +13,8 @@ | |||
| 13 | namespace VideoCommon { | 13 | namespace VideoCommon { |
| 14 | 14 | ||
| 15 | struct ViewParams { | 15 | struct ViewParams { |
| 16 | ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer, u32 num_layers, | 16 | constexpr explicit ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer, |
| 17 | u32 base_level, u32 num_levels) | 17 | u32 num_layers, u32 base_level, u32 num_levels) |
| 18 | : target{target}, base_layer{base_layer}, num_layers{num_layers}, base_level{base_level}, | 18 | : target{target}, base_layer{base_layer}, num_layers{num_layers}, base_level{base_level}, |
| 19 | num_levels{num_levels} {} | 19 | num_levels{num_levels} {} |
| 20 | 20 | ||
| @@ -22,12 +22,6 @@ struct ViewParams { | |||
| 22 | 22 | ||
| 23 | bool operator==(const ViewParams& rhs) const; | 23 | bool operator==(const ViewParams& rhs) const; |
| 24 | 24 | ||
| 25 | VideoCore::Surface::SurfaceTarget target{}; | ||
| 26 | u32 base_layer{}; | ||
| 27 | u32 num_layers{}; | ||
| 28 | u32 base_level{}; | ||
| 29 | u32 num_levels{}; | ||
| 30 | |||
| 31 | bool IsLayered() const { | 25 | bool IsLayered() const { |
| 32 | switch (target) { | 26 | switch (target) { |
| 33 | case VideoCore::Surface::SurfaceTarget::Texture1DArray: | 27 | case VideoCore::Surface::SurfaceTarget::Texture1DArray: |
| @@ -39,13 +33,19 @@ struct ViewParams { | |||
| 39 | return false; | 33 | return false; |
| 40 | } | 34 | } |
| 41 | } | 35 | } |
| 36 | |||
| 37 | VideoCore::Surface::SurfaceTarget target{}; | ||
| 38 | u32 base_layer{}; | ||
| 39 | u32 num_layers{}; | ||
| 40 | u32 base_level{}; | ||
| 41 | u32 num_levels{}; | ||
| 42 | }; | 42 | }; |
| 43 | 43 | ||
| 44 | class ViewBase { | 44 | class ViewBase { |
| 45 | public: | 45 | public: |
| 46 | ViewBase(const ViewParams& params) : params{params} {} | 46 | constexpr explicit ViewBase(const ViewParams& params) : params{params} {} |
| 47 | 47 | ||
| 48 | const ViewParams& GetViewParams() const { | 48 | constexpr const ViewParams& GetViewParams() const { |
| 49 | return params; | 49 | return params; |
| 50 | } | 50 | } |
| 51 | 51 | ||
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 2ec0203d1..877c6635d 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h | |||
| @@ -89,14 +89,29 @@ public: | |||
| 89 | } | 89 | } |
| 90 | } | 90 | } |
| 91 | 91 | ||
| 92 | TView GetTextureSurface(const Tegra::Texture::FullTextureInfo& config, | 92 | TView GetTextureSurface(const Tegra::Texture::TICEntry& tic, |
| 93 | const VideoCommon::Shader::Sampler& entry) { | 93 | const VideoCommon::Shader::Sampler& entry) { |
| 94 | std::lock_guard lock{mutex}; | 94 | std::lock_guard lock{mutex}; |
| 95 | const auto gpu_addr{config.tic.Address()}; | 95 | const auto gpu_addr{tic.Address()}; |
| 96 | if (!gpu_addr) { | 96 | if (!gpu_addr) { |
| 97 | return {}; | 97 | return {}; |
| 98 | } | 98 | } |
| 99 | const auto params{SurfaceParams::CreateForTexture(system, config, entry)}; | 99 | const auto params{SurfaceParams::CreateForTexture(tic, entry)}; |
| 100 | const auto [surface, view] = GetSurface(gpu_addr, params, true, false); | ||
| 101 | if (guard_samplers) { | ||
| 102 | sampled_textures.push_back(surface); | ||
| 103 | } | ||
| 104 | return view; | ||
| 105 | } | ||
| 106 | |||
| 107 | TView GetImageSurface(const Tegra::Texture::TICEntry& tic, | ||
| 108 | const VideoCommon::Shader::Image& entry) { | ||
| 109 | std::lock_guard lock{mutex}; | ||
| 110 | const auto gpu_addr{tic.Address()}; | ||
| 111 | if (!gpu_addr) { | ||
| 112 | return {}; | ||
| 113 | } | ||
| 114 | const auto params{SurfaceParams::CreateForImage(tic, entry)}; | ||
| 100 | const auto [surface, view] = GetSurface(gpu_addr, params, true, false); | 115 | const auto [surface, view] = GetSurface(gpu_addr, params, true, false); |
| 101 | if (guard_samplers) { | 116 | if (guard_samplers) { |
| 102 | sampled_textures.push_back(surface); | 117 | sampled_textures.push_back(surface); |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 0456248ac..f594106bf 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -517,10 +517,37 @@ void Config::ReadPathValues() { | |||
| 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.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString(); | 519 | UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString(); |
| 520 | UISettings::values.game_directory_path = | 520 | UISettings::values.game_dir_deprecated = |
| 521 | ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString(); | 521 | ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString(); |
| 522 | UISettings::values.game_directory_deepscan = | 522 | UISettings::values.game_dir_deprecated_deepscan = |
| 523 | 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 | } | ||
| 524 | UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); | 551 | UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); |
| 525 | 552 | ||
| 526 | qt_config->endGroup(); | 553 | qt_config->endGroup(); |
| @@ -899,10 +926,15 @@ void Config::SavePathValues() { | |||
| 899 | WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path); | 926 | WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path); |
| 900 | WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); | 927 | WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); |
| 901 | WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path); | 928 | WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path); |
| 902 | WriteSetting(QStringLiteral("gameListRootDir"), UISettings::values.game_directory_path, | 929 | qt_config->beginWriteArray(QStringLiteral("gamedirs")); |
| 903 | QStringLiteral(".")); | 930 | for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) { |
| 904 | WriteSetting(QStringLiteral("gameListDeepScan"), UISettings::values.game_directory_deepscan, | 931 | qt_config->setArrayIndex(i); |
| 905 | 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(); | ||
| 906 | WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); | 938 | WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); |
| 907 | 939 | ||
| 908 | 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 ac57229d5..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" |
| @@ -216,8 +219,7 @@ GMainWindow::GMainWindow() | |||
| 216 | OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); | 219 | OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); |
| 217 | 220 | ||
| 218 | game_list->LoadCompatibilityList(); | 221 | game_list->LoadCompatibilityList(); |
| 219 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 222 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 220 | UISettings::values.game_directory_deepscan); | ||
| 221 | 223 | ||
| 222 | // Show one-time "callout" messages to the user | 224 | // Show one-time "callout" messages to the user |
| 223 | ShowTelemetryCallout(); | 225 | ShowTelemetryCallout(); |
| @@ -427,6 +429,10 @@ void GMainWindow::InitializeWidgets() { | |||
| 427 | game_list = new GameList(vfs, provider.get(), this); | 429 | game_list = new GameList(vfs, provider.get(), this); |
| 428 | ui.horizontalLayout->addWidget(game_list); | 430 | ui.horizontalLayout->addWidget(game_list); |
| 429 | 431 | ||
| 432 | game_list_placeholder = new GameListPlaceholder(this); | ||
| 433 | ui.horizontalLayout->addWidget(game_list_placeholder); | ||
| 434 | game_list_placeholder->setVisible(false); | ||
| 435 | |||
| 430 | loading_screen = new LoadingScreen(this); | 436 | loading_screen = new LoadingScreen(this); |
| 431 | loading_screen->hide(); | 437 | loading_screen->hide(); |
| 432 | ui.horizontalLayout->addWidget(loading_screen); | 438 | ui.horizontalLayout->addWidget(loading_screen); |
| @@ -660,6 +666,7 @@ void GMainWindow::RestoreUIState() { | |||
| 660 | 666 | ||
| 661 | void GMainWindow::ConnectWidgetEvents() { | 667 | void GMainWindow::ConnectWidgetEvents() { |
| 662 | 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); | ||
| 663 | connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); | 670 | connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); |
| 664 | connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, | 671 | connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, |
| 665 | &GMainWindow::OnTransferableShaderCacheOpenFile); | 672 | &GMainWindow::OnTransferableShaderCacheOpenFile); |
| @@ -667,6 +674,11 @@ void GMainWindow::ConnectWidgetEvents() { | |||
| 667 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); | 674 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); |
| 668 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, | 675 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, |
| 669 | &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 | |||
| 670 | connect(game_list, &GameList::OpenPerGameGeneralRequested, this, | 682 | connect(game_list, &GameList::OpenPerGameGeneralRequested, this, |
| 671 | &GMainWindow::OnGameListOpenPerGameProperties); | 683 | &GMainWindow::OnGameListOpenPerGameProperties); |
| 672 | 684 | ||
| @@ -684,8 +696,6 @@ void GMainWindow::ConnectMenuEvents() { | |||
| 684 | connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); | 696 | connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); |
| 685 | connect(ui.action_Install_File_NAND, &QAction::triggered, this, | 697 | connect(ui.action_Install_File_NAND, &QAction::triggered, this, |
| 686 | &GMainWindow::OnMenuInstallToNAND); | 698 | &GMainWindow::OnMenuInstallToNAND); |
| 687 | connect(ui.action_Select_Game_List_Root, &QAction::triggered, this, | ||
| 688 | &GMainWindow::OnMenuSelectGameListRoot); | ||
| 689 | connect(ui.action_Select_NAND_Directory, &QAction::triggered, this, | 699 | connect(ui.action_Select_NAND_Directory, &QAction::triggered, this, |
| 690 | [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); }); | 700 | [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); }); |
| 691 | connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this, | 701 | connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this, |
| @@ -950,6 +960,7 @@ void GMainWindow::BootGame(const QString& filename) { | |||
| 950 | // Update the GUI | 960 | // Update the GUI |
| 951 | if (ui.action_Single_Window_Mode->isChecked()) { | 961 | if (ui.action_Single_Window_Mode->isChecked()) { |
| 952 | game_list->hide(); | 962 | game_list->hide(); |
| 963 | game_list_placeholder->hide(); | ||
| 953 | } | 964 | } |
| 954 | status_bar_update_timer.start(2000); | 965 | status_bar_update_timer.start(2000); |
| 955 | 966 | ||
| @@ -1007,7 +1018,10 @@ void GMainWindow::ShutdownGame() { | |||
| 1007 | render_window->hide(); | 1018 | render_window->hide(); |
| 1008 | loading_screen->hide(); | 1019 | loading_screen->hide(); |
| 1009 | loading_screen->Clear(); | 1020 | loading_screen->Clear(); |
| 1010 | game_list->show(); | 1021 | if (game_list->isEmpty()) |
| 1022 | game_list_placeholder->show(); | ||
| 1023 | else | ||
| 1024 | game_list->show(); | ||
| 1011 | game_list->setFilterFocus(); | 1025 | game_list->setFilterFocus(); |
| 1012 | 1026 | ||
| 1013 | UpdateWindowTitle(); | 1027 | UpdateWindowTitle(); |
| @@ -1298,6 +1312,47 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, | |||
| 1298 | QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); | 1312 | QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); |
| 1299 | } | 1313 | } |
| 1300 | 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 | |||
| 1301 | void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { | 1356 | void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { |
| 1302 | u64 title_id{}; | 1357 | u64 title_id{}; |
| 1303 | const auto v_file = Core::GetGameFileFromPath(vfs, file); | 1358 | const auto v_file = Core::GetGameFileFromPath(vfs, file); |
| @@ -1316,8 +1371,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { | |||
| 1316 | 1371 | ||
| 1317 | 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); |
| 1318 | if (reload) { | 1373 | if (reload) { |
| 1319 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 1374 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1320 | UISettings::values.game_directory_deepscan); | ||
| 1321 | } | 1375 | } |
| 1322 | 1376 | ||
| 1323 | config->Save(); | 1377 | config->Save(); |
| @@ -1407,8 +1461,7 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 1407 | const auto success = [this]() { | 1461 | const auto success = [this]() { |
| 1408 | QMessageBox::information(this, tr("Successfully Installed"), | 1462 | QMessageBox::information(this, tr("Successfully Installed"), |
| 1409 | tr("The file was successfully installed.")); | 1463 | tr("The file was successfully installed.")); |
| 1410 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 1464 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1411 | UISettings::values.game_directory_deepscan); | ||
| 1412 | FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + | 1465 | FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + |
| 1413 | DIR_SEP + "game_list"); | 1466 | DIR_SEP + "game_list"); |
| 1414 | }; | 1467 | }; |
| @@ -1533,14 +1586,6 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 1533 | } | 1586 | } |
| 1534 | } | 1587 | } |
| 1535 | 1588 | ||
| 1536 | void GMainWindow::OnMenuSelectGameListRoot() { | ||
| 1537 | QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); | ||
| 1538 | if (!dir_path.isEmpty()) { | ||
| 1539 | UISettings::values.game_directory_path = dir_path; | ||
| 1540 | game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan); | ||
| 1541 | } | ||
| 1542 | } | ||
| 1543 | |||
| 1544 | void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) { | 1589 | void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) { |
| 1545 | const auto res = QMessageBox::information( | 1590 | const auto res = QMessageBox::information( |
| 1546 | this, tr("Changing Emulated Directory"), | 1591 | this, tr("Changing Emulated Directory"), |
| @@ -1559,8 +1604,7 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) | |||
| 1559 | : FileUtil::UserPath::NANDDir, | 1604 | : FileUtil::UserPath::NANDDir, |
| 1560 | dir_path.toStdString()); | 1605 | dir_path.toStdString()); |
| 1561 | Service::FileSystem::CreateFactories(*vfs); | 1606 | Service::FileSystem::CreateFactories(*vfs); |
| 1562 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 1607 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1563 | UISettings::values.game_directory_deepscan); | ||
| 1564 | } | 1608 | } |
| 1565 | } | 1609 | } |
| 1566 | 1610 | ||
| @@ -1724,11 +1768,11 @@ void GMainWindow::OnConfigure() { | |||
| 1724 | if (UISettings::values.enable_discord_presence != old_discord_presence) { | 1768 | if (UISettings::values.enable_discord_presence != old_discord_presence) { |
| 1725 | SetDiscordEnabled(UISettings::values.enable_discord_presence); | 1769 | SetDiscordEnabled(UISettings::values.enable_discord_presence); |
| 1726 | } | 1770 | } |
| 1771 | emit UpdateThemedIcons(); | ||
| 1727 | 1772 | ||
| 1728 | 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); |
| 1729 | if (reload) { | 1774 | if (reload) { |
| 1730 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 1775 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1731 | UISettings::values.game_directory_deepscan); | ||
| 1732 | } | 1776 | } |
| 1733 | 1777 | ||
| 1734 | config->Save(); | 1778 | config->Save(); |
| @@ -1992,8 +2036,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { | |||
| 1992 | Service::FileSystem::CreateFactories(*vfs); | 2036 | Service::FileSystem::CreateFactories(*vfs); |
| 1993 | 2037 | ||
| 1994 | if (behavior == ReinitializeKeyBehavior::Warning) { | 2038 | if (behavior == ReinitializeKeyBehavior::Warning) { |
| 1995 | game_list->PopulateAsync(UISettings::values.game_directory_path, | 2039 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1996 | UISettings::values.game_directory_deepscan); | ||
| 1997 | } | 2040 | } |
| 1998 | } | 2041 | } |
| 1999 | 2042 | ||
| @@ -2158,7 +2201,6 @@ void GMainWindow::UpdateUITheme() { | |||
| 2158 | } | 2201 | } |
| 2159 | 2202 | ||
| 2160 | QIcon::setThemeSearchPaths(theme_paths); | 2203 | QIcon::setThemeSearchPaths(theme_paths); |
| 2161 | emit UpdateThemedIcons(); | ||
| 2162 | } | 2204 | } |
| 2163 | 2205 | ||
| 2164 | void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { | 2206 | void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { |
| @@ -2187,6 +2229,14 @@ int main(int argc, char* argv[]) { | |||
| 2187 | QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); | 2229 | QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); |
| 2188 | QCoreApplication::setApplicationName(QStringLiteral("yuzu")); | 2230 | QCoreApplication::setApplicationName(QStringLiteral("yuzu")); |
| 2189 | 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 | |||
| 2190 | // 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 |
| 2191 | QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); | 2241 | QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); |
| 2192 | QApplication app(argc, argv); | 2242 | QApplication app(argc, argv); |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 501608ddc..7d16188cb 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -30,6 +30,7 @@ class ProfilerWidget; | |||
| 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; |
| @@ -186,12 +187,13 @@ private slots: | |||
| 186 | void OnGameListCopyTID(u64 program_id); | 187 | void OnGameListCopyTID(u64 program_id); |
| 187 | void OnGameListNavigateToGamedbEntry(u64 program_id, | 188 | void OnGameListNavigateToGamedbEntry(u64 program_id, |
| 188 | const CompatibilityList& compatibility_list); | 189 | const CompatibilityList& compatibility_list); |
| 190 | void OnGameListOpenDirectory(const QString& directory); | ||
| 191 | void OnGameListAddDirectory(); | ||
| 192 | void OnGameListShowList(bool show); | ||
| 189 | void OnGameListOpenPerGameProperties(const std::string& file); | 193 | void OnGameListOpenPerGameProperties(const std::string& file); |
| 190 | void OnMenuLoadFile(); | 194 | void OnMenuLoadFile(); |
| 191 | void OnMenuLoadFolder(); | 195 | void OnMenuLoadFolder(); |
| 192 | void OnMenuInstallToNAND(); | 196 | void OnMenuInstallToNAND(); |
| 193 | /// Called whenever a user selects the "File->Select Game List Root" menu item | ||
| 194 | void OnMenuSelectGameListRoot(); | ||
| 195 | /// 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 |
| 196 | void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target); | 198 | void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target); |
| 197 | void OnMenuRecentFile(); | 199 | void OnMenuRecentFile(); |
| @@ -223,6 +225,8 @@ private: | |||
| 223 | GameList* game_list; | 225 | GameList* game_list; |
| 224 | LoadingScreen* loading_screen; | 226 | LoadingScreen* loading_screen; |
| 225 | 227 | ||
| 228 | GameListPlaceholder* game_list_placeholder; | ||
| 229 | |||
| 226 | // Status bar elements | 230 | // Status bar elements |
| 227 | QLabel* message_label = nullptr; | 231 | QLabel* message_label = nullptr; |
| 228 | 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*); | ||