summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/core.cpp3
-rw-r--r--src/core/crypto/key_manager.cpp241
-rw-r--r--src/core/crypto/key_manager.h116
-rw-r--r--src/core/file_sys/system_archive/mii_model.cpp46
-rw-r--r--src/core/file_sys/system_archive/mii_model.h13
-rw-r--r--src/core/file_sys/system_archive/system_archive.cpp3
-rw-r--r--src/core/hle/kernel/vm_manager.cpp72
-rw-r--r--src/core/hle/kernel/vm_manager.h8
-rw-r--r--src/core/hle/service/am/am.cpp63
-rw-r--r--src/core/hle/service/am/am.h31
-rw-r--r--src/core/hle/service/am/applet_ae.cpp14
-rw-r--r--src/core/hle/service/am/applet_oe.cpp9
-rw-r--r--src/core/hle/service/am/applets/applets.cpp25
-rw-r--r--src/core/hle/service/am/applets/applets.h17
-rw-r--r--src/core/hle/service/am/applets/error.cpp7
-rw-r--r--src/core/hle/service/am/applets/error.h7
-rw-r--r--src/core/hle/service/am/applets/general_backend.cpp13
-rw-r--r--src/core/hle/service/am/applets/general_backend.h12
-rw-r--r--src/core/hle/service/am/applets/profile_select.cpp5
-rw-r--r--src/core/hle/service/am/applets/profile_select.h7
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.cpp5
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.h7
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp19
-rw-r--r--src/core/hle/service/am/applets/web_browser.h12
-rw-r--r--src/core/hle/service/audio/audren_u.cpp33
-rw-r--r--src/core/hle/service/es/es.cpp230
-rw-r--r--src/core/hle/service/fatal/fatal.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp6
-rw-r--r--src/core/hle/service/hid/controllers/npad.h1
-rw-r--r--src/core/hle/service/hid/hid.cpp25
-rw-r--r--src/core/hle/service/hid/hid.h2
-rw-r--r--src/core/loader/nro.cpp9
-rw-r--r--src/core/loader/nro.h1
-rw-r--r--src/core/reporter.cpp2
-rw-r--r--src/video_core/engines/kepler_compute.cpp53
-rw-r--r--src/video_core/engines/kepler_compute.h23
-rw-r--r--src/video_core/engines/maxwell_3d.cpp7
-rw-r--r--src/video_core/engines/maxwell_3d.h4
-rw-r--r--src/video_core/macro_interpreter.cpp18
-rw-r--r--src/video_core/macro_interpreter.h8
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp43
-rw-r--r--src/video_core/renderer_opengl/gl_device.h6
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp154
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h19
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp68
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h21
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp948
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp86
-rw-r--r--src/video_core/renderer_opengl/gl_state.h19
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h22
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp4
-rw-r--r--src/video_core/shader/decode/half_set_predicate.cpp19
-rw-r--r--src/video_core/shader/decode/image.cpp40
-rw-r--r--src/video_core/shader/node.h48
-rw-r--r--src/video_core/shader/shader_ir.h8
-rw-r--r--src/video_core/texture_cache/surface_base.h12
-rw-r--r--src/video_core/texture_cache/surface_params.cpp134
-rw-r--r--src/video_core/texture_cache/surface_params.h9
-rw-r--r--src/video_core/texture_cache/surface_view.cpp2
-rw-r--r--src/video_core/texture_cache/surface_view.h20
-rw-r--r--src/video_core/texture_cache/texture_cache.h21
-rw-r--r--src/yuzu/configuration/config.cpp44
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp44
-rw-r--r--src/yuzu/configuration/configure_general.cpp12
-rw-r--r--src/yuzu/configuration/configure_general.ui30
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp6
-rw-r--r--src/yuzu/configuration/configure_graphics.ui27
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp17
-rw-r--r--src/yuzu/game_list.cpp436
-rw-r--r--src/yuzu/game_list.h43
-rw-r--r--src/yuzu/game_list_p.h127
-rw-r--r--src/yuzu/game_list_worker.cpp86
-rw-r--r--src/yuzu/game_list_worker.h26
-rw-r--r--src/yuzu/main.cpp98
-rw-r--r--src/yuzu/main.h8
-rw-r--r--src/yuzu/main.ui1
-rw-r--r--src/yuzu/uisettings.h21
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}
106struct System::Impl { 106struct 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 @@
37namespace Core::Crypto { 37namespace Core::Crypto {
38 38
39constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; 39constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
40constexpr u64 FULL_TICKET_SIZE = 0x400;
40 41
41using namespace Common; 42using 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
59namespace {
60template <std::size_t Size>
61bool 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
66u64 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
81u64 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
95SignatureType 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
109TicketData& 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
123const 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
137u64 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
144Ticket 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
58Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { 152Key128 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
232RSAKeyPair<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
138Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { 253Key128 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
240std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { 355std::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
308std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, 423std::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); 982void 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
1022void 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
902void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { 1035void 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
1133const std::map<u128, Ticket>& KeyManager::GetCommonTickets() const {
1134 return common_tickets;
1135}
1136
1137const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const {
1138 return personal_tickets;
1139}
1140
1141bool 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
1157bool 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
1000const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { 1173const 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;
30using Key128 = std::array<u8, 0x10>; 32using Key128 = std::array<u8, 0x10>;
31using Key256 = std::array<u8, 0x20>; 33using Key256 = std::array<u8, 0x20>;
32using SHA256Hash = std::array<u8, 0x20>; 34using SHA256Hash = std::array<u8, 0x20>;
33using TicketRaw = std::array<u8, 0x400>; 35
36enum 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
45u64 GetSignatureTypeDataSize(SignatureType type);
46u64 GetSignatureTypePaddingSize(SignatureType type);
47
48enum class TitleKeyType : u8 {
49 Common = 0,
50 Personalized = 1,
51};
52
53struct 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};
75static_assert(sizeof(TicketData) == 0x2C0, "TicketData has incorrect size.");
76
77struct RSA4096Ticket {
78 SignatureType sig_type;
79 std::array<u8, 0x200> sig_data;
80 INSERT_PADDING_BYTES(0x3C);
81 TicketData data;
82};
83
84struct RSA2048Ticket {
85 SignatureType sig_type;
86 std::array<u8, 0x100> sig_data;
87 INSERT_PADDING_BYTES(0x3C);
88 TicketData data;
89};
90
91struct ECDSATicket {
92 SignatureType sig_type;
93 std::array<u8, 0x3C> sig_data;
94 INSERT_PADDING_BYTES(0x40);
95 TicketData data;
96};
97
98struct 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
35static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); 109static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
36static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big."); 110static_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
120template <size_t bit_size, size_t byte_size>
121bool 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
127template <size_t bit_size, size_t byte_size>
128bool operator!=(const RSAKeyPair<bit_size, byte_size>& lhs,
129 const RSAKeyPair<bit_size, byte_size>& rhs) {
130 return !(lhs == rhs);
131}
132
46enum class KeyCategory : u8 { 133enum 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
164private: 259private:
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
195std::optional<Key128> DeriveSDSeed(); 297std::optional<Key128> DeriveSDSeed();
196Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); 298Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
197 299
198std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save); 300std::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)
202std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, 304std::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
8namespace FileSys::SystemArchive {
9
10namespace MiiModelData {
11
12constexpr std::array<u8, 0x10> NFTR_STANDARD{'N', 'F', 'T', 'R', 0x01, 0x00, 0x00, 0x00,
13 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
14constexpr std::array<u8, 0x10> NFSR_STANDARD{'N', 'F', 'S', 'R', 0x01, 0x00, 0x00, 0x00,
15 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
16
17constexpr auto TEXTURE_LOW_LINEAR = NFTR_STANDARD;
18constexpr auto TEXTURE_LOW_SRGB = NFTR_STANDARD;
19constexpr auto TEXTURE_MID_LINEAR = NFTR_STANDARD;
20constexpr auto TEXTURE_MID_SRGB = NFTR_STANDARD;
21constexpr auto SHAPE_HIGH = NFSR_STANDARD;
22constexpr auto SHAPE_MID = NFSR_STANDARD;
23
24} // namespace MiiModelData
25
26VirtualDir 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
9namespace FileSys::SystemArchive {
10
11VirtualDir 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 {
24constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{ 25constexpr 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
298ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { 298ResultCode 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
383ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { 380ResultCode 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};
57static_assert(sizeof(LaunchParameters) == 0x88); 57static_assert(sizeof(LaunchParameters) == 0x88);
58 58
59IWindowController::IWindowController() : ServiceFramework("IWindowController") { 59IWindowController::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") {
75IWindowController::~IWindowController() = default; 76IWindowController::~IWindowController() = default;
76 77
77void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) { 78void 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
232IDebugFunctions::~IDebugFunctions() = default; 233IDebugFunctions::~IDebugFunctions() = default;
233 234
234ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) 235ISelfController::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
504AppletMessageQueue::AppletMessageQueue() { 506AppletMessageQueue::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
940ILibraryAppletCreator::ILibraryAppletCreator(u64 current_process_title_id) 941ILibraryAppletCreator::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
1021IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") { 1020IApplicationFunctions::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
1069IApplicationFunctions::~IApplicationFunctions() = default; 1074IApplicationFunctions::~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
1293void 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
1288void InstallInterfaces(SM::ServiceManager& service_manager, 1301void 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
13namespace Service { 13namespace Kernel {
14namespace NVFlinger { 14class KernelCore;
15}
16
17namespace Service::NVFlinger {
15class NVFlinger; 18class NVFlinger;
16} 19}
17 20
18namespace AM { 21namespace Service::AM {
19 22
20enum SystemLanguage { 23enum 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
66class IWindowController final : public ServiceFramework<IWindowController> { 69class IWindowController final : public ServiceFramework<IWindowController> {
67public: 70public:
68 IWindowController(); 71 explicit IWindowController(Core::System& system_);
69 ~IWindowController() override; 72 ~IWindowController() override;
70 73
71private: 74private:
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
76class IAudioController final : public ServiceFramework<IAudioController> { 81class IAudioController final : public ServiceFramework<IAudioController> {
@@ -113,7 +118,8 @@ public:
113 118
114class ISelfController final : public ServiceFramework<ISelfController> { 119class ISelfController final : public ServiceFramework<ISelfController> {
115public: 120public:
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
119private: 125private:
@@ -208,7 +214,7 @@ private:
208 214
209class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { 215class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> {
210public: 216public:
211 ILibraryAppletCreator(u64 current_process_title_id); 217 explicit ILibraryAppletCreator(Core::System& system_);
212 ~ILibraryAppletCreator() override; 218 ~ILibraryAppletCreator() override;
213 219
214private: 220private:
@@ -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
222class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { 228class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
223public: 229public:
224 IApplicationFunctions(); 230 explicit IApplicationFunctions(Core::System& system_);
225 ~IApplicationFunctions() override; 231 ~IApplicationFunctions() override;
226 232
227private: 233private:
@@ -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
247class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { 257class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> {
@@ -275,5 +285,4 @@ public:
275void InstallInterfaces(SM::ServiceManager& service_manager, 285void 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
24namespace Service::AM::Applets { 24namespace Service::AM::Applets {
25 25
26AppletDataBroker::AppletDataBroker() { 26AppletDataBroker::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
124Applet::Applet() = default; 123Applet::Applet(Kernel::KernelCore& kernel_) : broker{kernel_} {}
125 124
126Applet::~Applet() = default; 125Applet::~Applet() = default;
127 126
@@ -154,7 +153,7 @@ AppletFrontendSet::AppletFrontendSet(AppletFrontendSet&&) noexcept = default;
154 153
155AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default; 154AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default;
156 155
157AppletManager::AppletManager() = default; 156AppletManager::AppletManager(Core::System& system_) : system{system_} {}
158 157
159AppletManager::~AppletManager() = default; 158AppletManager::~AppletManager() = default;
160 159
@@ -216,28 +215,28 @@ void AppletManager::ClearAll() {
216 frontend = {}; 215 frontend = {};
217} 216}
218 217
219std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, u64 current_process_title_id) const { 218std::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
13union ResultCode; 13union ResultCode;
14 14
15namespace Core {
16class System;
17}
18
15namespace Core::Frontend { 19namespace Core::Frontend {
16class ECommerceApplet; 20class ECommerceApplet;
17class ErrorApplet; 21class ErrorApplet;
@@ -22,6 +26,10 @@ class SoftwareKeyboardApplet;
22class WebBrowserApplet; 26class WebBrowserApplet;
23} // namespace Core::Frontend 27} // namespace Core::Frontend
24 28
29namespace Kernel {
30class KernelCore;
31}
32
25namespace Service::AM { 33namespace Service::AM {
26 34
27class IStorage; 35class IStorage;
@@ -53,7 +61,7 @@ enum class AppletId : u32 {
53 61
54class AppletDataBroker final { 62class AppletDataBroker final {
55public: 63public:
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
109class Applet { 117class Applet {
110public: 118public:
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
180class AppletManager { 188class AppletManager {
181public: 189public:
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
192private: 200private:
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
88Error::Error(const Core::Frontend::ErrorApplet& frontend) : frontend(frontend) {} 88Error::Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_)
89 : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
89 90
90Error::~Error() = default; 91Error::~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
10namespace Core {
11class System;
12}
13
10namespace Service::AM::Applets { 14namespace Service::AM::Applets {
11 15
12enum class ErrorAppletMode : u8 { 16enum class ErrorAppletMode : u8 {
@@ -21,7 +25,7 @@ enum class ErrorAppletMode : u8 {
21 25
22class Error final : public Applet { 26class Error final : public Applet {
23public: 27public:
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
40Auth::Auth(Core::Frontend::ParentalControlsApplet& frontend) : frontend(frontend) {} 40Auth::Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_)
41 : Applet{system_.Kernel()}, frontend(frontend_) {}
41 42
42Auth::~Auth() = default; 43Auth::~Auth() = default;
43 44
@@ -151,7 +152,8 @@ void Auth::AuthFinished(bool successful) {
151 broker.SignalStateChanged(); 152 broker.SignalStateChanged();
152} 153}
153 154
154PhotoViewer::PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend) : frontend(frontend) {} 155PhotoViewer::PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_)
156 : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
155 157
156PhotoViewer::~PhotoViewer() = default; 158PhotoViewer::~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
203StubApplet::StubApplet(AppletId id) : id(id) {} 205StubApplet::StubApplet(Core::System& system_, AppletId id_)
206 : Applet{system_.Kernel()}, id(id_), system{system_} {}
204 207
205StubApplet::~StubApplet() = default; 208StubApplet::~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
9namespace Core {
10class System;
11}
12
9namespace Service::AM::Applets { 13namespace Service::AM::Applets {
10 14
11enum class AuthAppletType : u32 { 15enum class AuthAppletType : u32 {
@@ -16,7 +20,7 @@ enum class AuthAppletType : u32 {
16 20
17class Auth final : public Applet { 21class Auth final : public Applet {
18public: 22public:
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
46class PhotoViewer final : public Applet { 50class PhotoViewer final : public Applet {
47public: 51public:
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
65class StubApplet final : public Applet { 70class StubApplet final : public Applet {
66public: 71public:
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
77private: 82private:
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
16constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; 16constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1};
17 17
18ProfileSelect::ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend) 18ProfileSelect::ProfileSelect(Core::System& system_,
19 : frontend(frontend) {} 19 const Core::Frontend::ProfileSelectApplet& frontend_)
20 : Applet{system_.Kernel()}, frontend(frontend_) {}
20 21
21ProfileSelect::~ProfileSelect() = default; 22ProfileSelect::~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
14namespace Core {
15class System;
16}
17
14namespace Service::AM::Applets { 18namespace Service::AM::Applets {
15 19
16struct UserSelectionConfig { 20struct UserSelectionConfig {
@@ -29,7 +33,8 @@ static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has inco
29 33
30class ProfileSelect final : public Applet { 34class ProfileSelect final : public Applet {
31public: 35public:
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
42SoftwareKeyboard::SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend) 42SoftwareKeyboard::SoftwareKeyboard(Core::System& system_,
43 : frontend(frontend) {} 43 const Core::Frontend::SoftwareKeyboardApplet& frontend_)
44 : Applet{system_.Kernel()}, frontend(frontend_) {}
44 45
45SoftwareKeyboard::~SoftwareKeyboard() = default; 46SoftwareKeyboard::~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
17union ResultCode; 17union ResultCode;
18 18
19namespace Core {
20class System;
21}
22
19namespace Service::AM::Applets { 23namespace Service::AM::Applets {
20 24
21enum class KeysetDisable : u32 { 25enum class KeysetDisable : u32 {
@@ -55,7 +59,8 @@ static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect siz
55 59
56class SoftwareKeyboard final : public Applet { 60class SoftwareKeyboard final : public Applet {
57public: 61public:
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
193FileSys::VirtualFile GetApplicationRomFS(u64 title_id, FileSys::ContentRecordType type) { 193FileSys::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
210WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id, 211WebBrowser::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
215WebBrowser::~WebBrowser() = default; 216WebBrowser::~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
12namespace Core {
13class System;
14}
15
12namespace Service::AM::Applets { 16namespace Service::AM::Applets {
13 17
14enum class ShimKind : u32; 18enum class ShimKind : u32;
@@ -17,8 +21,8 @@ enum class WebArgTLVType : u16;
17 21
18class WebBrowser final : public Applet { 22class WebBrowser final : public Applet {
19public: 23public:
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
7namespace Service::ES { 9namespace Service::ES {
8 10
11constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::ETicket, 2};
12constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3};
13
9class ETicket final : public ServiceFramework<ETicket> { 14class ETicket final : public ServiceFramework<ETicket> {
10public: 15public:
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
64private:
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
57void InstallInterfaces(SM::ServiceManager& service_manager) { 267void 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
639void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { 640void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
640 can_controllers_vibrate = can_vibrate; 641 can_controllers_vibrate = can_vibrate;
641} 642}
642 643
644bool Controller_NPad::IsVibrationEnabled() const {
645 return can_controllers_vibrate;
646}
647
643void Controller_NPad::ClearAllConnectedControllers() { 648void 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
651void Controller_NPad::DisconnectAllConnectedControllers() { 657void 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
682void 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
694void 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
682void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { 703void 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
261ResultStatus 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
261bool AppLoader_NRO::IsRomFSUpdatable() const { 270bool 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
48private: 49private:
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
53Tegra::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
67Texture::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
73u32 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
52void KeplerCompute::ProcessLaunch() { 80void 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
91Texture::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
108Texture::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
16namespace Core { 17namespace Core {
17class System; 18class 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
197private: 206private:
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);
218ASSERT_REG_POSITION(tsc, 0x557); 233ASSERT_REG_POSITION(tsc, 0x557);
219ASSERT_REG_POSITION(tic, 0x55D); 234ASSERT_REG_POSITION(tic, 0x55D);
220ASSERT_REG_POSITION(code_loc, 0x582); 235ASSERT_REG_POSITION(code_loc, 0x582);
221ASSERT_REG_POSITION(texture_const_buffer_index, 0x982); 236ASSERT_REG_POSITION(tex_cb_index, 0x982);
222ASSERT_LAUNCH_PARAM_POSITION(program_start, 0x8); 237ASSERT_LAUNCH_PARAM_POSITION(program_start, 0x8);
223ASSERT_LAUNCH_PARAM_POSITION(grid_dim_x, 0xC); 238ASSERT_LAUNCH_PARAM_POSITION(grid_dim_x, 0xC);
224ASSERT_LAUNCH_PARAM_POSITION(shared_alloc, 0x11); 239ASSERT_LAUNCH_PARAM_POSITION(shared_alloc, 0x11);
225ASSERT_LAUNCH_PARAM_POSITION(block_dim_x, 0x12); 240ASSERT_LAUNCH_PARAM_POSITION(block_dim_x, 0x12);
226ASSERT_LAUNCH_PARAM_POSITION(memory_config, 0x14); 241ASSERT_LAUNCH_PARAM_POSITION(const_buffer_enable_mask, 0x14);
227ASSERT_LAUNCH_PARAM_POSITION(const_buffer_config, 0x1D); 242ASSERT_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
247void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { 247void 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
258void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { 258void 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
15MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {} 15MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {}
16 16
17void MacroInterpreter::Execute(u32 offset, std::vector<u32> parameters) { 17void 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
33void MacroInterpreter::Reset() { 40void 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
231u32 MacroInterpreter::FetchParameter() { 238u32 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
235u32 MacroInterpreter::GetRegister(u32 register_id) const { 243u32 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
30private: 30private:
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 @@
14namespace OpenGL { 14namespace OpenGL {
15 15
16namespace { 16namespace {
17
17template <typename T> 18template <typename T>
18T GetInteger(GLenum pname) { 19T 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
25bool 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
25Device::Device() { 35Device::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
37Device::Device(std::nullptr_t) { 52Device::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
47bool Device::TestVariableAoffi() { 63bool 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.
50uniform sampler2D tex; 66uniform sampler2D tex;
51uniform ivec2 variable_offset; 67uniform ivec2 variable_offset;
52out vec4 output_attribute; 68out vec4 output_attribute;
53void main() { 69void 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
67bool Device::TestComponentIndexingBug() { 74bool 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
70layout (std430, binding = 0) buffer OutputBuffer { 76layout (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
120bool Device::TestPreciseBug() {
121 return !TestProgram(R"(#version 430 core
122in vec3 coords;
123out float out_value;
124uniform sampler2DShadow tex;
125void 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
49private: 53private:
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
985TextureBufferUsage RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader, 988TextureBufferUsage 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) { 1021TextureBufferUsage 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
1051bool 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
1073void 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
1091void 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
1032void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { 1107void 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
1157void RasterizerOpenGL::SyncColorMask() { 1233void 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
36namespace Core { 37namespace Core {
37class System; 38class 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
350std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVariant& variant) { 350std::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
381GLuint 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
407GLuint 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
424CachedProgram CachedShader::TryLoadProgram(const ProgramVariant& variant) const { 374CachedProgram 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
127class ShaderCacheOpenGL final : public RasterizerCache<Shader> { 106class 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;
39using Maxwell = Tegra::Engines::Maxwell3D::Regs; 39using Maxwell = Tegra::Engines::Maxwell3D::Regs;
40using Operation = const OperationNode&; 40using Operation = const OperationNode&;
41 41
42enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat }; 42enum class Type { Void, Bool, Bool2, Float, Int, Uint, HalfFloat };
43 43
44struct TextureAoffi {}; 44struct TextureAoffi {};
45using TextureArgument = std::pair<Type, Node>; 45using TextureArgument = std::pair<Type, Node>;
@@ -48,7 +48,7 @@ using TextureIR = std::variant<TextureAoffi, TextureArgument>;
48constexpr u32 MAX_CONSTBUFFER_ELEMENTS = 48constexpr u32 MAX_CONSTBUFFER_ELEMENTS =
49 static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float)); 49 static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float));
50 50
51class ShaderWriter { 51class ShaderWriter final {
52public: 52public:
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
96class Expression final {
97public:
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
219private:
220 std::string code;
221 Type type{};
222};
223
224constexpr 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.
97constexpr const char* GetSwizzle(u32 element) { 245constexpr 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
137constexpr Attribute::Index ToGenericAttribute(u32 value) { 285constexpr 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
141u32 GetGenericAttributeIndex(Attribute::Index index) { 289u32 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
2023std::string GetCommonDeclarations() { 2133std::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
2045ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage, 2147ProgramResult 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
37template <typename T>
38std::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
37void Enable(GLenum cap, bool enable) { 56void 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
498void OpenGLState::ApplyTextures() const { 513void 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
524void OpenGLState::ApplySamplers() const { 519void 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); 525void 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
608OpenGLState& OpenGLState::UnbindTexture(GLuint handle) { 592OpenGLState& 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
617OpenGLState& OpenGLState::ResetSampler(GLuint handle) { 601OpenGLState& 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
99private: 103private:
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
118class TextureCacheOpenGL final : public TextureCacheBase { 122class 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
78const Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { 80Image& 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
95const Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, 95Image& 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
276class Image { 276class Image final {
277public: 277public:
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
311private: 327private:
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
318struct GlobalMemoryBase { 336struct 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;
24using VideoCore::Surface::SurfaceTargetFromTextureType; 24using VideoCore::Surface::SurfaceTargetFromTextureType;
25using VideoCore::Surface::SurfaceType; 25using VideoCore::Surface::SurfaceType;
26 26
27SurfaceTarget TextureType2SurfaceTarget(Tegra::Shader::TextureType type, bool is_array) { 27namespace {
28
29SurfaceTarget 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
46SurfaceTarget 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
58namespace {
59constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { 66constexpr 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
64SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, 72SurfaceParams 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
131SurfaceParams 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;
23class SurfaceParams { 21class SurfaceParams {
24public: 22public:
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 @@
10namespace VideoCommon { 10namespace VideoCommon {
11 11
12std::size_t ViewParams::Hash() const { 12std::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 @@
13namespace VideoCommon { 13namespace VideoCommon {
14 14
15struct ViewParams { 15struct 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
44class ViewBase { 44class ViewBase {
45public: 45public:
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
71Q_DECLARE_METATYPE(QList<QWidget*>);
72
71void ConfigureDialog::PopulateSelectionList() { 73void 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
27ConfigureGeneral::~ConfigureGeneral() = default; 26ConfigureGeneral::~ConfigureGeneral() = default;
28 27
29void ConfigureGeneral::SetConfiguration() { 28void 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
36void ConfigureGeneral::ApplyConfiguration() { 38void 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
44void ConfigureGeneral::changeEvent(QEvent* event) { 48void 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() {
89void ConfigureGraphics::ApplyConfiguration() { 85void 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
90void GameListSearchField::setFilterResult(int visible, int total) { 79void 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
86QString 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
94void GameListSearchField::clear() { 105void 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
162void 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
151void GameList::onTextChanged(const QString& new_text) { 171void 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
229void 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
266void GameList::AddEntry(const QList<QStandardItem*>& entry_items) { 355void 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
270void GameList::ValidateEntry(const QModelIndex& item) { 362void 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}; 366void 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 398bool 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
297void GameList::DonePopulating(QStringList watch_list) { 412void 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
469void 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
504void 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)); 524void 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
362void GameList::LoadCompatibilityList() { 567void GameList::LoadCompatibilityList() {
@@ -403,14 +608,7 @@ void GameList::LoadCompatibilityList() {
403 } 608 }
404} 609}
405 610
406void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { 611void 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
473void GameList::RefreshGameDirectory() { 673void 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
680GameListPlaceholder::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
702GameListPlaceholder::~GameListPlaceholder() = default;
703
704void GameListPlaceholder::onUpdateThemedIcons() {
705 image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200));
706}
707
708void 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
24class GameListWorker; 27class GameListWorker;
25class GameListSearchField; 28class GameListSearchField;
29class GameListDir;
26class GMainWindow; 30class GMainWindow;
27 31
28namespace FileSys { 32namespace 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
78private slots: 87private 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
82private: 93private:
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
104Q_DECLARE_METATYPE(GameListOpenTarget); 120Q_DECLARE_METATYPE(GameListOpenTarget);
121
122class GameListPlaceholder : public QWidget {
123 Q_OBJECT
124public:
125 explicit GameListPlaceholder(GMainWindow* parent = nullptr);
126 ~GameListPlaceholder();
127
128signals:
129 void AddDirectory();
130
131private slots:
132 void onUpdateThemedIcons();
133
134protected:
135 void mouseDoubleClickEvent(QMouseEvent* event) override;
136
137private:
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
26enum 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
35Q_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) {
36class GameListItem : public QStandardItem { 48class GameListItem : public QStandardItem {
37 49
38public: 50public:
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 */
49class GameListItemPath : public GameListItem { 66class GameListItemPath : public GameListItem {
50public: 67public:
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:
103class GameListItemCompat : public GameListItem { 125class GameListItemCompat : public GameListItem {
104 Q_DECLARE_TR_FUNCTIONS(GameListItemCompat) 126 Q_DECLARE_TR_FUNCTIONS(GameListItemCompat)
105public: 127public:
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 */
148class GameListItemSize : public GameListItem { 176class GameListItemSize : public GameListItem {
149
150public: 177public:
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
212class GameListDir : public GameListItem {
213public:
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
266private:
267 GameListItemType dir_type;
268};
269
270class GameListAddDir : public GameListItem {
271public:
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
180class GameList; 288class GameList;
181class QHBoxLayout; 289class QHBoxLayout;
182class QTreeView; 290class 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
225GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, 225GameListWorker::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
231GameListWorker::~GameListWorker() = default; 232GameListWorker::~GameListWorker() = default;
232 233
233void GameListWorker::AddTitlesToGameList() { 234void 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
263void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, 280void 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
333void GameListWorker::run() { 351void 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
35public: 36public:
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
61private: 65private:
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
661void GMainWindow::ConnectWidgetEvents() { 667void 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
1315void 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
1336void 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
1349void 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
1301void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { 1356void 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
1536void 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
1544void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) { 1589void 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
2164void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { 2206void 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;
30class QLabel; 30class QLabel;
31class WaitTreeWidget; 31class WaitTreeWidget;
32enum class GameListOpenTarget; 32enum class GameListOpenTarget;
33class GameListPlaceholder;
33 34
34namespace Core::Frontend { 35namespace Core::Frontend {
35struct SoftwareKeyboardParameters; 36struct 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
15namespace UISettings { 17namespace UISettings {
@@ -25,6 +27,18 @@ struct Shortcut {
25using Themes = std::array<std::pair<const char*, const char*>, 2>; 27using Themes = std::array<std::pair<const char*, const char*>, 2>;
26extern const Themes themes; 28extern const Themes themes;
27 29
30struct 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
28struct Values { 42struct 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
85extern Values values; 100extern Values values;
86} // namespace UISettings 101} // namespace UISettings
102
103Q_DECLARE_METATYPE(UISettings::GameDir*);