summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/CMakeLists.txt4
-rw-r--r--src/common/assert.h18
-rw-r--r--src/common/zstd_compression.cpp53
-rw-r--r--src/common/zstd_compression.h42
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp2
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.h6
-rw-r--r--src/core/core.cpp26
-rw-r--r--src/core/core.h14
-rw-r--r--src/core/crypto/key_manager.cpp3
-rw-r--r--src/core/file_sys/patch_manager.cpp22
-rw-r--r--src/core/file_sys/patch_manager.h2
-rw-r--r--src/core/file_sys/registered_cache.cpp275
-rw-r--r--src/core/file_sys/registered_cache.h156
-rw-r--r--src/core/file_sys/romfs_factory.cpp2
-rw-r--r--src/core/file_sys/submission_package.cpp13
-rw-r--r--src/core/file_sys/submission_package.h11
-rw-r--r--src/core/hle/ipc_helpers.h4
-rw-r--r--src/core/hle/kernel/client_port.cpp10
-rw-r--r--src/core/hle/kernel/kernel.cpp2
-rw-r--r--src/core/hle/kernel/process.cpp5
-rw-r--r--src/core/hle/kernel/scheduler.cpp1
-rw-r--r--src/core/hle/kernel/server_port.cpp7
-rw-r--r--src/core/hle/kernel/server_port.h7
-rw-r--r--src/core/hle/kernel/server_session.cpp2
-rw-r--r--src/core/hle/kernel/server_session.h3
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp2
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp4
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp11
-rw-r--r--src/core/hle/service/filesystem/filesystem.h2
-rw-r--r--src/core/loader/nso.cpp6
-rw-r--r--src/core/memory.cpp4
-rw-r--r--src/core/memory.h13
-rw-r--r--src/tests/core/arm/arm_test_common.cpp3
-rw-r--r--src/video_core/engines/maxwell_3d.cpp4
-rw-r--r--src/video_core/engines/shader_bytecode.h5
-rw-r--r--src/video_core/gpu.cpp2
-rw-r--r--src/video_core/memory_manager.cpp87
-rw-r--r--src/video_core/memory_manager.h7
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp8
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h8
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp4
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp133
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp12
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.h1
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp610
-rw-r--r--src/video_core/renderer_opengl/gl_state.h52
-rw-r--r--src/video_core/shader/decode/texture.cpp114
-rw-r--r--src/video_core/shader/decode/xmad.cpp39
-rw-r--r--src/video_core/shader/shader_ir.h12
-rw-r--r--src/video_core/shader/track.cpp17
-rw-r--r--src/yuzu/CMakeLists.txt6
-rw-r--r--src/yuzu/applets/profile_select.cpp1
-rw-r--r--src/yuzu/applets/profile_select.h2
-rw-r--r--src/yuzu/configuration/config.cpp59
-rw-r--r--src/yuzu/configuration/config.h3
-rw-r--r--src/yuzu/configuration/configure.ui19
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp16
-rw-r--r--src/yuzu/configuration/configure_dialog.h3
-rw-r--r--src/yuzu/configuration/configure_general.cpp4
-rw-r--r--src/yuzu/configuration/configure_general.h1
-rw-r--r--src/yuzu/configuration/configure_general.ui24
-rw-r--r--src/yuzu/configuration/configure_hotkeys.cpp121
-rw-r--r--src/yuzu/configuration/configure_hotkeys.h48
-rw-r--r--src/yuzu/configuration/configure_hotkeys.ui42
-rw-r--r--src/yuzu/game_list.cpp9
-rw-r--r--src/yuzu/game_list.h7
-rw-r--r--src/yuzu/game_list_worker.cpp125
-rw-r--r--src/yuzu/game_list_worker.h16
-rw-r--r--src/yuzu/hotkeys.cpp73
-rw-r--r--src/yuzu/hotkeys.h42
-rw-r--r--src/yuzu/hotkeys.ui46
-rw-r--r--src/yuzu/loading_screen.cpp7
-rw-r--r--src/yuzu/main.cpp76
-rw-r--r--src/yuzu/main.h8
-rw-r--r--src/yuzu/ui_settings.cpp1
-rw-r--r--src/yuzu/ui_settings.h7
-rw-r--r--src/yuzu/util/sequence_dialog/sequence_dialog.cpp37
-rw-r--r--src/yuzu/util/sequence_dialog/sequence_dialog.h24
-rw-r--r--src/yuzu_cmd/yuzu.cpp2
79 files changed, 1701 insertions, 978 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 5639021d3..1e8e1b215 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -125,6 +125,8 @@ add_library(common STATIC
125 uint128.h 125 uint128.h
126 vector_math.h 126 vector_math.h
127 web_result.h 127 web_result.h
128 zstd_compression.cpp
129 zstd_compression.h
128) 130)
129 131
130if(ARCHITECTURE_x86_64) 132if(ARCHITECTURE_x86_64)
@@ -138,4 +140,4 @@ endif()
138create_target_directory_groups(common) 140create_target_directory_groups(common)
139 141
140target_link_libraries(common PUBLIC Boost::boost fmt microprofile) 142target_link_libraries(common PUBLIC Boost::boost fmt microprofile)
141target_link_libraries(common PRIVATE lz4_static) 143target_link_libraries(common PRIVATE lz4_static libzstd_static)
diff --git a/src/common/assert.h b/src/common/assert.h
index 6002f7ab1..4b0e3f64e 100644
--- a/src/common/assert.h
+++ b/src/common/assert.h
@@ -57,3 +57,21 @@ __declspec(noinline, noreturn)
57 57
58#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!") 58#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!")
59#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__) 59#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__)
60
61// If the assert is ignored, execute _b_
62#define ASSERT_OR_EXECUTE(_a_, _b_) \
63 do { \
64 ASSERT(_a_); \
65 if (!(_a_)) { \
66 _b_ \
67 } \
68 } while (0)
69
70// If the assert is ignored, execute _b_
71#define ASSERT_OR_EXECUTE_MSG(_a_, _b_, ...) \
72 do { \
73 ASSERT_MSG(_a_, __VA_ARGS__); \
74 if (!(_a_)) { \
75 _b_ \
76 } \
77 } while (0)
diff --git a/src/common/zstd_compression.cpp b/src/common/zstd_compression.cpp
new file mode 100644
index 000000000..60a35c67c
--- /dev/null
+++ b/src/common/zstd_compression.cpp
@@ -0,0 +1,53 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <algorithm>
8#include <zstd.h>
9
10#include "common/assert.h"
11#include "common/zstd_compression.h"
12
13namespace Common::Compression {
14
15std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size, s32 compression_level) {
16 compression_level = std::clamp(compression_level, 1, ZSTD_maxCLevel());
17
18 const std::size_t max_compressed_size = ZSTD_compressBound(source_size);
19 std::vector<u8> compressed(max_compressed_size);
20
21 const std::size_t compressed_size =
22 ZSTD_compress(compressed.data(), compressed.size(), source, source_size, compression_level);
23
24 if (ZSTD_isError(compressed_size)) {
25 // Compression failed
26 return {};
27 }
28
29 compressed.resize(compressed_size);
30
31 return compressed;
32}
33
34std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size) {
35 return CompressDataZSTD(source, source_size, ZSTD_CLEVEL_DEFAULT);
36}
37
38std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed) {
39 const std::size_t decompressed_size =
40 ZSTD_getDecompressedSize(compressed.data(), compressed.size());
41 std::vector<u8> decompressed(decompressed_size);
42
43 const std::size_t uncompressed_result_size = ZSTD_decompress(
44 decompressed.data(), decompressed.size(), compressed.data(), compressed.size());
45
46 if (decompressed_size != uncompressed_result_size || ZSTD_isError(uncompressed_result_size)) {
47 // Decompression failed
48 return {};
49 }
50 return decompressed;
51}
52
53} // namespace Common::Compression
diff --git a/src/common/zstd_compression.h b/src/common/zstd_compression.h
new file mode 100644
index 000000000..e0a64b035
--- /dev/null
+++ b/src/common/zstd_compression.h
@@ -0,0 +1,42 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <vector>
6
7#include "common/common_types.h"
8
9namespace Common::Compression {
10
11/**
12 * Compresses a source memory region with Zstandard and returns the compressed data in a vector.
13 *
14 * @param source the uncompressed source memory region.
15 * @param source_size the size in bytes of the uncompressed source memory region.
16 * @param compression_level the used compression level. Should be between 1 and 22.
17 *
18 * @return the compressed data.
19 */
20std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size, s32 compression_level);
21
22/**
23 * Compresses a source memory region with Zstandard with the default compression level and returns
24 * the compressed data in a vector.
25 *
26 * @param source the uncompressed source memory region.
27 * @param source_size the size in bytes of the uncompressed source memory region.
28 *
29 * @return the compressed data.
30 */
31std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size);
32
33/**
34 * Decompresses a source memory region with Zstandard and returns the uncompressed data in a vector.
35 *
36 * @param compressed the compressed source memory region.
37 *
38 * @return the decompressed data.
39 */
40std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed);
41
42} // namespace Common::Compression \ No newline at end of file
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index f64e4c6a6..49145911b 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -163,7 +163,6 @@ MICROPROFILE_DEFINE(ARM_Jit_Dynarmic, "ARM JIT", "Dynarmic", MP_RGB(255, 64, 64)
163 163
164void ARM_Dynarmic::Run() { 164void ARM_Dynarmic::Run() {
165 MICROPROFILE_SCOPE(ARM_Jit_Dynarmic); 165 MICROPROFILE_SCOPE(ARM_Jit_Dynarmic);
166 ASSERT(Memory::GetCurrentPageTable() == current_page_table);
167 166
168 jit->Run(); 167 jit->Run();
169} 168}
@@ -278,7 +277,6 @@ void ARM_Dynarmic::ClearExclusiveState() {
278 277
279void ARM_Dynarmic::PageTableChanged() { 278void ARM_Dynarmic::PageTableChanged() {
280 jit = MakeJit(); 279 jit = MakeJit();
281 current_page_table = Memory::GetCurrentPageTable();
282} 280}
283 281
284DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(std::size_t core_count) : monitor(core_count) {} 282DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(std::size_t core_count) : monitor(core_count) {}
diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h
index 81e0b4ac0..d867c2a50 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.h
+++ b/src/core/arm/dynarmic/arm_dynarmic.h
@@ -12,10 +12,6 @@
12#include "core/arm/exclusive_monitor.h" 12#include "core/arm/exclusive_monitor.h"
13#include "core/arm/unicorn/arm_unicorn.h" 13#include "core/arm/unicorn/arm_unicorn.h"
14 14
15namespace Common {
16struct PageTable;
17}
18
19namespace Core::Timing { 15namespace Core::Timing {
20class CoreTiming; 16class CoreTiming;
21} 17}
@@ -69,8 +65,6 @@ private:
69 std::size_t core_index; 65 std::size_t core_index;
70 Timing::CoreTiming& core_timing; 66 Timing::CoreTiming& core_timing;
71 DynarmicExclusiveMonitor& exclusive_monitor; 67 DynarmicExclusiveMonitor& exclusive_monitor;
72
73 Common::PageTable* current_page_table = nullptr;
74}; 68};
75 69
76class DynarmicExclusiveMonitor final : public ExclusiveMonitor { 70class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 4fe77c25b..bc9e887b6 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -17,6 +17,7 @@
17#include "core/core_timing.h" 17#include "core/core_timing.h"
18#include "core/cpu_core_manager.h" 18#include "core/cpu_core_manager.h"
19#include "core/file_sys/mode.h" 19#include "core/file_sys/mode.h"
20#include "core/file_sys/registered_cache.h"
20#include "core/file_sys/vfs_concat.h" 21#include "core/file_sys/vfs_concat.h"
21#include "core/file_sys/vfs_real.h" 22#include "core/file_sys/vfs_real.h"
22#include "core/gdbstub/gdbstub.h" 23#include "core/gdbstub/gdbstub.h"
@@ -108,6 +109,8 @@ struct System::Impl {
108 // Create a default fs if one doesn't already exist. 109 // Create a default fs if one doesn't already exist.
109 if (virtual_filesystem == nullptr) 110 if (virtual_filesystem == nullptr)
110 virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>(); 111 virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
112 if (content_provider == nullptr)
113 content_provider = std::make_unique<FileSys::ContentProviderUnion>();
111 114
112 /// Create default implementations of applets if one is not provided. 115 /// Create default implementations of applets if one is not provided.
113 if (profile_selector == nullptr) 116 if (profile_selector == nullptr)
@@ -249,6 +252,8 @@ struct System::Impl {
249 Kernel::KernelCore kernel; 252 Kernel::KernelCore kernel;
250 /// RealVfsFilesystem instance 253 /// RealVfsFilesystem instance
251 FileSys::VirtualFilesystem virtual_filesystem; 254 FileSys::VirtualFilesystem virtual_filesystem;
255 /// ContentProviderUnion instance
256 std::unique_ptr<FileSys::ContentProviderUnion> content_provider;
252 /// AppLoader used to load the current executing application 257 /// AppLoader used to load the current executing application
253 std::unique_ptr<Loader::AppLoader> app_loader; 258 std::unique_ptr<Loader::AppLoader> app_loader;
254 std::unique_ptr<VideoCore::RendererBase> renderer; 259 std::unique_ptr<VideoCore::RendererBase> renderer;
@@ -488,6 +493,27 @@ const Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const {
488 return *impl->software_keyboard; 493 return *impl->software_keyboard;
489} 494}
490 495
496void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) {
497 impl->content_provider = std::move(provider);
498}
499
500FileSys::ContentProvider& System::GetContentProvider() {
501 return *impl->content_provider;
502}
503
504const FileSys::ContentProvider& System::GetContentProvider() const {
505 return *impl->content_provider;
506}
507
508void System::RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
509 FileSys::ContentProvider* provider) {
510 impl->content_provider->SetSlot(slot, provider);
511}
512
513void System::ClearContentProvider(FileSys::ContentProviderUnionSlot slot) {
514 impl->content_provider->ClearSlot(slot);
515}
516
491void System::SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet) { 517void System::SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet) {
492 impl->web_browser = std::move(applet); 518 impl->web_browser = std::move(applet);
493} 519}
diff --git a/src/core/core.h b/src/core/core.h
index 4d83b93cc..82b2e087e 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -21,6 +21,9 @@ class WebBrowserApplet;
21 21
22namespace FileSys { 22namespace FileSys {
23class CheatList; 23class CheatList;
24class ContentProvider;
25class ContentProviderUnion;
26enum class ContentProviderUnionSlot;
24class VfsFilesystem; 27class VfsFilesystem;
25} // namespace FileSys 28} // namespace FileSys
26 29
@@ -270,6 +273,17 @@ public:
270 Frontend::WebBrowserApplet& GetWebBrowser(); 273 Frontend::WebBrowserApplet& GetWebBrowser();
271 const Frontend::WebBrowserApplet& GetWebBrowser() const; 274 const Frontend::WebBrowserApplet& GetWebBrowser() const;
272 275
276 void SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider);
277
278 FileSys::ContentProvider& GetContentProvider();
279
280 const FileSys::ContentProvider& GetContentProvider() const;
281
282 void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
283 FileSys::ContentProvider* provider);
284
285 void ClearContentProvider(FileSys::ContentProviderUnionSlot slot);
286
273private: 287private:
274 System(); 288 System();
275 289
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index dfac9a4b3..dc006e2bb 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -22,6 +22,7 @@
22#include "common/file_util.h" 22#include "common/file_util.h"
23#include "common/hex_util.h" 23#include "common/hex_util.h"
24#include "common/logging/log.h" 24#include "common/logging/log.h"
25#include "core/core.h"
25#include "core/crypto/aes_util.h" 26#include "core/crypto/aes_util.h"
26#include "core/crypto/key_manager.h" 27#include "core/crypto/key_manager.h"
27#include "core/crypto/partition_data_manager.h" 28#include "core/crypto/partition_data_manager.h"
@@ -794,7 +795,7 @@ void KeyManager::DeriveBase() {
794 795
795void KeyManager::DeriveETicket(PartitionDataManager& data) { 796void KeyManager::DeriveETicket(PartitionDataManager& data) {
796 // ETicket keys 797 // ETicket keys
797 const auto es = Service::FileSystem::GetUnionContents().GetEntry( 798 const auto es = Core::System::GetInstance().GetContentProvider().GetEntry(
798 0x0100000000000033, FileSys::ContentRecordType::Program); 799 0x0100000000000033, FileSys::ContentRecordType::Program);
799 800
800 if (es == nullptr) 801 if (es == nullptr)
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index e11217708..78dbadee3 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -10,6 +10,7 @@
10#include "common/file_util.h" 10#include "common/file_util.h"
11#include "common/hex_util.h" 11#include "common/hex_util.h"
12#include "common/logging/log.h" 12#include "common/logging/log.h"
13#include "core/core.h"
13#include "core/file_sys/content_archive.h" 14#include "core/file_sys/content_archive.h"
14#include "core/file_sys/control_metadata.h" 15#include "core/file_sys/control_metadata.h"
15#include "core/file_sys/ips_layer.h" 16#include "core/file_sys/ips_layer.h"
@@ -69,7 +70,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
69 } 70 }
70 } 71 }
71 72
72 const auto installed = Service::FileSystem::GetUnionContents(); 73 const auto& installed = Core::System::GetInstance().GetContentProvider();
73 74
74 const auto& disabled = Settings::values.disabled_addons[title_id]; 75 const auto& disabled = Settings::values.disabled_addons[title_id];
75 const auto update_disabled = 76 const auto update_disabled =
@@ -155,7 +156,7 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
155 return out; 156 return out;
156} 157}
157 158
158std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { 159std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::string& name) const {
159 if (nso.size() < sizeof(Loader::NSOHeader)) { 160 if (nso.size() < sizeof(Loader::NSOHeader)) {
160 return nso; 161 return nso;
161 } 162 }
@@ -171,18 +172,19 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
171 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); 172 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
172 173
173 if (Settings::values.dump_nso) { 174 if (Settings::values.dump_nso) {
174 LOG_INFO(Loader, "Dumping NSO for build_id={}, title_id={:016X}", build_id, title_id); 175 LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id,
176 title_id);
175 const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id); 177 const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id);
176 if (dump_dir != nullptr) { 178 if (dump_dir != nullptr) {
177 const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso"); 179 const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso");
178 const auto file = nso_dir->CreateFile(fmt::format("{}.nso", build_id)); 180 const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id));
179 181
180 file->Resize(nso.size()); 182 file->Resize(nso.size());
181 file->WriteBytes(nso); 183 file->WriteBytes(nso);
182 } 184 }
183 } 185 }
184 186
185 LOG_INFO(Loader, "Patching NSO for build_id={}", build_id); 187 LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
186 188
187 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); 189 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
188 auto patch_dirs = load_dir->GetSubdirectories(); 190 auto patch_dirs = load_dir->GetSubdirectories();
@@ -345,7 +347,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
345 if (romfs == nullptr) 347 if (romfs == nullptr)
346 return romfs; 348 return romfs;
347 349
348 const auto installed = Service::FileSystem::GetUnionContents(); 350 const auto& installed = Core::System::GetInstance().GetContentProvider();
349 351
350 // Game Updates 352 // Game Updates
351 const auto update_tid = GetUpdateTitleID(title_id); 353 const auto update_tid = GetUpdateTitleID(title_id);
@@ -392,7 +394,7 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
392std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames( 394std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
393 VirtualFile update_raw) const { 395 VirtualFile update_raw) const {
394 std::map<std::string, std::string, std::less<>> out; 396 std::map<std::string, std::string, std::less<>> out;
395 const auto installed = Service::FileSystem::GetUnionContents(); 397 const auto& installed = Core::System::GetInstance().GetContentProvider();
396 const auto& disabled = Settings::values.disabled_addons[title_id]; 398 const auto& disabled = Settings::values.disabled_addons[title_id];
397 399
398 // Game Updates 400 // Game Updates
@@ -466,10 +468,10 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
466 468
467 // DLC 469 // DLC
468 const auto dlc_entries = installed.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data); 470 const auto dlc_entries = installed.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
469 std::vector<RegisteredCacheEntry> dlc_match; 471 std::vector<ContentProviderEntry> dlc_match;
470 dlc_match.reserve(dlc_entries.size()); 472 dlc_match.reserve(dlc_entries.size());
471 std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), 473 std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
472 [this, &installed](const RegisteredCacheEntry& entry) { 474 [this, &installed](const ContentProviderEntry& entry) {
473 return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id && 475 return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
474 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; 476 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
475 }); 477 });
@@ -492,7 +494,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
492} 494}
493 495
494std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { 496std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
495 const auto installed{Service::FileSystem::GetUnionContents()}; 497 const auto& installed = Core::System::GetInstance().GetContentProvider();
496 498
497 const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control); 499 const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control);
498 if (base_control_nca == nullptr) 500 if (base_control_nca == nullptr)
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index de2672c76..769f8c6f0 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -44,7 +44,7 @@ public:
44 // Currently tracked NSO patches: 44 // Currently tracked NSO patches:
45 // - IPS 45 // - IPS
46 // - IPSwitch 46 // - IPSwitch
47 std::vector<u8> PatchNSO(const std::vector<u8>& nso) const; 47 std::vector<u8> PatchNSO(const std::vector<u8>& nso, const std::string& name) const;
48 48
49 // Checks to see if PatchNSO() will have any effect given the NSO's build ID. 49 // Checks to see if PatchNSO() will have any effect given the NSO's build ID.
50 // Used to prevent expensive copies in NSO loader. 50 // Used to prevent expensive copies in NSO loader.
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 1c6bacace..3946ff871 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -23,19 +23,19 @@ namespace FileSys {
23// The size of blocks to use when vfs raw copying into nand. 23// The size of blocks to use when vfs raw copying into nand.
24constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000; 24constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000;
25 25
26std::string RegisteredCacheEntry::DebugInfo() const { 26std::string ContentProviderEntry::DebugInfo() const {
27 return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type)); 27 return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
28} 28}
29 29
30bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) { 30bool operator<(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
31 return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type); 31 return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
32} 32}
33 33
34bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) { 34bool operator==(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
35 return std::tie(lhs.title_id, lhs.type) == std::tie(rhs.title_id, rhs.type); 35 return std::tie(lhs.title_id, lhs.type) == std::tie(rhs.title_id, rhs.type);
36} 36}
37 37
38bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) { 38bool operator!=(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
39 return !operator==(lhs, rhs); 39 return !operator==(lhs, rhs);
40} 40}
41 41
@@ -84,7 +84,7 @@ static std::string GetCNMTName(TitleType type, u64 title_id) {
84 return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); 84 return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
85} 85}
86 86
87static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { 87ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
88 switch (type) { 88 switch (type) {
89 case NCAContentType::Program: 89 case NCAContentType::Program:
90 // TODO(DarkLordZach): Differentiate between Program and Patch 90 // TODO(DarkLordZach): Differentiate between Program and Patch
@@ -104,6 +104,28 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
104 } 104 }
105} 105}
106 106
107ContentProvider::~ContentProvider() = default;
108
109bool ContentProvider::HasEntry(ContentProviderEntry entry) const {
110 return HasEntry(entry.title_id, entry.type);
111}
112
113VirtualFile ContentProvider::GetEntryUnparsed(ContentProviderEntry entry) const {
114 return GetEntryUnparsed(entry.title_id, entry.type);
115}
116
117VirtualFile ContentProvider::GetEntryRaw(ContentProviderEntry entry) const {
118 return GetEntryRaw(entry.title_id, entry.type);
119}
120
121std::unique_ptr<NCA> ContentProvider::GetEntry(ContentProviderEntry entry) const {
122 return GetEntry(entry.title_id, entry.type);
123}
124
125std::vector<ContentProviderEntry> ContentProvider::ListEntries() const {
126 return ListEntriesFilter(std::nullopt, std::nullopt, std::nullopt);
127}
128
107VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, 129VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
108 std::string_view path) const { 130 std::string_view path) const {
109 const auto file = dir->GetFileRelative(path); 131 const auto file = dir->GetFileRelative(path);
@@ -161,8 +183,8 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
161 return file; 183 return file;
162} 184}
163 185
164static std::optional<NcaID> CheckMapForContentRecord( 186static std::optional<NcaID> CheckMapForContentRecord(const std::map<u64, CNMT>& map, u64 title_id,
165 const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) { 187 ContentRecordType type) {
166 if (map.find(title_id) == map.end()) 188 if (map.find(title_id) == map.end())
167 return {}; 189 return {};
168 190
@@ -268,7 +290,7 @@ void RegisteredCache::Refresh() {
268 AccumulateYuzuMeta(); 290 AccumulateYuzuMeta();
269} 291}
270 292
271RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function) 293RegisteredCache::RegisteredCache(VirtualDir dir_, ContentProviderParsingFunction parsing_function)
272 : dir(std::move(dir_)), parser(std::move(parsing_function)) { 294 : dir(std::move(dir_)), parser(std::move(parsing_function)) {
273 Refresh(); 295 Refresh();
274} 296}
@@ -279,19 +301,11 @@ bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
279 return GetEntryRaw(title_id, type) != nullptr; 301 return GetEntryRaw(title_id, type) != nullptr;
280} 302}
281 303
282bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
283 return GetEntryRaw(entry) != nullptr;
284}
285
286VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const { 304VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
287 const auto id = GetNcaIDFromMetadata(title_id, type); 305 const auto id = GetNcaIDFromMetadata(title_id, type);
288 return id ? GetFileAtID(*id) : nullptr; 306 return id ? GetFileAtID(*id) : nullptr;
289} 307}
290 308
291VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const {
292 return GetEntryUnparsed(entry.title_id, entry.type);
293}
294
295std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const { 309std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
296 const auto meta_iter = meta.find(title_id); 310 const auto meta_iter = meta.find(title_id);
297 if (meta_iter != meta.end()) 311 if (meta_iter != meta.end())
@@ -309,10 +323,6 @@ VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) c
309 return id ? parser(GetFileAtID(*id), *id) : nullptr; 323 return id ? parser(GetFileAtID(*id), *id) : nullptr;
310} 324}
311 325
312VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
313 return GetEntryRaw(entry.title_id, entry.type);
314}
315
316std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const { 326std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
317 const auto raw = GetEntryRaw(title_id, type); 327 const auto raw = GetEntryRaw(title_id, type);
318 if (raw == nullptr) 328 if (raw == nullptr)
@@ -320,10 +330,6 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t
320 return std::make_unique<NCA>(raw, nullptr, 0, keys); 330 return std::make_unique<NCA>(raw, nullptr, 0, keys);
321} 331}
322 332
323std::unique_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
324 return GetEntry(entry.title_id, entry.type);
325}
326
327template <typename T> 333template <typename T>
328void RegisteredCache::IterateAllMetadata( 334void RegisteredCache::IterateAllMetadata(
329 std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc, 335 std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc,
@@ -348,25 +354,14 @@ void RegisteredCache::IterateAllMetadata(
348 } 354 }
349} 355}
350 356
351std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const { 357std::vector<ContentProviderEntry> RegisteredCache::ListEntriesFilter(
352 std::vector<RegisteredCacheEntry> out;
353 IterateAllMetadata<RegisteredCacheEntry>(
354 out,
355 [](const CNMT& c, const ContentRecord& r) {
356 return RegisteredCacheEntry{c.GetTitleID(), r.type};
357 },
358 [](const CNMT& c, const ContentRecord& r) { return true; });
359 return out;
360}
361
362std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
363 std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type, 358 std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
364 std::optional<u64> title_id) const { 359 std::optional<u64> title_id) const {
365 std::vector<RegisteredCacheEntry> out; 360 std::vector<ContentProviderEntry> out;
366 IterateAllMetadata<RegisteredCacheEntry>( 361 IterateAllMetadata<ContentProviderEntry>(
367 out, 362 out,
368 [](const CNMT& c, const ContentRecord& r) { 363 [](const CNMT& c, const ContentRecord& r) {
369 return RegisteredCacheEntry{c.GetTitleID(), r.type}; 364 return ContentProviderEntry{c.GetTitleID(), r.type};
370 }, 365 },
371 [&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) { 366 [&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
372 if (title_type && *title_type != c.GetType()) 367 if (title_type && *title_type != c.GetType())
@@ -521,37 +516,56 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
521 }) != yuzu_meta.end(); 516 }) != yuzu_meta.end();
522} 517}
523 518
524RegisteredCacheUnion::RegisteredCacheUnion(std::vector<RegisteredCache*> caches) 519ContentProviderUnion::~ContentProviderUnion() = default;
525 : caches(std::move(caches)) {}
526 520
527void RegisteredCacheUnion::Refresh() { 521void ContentProviderUnion::SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider) {
528 for (const auto& c : caches) 522 providers[slot] = provider;
529 c->Refresh();
530} 523}
531 524
532bool RegisteredCacheUnion::HasEntry(u64 title_id, ContentRecordType type) const { 525void ContentProviderUnion::ClearSlot(ContentProviderUnionSlot slot) {
533 return std::any_of(caches.begin(), caches.end(), [title_id, type](const auto& cache) { 526 providers[slot] = nullptr;
534 return cache->HasEntry(title_id, type);
535 });
536} 527}
537 528
538bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const { 529void ContentProviderUnion::Refresh() {
539 return HasEntry(entry.title_id, entry.type); 530 for (auto& provider : providers) {
531 if (provider.second == nullptr)
532 continue;
533
534 provider.second->Refresh();
535 }
540} 536}
541 537
542std::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const { 538bool ContentProviderUnion::HasEntry(u64 title_id, ContentRecordType type) const {
543 for (const auto& c : caches) { 539 for (const auto& provider : providers) {
544 const auto res = c->GetEntryVersion(title_id); 540 if (provider.second == nullptr)
545 if (res) 541 continue;
542
543 if (provider.second->HasEntry(title_id, type))
544 return true;
545 }
546
547 return false;
548}
549
550std::optional<u32> ContentProviderUnion::GetEntryVersion(u64 title_id) const {
551 for (const auto& provider : providers) {
552 if (provider.second == nullptr)
553 continue;
554
555 const auto res = provider.second->GetEntryVersion(title_id);
556 if (res != std::nullopt)
546 return res; 557 return res;
547 } 558 }
548 559
549 return {}; 560 return std::nullopt;
550} 561}
551 562
552VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const { 563VirtualFile ContentProviderUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
553 for (const auto& c : caches) { 564 for (const auto& provider : providers) {
554 const auto res = c->GetEntryUnparsed(title_id, type); 565 if (provider.second == nullptr)
566 continue;
567
568 const auto res = provider.second->GetEntryUnparsed(title_id, type);
555 if (res != nullptr) 569 if (res != nullptr)
556 return res; 570 return res;
557 } 571 }
@@ -559,13 +573,12 @@ VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordTy
559 return nullptr; 573 return nullptr;
560} 574}
561 575
562VirtualFile RegisteredCacheUnion::GetEntryUnparsed(RegisteredCacheEntry entry) const { 576VirtualFile ContentProviderUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
563 return GetEntryUnparsed(entry.title_id, entry.type); 577 for (const auto& provider : providers) {
564} 578 if (provider.second == nullptr)
579 continue;
565 580
566VirtualFile RegisteredCacheUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const { 581 const auto res = provider.second->GetEntryRaw(title_id, type);
567 for (const auto& c : caches) {
568 const auto res = c->GetEntryRaw(title_id, type);
569 if (res != nullptr) 582 if (res != nullptr)
570 return res; 583 return res;
571 } 584 }
@@ -573,30 +586,56 @@ VirtualFile RegisteredCacheUnion::GetEntryRaw(u64 title_id, ContentRecordType ty
573 return nullptr; 586 return nullptr;
574} 587}
575 588
576VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const { 589std::unique_ptr<NCA> ContentProviderUnion::GetEntry(u64 title_id, ContentRecordType type) const {
577 return GetEntryRaw(entry.title_id, entry.type); 590 for (const auto& provider : providers) {
578} 591 if (provider.second == nullptr)
592 continue;
579 593
580std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const { 594 auto res = provider.second->GetEntry(title_id, type);
581 const auto raw = GetEntryRaw(title_id, type); 595 if (res != nullptr)
582 if (raw == nullptr) 596 return res;
583 return nullptr; 597 }
584 return std::make_unique<NCA>(raw); 598
599 return nullptr;
585} 600}
586 601
587std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const { 602std::vector<ContentProviderEntry> ContentProviderUnion::ListEntriesFilter(
588 return GetEntry(entry.title_id, entry.type); 603 std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
604 std::optional<u64> title_id) const {
605 std::vector<ContentProviderEntry> out;
606
607 for (const auto& provider : providers) {
608 if (provider.second == nullptr)
609 continue;
610
611 const auto vec = provider.second->ListEntriesFilter(title_type, record_type, title_id);
612 std::copy(vec.begin(), vec.end(), std::back_inserter(out));
613 }
614
615 std::sort(out.begin(), out.end());
616 out.erase(std::unique(out.begin(), out.end()), out.end());
617 return out;
589} 618}
590 619
591std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const { 620std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>>
592 std::vector<RegisteredCacheEntry> out; 621ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnionSlot> origin,
593 for (const auto& c : caches) { 622 std::optional<TitleType> title_type,
594 c->IterateAllMetadata<RegisteredCacheEntry>( 623 std::optional<ContentRecordType> record_type,
595 out, 624 std::optional<u64> title_id) const {
596 [](const CNMT& c, const ContentRecord& r) { 625 std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> out;
597 return RegisteredCacheEntry{c.GetTitleID(), r.type}; 626
598 }, 627 for (const auto& provider : providers) {
599 [](const CNMT& c, const ContentRecord& r) { return true; }); 628 if (provider.second == nullptr)
629 continue;
630
631 if (origin.has_value() && *origin != provider.first)
632 continue;
633
634 const auto vec = provider.second->ListEntriesFilter(title_type, record_type, title_id);
635 std::transform(vec.begin(), vec.end(), std::back_inserter(out),
636 [&provider](const ContentProviderEntry& entry) {
637 return std::make_pair(provider.first, entry);
638 });
600 } 639 }
601 640
602 std::sort(out.begin(), out.end()); 641 std::sort(out.begin(), out.end());
@@ -604,25 +643,61 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
604 return out; 643 return out;
605} 644}
606 645
607std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter( 646ManualContentProvider::~ManualContentProvider() = default;
647
648void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type,
649 u64 title_id, VirtualFile file) {
650 entries.insert_or_assign({title_type, content_type, title_id}, file);
651}
652
653void ManualContentProvider::ClearAllEntries() {
654 entries.clear();
655}
656
657void ManualContentProvider::Refresh() {}
658
659bool ManualContentProvider::HasEntry(u64 title_id, ContentRecordType type) const {
660 return GetEntryRaw(title_id, type) != nullptr;
661}
662
663std::optional<u32> ManualContentProvider::GetEntryVersion(u64 title_id) const {
664 return std::nullopt;
665}
666
667VirtualFile ManualContentProvider::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
668 return GetEntryRaw(title_id, type);
669}
670
671VirtualFile ManualContentProvider::GetEntryRaw(u64 title_id, ContentRecordType type) const {
672 const auto iter =
673 std::find_if(entries.begin(), entries.end(), [title_id, type](const auto& entry) {
674 const auto [title_type, content_type, e_title_id] = entry.first;
675 return content_type == type && e_title_id == title_id;
676 });
677 if (iter == entries.end())
678 return nullptr;
679 return iter->second;
680}
681
682std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecordType type) const {
683 const auto res = GetEntryRaw(title_id, type);
684 if (res == nullptr)
685 return nullptr;
686 return std::make_unique<NCA>(res, nullptr, 0, keys);
687}
688
689std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(
608 std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type, 690 std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
609 std::optional<u64> title_id) const { 691 std::optional<u64> title_id) const {
610 std::vector<RegisteredCacheEntry> out; 692 std::vector<ContentProviderEntry> out;
611 for (const auto& c : caches) { 693
612 c->IterateAllMetadata<RegisteredCacheEntry>( 694 for (const auto& entry : entries) {
613 out, 695 const auto [e_title_type, e_content_type, e_title_id] = entry.first;
614 [](const CNMT& c, const ContentRecord& r) { 696 if ((title_type == std::nullopt || e_title_type == *title_type) &&
615 return RegisteredCacheEntry{c.GetTitleID(), r.type}; 697 (record_type == std::nullopt || e_content_type == *record_type) &&
616 }, 698 (title_id == std::nullopt || e_title_id == *title_id)) {
617 [&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) { 699 out.emplace_back(ContentProviderEntry{e_title_id, e_content_type});
618 if (title_type && *title_type != c.GetType()) 700 }
619 return false;
620 if (record_type && *record_type != r.type)
621 return false;
622 if (title_id && *title_id != c.GetTitleID())
623 return false;
624 return true;
625 });
626 } 701 }
627 702
628 std::sort(out.begin(), out.end()); 703 std::sort(out.begin(), out.end());
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index 3b77af4e0..ec9052653 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -21,12 +21,13 @@ class NSP;
21class XCI; 21class XCI;
22 22
23enum class ContentRecordType : u8; 23enum class ContentRecordType : u8;
24enum class NCAContentType : u8;
24enum class TitleType : u8; 25enum class TitleType : u8;
25 26
26struct ContentRecord; 27struct ContentRecord;
27 28
28using NcaID = std::array<u8, 0x10>; 29using NcaID = std::array<u8, 0x10>;
29using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; 30using ContentProviderParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
30using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>; 31using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>;
31 32
32enum class InstallResult { 33enum class InstallResult {
@@ -36,7 +37,7 @@ enum class InstallResult {
36 ErrorMetaFailed, 37 ErrorMetaFailed,
37}; 38};
38 39
39struct RegisteredCacheEntry { 40struct ContentProviderEntry {
40 u64 title_id; 41 u64 title_id;
41 ContentRecordType type; 42 ContentRecordType type;
42 43
@@ -47,12 +48,46 @@ constexpr u64 GetUpdateTitleID(u64 base_title_id) {
47 return base_title_id | 0x800; 48 return base_title_id | 0x800;
48} 49}
49 50
51ContentRecordType GetCRTypeFromNCAType(NCAContentType type);
52
50// boost flat_map requires operator< for O(log(n)) lookups. 53// boost flat_map requires operator< for O(log(n)) lookups.
51bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); 54bool operator<(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
52 55
53// std unique requires operator== to identify duplicates. 56// std unique requires operator== to identify duplicates.
54bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); 57bool operator==(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
55bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); 58bool operator!=(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
59
60class ContentProvider {
61public:
62 virtual ~ContentProvider();
63
64 virtual void Refresh() = 0;
65
66 virtual bool HasEntry(u64 title_id, ContentRecordType type) const = 0;
67 virtual bool HasEntry(ContentProviderEntry entry) const;
68
69 virtual std::optional<u32> GetEntryVersion(u64 title_id) const = 0;
70
71 virtual VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const = 0;
72 virtual VirtualFile GetEntryUnparsed(ContentProviderEntry entry) const;
73
74 virtual VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const = 0;
75 virtual VirtualFile GetEntryRaw(ContentProviderEntry entry) const;
76
77 virtual std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const = 0;
78 virtual std::unique_ptr<NCA> GetEntry(ContentProviderEntry entry) const;
79
80 virtual std::vector<ContentProviderEntry> ListEntries() const;
81
82 // If a parameter is not std::nullopt, it will be filtered for from all entries.
83 virtual std::vector<ContentProviderEntry> ListEntriesFilter(
84 std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
85 std::optional<u64> title_id = {}) const = 0;
86
87protected:
88 // A single instance of KeyManager to be used by GetEntry()
89 Core::Crypto::KeyManager keys;
90};
56 91
57/* 92/*
58 * A class that catalogues NCAs in the registered directory structure. 93 * A class that catalogues NCAs in the registered directory structure.
@@ -67,39 +102,32 @@ bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs
67 * (This impl also supports substituting the nca dir for an nca file, as that's more convenient 102 * (This impl also supports substituting the nca dir for an nca file, as that's more convenient
68 * when 4GB splitting can be ignored.) 103 * when 4GB splitting can be ignored.)
69 */ 104 */
70class RegisteredCache { 105class RegisteredCache : public ContentProvider {
71 friend class RegisteredCacheUnion;
72
73public: 106public:
74 // Parsing function defines the conversion from raw file to NCA. If there are other steps 107 // Parsing function defines the conversion from raw file to NCA. If there are other steps
75 // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom 108 // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
76 // parsing function. 109 // parsing function.
77 explicit RegisteredCache(VirtualDir dir, 110 explicit RegisteredCache(VirtualDir dir,
78 RegisteredCacheParsingFunction parsing_function = 111 ContentProviderParsingFunction parsing_function =
79 [](const VirtualFile& file, const NcaID& id) { return file; }); 112 [](const VirtualFile& file, const NcaID& id) { return file; });
80 ~RegisteredCache(); 113 ~RegisteredCache() override;
81 114
82 void Refresh(); 115 void Refresh() override;
83 116
84 bool HasEntry(u64 title_id, ContentRecordType type) const; 117 bool HasEntry(u64 title_id, ContentRecordType type) const override;
85 bool HasEntry(RegisteredCacheEntry entry) const;
86 118
87 std::optional<u32> GetEntryVersion(u64 title_id) const; 119 std::optional<u32> GetEntryVersion(u64 title_id) const override;
88 120
89 VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const; 121 VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
90 VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
91 122
92 VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const; 123 VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
93 VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
94 124
95 std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const; 125 std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
96 std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
97 126
98 std::vector<RegisteredCacheEntry> ListEntries() const;
99 // If a parameter is not std::nullopt, it will be filtered for from all entries. 127 // If a parameter is not std::nullopt, it will be filtered for from all entries.
100 std::vector<RegisteredCacheEntry> ListEntriesFilter( 128 std::vector<ContentProviderEntry> ListEntriesFilter(
101 std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {}, 129 std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
102 std::optional<u64> title_id = {}) const; 130 std::optional<u64> title_id = {}) const override;
103 131
104 // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure 132 // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
105 // there is a meta NCA and all of them are accessible. 133 // there is a meta NCA and all of them are accessible.
@@ -131,46 +159,70 @@ private:
131 bool RawInstallYuzuMeta(const CNMT& cnmt); 159 bool RawInstallYuzuMeta(const CNMT& cnmt);
132 160
133 VirtualDir dir; 161 VirtualDir dir;
134 RegisteredCacheParsingFunction parser; 162 ContentProviderParsingFunction parser;
135 Core::Crypto::KeyManager keys;
136 163
137 // maps tid -> NcaID of meta 164 // maps tid -> NcaID of meta
138 boost::container::flat_map<u64, NcaID> meta_id; 165 std::map<u64, NcaID> meta_id;
139 // maps tid -> meta 166 // maps tid -> meta
140 boost::container::flat_map<u64, CNMT> meta; 167 std::map<u64, CNMT> meta;
141 // maps tid -> meta for CNMT in yuzu_meta 168 // maps tid -> meta for CNMT in yuzu_meta
142 boost::container::flat_map<u64, CNMT> yuzu_meta; 169 std::map<u64, CNMT> yuzu_meta;
143}; 170};
144 171
145// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface. 172enum class ContentProviderUnionSlot {
146class RegisteredCacheUnion { 173 SysNAND, ///< System NAND
147public: 174 UserNAND, ///< User NAND
148 explicit RegisteredCacheUnion(std::vector<RegisteredCache*> caches); 175 SDMC, ///< SD Card
149 176 FrontendManual, ///< Frontend-defined game list or similar
150 void Refresh(); 177};
151
152 bool HasEntry(u64 title_id, ContentRecordType type) const;
153 bool HasEntry(RegisteredCacheEntry entry) const;
154
155 std::optional<u32> GetEntryVersion(u64 title_id) const;
156
157 VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
158 VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
159
160 VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
161 VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
162
163 std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
164 std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
165 178
166 std::vector<RegisteredCacheEntry> ListEntries() const; 179// Combines multiple ContentProvider(s) (i.e. SysNAND, UserNAND, SDMC) into one interface.
167 // If a parameter is not std::nullopt, it will be filtered for from all entries. 180class ContentProviderUnion : public ContentProvider {
168 std::vector<RegisteredCacheEntry> ListEntriesFilter( 181public:
182 ~ContentProviderUnion() override;
183
184 void SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider);
185 void ClearSlot(ContentProviderUnionSlot slot);
186
187 void Refresh() override;
188 bool HasEntry(u64 title_id, ContentRecordType type) const override;
189 std::optional<u32> GetEntryVersion(u64 title_id) const override;
190 VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
191 VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
192 std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
193 std::vector<ContentProviderEntry> ListEntriesFilter(
194 std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
195 std::optional<u64> title_id) const override;
196
197 std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> ListEntriesFilterOrigin(
198 std::optional<ContentProviderUnionSlot> origin = {},
169 std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {}, 199 std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
170 std::optional<u64> title_id = {}) const; 200 std::optional<u64> title_id = {}) const;
171 201
172private: 202private:
173 std::vector<RegisteredCache*> caches; 203 std::map<ContentProviderUnionSlot, ContentProvider*> providers;
204};
205
206class ManualContentProvider : public ContentProvider {
207public:
208 ~ManualContentProvider() override;
209
210 void AddEntry(TitleType title_type, ContentRecordType content_type, u64 title_id,
211 VirtualFile file);
212 void ClearAllEntries();
213
214 void Refresh() override;
215 bool HasEntry(u64 title_id, ContentRecordType type) const override;
216 std::optional<u32> GetEntryVersion(u64 title_id) const override;
217 VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
218 VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
219 std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
220 std::vector<ContentProviderEntry> ListEntriesFilter(
221 std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
222 std::optional<u64> title_id) const override;
223
224private:
225 std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
174}; 226};
175 227
176} // namespace FileSys 228} // namespace FileSys
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 6ad1e4f86..b2ccb2926 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -48,7 +48,7 @@ ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, Conte
48 48
49 switch (storage) { 49 switch (storage) {
50 case StorageId::None: 50 case StorageId::None:
51 res = Service::FileSystem::GetUnionContents().GetEntry(title_id, type); 51 res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type);
52 break; 52 break;
53 case StorageId::NandSystem: 53 case StorageId::NandSystem:
54 res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type); 54 res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index e1a4210db..c69caae0f 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -143,11 +143,12 @@ std::multimap<u64, std::shared_ptr<NCA>> NSP::GetNCAsByTitleID() const {
143 return out; 143 return out;
144} 144}
145 145
146std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> NSP::GetNCAs() const { 146std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>>
147NSP::GetNCAs() const {
147 return ncas; 148 return ncas;
148} 149}
149 150
150std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const { 151std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type, TitleType title_type) const {
151 if (extracted) 152 if (extracted)
152 LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); 153 LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
153 154
@@ -155,14 +156,14 @@ std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const {
155 if (title_id_iter == ncas.end()) 156 if (title_id_iter == ncas.end())
156 return nullptr; 157 return nullptr;
157 158
158 const auto type_iter = title_id_iter->second.find(type); 159 const auto type_iter = title_id_iter->second.find({title_type, type});
159 if (type_iter == title_id_iter->second.end()) 160 if (type_iter == title_id_iter->second.end())
160 return nullptr; 161 return nullptr;
161 162
162 return type_iter->second; 163 return type_iter->second;
163} 164}
164 165
165VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type) const { 166VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type, TitleType title_type) const {
166 if (extracted) 167 if (extracted)
167 LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); 168 LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
168 const auto nca = GetNCA(title_id, type); 169 const auto nca = GetNCA(title_id, type);
@@ -240,7 +241,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
240 const CNMT cnmt(inner_file); 241 const CNMT cnmt(inner_file);
241 auto& ncas_title = ncas[cnmt.GetTitleID()]; 242 auto& ncas_title = ncas[cnmt.GetTitleID()];
242 243
243 ncas_title[ContentRecordType::Meta] = nca; 244 ncas_title[{cnmt.GetType(), ContentRecordType::Meta}] = nca;
244 for (const auto& rec : cnmt.GetContentRecords()) { 245 for (const auto& rec : cnmt.GetContentRecords()) {
245 const auto id_string = Common::HexArrayToString(rec.nca_id, false); 246 const auto id_string = Common::HexArrayToString(rec.nca_id, false);
246 const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string)); 247 const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
@@ -258,7 +259,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
258 if (next_nca->GetStatus() == Loader::ResultStatus::Success || 259 if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
259 (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS && 260 (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
260 (cnmt.GetTitleID() & 0x800) != 0)) { 261 (cnmt.GetTitleID() & 0x800) != 0)) {
261 ncas_title[rec.type] = std::move(next_nca); 262 ncas_title[{cnmt.GetType(), rec.type}] = std::move(next_nca);
262 } 263 }
263 } 264 }
264 265
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index 9a28ed5bb..ee9b6ce17 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -42,9 +42,12 @@ public:
42 // Type 0 Only (Collection of NCAs + Certificate + Ticket + Meta XML) 42 // Type 0 Only (Collection of NCAs + Certificate + Ticket + Meta XML)
43 std::vector<std::shared_ptr<NCA>> GetNCAsCollapsed() const; 43 std::vector<std::shared_ptr<NCA>> GetNCAsCollapsed() const;
44 std::multimap<u64, std::shared_ptr<NCA>> GetNCAsByTitleID() const; 44 std::multimap<u64, std::shared_ptr<NCA>> GetNCAsByTitleID() const;
45 std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> GetNCAs() const; 45 std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> GetNCAs()
46 std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type) const; 46 const;
47 VirtualFile GetNCAFile(u64 title_id, ContentRecordType type) const; 47 std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type,
48 TitleType title_type = TitleType::Application) const;
49 VirtualFile GetNCAFile(u64 title_id, ContentRecordType type,
50 TitleType title_type = TitleType::Application) const;
48 std::vector<Core::Crypto::Key128> GetTitlekey() const; 51 std::vector<Core::Crypto::Key128> GetTitlekey() const;
49 52
50 std::vector<VirtualFile> GetFiles() const override; 53 std::vector<VirtualFile> GetFiles() const override;
@@ -67,7 +70,7 @@ private:
67 70
68 std::shared_ptr<PartitionFilesystem> pfs; 71 std::shared_ptr<PartitionFilesystem> pfs;
69 // Map title id -> {map type -> NCA} 72 // Map title id -> {map type -> NCA}
70 std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> ncas; 73 std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas;
71 std::vector<VirtualFile> ticket_files; 74 std::vector<VirtualFile> ticket_files;
72 75
73 Core::Crypto::KeyManager keys; 76 Core::Crypto::KeyManager keys;
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index 68406eb63..ac0e1d796 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -139,10 +139,8 @@ public:
139 context->AddDomainObject(std::move(iface)); 139 context->AddDomainObject(std::move(iface));
140 } else { 140 } else {
141 auto& kernel = Core::System::GetInstance().Kernel(); 141 auto& kernel = Core::System::GetInstance().Kernel();
142 auto sessions = 142 auto [server, client] =
143 Kernel::ServerSession::CreateSessionPair(kernel, iface->GetServiceName()); 143 Kernel::ServerSession::CreateSessionPair(kernel, iface->GetServiceName());
144 auto server = std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions);
145 auto client = std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions);
146 iface->ClientConnected(server); 144 iface->ClientConnected(server);
147 context->AddMoveObject(std::move(client)); 145 context->AddMoveObject(std::move(client));
148 } 146 }
diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp
index aa432658e..744b1697d 100644
--- a/src/core/hle/kernel/client_port.cpp
+++ b/src/core/hle/kernel/client_port.cpp
@@ -2,8 +2,6 @@
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 <tuple>
6
7#include "core/hle/kernel/client_port.h" 5#include "core/hle/kernel/client_port.h"
8#include "core/hle/kernel/client_session.h" 6#include "core/hle/kernel/client_session.h"
9#include "core/hle/kernel/errors.h" 7#include "core/hle/kernel/errors.h"
@@ -31,18 +29,18 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
31 active_sessions++; 29 active_sessions++;
32 30
33 // Create a new session pair, let the created sessions inherit the parent port's HLE handler. 31 // Create a new session pair, let the created sessions inherit the parent port's HLE handler.
34 auto sessions = ServerSession::CreateSessionPair(kernel, server_port->GetName(), this); 32 auto [server, client] = ServerSession::CreateSessionPair(kernel, server_port->GetName(), this);
35 33
36 if (server_port->HasHLEHandler()) { 34 if (server_port->HasHLEHandler()) {
37 server_port->GetHLEHandler()->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions)); 35 server_port->GetHLEHandler()->ClientConnected(server);
38 } else { 36 } else {
39 server_port->AppendPendingSession(std::get<SharedPtr<ServerSession>>(sessions)); 37 server_port->AppendPendingSession(server);
40 } 38 }
41 39
42 // Wake the threads waiting on the ServerPort 40 // Wake the threads waiting on the ServerPort
43 server_port->WakeupAllWaitingThreads(); 41 server_port->WakeupAllWaitingThreads();
44 42
45 return MakeResult(std::get<SharedPtr<ClientSession>>(sessions)); 43 return MakeResult(client);
46} 44}
47 45
48void ClientPort::ConnectionClosed() { 46void ClientPort::ConnectionClosed() {
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 3f14bfa86..4d58e7c69 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -21,6 +21,7 @@
21#include "core/hle/kernel/thread.h" 21#include "core/hle/kernel/thread.h"
22#include "core/hle/lock.h" 22#include "core/hle/lock.h"
23#include "core/hle/result.h" 23#include "core/hle/result.h"
24#include "core/memory.h"
24 25
25namespace Kernel { 26namespace Kernel {
26 27
@@ -181,6 +182,7 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) {
181 182
182void KernelCore::MakeCurrentProcess(Process* process) { 183void KernelCore::MakeCurrentProcess(Process* process) {
183 impl->current_process = process; 184 impl->current_process = process;
185 Memory::SetCurrentPageTable(&process->VMManager().page_table);
184} 186}
185 187
186Process* KernelCore::CurrentProcess() { 188Process* KernelCore::CurrentProcess() {
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 041267318..4e94048da 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -32,9 +32,6 @@ namespace {
32 * @param priority The priority to give the main thread 32 * @param priority The priority to give the main thread
33 */ 33 */
34void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_point, u32 priority) { 34void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_point, u32 priority) {
35 // Setup page table so we can write to memory
36 Memory::SetCurrentPageTable(&owner_process.VMManager().page_table);
37
38 // Initialize new "main" thread 35 // Initialize new "main" thread
39 const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress(); 36 const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress();
40 auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0, 37 auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0,
@@ -109,6 +106,8 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
109 is_64bit_process = metadata.Is64BitProgram(); 106 is_64bit_process = metadata.Is64BitProgram();
110 107
111 vm_manager.Reset(metadata.GetAddressSpaceType()); 108 vm_manager.Reset(metadata.GetAddressSpaceType());
109 // Ensure that the potentially resized page table is seen by CPU backends.
110 Memory::SetCurrentPageTable(&vm_manager.page_table);
112 111
113 const auto& caps = metadata.GetKernelCapabilities(); 112 const auto& caps = metadata.GetKernelCapabilities();
114 const auto capability_init_result = 113 const auto capability_init_result =
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index ac501bf7f..e8447b69a 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -101,7 +101,6 @@ void Scheduler::SwitchContext(Thread* new_thread) {
101 auto* const thread_owner_process = current_thread->GetOwnerProcess(); 101 auto* const thread_owner_process = current_thread->GetOwnerProcess();
102 if (previous_process != thread_owner_process) { 102 if (previous_process != thread_owner_process) {
103 system.Kernel().MakeCurrentProcess(thread_owner_process); 103 system.Kernel().MakeCurrentProcess(thread_owner_process);
104 Memory::SetCurrentPageTable(&thread_owner_process->VMManager().page_table);
105 } 104 }
106 105
107 cpu_core.LoadContext(new_thread->GetContext()); 106 cpu_core.LoadContext(new_thread->GetContext());
diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp
index 708fdf9e1..02e7c60e6 100644
--- a/src/core/hle/kernel/server_port.cpp
+++ b/src/core/hle/kernel/server_port.cpp
@@ -39,9 +39,8 @@ void ServerPort::Acquire(Thread* thread) {
39 ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); 39 ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
40} 40}
41 41
42std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair( 42ServerPort::PortPair ServerPort::CreatePortPair(KernelCore& kernel, u32 max_sessions,
43 KernelCore& kernel, u32 max_sessions, std::string name) { 43 std::string name) {
44
45 SharedPtr<ServerPort> server_port(new ServerPort(kernel)); 44 SharedPtr<ServerPort> server_port(new ServerPort(kernel));
46 SharedPtr<ClientPort> client_port(new ClientPort(kernel)); 45 SharedPtr<ClientPort> client_port(new ClientPort(kernel));
47 46
@@ -51,7 +50,7 @@ std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortP
51 client_port->max_sessions = max_sessions; 50 client_port->max_sessions = max_sessions;
52 client_port->active_sessions = 0; 51 client_port->active_sessions = 0;
53 52
54 return std::make_tuple(std::move(server_port), std::move(client_port)); 53 return std::make_pair(std::move(server_port), std::move(client_port));
55} 54}
56 55
57} // namespace Kernel 56} // namespace Kernel
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index 76293cb8b..fef573b71 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -6,7 +6,7 @@
6 6
7#include <memory> 7#include <memory>
8#include <string> 8#include <string>
9#include <tuple> 9#include <utility>
10#include <vector> 10#include <vector>
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "core/hle/kernel/object.h" 12#include "core/hle/kernel/object.h"
@@ -23,6 +23,7 @@ class SessionRequestHandler;
23class ServerPort final : public WaitObject { 23class ServerPort final : public WaitObject {
24public: 24public:
25 using HLEHandler = std::shared_ptr<SessionRequestHandler>; 25 using HLEHandler = std::shared_ptr<SessionRequestHandler>;
26 using PortPair = std::pair<SharedPtr<ServerPort>, SharedPtr<ClientPort>>;
26 27
27 /** 28 /**
28 * Creates a pair of ServerPort and an associated ClientPort. 29 * Creates a pair of ServerPort and an associated ClientPort.
@@ -32,8 +33,8 @@ public:
32 * @param name Optional name of the ports 33 * @param name Optional name of the ports
33 * @return The created port tuple 34 * @return The created port tuple
34 */ 35 */
35 static std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> CreatePortPair( 36 static PortPair CreatePortPair(KernelCore& kernel, u32 max_sessions,
36 KernelCore& kernel, u32 max_sessions, std::string name = "UnknownPort"); 37 std::string name = "UnknownPort");
37 38
38 std::string GetTypeName() const override { 39 std::string GetTypeName() const override {
39 return "ServerPort"; 40 return "ServerPort";
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 40cec143e..a6b2cf06a 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -204,6 +204,6 @@ ServerSession::SessionPair ServerSession::CreateSessionPair(KernelCore& kernel,
204 client_session->parent = parent; 204 client_session->parent = parent;
205 server_session->parent = parent; 205 server_session->parent = parent;
206 206
207 return std::make_tuple(std::move(server_session), std::move(client_session)); 207 return std::make_pair(std::move(server_session), std::move(client_session));
208} 208}
209} // namespace Kernel 209} // namespace Kernel
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index 3429a326f..09b835ff8 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -6,6 +6,7 @@
6 6
7#include <memory> 7#include <memory>
8#include <string> 8#include <string>
9#include <utility>
9#include <vector> 10#include <vector>
10 11
11#include "core/hle/kernel/object.h" 12#include "core/hle/kernel/object.h"
@@ -58,7 +59,7 @@ public:
58 return parent.get(); 59 return parent.get();
59 } 60 }
60 61
61 using SessionPair = std::tuple<SharedPtr<ServerSession>, SharedPtr<ClientSession>>; 62 using SessionPair = std::pair<SharedPtr<ServerSession>, SharedPtr<ClientSession>>;
62 63
63 /** 64 /**
64 * Creates a pair of ServerSession and an associated ClientSession. 65 * Creates a pair of ServerSession and an associated ClientSession.
diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp
index 9b0aa7f5f..7e17df98a 100644
--- a/src/core/hle/service/am/applets/web_browser.cpp
+++ b/src/core/hle/service/am/applets/web_browser.cpp
@@ -86,7 +86,7 @@ static FileSys::VirtualFile GetManualRomFS() {
86 if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success) 86 if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success)
87 return out; 87 return out;
88 88
89 const auto& installed{FileSystem::GetUnionContents()}; 89 const auto& installed{Core::System::GetInstance().GetContentProvider()};
90 const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(), 90 const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(),
91 FileSys::ContentRecordType::Manual); 91 FileSys::ContentRecordType::Manual);
92 92
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index b506bc3dd..2d768d9fc 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -33,11 +33,11 @@ static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
33 33
34static std::vector<u64> AccumulateAOCTitleIDs() { 34static std::vector<u64> AccumulateAOCTitleIDs() {
35 std::vector<u64> add_on_content; 35 std::vector<u64> add_on_content;
36 const auto rcu = FileSystem::GetUnionContents(); 36 const auto& rcu = Core::System::GetInstance().GetContentProvider();
37 const auto list = 37 const auto list =
38 rcu.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); 38 rcu.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
39 std::transform(list.begin(), list.end(), std::back_inserter(add_on_content), 39 std::transform(list.begin(), list.end(), std::back_inserter(add_on_content),
40 [](const FileSys::RegisteredCacheEntry& rce) { return rce.title_id; }); 40 [](const FileSys::ContentProviderEntry& rce) { return rce.title_id; });
41 add_on_content.erase( 41 add_on_content.erase(
42 std::remove_if( 42 std::remove_if(
43 add_on_content.begin(), add_on_content.end(), 43 add_on_content.begin(), add_on_content.end(),
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 4c2b371c3..1ebfeb4bf 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -391,11 +391,6 @@ void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
391 save_data_factory->WriteSaveDataSize(type, title_id, user_id, new_value); 391 save_data_factory->WriteSaveDataSize(type, title_id, user_id, new_value);
392} 392}
393 393
394FileSys::RegisteredCacheUnion GetUnionContents() {
395 return FileSys::RegisteredCacheUnion{
396 {GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()}};
397}
398
399FileSys::RegisteredCache* GetSystemNANDContents() { 394FileSys::RegisteredCache* GetSystemNANDContents() {
400 LOG_TRACE(Service_FS, "Opening System NAND Contents"); 395 LOG_TRACE(Service_FS, "Opening System NAND Contents");
401 396
@@ -460,6 +455,10 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
460 if (bis_factory == nullptr) { 455 if (bis_factory == nullptr) {
461 bis_factory = 456 bis_factory =
462 std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory); 457 std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory);
458 Core::System::GetInstance().RegisterContentProvider(
459 FileSys::ContentProviderUnionSlot::SysNAND, bis_factory->GetSystemNANDContents());
460 Core::System::GetInstance().RegisterContentProvider(
461 FileSys::ContentProviderUnionSlot::UserNAND, bis_factory->GetUserNANDContents());
463 } 462 }
464 463
465 if (save_data_factory == nullptr) { 464 if (save_data_factory == nullptr) {
@@ -468,6 +467,8 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
468 467
469 if (sdmc_factory == nullptr) { 468 if (sdmc_factory == nullptr) {
470 sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory)); 469 sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
470 Core::System::GetInstance().RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
471 sdmc_factory->GetSDMCContents());
471 } 472 }
472} 473}
473 474
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 7cfc0d902..6481f237c 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -54,8 +54,6 @@ FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id,
54void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id, 54void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
55 FileSys::SaveDataSize new_value); 55 FileSys::SaveDataSize new_value);
56 56
57FileSys::RegisteredCacheUnion GetUnionContents();
58
59FileSys::RegisteredCache* GetSystemNANDContents(); 57FileSys::RegisteredCache* GetSystemNANDContents();
60FileSys::RegisteredCache* GetUserNANDContents(); 58FileSys::RegisteredCache* GetUserNANDContents();
61FileSys::RegisteredCache* GetSDMCContents(); 59FileSys::RegisteredCache* GetSDMCContents();
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index ffe2eea8a..d7c47c197 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -21,6 +21,8 @@
21#include "core/memory.h" 21#include "core/memory.h"
22#include "core/settings.h" 22#include "core/settings.h"
23 23
24#pragma optimize("", off)
25
24namespace Loader { 26namespace Loader {
25namespace { 27namespace {
26struct MODHeader { 28struct MODHeader {
@@ -136,13 +138,13 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
136 138
137 // Apply patches if necessary 139 // Apply patches if necessary
138 if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { 140 if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
139 std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); 141 std::vector<u8> pi_header;
140 pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header), 142 pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header),
141 reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader)); 143 reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader));
142 pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(), 144 pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(),
143 program_image.end()); 145 program_image.end());
144 146
145 pi_header = pm->PatchNSO(pi_header); 147 pi_header = pm->PatchNSO(pi_header, file.GetName());
146 148
147 std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin()); 149 std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin());
148 } 150 }
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 332c1037c..4e0538bc2 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -38,10 +38,6 @@ void SetCurrentPageTable(Common::PageTable* page_table) {
38 } 38 }
39} 39}
40 40
41Common::PageTable* GetCurrentPageTable() {
42 return current_page_table;
43}
44
45static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* memory, 41static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* memory,
46 Common::PageType type) { 42 Common::PageType type) {
47 LOG_DEBUG(HW_Memory, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * PAGE_SIZE, 43 LOG_DEBUG(HW_Memory, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * PAGE_SIZE,
diff --git a/src/core/memory.h b/src/core/memory.h
index 1d38cdca8..6845f5fe1 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -28,16 +28,6 @@ constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
28 28
29/// Virtual user-space memory regions 29/// Virtual user-space memory regions
30enum : VAddr { 30enum : VAddr {
31 /// Read-only page containing kernel and system configuration values.
32 CONFIG_MEMORY_VADDR = 0x1FF80000,
33 CONFIG_MEMORY_SIZE = 0x00001000,
34 CONFIG_MEMORY_VADDR_END = CONFIG_MEMORY_VADDR + CONFIG_MEMORY_SIZE,
35
36 /// Usually read-only page containing mostly values read from hardware.
37 SHARED_PAGE_VADDR = 0x1FF81000,
38 SHARED_PAGE_SIZE = 0x00001000,
39 SHARED_PAGE_VADDR_END = SHARED_PAGE_VADDR + SHARED_PAGE_SIZE,
40
41 /// TLS (Thread-Local Storage) related. 31 /// TLS (Thread-Local Storage) related.
42 TLS_ENTRY_SIZE = 0x200, 32 TLS_ENTRY_SIZE = 0x200,
43 33
@@ -50,9 +40,8 @@ enum : VAddr {
50 KERNEL_REGION_END = KERNEL_REGION_VADDR + KERNEL_REGION_SIZE, 40 KERNEL_REGION_END = KERNEL_REGION_VADDR + KERNEL_REGION_SIZE,
51}; 41};
52 42
53/// Currently active page table 43/// Changes the currently active page table.
54void SetCurrentPageTable(Common::PageTable* page_table); 44void SetCurrentPageTable(Common::PageTable* page_table);
55Common::PageTable* GetCurrentPageTable();
56 45
57/// Determines if the given VAddr is valid for the specified process. 46/// Determines if the given VAddr is valid for the specified process.
58bool IsValidVirtualAddress(const Kernel::Process& process, VAddr vaddr); 47bool IsValidVirtualAddress(const Kernel::Process& process, VAddr vaddr);
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
index 3e1a735c3..58af41f6e 100644
--- a/src/tests/core/arm/arm_test_common.cpp
+++ b/src/tests/core/arm/arm_test_common.cpp
@@ -17,7 +17,6 @@ TestEnvironment::TestEnvironment(bool mutable_memory_)
17 : mutable_memory(mutable_memory_), 17 : mutable_memory(mutable_memory_),
18 test_memory(std::make_shared<TestMemory>(this)), kernel{Core::System::GetInstance()} { 18 test_memory(std::make_shared<TestMemory>(this)), kernel{Core::System::GetInstance()} {
19 auto process = Kernel::Process::Create(Core::System::GetInstance(), ""); 19 auto process = Kernel::Process::Create(Core::System::GetInstance(), "");
20 kernel.MakeCurrentProcess(process.get());
21 page_table = &process->VMManager().page_table; 20 page_table = &process->VMManager().page_table;
22 21
23 std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr); 22 std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr);
@@ -28,7 +27,7 @@ TestEnvironment::TestEnvironment(bool mutable_memory_)
28 Memory::MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory); 27 Memory::MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory);
29 Memory::MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory); 28 Memory::MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory);
30 29
31 Memory::SetCurrentPageTable(page_table); 30 kernel.MakeCurrentProcess(process.get());
32} 31}
33 32
34TestEnvironment::~TestEnvironment() { 33TestEnvironment::~TestEnvironment() {
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 8194a4b4a..74403eed4 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -334,8 +334,8 @@ void Maxwell3D::ProcessSyncPoint() {
334 const u32 sync_point = regs.sync_info.sync_point.Value(); 334 const u32 sync_point = regs.sync_info.sync_point.Value();
335 const u32 increment = regs.sync_info.increment.Value(); 335 const u32 increment = regs.sync_info.increment.Value();
336 const u32 cache_flush = regs.sync_info.unknown.Value(); 336 const u32 cache_flush = regs.sync_info.unknown.Value();
337 UNIMPLEMENTED_MSG("Syncpoint Set {}, increment: {}, unk: {}", sync_point, increment, 337 LOG_DEBUG(HW_GPU, "Syncpoint set {}, increment: {}, unk: {}", sync_point, increment,
338 cache_flush); 338 cache_flush);
339} 339}
340 340
341void Maxwell3D::DrawArrays() { 341void Maxwell3D::DrawArrays() {
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 7f613370b..2e1e96c81 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -1238,13 +1238,16 @@ union Instruction {
1238 1238
1239 union { 1239 union {
1240 BitField<20, 16, u64> imm20_16; 1240 BitField<20, 16, u64> imm20_16;
1241 BitField<35, 1, u64> high_b_rr; // used on RR
1241 BitField<36, 1, u64> product_shift_left; 1242 BitField<36, 1, u64> product_shift_left;
1242 BitField<37, 1, u64> merge_37; 1243 BitField<37, 1, u64> merge_37;
1243 BitField<48, 1, u64> sign_a; 1244 BitField<48, 1, u64> sign_a;
1244 BitField<49, 1, u64> sign_b; 1245 BitField<49, 1, u64> sign_b;
1246 BitField<50, 2, XmadMode> mode_cbf; // used by CR, RC
1245 BitField<50, 3, XmadMode> mode; 1247 BitField<50, 3, XmadMode> mode;
1246 BitField<52, 1, u64> high_b; 1248 BitField<52, 1, u64> high_b;
1247 BitField<53, 1, u64> high_a; 1249 BitField<53, 1, u64> high_a;
1250 BitField<55, 1, u64> product_shift_left_second; // used on CR
1248 BitField<56, 1, u64> merge_56; 1251 BitField<56, 1, u64> merge_56;
1249 } xmad; 1252 } xmad;
1250 1253
@@ -1662,7 +1665,7 @@ private:
1662 INST("0011011-11110---", Id::BFI_IMM_R, Type::Bfi, "BFI_IMM_R"), 1665 INST("0011011-11110---", Id::BFI_IMM_R, Type::Bfi, "BFI_IMM_R"),
1663 INST("0100110001000---", Id::LOP_C, Type::ArithmeticInteger, "LOP_C"), 1666 INST("0100110001000---", Id::LOP_C, Type::ArithmeticInteger, "LOP_C"),
1664 INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"), 1667 INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"),
1665 INST("0011100001000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"), 1668 INST("0011100-01000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"),
1666 INST("000001----------", Id::LOP32I, Type::ArithmeticIntegerImmediate, "LOP32I"), 1669 INST("000001----------", Id::LOP32I, Type::ArithmeticIntegerImmediate, "LOP32I"),
1667 INST("0000001---------", Id::LOP3_C, Type::ArithmeticInteger, "LOP3_C"), 1670 INST("0000001---------", Id::LOP3_C, Type::ArithmeticInteger, "LOP3_C"),
1668 INST("0101101111100---", Id::LOP3_R, Type::ArithmeticInteger, "LOP3_R"), 1671 INST("0101101111100---", Id::LOP3_R, Type::ArithmeticInteger, "LOP3_R"),
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index 30b29e14d..4461083ff 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -31,7 +31,7 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) {
31 31
32GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer) : renderer{renderer} { 32GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer) : renderer{renderer} {
33 auto& rasterizer{renderer.Rasterizer()}; 33 auto& rasterizer{renderer.Rasterizer()};
34 memory_manager = std::make_unique<Tegra::MemoryManager>(); 34 memory_manager = std::make_unique<Tegra::MemoryManager>(rasterizer);
35 dma_pusher = std::make_unique<Tegra::DmaPusher>(*this); 35 dma_pusher = std::make_unique<Tegra::DmaPusher>(*this);
36 maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, rasterizer, *memory_manager); 36 maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, rasterizer, *memory_manager);
37 fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager); 37 fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager);
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 8417324ff..0f4e820aa 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -5,16 +5,13 @@
5#include "common/alignment.h" 5#include "common/alignment.h"
6#include "common/assert.h" 6#include "common/assert.h"
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "core/core.h"
9#include "core/memory.h" 8#include "core/memory.h"
10#include "video_core/gpu.h"
11#include "video_core/memory_manager.h" 9#include "video_core/memory_manager.h"
12#include "video_core/rasterizer_interface.h" 10#include "video_core/rasterizer_interface.h"
13#include "video_core/renderer_base.h"
14 11
15namespace Tegra { 12namespace Tegra {
16 13
17MemoryManager::MemoryManager() { 14MemoryManager::MemoryManager(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {
18 std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr); 15 std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr);
19 std::fill(page_table.attributes.begin(), page_table.attributes.end(), 16 std::fill(page_table.attributes.begin(), page_table.attributes.end(),
20 Common::PageType::Unmapped); 17 Common::PageType::Unmapped);
@@ -70,8 +67,7 @@ GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
70 const u64 aligned_size{Common::AlignUp(size, page_size)}; 67 const u64 aligned_size{Common::AlignUp(size, page_size)};
71 const CacheAddr cache_addr{ToCacheAddr(GetPointer(gpu_addr))}; 68 const CacheAddr cache_addr{ToCacheAddr(GetPointer(gpu_addr))};
72 69
73 Core::System::GetInstance().Renderer().Rasterizer().FlushAndInvalidateRegion(cache_addr, 70 rasterizer.FlushAndInvalidateRegion(cache_addr, aligned_size);
74 aligned_size);
75 UnmapRange(gpu_addr, aligned_size); 71 UnmapRange(gpu_addr, aligned_size);
76 72
77 return gpu_addr; 73 return gpu_addr;
@@ -204,14 +200,85 @@ const u8* MemoryManager::GetPointer(GPUVAddr addr) const {
204} 200}
205 201
206void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size) const { 202void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size) const {
207 std::memcpy(dest_buffer, GetPointer(src_addr), size); 203 std::size_t remaining_size{size};
204 std::size_t page_index{src_addr >> page_bits};
205 std::size_t page_offset{src_addr & page_mask};
206
207 while (remaining_size > 0) {
208 const std::size_t copy_amount{
209 std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
210
211 switch (page_table.attributes[page_index]) {
212 case Common::PageType::Memory: {
213 const u8* src_ptr{page_table.pointers[page_index] + page_offset};
214 rasterizer.FlushRegion(ToCacheAddr(src_ptr), copy_amount);
215 std::memcpy(dest_buffer, src_ptr, copy_amount);
216 break;
217 }
218 default:
219 UNREACHABLE();
220 }
221
222 page_index++;
223 page_offset = 0;
224 dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
225 remaining_size -= copy_amount;
226 }
208} 227}
228
209void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size) { 229void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size) {
210 std::memcpy(GetPointer(dest_addr), src_buffer, size); 230 std::size_t remaining_size{size};
231 std::size_t page_index{dest_addr >> page_bits};
232 std::size_t page_offset{dest_addr & page_mask};
233
234 while (remaining_size > 0) {
235 const std::size_t copy_amount{
236 std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
237
238 switch (page_table.attributes[page_index]) {
239 case Common::PageType::Memory: {
240 u8* dest_ptr{page_table.pointers[page_index] + page_offset};
241 rasterizer.InvalidateRegion(ToCacheAddr(dest_ptr), copy_amount);
242 std::memcpy(dest_ptr, src_buffer, copy_amount);
243 break;
244 }
245 default:
246 UNREACHABLE();
247 }
248
249 page_index++;
250 page_offset = 0;
251 src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
252 remaining_size -= copy_amount;
253 }
211} 254}
212 255
213void MemoryManager::CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size) { 256void MemoryManager::CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size) {
214 std::memcpy(GetPointer(dest_addr), GetPointer(src_addr), size); 257 std::size_t remaining_size{size};
258 std::size_t page_index{src_addr >> page_bits};
259 std::size_t page_offset{src_addr & page_mask};
260
261 while (remaining_size > 0) {
262 const std::size_t copy_amount{
263 std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
264
265 switch (page_table.attributes[page_index]) {
266 case Common::PageType::Memory: {
267 const u8* src_ptr{page_table.pointers[page_index] + page_offset};
268 rasterizer.FlushRegion(ToCacheAddr(src_ptr), copy_amount);
269 WriteBlock(dest_addr, src_ptr, copy_amount);
270 break;
271 }
272 default:
273 UNREACHABLE();
274 }
275
276 page_index++;
277 page_offset = 0;
278 dest_addr += static_cast<VAddr>(copy_amount);
279 src_addr += static_cast<VAddr>(copy_amount);
280 remaining_size -= copy_amount;
281 }
215} 282}
216 283
217void MemoryManager::MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type, 284void MemoryManager::MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type,
@@ -351,7 +418,7 @@ MemoryManager::VMAIter MemoryManager::CarveVMA(GPUVAddr base, u64 size) {
351 const VirtualMemoryArea& vma{vma_handle->second}; 418 const VirtualMemoryArea& vma{vma_handle->second};
352 if (vma.type == VirtualMemoryArea::Type::Mapped) { 419 if (vma.type == VirtualMemoryArea::Type::Mapped) {
353 // Region is already allocated 420 // Region is already allocated
354 return {}; 421 return vma_handle;
355 } 422 }
356 423
357 const VAddr start_in_vma{base - vma.base}; 424 const VAddr start_in_vma{base - vma.base};
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index 178e2f655..647cbf93a 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -10,6 +10,10 @@
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "common/page_table.h" 11#include "common/page_table.h"
12 12
13namespace VideoCore {
14class RasterizerInterface;
15}
16
13namespace Tegra { 17namespace Tegra {
14 18
15/** 19/**
@@ -43,7 +47,7 @@ struct VirtualMemoryArea {
43 47
44class MemoryManager final { 48class MemoryManager final {
45public: 49public:
46 MemoryManager(); 50 MemoryManager(VideoCore::RasterizerInterface& rasterizer);
47 51
48 GPUVAddr AllocateSpace(u64 size, u64 align); 52 GPUVAddr AllocateSpace(u64 size, u64 align);
49 GPUVAddr AllocateSpace(GPUVAddr addr, u64 size, u64 align); 53 GPUVAddr AllocateSpace(GPUVAddr addr, u64 size, u64 align);
@@ -144,6 +148,7 @@ private:
144 148
145 Common::PageTable page_table{page_bits}; 149 Common::PageTable page_table{page_bits};
146 VMAMap vma_map; 150 VMAMap vma_map;
151 VideoCore::RasterizerInterface& rasterizer;
147}; 152};
148 153
149} // namespace Tegra 154} // namespace Tegra
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 0aa66fa5b..aa6da1944 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -662,8 +662,8 @@ void CachedSurface::FlushGLBuffer() {
662 gl_buffer[0].resize(GetSizeInBytes()); 662 gl_buffer[0].resize(GetSizeInBytes());
663 663
664 const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type); 664 const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
665 // Ensure no bad interactions with GL_UNPACK_ALIGNMENT 665 const u32 align = std::clamp(params.RowAlign(0), 1U, 8U);
666 ASSERT(params.width * GetBytesPerPixel(params.pixel_format) % 4 == 0); 666 glPixelStorei(GL_PACK_ALIGNMENT, align);
667 glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width)); 667 glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width));
668 ASSERT(!tuple.compressed); 668 ASSERT(!tuple.compressed);
669 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); 669 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
@@ -708,8 +708,8 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
708 708
709 const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type); 709 const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
710 710
711 // Ensure no bad interactions with GL_UNPACK_ALIGNMENT 711 const u32 align = std::clamp(params.RowAlign(mip_map), 1U, 8U);
712 ASSERT(params.MipWidth(mip_map) * GetBytesPerPixel(params.pixel_format) % 4 == 0); 712 glPixelStorei(GL_UNPACK_ALIGNMENT, align);
713 glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.MipWidth(mip_map))); 713 glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.MipWidth(mip_map)));
714 714
715 const auto image_size = static_cast<GLsizei>(params.GetMipmapSizeGL(mip_map, false)); 715 const auto image_size = static_cast<GLsizei>(params.GetMipmapSizeGL(mip_map, false));
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index ad4fd3ad2..db280dbb3 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -11,6 +11,7 @@
11#include <vector> 11#include <vector>
12 12
13#include "common/alignment.h" 13#include "common/alignment.h"
14#include "common/bit_util.h"
14#include "common/common_types.h" 15#include "common/common_types.h"
15#include "common/hash.h" 16#include "common/hash.h"
16#include "common/math_util.h" 17#include "common/math_util.h"
@@ -205,6 +206,13 @@ struct SurfaceParams {
205 return bd; 206 return bd;
206 } 207 }
207 208
209 u32 RowAlign(u32 mip_level) const {
210 const u32 m_width = MipWidth(mip_level);
211 const u32 bytes_per_pixel = GetBytesPerPixel(pixel_format);
212 const u32 l2 = Common::CountTrailingZeroes32(m_width * bytes_per_pixel);
213 return (1U << l2);
214 }
215
208 /// Creates SurfaceParams from a texture configuration 216 /// Creates SurfaceParams from a texture configuration
209 static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config, 217 static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config,
210 const GLShader::SamplerEntry& entry); 218 const GLShader::SamplerEntry& entry);
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index ffaff82e5..99f67494c 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -40,6 +40,10 @@ GPUVAddr GetShaderAddress(Maxwell::ShaderProgram program) {
40/// Gets the shader program code from memory for the specified address 40/// Gets the shader program code from memory for the specified address
41ProgramCode GetShaderCode(const u8* host_ptr) { 41ProgramCode GetShaderCode(const u8* host_ptr) {
42 ProgramCode program_code(VideoCommon::Shader::MAX_PROGRAM_LENGTH); 42 ProgramCode program_code(VideoCommon::Shader::MAX_PROGRAM_LENGTH);
43 ASSERT_OR_EXECUTE(host_ptr != nullptr, {
44 std::fill(program_code.begin(), program_code.end(), 0);
45 return program_code;
46 });
43 std::memcpy(program_code.data(), host_ptr, program_code.size() * sizeof(u64)); 47 std::memcpy(program_code.data(), host_ptr, program_code.size() * sizeof(u64));
44 return program_code; 48 return program_code;
45} 49}
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index a1a51f226..3ea08ef7b 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -21,6 +21,8 @@
21 21
22namespace OpenGL::GLShader { 22namespace OpenGL::GLShader {
23 23
24namespace {
25
24using Tegra::Shader::Attribute; 26using Tegra::Shader::Attribute;
25using Tegra::Shader::AttributeUse; 27using Tegra::Shader::AttributeUse;
26using Tegra::Shader::Header; 28using Tegra::Shader::Header;
@@ -34,14 +36,18 @@ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
34using ShaderStage = Tegra::Engines::Maxwell3D::Regs::ShaderStage; 36using ShaderStage = Tegra::Engines::Maxwell3D::Regs::ShaderStage;
35using Operation = const OperationNode&; 37using Operation = const OperationNode&;
36 38
39enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat };
40
41struct TextureAoffi {};
42using TextureArgument = std::pair<Type, Node>;
43using TextureIR = std::variant<TextureAoffi, TextureArgument>;
44
37enum : u32 { POSITION_VARYING_LOCATION = 0, GENERIC_VARYING_START_LOCATION = 1 }; 45enum : u32 { POSITION_VARYING_LOCATION = 0, GENERIC_VARYING_START_LOCATION = 1 };
38constexpr u32 MAX_CONSTBUFFER_ELEMENTS = 46constexpr u32 MAX_CONSTBUFFER_ELEMENTS =
39 static_cast<u32>(RasterizerOpenGL::MaxConstbufferSize) / (4 * sizeof(float)); 47 static_cast<u32>(RasterizerOpenGL::MaxConstbufferSize) / (4 * sizeof(float));
40constexpr u32 MAX_GLOBALMEMORY_ELEMENTS = 48constexpr u32 MAX_GLOBALMEMORY_ELEMENTS =
41 static_cast<u32>(RasterizerOpenGL::MaxGlobalMemorySize) / sizeof(float); 49 static_cast<u32>(RasterizerOpenGL::MaxGlobalMemorySize) / sizeof(float);
42 50
43enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat };
44
45class ShaderWriter { 51class ShaderWriter {
46public: 52public:
47 void AddExpression(std::string_view text) { 53 void AddExpression(std::string_view text) {
@@ -91,7 +97,7 @@ private:
91}; 97};
92 98
93/// Generates code to use for a swizzle operation. 99/// Generates code to use for a swizzle operation.
94static std::string GetSwizzle(u32 elem) { 100std::string GetSwizzle(u32 elem) {
95 ASSERT(elem <= 3); 101 ASSERT(elem <= 3);
96 std::string swizzle = "."; 102 std::string swizzle = ".";
97 swizzle += "xyzw"[elem]; 103 swizzle += "xyzw"[elem];
@@ -99,7 +105,7 @@ static std::string GetSwizzle(u32 elem) {
99} 105}
100 106
101/// Translate topology 107/// Translate topology
102static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) { 108std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
103 switch (topology) { 109 switch (topology) {
104 case Tegra::Shader::OutputTopology::PointList: 110 case Tegra::Shader::OutputTopology::PointList:
105 return "points"; 111 return "points";
@@ -114,7 +120,7 @@ static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
114} 120}
115 121
116/// Returns true if an object has to be treated as precise 122/// Returns true if an object has to be treated as precise
117static bool IsPrecise(Operation operand) { 123bool IsPrecise(Operation operand) {
118 const auto& meta = operand.GetMeta(); 124 const auto& meta = operand.GetMeta();
119 125
120 if (const auto arithmetic = std::get_if<MetaArithmetic>(&meta)) { 126 if (const auto arithmetic = std::get_if<MetaArithmetic>(&meta)) {
@@ -126,7 +132,7 @@ static bool IsPrecise(Operation operand) {
126 return false; 132 return false;
127} 133}
128 134
129static bool IsPrecise(Node node) { 135bool IsPrecise(Node node) {
130 if (const auto operation = std::get_if<OperationNode>(node)) { 136 if (const auto operation = std::get_if<OperationNode>(node)) {
131 return IsPrecise(*operation); 137 return IsPrecise(*operation);
132 } 138 }
@@ -723,8 +729,8 @@ private:
723 result_type)); 729 result_type));
724 } 730 }
725 731
726 std::string GenerateTexture(Operation operation, const std::string& func, 732 std::string GenerateTexture(Operation operation, const std::string& function_suffix,
727 const std::vector<std::pair<Type, Node>>& extras) { 733 const std::vector<TextureIR>& extras) {
728 constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"}; 734 constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"};
729 735
730 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 736 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
@@ -734,11 +740,11 @@ private:
734 const bool has_array = meta->sampler.IsArray(); 740 const bool has_array = meta->sampler.IsArray();
735 const bool has_shadow = meta->sampler.IsShadow(); 741 const bool has_shadow = meta->sampler.IsShadow();
736 742
737 std::string expr = func; 743 std::string expr = "texture" + function_suffix;
738 expr += '('; 744 if (!meta->aoffi.empty()) {
739 expr += GetSampler(meta->sampler); 745 expr += "Offset";
740 expr += ", "; 746 }
741 747 expr += '(' + GetSampler(meta->sampler) + ", ";
742 expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1); 748 expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1);
743 expr += '('; 749 expr += '(';
744 for (std::size_t i = 0; i < count; ++i) { 750 for (std::size_t i = 0; i < count; ++i) {
@@ -756,36 +762,74 @@ private:
756 } 762 }
757 expr += ')'; 763 expr += ')';
758 764
759 for (const auto& extra_pair : extras) { 765 for (const auto& variant : extras) {
760 const auto [type, operand] = extra_pair; 766 if (const auto argument = std::get_if<TextureArgument>(&variant)) {
761 if (operand == nullptr) { 767 expr += GenerateTextureArgument(*argument);
762 continue; 768 } else if (std::get_if<TextureAoffi>(&variant)) {
769 expr += GenerateTextureAoffi(meta->aoffi);
770 } else {
771 UNREACHABLE();
763 } 772 }
764 expr += ", "; 773 }
765 774
766 switch (type) { 775 return expr + ')';
767 case Type::Int: 776 }
768 if (const auto immediate = std::get_if<ImmediateNode>(operand)) { 777
769 // Inline the string as an immediate integer in GLSL (some extra arguments are 778 std::string GenerateTextureArgument(TextureArgument argument) {
770 // required to be constant) 779 const auto [type, operand] = argument;
771 expr += std::to_string(static_cast<s32>(immediate->GetValue())); 780 if (operand == nullptr) {
772 } else { 781 return {};
773 expr += "ftoi(" + Visit(operand) + ')'; 782 }
774 } 783
775 break; 784 std::string expr = ", ";
776 case Type::Float: 785 switch (type) {
777 expr += Visit(operand); 786 case Type::Int:
778 break; 787 if (const auto immediate = std::get_if<ImmediateNode>(operand)) {
779 default: { 788 // Inline the string as an immediate integer in GLSL (some extra arguments are
780 const auto type_int = static_cast<u32>(type); 789 // required to be constant)
781 UNIMPLEMENTED_MSG("Unimplemented extra type={}", type_int); 790 expr += std::to_string(static_cast<s32>(immediate->GetValue()));
782 expr += '0'; 791 } else {
783 break; 792 expr += "ftoi(" + Visit(operand) + ')';
793 }
794 break;
795 case Type::Float:
796 expr += Visit(operand);
797 break;
798 default: {
799 const auto type_int = static_cast<u32>(type);
800 UNIMPLEMENTED_MSG("Unimplemented extra type={}", type_int);
801 expr += '0';
802 break;
803 }
804 }
805 return expr;
806 }
807
808 std::string GenerateTextureAoffi(const std::vector<Node>& aoffi) {
809 if (aoffi.empty()) {
810 return {};
811 }
812 constexpr std::array<const char*, 3> coord_constructors = {"int", "ivec2", "ivec3"};
813 std::string expr = ", ";
814 expr += coord_constructors.at(aoffi.size() - 1);
815 expr += '(';
816
817 for (std::size_t index = 0; index < aoffi.size(); ++index) {
818 const auto operand{aoffi.at(index)};
819 if (const auto immediate = std::get_if<ImmediateNode>(operand)) {
820 // Inline the string as an immediate integer in GLSL (AOFFI arguments are required
821 // to be constant by the standard).
822 expr += std::to_string(static_cast<s32>(immediate->GetValue()));
823 } else {
824 expr += "ftoi(" + Visit(operand) + ')';
784 } 825 }
826 if (index + 1 < aoffi.size()) {
827 expr += ", ";
785 } 828 }
786 } 829 }
830 expr += ')';
787 831
788 return expr + ')'; 832 return expr;
789 } 833 }
790 834
791 std::string Assign(Operation operation) { 835 std::string Assign(Operation operation) {
@@ -1164,7 +1208,8 @@ private:
1164 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1208 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1165 ASSERT(meta); 1209 ASSERT(meta);
1166 1210
1167 std::string expr = GenerateTexture(operation, "texture", {{Type::Float, meta->bias}}); 1211 std::string expr = GenerateTexture(
1212 operation, "", {TextureAoffi{}, TextureArgument{Type::Float, meta->bias}});
1168 if (meta->sampler.IsShadow()) { 1213 if (meta->sampler.IsShadow()) {
1169 expr = "vec4(" + expr + ')'; 1214 expr = "vec4(" + expr + ')';
1170 } 1215 }
@@ -1175,7 +1220,8 @@ private:
1175 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1220 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1176 ASSERT(meta); 1221 ASSERT(meta);
1177 1222
1178 std::string expr = GenerateTexture(operation, "textureLod", {{Type::Float, meta->lod}}); 1223 std::string expr = GenerateTexture(
1224 operation, "Lod", {TextureArgument{Type::Float, meta->lod}, TextureAoffi{}});
1179 if (meta->sampler.IsShadow()) { 1225 if (meta->sampler.IsShadow()) {
1180 expr = "vec4(" + expr + ')'; 1226 expr = "vec4(" + expr + ')';
1181 } 1227 }
@@ -1187,7 +1233,8 @@ private:
1187 ASSERT(meta); 1233 ASSERT(meta);
1188 1234
1189 const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int; 1235 const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int;
1190 return GenerateTexture(operation, "textureGather", {{type, meta->component}}) + 1236 return GenerateTexture(operation, "Gather",
1237 {TextureArgument{type, meta->component}, TextureAoffi{}}) +
1191 GetSwizzle(meta->element); 1238 GetSwizzle(meta->element);
1192 } 1239 }
1193 1240
@@ -1217,8 +1264,8 @@ private:
1217 ASSERT(meta); 1264 ASSERT(meta);
1218 1265
1219 if (meta->element < 2) { 1266 if (meta->element < 2) {
1220 return "itof(int((" + GenerateTexture(operation, "textureQueryLod", {}) + 1267 return "itof(int((" + GenerateTexture(operation, "QueryLod", {}) + " * vec2(256))" +
1221 " * vec2(256))" + GetSwizzle(meta->element) + "))"; 1268 GetSwizzle(meta->element) + "))";
1222 } 1269 }
1223 return "0"; 1270 return "0";
1224 } 1271 }
@@ -1571,6 +1618,8 @@ private:
1571 ShaderWriter code; 1618 ShaderWriter code;
1572}; 1619};
1573 1620
1621} // Anonymous namespace
1622
1574std::string GetCommonDeclarations() { 1623std::string GetCommonDeclarations() {
1575 const auto cbuf = std::to_string(MAX_CONSTBUFFER_ELEMENTS); 1624 const auto cbuf = std::to_string(MAX_CONSTBUFFER_ELEMENTS);
1576 const auto gmem = std::to_string(MAX_GLOBALMEMORY_ELEMENTS); 1625 const auto gmem = std::to_string(MAX_GLOBALMEMORY_ELEMENTS);
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 d2d979997..8a43eb157 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -10,8 +10,8 @@
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "common/file_util.h" 11#include "common/file_util.h"
12#include "common/logging/log.h" 12#include "common/logging/log.h"
13#include "common/lz4_compression.h"
14#include "common/scm_rev.h" 13#include "common/scm_rev.h"
14#include "common/zstd_compression.h"
15 15
16#include "core/core.h" 16#include "core/core.h"
17#include "core/hle/kernel/process.h" 17#include "core/hle/kernel/process.h"
@@ -259,7 +259,7 @@ ShaderDiskCacheOpenGL::LoadPrecompiledFile(FileUtil::IOFile& file) {
259 return {}; 259 return {};
260 } 260 }
261 261
262 dump.binary = Common::Compression::DecompressDataLZ4(compressed_binary, binary_length); 262 dump.binary = Common::Compression::DecompressDataZSTD(compressed_binary);
263 if (dump.binary.empty()) { 263 if (dump.binary.empty()) {
264 return {}; 264 return {};
265 } 265 }
@@ -288,7 +288,7 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
288 return {}; 288 return {};
289 } 289 }
290 290
291 const std::vector<u8> code = Common::Compression::DecompressDataLZ4(compressed_code, code_size); 291 const std::vector<u8> code = Common::Compression::DecompressDataZSTD(compressed_code);
292 if (code.empty()) { 292 if (code.empty()) {
293 return {}; 293 return {};
294 } 294 }
@@ -474,8 +474,8 @@ void ShaderDiskCacheOpenGL::SaveDecompiled(u64 unique_identifier, const std::str
474 if (!IsUsable()) 474 if (!IsUsable())
475 return; 475 return;
476 476
477 const std::vector<u8> compressed_code{Common::Compression::CompressDataLZ4HC( 477 const std::vector<u8> compressed_code{Common::Compression::CompressDataZSTDDefault(
478 reinterpret_cast<const u8*>(code.data()), code.size(), 9)}; 478 reinterpret_cast<const u8*>(code.data()), code.size())};
479 if (compressed_code.empty()) { 479 if (compressed_code.empty()) {
480 LOG_ERROR(Render_OpenGL, "Failed to compress GLSL code - skipping shader {:016x}", 480 LOG_ERROR(Render_OpenGL, "Failed to compress GLSL code - skipping shader {:016x}",
481 unique_identifier); 481 unique_identifier);
@@ -506,7 +506,7 @@ void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint p
506 glGetProgramBinary(program, binary_length, nullptr, &binary_format, binary.data()); 506 glGetProgramBinary(program, binary_length, nullptr, &binary_format, binary.data());
507 507
508 const std::vector<u8> compressed_binary = 508 const std::vector<u8> compressed_binary =
509 Common::Compression::CompressDataLZ4HC(binary.data(), binary.size(), 9); 509 Common::Compression::CompressDataZSTDDefault(binary.data(), binary.size());
510 510
511 if (compressed_binary.empty()) { 511 if (compressed_binary.empty()) {
512 LOG_ERROR(Render_OpenGL, "Failed to compress binary program in shader={:016x}", 512 LOG_ERROR(Render_OpenGL, "Failed to compress binary program in shader={:016x}",
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h
index 8eef2a920..37dcfefdb 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.h
+++ b/src/video_core/renderer_opengl/gl_shader_manager.h
@@ -62,7 +62,6 @@ public:
62 UpdatePipeline(); 62 UpdatePipeline();
63 state.draw.shader_program = 0; 63 state.draw.shader_program = 0;
64 state.draw.program_pipeline = pipeline.handle; 64 state.draw.program_pipeline = pipeline.handle;
65 state.geometry_shaders.enabled = (gs != 0);
66 } 65 }
67 66
68private: 67private:
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index 9419326a3..52d569a1b 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -10,16 +10,62 @@
10 10
11namespace OpenGL { 11namespace OpenGL {
12 12
13OpenGLState OpenGLState::cur_state; 13using Maxwell = Tegra::Engines::Maxwell3D::Regs;
14 14
15OpenGLState OpenGLState::cur_state;
15bool OpenGLState::s_rgb_used; 16bool OpenGLState::s_rgb_used;
16 17
18namespace {
19
20template <typename T>
21bool UpdateValue(T& current_value, const T new_value) {
22 const bool changed = current_value != new_value;
23 current_value = new_value;
24 return changed;
25}
26
27template <typename T1, typename T2>
28bool UpdateTie(T1 current_value, const T2 new_value) {
29 const bool changed = current_value != new_value;
30 current_value = new_value;
31 return changed;
32}
33
34void Enable(GLenum cap, bool enable) {
35 if (enable) {
36 glEnable(cap);
37 } else {
38 glDisable(cap);
39 }
40}
41
42void Enable(GLenum cap, GLuint index, bool enable) {
43 if (enable) {
44 glEnablei(cap, index);
45 } else {
46 glDisablei(cap, index);
47 }
48}
49
50void Enable(GLenum cap, bool& current_value, bool new_value) {
51 if (UpdateValue(current_value, new_value))
52 Enable(cap, new_value);
53}
54
55void Enable(GLenum cap, GLuint index, bool& current_value, bool new_value) {
56 if (UpdateValue(current_value, new_value))
57 Enable(cap, index, new_value);
58}
59
60} // namespace
61
17OpenGLState::OpenGLState() { 62OpenGLState::OpenGLState() {
18 // These all match default OpenGL values 63 // These all match default OpenGL values
19 geometry_shaders.enabled = false;
20 framebuffer_srgb.enabled = false; 64 framebuffer_srgb.enabled = false;
65
21 multisample_control.alpha_to_coverage = false; 66 multisample_control.alpha_to_coverage = false;
22 multisample_control.alpha_to_one = false; 67 multisample_control.alpha_to_one = false;
68
23 cull.enabled = false; 69 cull.enabled = false;
24 cull.mode = GL_BACK; 70 cull.mode = GL_BACK;
25 cull.front_face = GL_CCW; 71 cull.front_face = GL_CCW;
@@ -30,14 +76,15 @@ OpenGLState::OpenGLState() {
30 76
31 primitive_restart.enabled = false; 77 primitive_restart.enabled = false;
32 primitive_restart.index = 0; 78 primitive_restart.index = 0;
79
33 for (auto& item : color_mask) { 80 for (auto& item : color_mask) {
34 item.red_enabled = GL_TRUE; 81 item.red_enabled = GL_TRUE;
35 item.green_enabled = GL_TRUE; 82 item.green_enabled = GL_TRUE;
36 item.blue_enabled = GL_TRUE; 83 item.blue_enabled = GL_TRUE;
37 item.alpha_enabled = GL_TRUE; 84 item.alpha_enabled = GL_TRUE;
38 } 85 }
39 stencil.test_enabled = false; 86
40 auto reset_stencil = [](auto& config) { 87 const auto ResetStencil = [](auto& config) {
41 config.test_func = GL_ALWAYS; 88 config.test_func = GL_ALWAYS;
42 config.test_ref = 0; 89 config.test_ref = 0;
43 config.test_mask = 0xFFFFFFFF; 90 config.test_mask = 0xFFFFFFFF;
@@ -46,8 +93,10 @@ OpenGLState::OpenGLState() {
46 config.action_depth_pass = GL_KEEP; 93 config.action_depth_pass = GL_KEEP;
47 config.action_stencil_fail = GL_KEEP; 94 config.action_stencil_fail = GL_KEEP;
48 }; 95 };
49 reset_stencil(stencil.front); 96 stencil.test_enabled = false;
50 reset_stencil(stencil.back); 97 ResetStencil(stencil.front);
98 ResetStencil(stencil.back);
99
51 for (auto& item : viewports) { 100 for (auto& item : viewports) {
52 item.x = 0; 101 item.x = 0;
53 item.y = 0; 102 item.y = 0;
@@ -61,6 +110,7 @@ OpenGLState::OpenGLState() {
61 item.scissor.width = 0; 110 item.scissor.width = 0;
62 item.scissor.height = 0; 111 item.scissor.height = 0;
63 } 112 }
113
64 for (auto& item : blend) { 114 for (auto& item : blend) {
65 item.enabled = true; 115 item.enabled = true;
66 item.rgb_equation = GL_FUNC_ADD; 116 item.rgb_equation = GL_FUNC_ADD;
@@ -70,11 +120,14 @@ OpenGLState::OpenGLState() {
70 item.src_a_func = GL_ONE; 120 item.src_a_func = GL_ONE;
71 item.dst_a_func = GL_ZERO; 121 item.dst_a_func = GL_ZERO;
72 } 122 }
123
73 independant_blend.enabled = false; 124 independant_blend.enabled = false;
125
74 blend_color.red = 0.0f; 126 blend_color.red = 0.0f;
75 blend_color.green = 0.0f; 127 blend_color.green = 0.0f;
76 blend_color.blue = 0.0f; 128 blend_color.blue = 0.0f;
77 blend_color.alpha = 0.0f; 129 blend_color.alpha = 0.0f;
130
78 logic_op.enabled = false; 131 logic_op.enabled = false;
79 logic_op.operation = GL_COPY; 132 logic_op.operation = GL_COPY;
80 133
@@ -91,9 +144,12 @@ OpenGLState::OpenGLState() {
91 clip_distance = {}; 144 clip_distance = {};
92 145
93 point.size = 1; 146 point.size = 1;
147
94 fragment_color_clamp.enabled = false; 148 fragment_color_clamp.enabled = false;
149
95 depth_clamp.far_plane = false; 150 depth_clamp.far_plane = false;
96 depth_clamp.near_plane = false; 151 depth_clamp.near_plane = false;
152
97 polygon_offset.fill_enable = false; 153 polygon_offset.fill_enable = false;
98 polygon_offset.line_enable = false; 154 polygon_offset.line_enable = false;
99 polygon_offset.point_enable = false; 155 polygon_offset.point_enable = false;
@@ -103,260 +159,255 @@ OpenGLState::OpenGLState() {
103} 159}
104 160
105void OpenGLState::ApplyDefaultState() { 161void OpenGLState::ApplyDefaultState() {
162 glEnable(GL_BLEND);
106 glDisable(GL_FRAMEBUFFER_SRGB); 163 glDisable(GL_FRAMEBUFFER_SRGB);
107 glDisable(GL_CULL_FACE); 164 glDisable(GL_CULL_FACE);
108 glDisable(GL_DEPTH_TEST); 165 glDisable(GL_DEPTH_TEST);
109 glDisable(GL_PRIMITIVE_RESTART); 166 glDisable(GL_PRIMITIVE_RESTART);
110 glDisable(GL_STENCIL_TEST); 167 glDisable(GL_STENCIL_TEST);
111 glEnable(GL_BLEND);
112 glDisable(GL_COLOR_LOGIC_OP); 168 glDisable(GL_COLOR_LOGIC_OP);
113 glDisable(GL_SCISSOR_TEST); 169 glDisable(GL_SCISSOR_TEST);
114} 170}
115 171
172void OpenGLState::ApplyFramebufferState() const {
173 if (UpdateValue(cur_state.draw.read_framebuffer, draw.read_framebuffer)) {
174 glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer);
175 }
176 if (UpdateValue(cur_state.draw.draw_framebuffer, draw.draw_framebuffer)) {
177 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw.draw_framebuffer);
178 }
179}
180
181void OpenGLState::ApplyVertexArrayState() const {
182 if (UpdateValue(cur_state.draw.vertex_array, draw.vertex_array)) {
183 glBindVertexArray(draw.vertex_array);
184 }
185}
186
187void OpenGLState::ApplyShaderProgram() const {
188 if (UpdateValue(cur_state.draw.shader_program, draw.shader_program)) {
189 glUseProgram(draw.shader_program);
190 }
191}
192
193void OpenGLState::ApplyProgramPipeline() const {
194 if (UpdateValue(cur_state.draw.program_pipeline, draw.program_pipeline)) {
195 glBindProgramPipeline(draw.program_pipeline);
196 }
197}
198
199void OpenGLState::ApplyClipDistances() const {
200 for (std::size_t i = 0; i < clip_distance.size(); ++i) {
201 Enable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i), cur_state.clip_distance[i],
202 clip_distance[i]);
203 }
204}
205
206void OpenGLState::ApplyPointSize() const {
207 if (UpdateValue(cur_state.point.size, point.size)) {
208 glPointSize(point.size);
209 }
210}
211
212void OpenGLState::ApplyFragmentColorClamp() const {
213 if (UpdateValue(cur_state.fragment_color_clamp.enabled, fragment_color_clamp.enabled)) {
214 glClampColor(GL_CLAMP_FRAGMENT_COLOR_ARB,
215 fragment_color_clamp.enabled ? GL_TRUE : GL_FALSE);
216 }
217}
218
219void OpenGLState::ApplyMultisample() const {
220 Enable(GL_SAMPLE_ALPHA_TO_COVERAGE, cur_state.multisample_control.alpha_to_coverage,
221 multisample_control.alpha_to_coverage);
222 Enable(GL_SAMPLE_ALPHA_TO_ONE, cur_state.multisample_control.alpha_to_one,
223 multisample_control.alpha_to_one);
224}
225
226void OpenGLState::ApplyDepthClamp() const {
227 if (depth_clamp.far_plane == cur_state.depth_clamp.far_plane &&
228 depth_clamp.near_plane == cur_state.depth_clamp.near_plane) {
229 return;
230 }
231 cur_state.depth_clamp = depth_clamp;
232
233 UNIMPLEMENTED_IF_MSG(depth_clamp.far_plane != depth_clamp.near_plane,
234 "Unimplemented Depth Clamp Separation!");
235
236 Enable(GL_DEPTH_CLAMP, depth_clamp.far_plane || depth_clamp.near_plane);
237}
238
116void OpenGLState::ApplySRgb() const { 239void OpenGLState::ApplySRgb() const {
117 if (framebuffer_srgb.enabled != cur_state.framebuffer_srgb.enabled) { 240 if (cur_state.framebuffer_srgb.enabled == framebuffer_srgb.enabled)
118 if (framebuffer_srgb.enabled) { 241 return;
119 // Track if sRGB is used 242 cur_state.framebuffer_srgb.enabled = framebuffer_srgb.enabled;
120 s_rgb_used = true; 243 if (framebuffer_srgb.enabled) {
121 glEnable(GL_FRAMEBUFFER_SRGB); 244 // Track if sRGB is used
122 } else { 245 s_rgb_used = true;
123 glDisable(GL_FRAMEBUFFER_SRGB); 246 glEnable(GL_FRAMEBUFFER_SRGB);
124 } 247 } else {
248 glDisable(GL_FRAMEBUFFER_SRGB);
125 } 249 }
126} 250}
127 251
128void OpenGLState::ApplyCulling() const { 252void OpenGLState::ApplyCulling() const {
129 if (cull.enabled != cur_state.cull.enabled) { 253 Enable(GL_CULL_FACE, cur_state.cull.enabled, cull.enabled);
130 if (cull.enabled) {
131 glEnable(GL_CULL_FACE);
132 } else {
133 glDisable(GL_CULL_FACE);
134 }
135 }
136 254
137 if (cull.mode != cur_state.cull.mode) { 255 if (UpdateValue(cur_state.cull.mode, cull.mode)) {
138 glCullFace(cull.mode); 256 glCullFace(cull.mode);
139 } 257 }
140 258
141 if (cull.front_face != cur_state.cull.front_face) { 259 if (UpdateValue(cur_state.cull.front_face, cull.front_face)) {
142 glFrontFace(cull.front_face); 260 glFrontFace(cull.front_face);
143 } 261 }
144} 262}
145 263
146void OpenGLState::ApplyColorMask() const { 264void OpenGLState::ApplyColorMask() const {
147 if (independant_blend.enabled) { 265 for (std::size_t i = 0; i < Maxwell::NumRenderTargets; ++i) {
148 for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { 266 const auto& updated = color_mask[i];
149 const auto& updated = color_mask[i]; 267 auto& current = cur_state.color_mask[i];
150 const auto& current = cur_state.color_mask[i];
151 if (updated.red_enabled != current.red_enabled ||
152 updated.green_enabled != current.green_enabled ||
153 updated.blue_enabled != current.blue_enabled ||
154 updated.alpha_enabled != current.alpha_enabled) {
155 glColorMaski(static_cast<GLuint>(i), updated.red_enabled, updated.green_enabled,
156 updated.blue_enabled, updated.alpha_enabled);
157 }
158 }
159 } else {
160 const auto& updated = color_mask[0];
161 const auto& current = cur_state.color_mask[0];
162 if (updated.red_enabled != current.red_enabled || 268 if (updated.red_enabled != current.red_enabled ||
163 updated.green_enabled != current.green_enabled || 269 updated.green_enabled != current.green_enabled ||
164 updated.blue_enabled != current.blue_enabled || 270 updated.blue_enabled != current.blue_enabled ||
165 updated.alpha_enabled != current.alpha_enabled) { 271 updated.alpha_enabled != current.alpha_enabled) {
166 glColorMask(updated.red_enabled, updated.green_enabled, updated.blue_enabled, 272 current = updated;
167 updated.alpha_enabled); 273 glColorMaski(static_cast<GLuint>(i), updated.red_enabled, updated.green_enabled,
274 updated.blue_enabled, updated.alpha_enabled);
168 } 275 }
169 } 276 }
170} 277}
171 278
172void OpenGLState::ApplyDepth() const { 279void OpenGLState::ApplyDepth() const {
173 if (depth.test_enabled != cur_state.depth.test_enabled) { 280 Enable(GL_DEPTH_TEST, cur_state.depth.test_enabled, depth.test_enabled);
174 if (depth.test_enabled) {
175 glEnable(GL_DEPTH_TEST);
176 } else {
177 glDisable(GL_DEPTH_TEST);
178 }
179 }
180 281
181 if (depth.test_func != cur_state.depth.test_func) { 282 if (cur_state.depth.test_func != depth.test_func) {
283 cur_state.depth.test_func = depth.test_func;
182 glDepthFunc(depth.test_func); 284 glDepthFunc(depth.test_func);
183 } 285 }
184 286
185 if (depth.write_mask != cur_state.depth.write_mask) { 287 if (cur_state.depth.write_mask != depth.write_mask) {
288 cur_state.depth.write_mask = depth.write_mask;
186 glDepthMask(depth.write_mask); 289 glDepthMask(depth.write_mask);
187 } 290 }
188} 291}
189 292
190void OpenGLState::ApplyPrimitiveRestart() const { 293void OpenGLState::ApplyPrimitiveRestart() const {
191 if (primitive_restart.enabled != cur_state.primitive_restart.enabled) { 294 Enable(GL_PRIMITIVE_RESTART, cur_state.primitive_restart.enabled, primitive_restart.enabled);
192 if (primitive_restart.enabled) {
193 glEnable(GL_PRIMITIVE_RESTART);
194 } else {
195 glDisable(GL_PRIMITIVE_RESTART);
196 }
197 }
198 295
199 if (primitive_restart.index != cur_state.primitive_restart.index) { 296 if (cur_state.primitive_restart.index != primitive_restart.index) {
297 cur_state.primitive_restart.index = primitive_restart.index;
200 glPrimitiveRestartIndex(primitive_restart.index); 298 glPrimitiveRestartIndex(primitive_restart.index);
201 } 299 }
202} 300}
203 301
204void OpenGLState::ApplyStencilTest() const { 302void OpenGLState::ApplyStencilTest() const {
205 if (stencil.test_enabled != cur_state.stencil.test_enabled) { 303 Enable(GL_STENCIL_TEST, cur_state.stencil.test_enabled, stencil.test_enabled);
206 if (stencil.test_enabled) { 304
207 glEnable(GL_STENCIL_TEST); 305 const auto ConfigStencil = [](GLenum face, const auto& config, auto& current) {
208 } else { 306 if (current.test_func != config.test_func || current.test_ref != config.test_ref ||
209 glDisable(GL_STENCIL_TEST); 307 current.test_mask != config.test_mask) {
210 } 308 current.test_func = config.test_func;
211 } 309 current.test_ref = config.test_ref;
212 310 current.test_mask = config.test_mask;
213 const auto ConfigStencil = [](GLenum face, const auto& config, const auto& prev_config) {
214 if (config.test_func != prev_config.test_func || config.test_ref != prev_config.test_ref ||
215 config.test_mask != prev_config.test_mask) {
216 glStencilFuncSeparate(face, config.test_func, config.test_ref, config.test_mask); 311 glStencilFuncSeparate(face, config.test_func, config.test_ref, config.test_mask);
217 } 312 }
218 if (config.action_depth_fail != prev_config.action_depth_fail || 313 if (current.action_depth_fail != config.action_depth_fail ||
219 config.action_depth_pass != prev_config.action_depth_pass || 314 current.action_depth_pass != config.action_depth_pass ||
220 config.action_stencil_fail != prev_config.action_stencil_fail) { 315 current.action_stencil_fail != config.action_stencil_fail) {
316 current.action_depth_fail = config.action_depth_fail;
317 current.action_depth_pass = config.action_depth_pass;
318 current.action_stencil_fail = config.action_stencil_fail;
221 glStencilOpSeparate(face, config.action_stencil_fail, config.action_depth_fail, 319 glStencilOpSeparate(face, config.action_stencil_fail, config.action_depth_fail,
222 config.action_depth_pass); 320 config.action_depth_pass);
223 } 321 }
224 if (config.write_mask != prev_config.write_mask) { 322 if (current.write_mask != config.write_mask) {
323 current.write_mask = config.write_mask;
225 glStencilMaskSeparate(face, config.write_mask); 324 glStencilMaskSeparate(face, config.write_mask);
226 } 325 }
227 }; 326 };
228 ConfigStencil(GL_FRONT, stencil.front, cur_state.stencil.front); 327 ConfigStencil(GL_FRONT, stencil.front, cur_state.stencil.front);
229 ConfigStencil(GL_BACK, stencil.back, cur_state.stencil.back); 328 ConfigStencil(GL_BACK, stencil.back, cur_state.stencil.back);
230} 329}
231// Viewport does not affects glClearBuffer so emulate viewport using scissor test
232void OpenGLState::EmulateViewportWithScissor() {
233 auto& current = viewports[0];
234 if (current.scissor.enabled) {
235 const GLint left = std::max(current.x, current.scissor.x);
236 const GLint right =
237 std::max(current.x + current.width, current.scissor.x + current.scissor.width);
238 const GLint bottom = std::max(current.y, current.scissor.y);
239 const GLint top =
240 std::max(current.y + current.height, current.scissor.y + current.scissor.height);
241 current.scissor.x = std::max(left, 0);
242 current.scissor.y = std::max(bottom, 0);
243 current.scissor.width = std::max(right - left, 0);
244 current.scissor.height = std::max(top - bottom, 0);
245 } else {
246 current.scissor.enabled = true;
247 current.scissor.x = current.x;
248 current.scissor.y = current.y;
249 current.scissor.width = current.width;
250 current.scissor.height = current.height;
251 }
252}
253 330
254void OpenGLState::ApplyViewport() const { 331void OpenGLState::ApplyViewport() const {
255 if (geometry_shaders.enabled) { 332 for (GLuint i = 0; i < static_cast<GLuint>(Maxwell::NumViewports); ++i) {
256 for (GLuint i = 0; i < static_cast<GLuint>(Tegra::Engines::Maxwell3D::Regs::NumViewports); 333 const auto& updated = viewports[i];
257 i++) { 334 auto& current = cur_state.viewports[i];
258 const auto& current = cur_state.viewports[i]; 335
259 const auto& updated = viewports[i]; 336 if (current.x != updated.x || current.y != updated.y || current.width != updated.width ||
260 if (updated.x != current.x || updated.y != current.y || 337 current.height != updated.height) {
261 updated.width != current.width || updated.height != current.height) { 338 current.x = updated.x;
262 glViewportIndexedf( 339 current.y = updated.y;
263 i, static_cast<GLfloat>(updated.x), static_cast<GLfloat>(updated.y), 340 current.width = updated.width;
264 static_cast<GLfloat>(updated.width), static_cast<GLfloat>(updated.height)); 341 current.height = updated.height;
265 } 342 glViewportIndexedf(i, static_cast<GLfloat>(updated.x), static_cast<GLfloat>(updated.y),
266 if (updated.depth_range_near != current.depth_range_near || 343 static_cast<GLfloat>(updated.width),
267 updated.depth_range_far != current.depth_range_far) { 344 static_cast<GLfloat>(updated.height));
268 glDepthRangeIndexed(i, updated.depth_range_near, updated.depth_range_far);
269 }
270
271 if (updated.scissor.enabled != current.scissor.enabled) {
272 if (updated.scissor.enabled) {
273 glEnablei(GL_SCISSOR_TEST, i);
274 } else {
275 glDisablei(GL_SCISSOR_TEST, i);
276 }
277 }
278
279 if (updated.scissor.x != current.scissor.x || updated.scissor.y != current.scissor.y ||
280 updated.scissor.width != current.scissor.width ||
281 updated.scissor.height != current.scissor.height) {
282 glScissorIndexed(i, updated.scissor.x, updated.scissor.y, updated.scissor.width,
283 updated.scissor.height);
284 }
285 }
286 } else {
287 const auto& current = cur_state.viewports[0];
288 const auto& updated = viewports[0];
289 if (updated.x != current.x || updated.y != current.y || updated.width != current.width ||
290 updated.height != current.height) {
291 glViewport(updated.x, updated.y, updated.width, updated.height);
292 }
293
294 if (updated.depth_range_near != current.depth_range_near ||
295 updated.depth_range_far != current.depth_range_far) {
296 glDepthRange(updated.depth_range_near, updated.depth_range_far);
297 } 345 }
298 346 if (current.depth_range_near != updated.depth_range_near ||
299 if (updated.scissor.enabled != current.scissor.enabled) { 347 current.depth_range_far != updated.depth_range_far) {
300 if (updated.scissor.enabled) { 348 current.depth_range_near = updated.depth_range_near;
301 glEnable(GL_SCISSOR_TEST); 349 current.depth_range_far = updated.depth_range_far;
302 } else { 350 glDepthRangeIndexed(i, updated.depth_range_near, updated.depth_range_far);
303 glDisable(GL_SCISSOR_TEST);
304 }
305 } 351 }
306 352
307 if (updated.scissor.x != current.scissor.x || updated.scissor.y != current.scissor.y || 353 Enable(GL_SCISSOR_TEST, i, current.scissor.enabled, updated.scissor.enabled);
308 updated.scissor.width != current.scissor.width || 354
309 updated.scissor.height != current.scissor.height) { 355 if (current.scissor.x != updated.scissor.x || current.scissor.y != updated.scissor.y ||
310 glScissor(updated.scissor.x, updated.scissor.y, updated.scissor.width, 356 current.scissor.width != updated.scissor.width ||
311 updated.scissor.height); 357 current.scissor.height != updated.scissor.height) {
358 current.scissor.x = updated.scissor.x;
359 current.scissor.y = updated.scissor.y;
360 current.scissor.width = updated.scissor.width;
361 current.scissor.height = updated.scissor.height;
362 glScissorIndexed(i, updated.scissor.x, updated.scissor.y, updated.scissor.width,
363 updated.scissor.height);
312 } 364 }
313 } 365 }
314} 366}
315 367
316void OpenGLState::ApplyGlobalBlending() const { 368void OpenGLState::ApplyGlobalBlending() const {
317 const Blend& current = cur_state.blend[0];
318 const Blend& updated = blend[0]; 369 const Blend& updated = blend[0];
319 if (updated.enabled != current.enabled) { 370 Blend& current = cur_state.blend[0];
320 if (updated.enabled) { 371
321 glEnable(GL_BLEND); 372 Enable(GL_BLEND, current.enabled, updated.enabled);
322 } else { 373
323 glDisable(GL_BLEND); 374 if (current.src_rgb_func != updated.src_rgb_func ||
324 } 375 current.dst_rgb_func != updated.dst_rgb_func || current.src_a_func != updated.src_a_func ||
325 } 376 current.dst_a_func != updated.dst_a_func) {
326 if (!updated.enabled) { 377 current.src_rgb_func = updated.src_rgb_func;
327 return; 378 current.dst_rgb_func = updated.dst_rgb_func;
328 } 379 current.src_a_func = updated.src_a_func;
329 if (updated.src_rgb_func != current.src_rgb_func || 380 current.dst_a_func = updated.dst_a_func;
330 updated.dst_rgb_func != current.dst_rgb_func || updated.src_a_func != current.src_a_func ||
331 updated.dst_a_func != current.dst_a_func) {
332 glBlendFuncSeparate(updated.src_rgb_func, updated.dst_rgb_func, updated.src_a_func, 381 glBlendFuncSeparate(updated.src_rgb_func, updated.dst_rgb_func, updated.src_a_func,
333 updated.dst_a_func); 382 updated.dst_a_func);
334 } 383 }
335 384
336 if (updated.rgb_equation != current.rgb_equation || updated.a_equation != current.a_equation) { 385 if (current.rgb_equation != updated.rgb_equation || current.a_equation != updated.a_equation) {
386 current.rgb_equation = updated.rgb_equation;
387 current.a_equation = updated.a_equation;
337 glBlendEquationSeparate(updated.rgb_equation, updated.a_equation); 388 glBlendEquationSeparate(updated.rgb_equation, updated.a_equation);
338 } 389 }
339} 390}
340 391
341void OpenGLState::ApplyTargetBlending(std::size_t target, bool force) const { 392void OpenGLState::ApplyTargetBlending(std::size_t target, bool force) const {
342 const Blend& updated = blend[target]; 393 const Blend& updated = blend[target];
343 const Blend& current = cur_state.blend[target]; 394 Blend& current = cur_state.blend[target];
344 if (updated.enabled != current.enabled || force) { 395
345 if (updated.enabled) { 396 if (current.enabled != updated.enabled || force) {
346 glEnablei(GL_BLEND, static_cast<GLuint>(target)); 397 current.enabled = updated.enabled;
347 } else { 398 Enable(GL_BLEND, static_cast<GLuint>(target), updated.enabled);
348 glDisablei(GL_BLEND, static_cast<GLuint>(target));
349 }
350 } 399 }
351 400
352 if (updated.src_rgb_func != current.src_rgb_func || 401 if (UpdateTie(std::tie(current.src_rgb_func, current.dst_rgb_func, current.src_a_func,
353 updated.dst_rgb_func != current.dst_rgb_func || updated.src_a_func != current.src_a_func || 402 current.dst_a_func),
354 updated.dst_a_func != current.dst_a_func) { 403 std::tie(updated.src_rgb_func, updated.dst_rgb_func, updated.src_a_func,
404 updated.dst_a_func))) {
355 glBlendFuncSeparatei(static_cast<GLuint>(target), updated.src_rgb_func, 405 glBlendFuncSeparatei(static_cast<GLuint>(target), updated.src_rgb_func,
356 updated.dst_rgb_func, updated.src_a_func, updated.dst_a_func); 406 updated.dst_rgb_func, updated.src_a_func, updated.dst_a_func);
357 } 407 }
358 408
359 if (updated.rgb_equation != current.rgb_equation || updated.a_equation != current.a_equation) { 409 if (UpdateTie(std::tie(current.rgb_equation, current.a_equation),
410 std::tie(updated.rgb_equation, updated.a_equation))) {
360 glBlendEquationSeparatei(static_cast<GLuint>(target), updated.rgb_equation, 411 glBlendEquationSeparatei(static_cast<GLuint>(target), updated.rgb_equation,
361 updated.a_equation); 412 updated.a_equation);
362 } 413 }
@@ -364,77 +415,48 @@ void OpenGLState::ApplyTargetBlending(std::size_t target, bool force) const {
364 415
365void OpenGLState::ApplyBlending() const { 416void OpenGLState::ApplyBlending() const {
366 if (independant_blend.enabled) { 417 if (independant_blend.enabled) {
367 for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { 418 const bool force = independant_blend.enabled != cur_state.independant_blend.enabled;
368 ApplyTargetBlending(i, 419 for (std::size_t target = 0; target < Maxwell::NumRenderTargets; ++target) {
369 independant_blend.enabled != cur_state.independant_blend.enabled); 420 ApplyTargetBlending(target, force);
370 } 421 }
371 } else { 422 } else {
372 ApplyGlobalBlending(); 423 ApplyGlobalBlending();
373 } 424 }
374 if (blend_color.red != cur_state.blend_color.red || 425 cur_state.independant_blend.enabled = independant_blend.enabled;
375 blend_color.green != cur_state.blend_color.green || 426
376 blend_color.blue != cur_state.blend_color.blue || 427 if (UpdateTie(
377 blend_color.alpha != cur_state.blend_color.alpha) { 428 std::tie(cur_state.blend_color.red, cur_state.blend_color.green,
429 cur_state.blend_color.blue, cur_state.blend_color.alpha),
430 std::tie(blend_color.red, blend_color.green, blend_color.blue, blend_color.alpha))) {
378 glBlendColor(blend_color.red, blend_color.green, blend_color.blue, blend_color.alpha); 431 glBlendColor(blend_color.red, blend_color.green, blend_color.blue, blend_color.alpha);
379 } 432 }
380} 433}
381 434
382void OpenGLState::ApplyLogicOp() const { 435void OpenGLState::ApplyLogicOp() const {
383 if (logic_op.enabled != cur_state.logic_op.enabled) { 436 Enable(GL_COLOR_LOGIC_OP, cur_state.logic_op.enabled, logic_op.enabled);
384 if (logic_op.enabled) {
385 glEnable(GL_COLOR_LOGIC_OP);
386 } else {
387 glDisable(GL_COLOR_LOGIC_OP);
388 }
389 }
390 437
391 if (logic_op.operation != cur_state.logic_op.operation) { 438 if (UpdateValue(cur_state.logic_op.operation, logic_op.operation)) {
392 glLogicOp(logic_op.operation); 439 glLogicOp(logic_op.operation);
393 } 440 }
394} 441}
395 442
396void OpenGLState::ApplyPolygonOffset() const { 443void OpenGLState::ApplyPolygonOffset() const {
397 const bool fill_enable_changed = 444 Enable(GL_POLYGON_OFFSET_FILL, cur_state.polygon_offset.fill_enable,
398 polygon_offset.fill_enable != cur_state.polygon_offset.fill_enable; 445 polygon_offset.fill_enable);
399 const bool line_enable_changed = 446 Enable(GL_POLYGON_OFFSET_LINE, cur_state.polygon_offset.line_enable,
400 polygon_offset.line_enable != cur_state.polygon_offset.line_enable; 447 polygon_offset.line_enable);
401 const bool point_enable_changed = 448 Enable(GL_POLYGON_OFFSET_POINT, cur_state.polygon_offset.point_enable,
402 polygon_offset.point_enable != cur_state.polygon_offset.point_enable; 449 polygon_offset.point_enable);
403 const bool factor_changed = polygon_offset.factor != cur_state.polygon_offset.factor; 450
404 const bool units_changed = polygon_offset.units != cur_state.polygon_offset.units; 451 if (UpdateTie(std::tie(cur_state.polygon_offset.factor, cur_state.polygon_offset.units,
405 const bool clamp_changed = polygon_offset.clamp != cur_state.polygon_offset.clamp; 452 cur_state.polygon_offset.clamp),
406 453 std::tie(polygon_offset.factor, polygon_offset.units, polygon_offset.clamp))) {
407 if (fill_enable_changed) {
408 if (polygon_offset.fill_enable) {
409 glEnable(GL_POLYGON_OFFSET_FILL);
410 } else {
411 glDisable(GL_POLYGON_OFFSET_FILL);
412 }
413 }
414
415 if (line_enable_changed) {
416 if (polygon_offset.line_enable) {
417 glEnable(GL_POLYGON_OFFSET_LINE);
418 } else {
419 glDisable(GL_POLYGON_OFFSET_LINE);
420 }
421 }
422
423 if (point_enable_changed) {
424 if (polygon_offset.point_enable) {
425 glEnable(GL_POLYGON_OFFSET_POINT);
426 } else {
427 glDisable(GL_POLYGON_OFFSET_POINT);
428 }
429 }
430
431 if (factor_changed || units_changed || clamp_changed) {
432 if (GLAD_GL_EXT_polygon_offset_clamp && polygon_offset.clamp != 0) { 454 if (GLAD_GL_EXT_polygon_offset_clamp && polygon_offset.clamp != 0) {
433 glPolygonOffsetClamp(polygon_offset.factor, polygon_offset.units, polygon_offset.clamp); 455 glPolygonOffsetClamp(polygon_offset.factor, polygon_offset.units, polygon_offset.clamp);
434 } else { 456 } else {
435 glPolygonOffset(polygon_offset.factor, polygon_offset.units);
436 UNIMPLEMENTED_IF_MSG(polygon_offset.clamp != 0, 457 UNIMPLEMENTED_IF_MSG(polygon_offset.clamp != 0,
437 "Unimplemented Depth polygon offset clamp."); 458 "Unimplemented Depth polygon offset clamp.");
459 glPolygonOffset(polygon_offset.factor, polygon_offset.units);
438 } 460 }
439 } 461 }
440} 462}
@@ -443,22 +465,21 @@ void OpenGLState::ApplyTextures() const {
443 bool has_delta{}; 465 bool has_delta{};
444 std::size_t first{}; 466 std::size_t first{};
445 std::size_t last{}; 467 std::size_t last{};
446 std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> textures; 468 std::array<GLuint, Maxwell::NumTextureSamplers> textures;
447 469
448 for (std::size_t i = 0; i < std::size(texture_units); ++i) { 470 for (std::size_t i = 0; i < std::size(texture_units); ++i) {
449 const auto& texture_unit = texture_units[i]; 471 const auto& texture_unit = texture_units[i];
450 const auto& cur_state_texture_unit = cur_state.texture_units[i]; 472 auto& cur_state_texture_unit = cur_state.texture_units[i];
451 textures[i] = texture_unit.texture; 473 textures[i] = texture_unit.texture;
452 474 if (cur_state_texture_unit.texture == textures[i])
453 if (textures[i] != cur_state_texture_unit.texture) { 475 continue;
454 if (!has_delta) { 476 cur_state_texture_unit.texture = textures[i];
455 first = i; 477 if (!has_delta) {
456 has_delta = true; 478 first = i;
457 } 479 has_delta = true;
458 last = i;
459 } 480 }
481 last = i;
460 } 482 }
461
462 if (has_delta) { 483 if (has_delta) {
463 glBindTextures(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1), 484 glBindTextures(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1),
464 textures.data() + first); 485 textures.data() + first);
@@ -469,16 +490,18 @@ void OpenGLState::ApplySamplers() const {
469 bool has_delta{}; 490 bool has_delta{};
470 std::size_t first{}; 491 std::size_t first{};
471 std::size_t last{}; 492 std::size_t last{};
472 std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> samplers; 493 std::array<GLuint, Maxwell::NumTextureSamplers> samplers;
494
473 for (std::size_t i = 0; i < std::size(samplers); ++i) { 495 for (std::size_t i = 0; i < std::size(samplers); ++i) {
496 if (cur_state.texture_units[i].sampler == texture_units[i].sampler)
497 continue;
498 cur_state.texture_units[i].sampler = texture_units[i].sampler;
474 samplers[i] = texture_units[i].sampler; 499 samplers[i] = texture_units[i].sampler;
475 if (samplers[i] != cur_state.texture_units[i].sampler) { 500 if (!has_delta) {
476 if (!has_delta) { 501 first = i;
477 first = i; 502 has_delta = true;
478 has_delta = true;
479 }
480 last = i;
481 } 503 }
504 last = i;
482 } 505 }
483 if (has_delta) { 506 if (has_delta) {
484 glBindSamplers(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1), 507 glBindSamplers(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1),
@@ -486,81 +509,15 @@ void OpenGLState::ApplySamplers() const {
486 } 509 }
487} 510}
488 511
489void OpenGLState::ApplyFramebufferState() const {
490 if (draw.read_framebuffer != cur_state.draw.read_framebuffer) {
491 glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer);
492 }
493 if (draw.draw_framebuffer != cur_state.draw.draw_framebuffer) {
494 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw.draw_framebuffer);
495 }
496}
497
498void OpenGLState::ApplyVertexArrayState() const {
499 if (draw.vertex_array != cur_state.draw.vertex_array) {
500 glBindVertexArray(draw.vertex_array);
501 }
502}
503
504void OpenGLState::ApplyDepthClamp() const {
505 if (depth_clamp.far_plane == cur_state.depth_clamp.far_plane &&
506 depth_clamp.near_plane == cur_state.depth_clamp.near_plane) {
507 return;
508 }
509 UNIMPLEMENTED_IF_MSG(depth_clamp.far_plane != depth_clamp.near_plane,
510 "Unimplemented Depth Clamp Separation!");
511
512 if (depth_clamp.far_plane || depth_clamp.near_plane) {
513 glEnable(GL_DEPTH_CLAMP);
514 } else {
515 glDisable(GL_DEPTH_CLAMP);
516 }
517}
518
519void OpenGLState::Apply() const { 512void OpenGLState::Apply() const {
520 ApplyFramebufferState(); 513 ApplyFramebufferState();
521 ApplyVertexArrayState(); 514 ApplyVertexArrayState();
522 515 ApplyShaderProgram();
523 // Shader program 516 ApplyProgramPipeline();
524 if (draw.shader_program != cur_state.draw.shader_program) { 517 ApplyClipDistances();
525 glUseProgram(draw.shader_program); 518 ApplyPointSize();
526 } 519 ApplyFragmentColorClamp();
527 520 ApplyMultisample();
528 // Program pipeline
529 if (draw.program_pipeline != cur_state.draw.program_pipeline) {
530 glBindProgramPipeline(draw.program_pipeline);
531 }
532 // Clip distance
533 for (std::size_t i = 0; i < clip_distance.size(); ++i) {
534 if (clip_distance[i] != cur_state.clip_distance[i]) {
535 if (clip_distance[i]) {
536 glEnable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i));
537 } else {
538 glDisable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i));
539 }
540 }
541 }
542 // Point
543 if (point.size != cur_state.point.size) {
544 glPointSize(point.size);
545 }
546 if (fragment_color_clamp.enabled != cur_state.fragment_color_clamp.enabled) {
547 glClampColor(GL_CLAMP_FRAGMENT_COLOR_ARB,
548 fragment_color_clamp.enabled ? GL_TRUE : GL_FALSE);
549 }
550 if (multisample_control.alpha_to_coverage != cur_state.multisample_control.alpha_to_coverage) {
551 if (multisample_control.alpha_to_coverage) {
552 glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
553 } else {
554 glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
555 }
556 }
557 if (multisample_control.alpha_to_one != cur_state.multisample_control.alpha_to_one) {
558 if (multisample_control.alpha_to_one) {
559 glEnable(GL_SAMPLE_ALPHA_TO_ONE);
560 } else {
561 glDisable(GL_SAMPLE_ALPHA_TO_ONE);
562 }
563 }
564 ApplyDepthClamp(); 521 ApplyDepthClamp();
565 ApplyColorMask(); 522 ApplyColorMask();
566 ApplyViewport(); 523 ApplyViewport();
@@ -574,7 +531,28 @@ void OpenGLState::Apply() const {
574 ApplyTextures(); 531 ApplyTextures();
575 ApplySamplers(); 532 ApplySamplers();
576 ApplyPolygonOffset(); 533 ApplyPolygonOffset();
577 cur_state = *this; 534}
535
536void OpenGLState::EmulateViewportWithScissor() {
537 auto& current = viewports[0];
538 if (current.scissor.enabled) {
539 const GLint left = std::max(current.x, current.scissor.x);
540 const GLint right =
541 std::max(current.x + current.width, current.scissor.x + current.scissor.width);
542 const GLint bottom = std::max(current.y, current.scissor.y);
543 const GLint top =
544 std::max(current.y + current.height, current.scissor.y + current.scissor.height);
545 current.scissor.x = std::max(left, 0);
546 current.scissor.y = std::max(bottom, 0);
547 current.scissor.width = std::max(right - left, 0);
548 current.scissor.height = std::max(top - bottom, 0);
549 } else {
550 current.scissor.enabled = true;
551 current.scissor.x = current.x;
552 current.scissor.y = current.y;
553 current.scissor.width = current.width;
554 current.scissor.height = current.height;
555 }
578} 556}
579 557
580OpenGLState& OpenGLState::UnbindTexture(GLuint handle) { 558OpenGLState& OpenGLState::UnbindTexture(GLuint handle) {
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index 9e1eda5b1..41418a7b8 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -54,10 +54,6 @@ public:
54 } depth_clamp; // GL_DEPTH_CLAMP 54 } depth_clamp; // GL_DEPTH_CLAMP
55 55
56 struct { 56 struct {
57 bool enabled; // viewports arrays are only supported when geometry shaders are enabled.
58 } geometry_shaders;
59
60 struct {
61 bool enabled; // GL_CULL_FACE 57 bool enabled; // GL_CULL_FACE
62 GLenum mode; // GL_CULL_FACE_MODE 58 GLenum mode; // GL_CULL_FACE_MODE
63 GLenum front_face; // GL_FRONT_FACE 59 GLenum front_face; // GL_FRONT_FACE
@@ -184,34 +180,26 @@ public:
184 static OpenGLState GetCurState() { 180 static OpenGLState GetCurState() {
185 return cur_state; 181 return cur_state;
186 } 182 }
183
187 static bool GetsRGBUsed() { 184 static bool GetsRGBUsed() {
188 return s_rgb_used; 185 return s_rgb_used;
189 } 186 }
187
190 static void ClearsRGBUsed() { 188 static void ClearsRGBUsed() {
191 s_rgb_used = false; 189 s_rgb_used = false;
192 } 190 }
191
193 /// Apply this state as the current OpenGL state 192 /// Apply this state as the current OpenGL state
194 void Apply() const; 193 void Apply() const;
195 /// Apply only the state affecting the framebuffer 194
196 void ApplyFramebufferState() const; 195 void ApplyFramebufferState() const;
197 /// Apply only the state affecting the vertex array
198 void ApplyVertexArrayState() const; 196 void ApplyVertexArrayState() const;
199 /// Set the initial OpenGL state 197 void ApplyShaderProgram() const;
200 static void ApplyDefaultState(); 198 void ApplyProgramPipeline() const;
201 /// Resets any references to the given resource 199 void ApplyClipDistances() const;
202 OpenGLState& UnbindTexture(GLuint handle); 200 void ApplyPointSize() const;
203 OpenGLState& ResetSampler(GLuint handle); 201 void ApplyFragmentColorClamp() const;
204 OpenGLState& ResetProgram(GLuint handle); 202 void ApplyMultisample() const;
205 OpenGLState& ResetPipeline(GLuint handle);
206 OpenGLState& ResetVertexArray(GLuint handle);
207 OpenGLState& ResetFramebuffer(GLuint handle);
208 void EmulateViewportWithScissor();
209
210private:
211 static OpenGLState cur_state;
212 // Workaround for sRGB problems caused by
213 // QT not supporting srgb output
214 static bool s_rgb_used;
215 void ApplySRgb() const; 203 void ApplySRgb() const;
216 void ApplyCulling() const; 204 void ApplyCulling() const;
217 void ApplyColorMask() const; 205 void ApplyColorMask() const;
@@ -227,6 +215,26 @@ private:
227 void ApplySamplers() const; 215 void ApplySamplers() const;
228 void ApplyDepthClamp() const; 216 void ApplyDepthClamp() const;
229 void ApplyPolygonOffset() const; 217 void ApplyPolygonOffset() const;
218
219 /// Set the initial OpenGL state
220 static void ApplyDefaultState();
221
222 /// Resets any references to the given resource
223 OpenGLState& UnbindTexture(GLuint handle);
224 OpenGLState& ResetSampler(GLuint handle);
225 OpenGLState& ResetProgram(GLuint handle);
226 OpenGLState& ResetPipeline(GLuint handle);
227 OpenGLState& ResetVertexArray(GLuint handle);
228 OpenGLState& ResetFramebuffer(GLuint handle);
229
230 /// Viewport does not affects glClearBuffer so emulate viewport using scissor test
231 void EmulateViewportWithScissor();
232
233private:
234 static OpenGLState cur_state;
235
236 // Workaround for sRGB problems caused by QT not supporting srgb output
237 static bool s_rgb_used;
230}; 238};
231 239
232} // namespace OpenGL 240} // namespace OpenGL
diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp
index a99ae19bf..a775b402b 100644
--- a/src/video_core/shader/decode/texture.cpp
+++ b/src/video_core/shader/decode/texture.cpp
@@ -7,7 +7,9 @@
7#include <fmt/format.h> 7#include <fmt/format.h>
8 8
9#include "common/assert.h" 9#include "common/assert.h"
10#include "common/bit_field.h"
10#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/logging/log.h"
11#include "video_core/engines/shader_bytecode.h" 13#include "video_core/engines/shader_bytecode.h"
12#include "video_core/shader/shader_ir.h" 14#include "video_core/shader/shader_ir.h"
13 15
@@ -41,19 +43,18 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
41 43
42 switch (opcode->get().GetId()) { 44 switch (opcode->get().GetId()) {
43 case OpCode::Id::TEX: { 45 case OpCode::Id::TEX: {
44 UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(TextureMiscMode::AOFFI),
45 "AOFFI is not implemented");
46
47 if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) { 46 if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) {
48 LOG_WARNING(HW_GPU, "TEX.NODEP implementation is incomplete"); 47 LOG_WARNING(HW_GPU, "TEX.NODEP implementation is incomplete");
49 } 48 }
50 49
51 const TextureType texture_type{instr.tex.texture_type}; 50 const TextureType texture_type{instr.tex.texture_type};
52 const bool is_array = instr.tex.array != 0; 51 const bool is_array = instr.tex.array != 0;
52 const bool is_aoffi = instr.tex.UsesMiscMode(TextureMiscMode::AOFFI);
53 const bool depth_compare = instr.tex.UsesMiscMode(TextureMiscMode::DC); 53 const bool depth_compare = instr.tex.UsesMiscMode(TextureMiscMode::DC);
54 const auto process_mode = instr.tex.GetTextureProcessMode(); 54 const auto process_mode = instr.tex.GetTextureProcessMode();
55 WriteTexInstructionFloat( 55 WriteTexInstructionFloat(
56 bb, instr, GetTexCode(instr, texture_type, process_mode, depth_compare, is_array)); 56 bb, instr,
57 GetTexCode(instr, texture_type, process_mode, depth_compare, is_array, is_aoffi));
57 break; 58 break;
58 } 59 }
59 case OpCode::Id::TEXS: { 60 case OpCode::Id::TEXS: {
@@ -78,8 +79,6 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
78 } 79 }
79 case OpCode::Id::TLD4: { 80 case OpCode::Id::TLD4: {
80 ASSERT(instr.tld4.array == 0); 81 ASSERT(instr.tld4.array == 0);
81 UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::AOFFI),
82 "AOFFI is not implemented");
83 UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::NDV), 82 UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::NDV),
84 "NDV is not implemented"); 83 "NDV is not implemented");
85 UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::PTP), 84 UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::PTP),
@@ -92,8 +91,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
92 const auto texture_type = instr.tld4.texture_type.Value(); 91 const auto texture_type = instr.tld4.texture_type.Value();
93 const bool depth_compare = instr.tld4.UsesMiscMode(TextureMiscMode::DC); 92 const bool depth_compare = instr.tld4.UsesMiscMode(TextureMiscMode::DC);
94 const bool is_array = instr.tld4.array != 0; 93 const bool is_array = instr.tld4.array != 0;
95 WriteTexInstructionFloat(bb, instr, 94 const bool is_aoffi = instr.tld4.UsesMiscMode(TextureMiscMode::AOFFI);
96 GetTld4Code(instr, texture_type, depth_compare, is_array)); 95 WriteTexInstructionFloat(
96 bb, instr, GetTld4Code(instr, texture_type, depth_compare, is_array, is_aoffi));
97 break; 97 break;
98 } 98 }
99 case OpCode::Id::TLD4S: { 99 case OpCode::Id::TLD4S: {
@@ -127,7 +127,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
127 Node4 values; 127 Node4 values;
128 for (u32 element = 0; element < values.size(); ++element) { 128 for (u32 element = 0; element < values.size(); ++element) {
129 auto coords_copy = coords; 129 auto coords_copy = coords;
130 MetaTexture meta{sampler, {}, {}, {}, {}, component, element}; 130 MetaTexture meta{sampler, {}, {}, {}, {}, {}, component, element};
131 values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy)); 131 values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
132 } 132 }
133 133
@@ -152,7 +152,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
152 if (!instr.txq.IsComponentEnabled(element)) { 152 if (!instr.txq.IsComponentEnabled(element)) {
153 continue; 153 continue;
154 } 154 }
155 MetaTexture meta{sampler, {}, {}, {}, {}, {}, element}; 155 MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, element};
156 const Node value = 156 const Node value =
157 Operation(OperationCode::TextureQueryDimensions, meta, GetRegister(instr.gpr8)); 157 Operation(OperationCode::TextureQueryDimensions, meta, GetRegister(instr.gpr8));
158 SetTemporal(bb, indexer++, value); 158 SetTemporal(bb, indexer++, value);
@@ -202,7 +202,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
202 202
203 for (u32 element = 0; element < 2; ++element) { 203 for (u32 element = 0; element < 2; ++element) {
204 auto params = coords; 204 auto params = coords;
205 MetaTexture meta{sampler, {}, {}, {}, {}, {}, element}; 205 MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, element};
206 const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params)); 206 const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params));
207 SetTemporal(bb, element, value); 207 SetTemporal(bb, element, value);
208 } 208 }
@@ -325,7 +325,8 @@ void ShaderIR::WriteTexsInstructionHalfFloat(NodeBlock& bb, Instruction instr,
325 325
326Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type, 326Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
327 TextureProcessMode process_mode, std::vector<Node> coords, 327 TextureProcessMode process_mode, std::vector<Node> coords,
328 Node array, Node depth_compare, u32 bias_offset) { 328 Node array, Node depth_compare, u32 bias_offset,
329 std::vector<Node> aoffi) {
329 const bool is_array = array; 330 const bool is_array = array;
330 const bool is_shadow = depth_compare; 331 const bool is_shadow = depth_compare;
331 332
@@ -374,7 +375,7 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
374 Node4 values; 375 Node4 values;
375 for (u32 element = 0; element < values.size(); ++element) { 376 for (u32 element = 0; element < values.size(); ++element) {
376 auto copy_coords = coords; 377 auto copy_coords = coords;
377 MetaTexture meta{sampler, array, depth_compare, bias, lod, {}, element}; 378 MetaTexture meta{sampler, array, depth_compare, aoffi, bias, lod, {}, element};
378 values[element] = Operation(read_method, meta, std::move(copy_coords)); 379 values[element] = Operation(read_method, meta, std::move(copy_coords));
379 } 380 }
380 381
@@ -382,9 +383,15 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
382} 383}
383 384
384Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type, 385Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
385 TextureProcessMode process_mode, bool depth_compare, bool is_array) { 386 TextureProcessMode process_mode, bool depth_compare, bool is_array,
386 const bool lod_bias_enabled = 387 bool is_aoffi) {
387 (process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ); 388 const bool lod_bias_enabled{
389 (process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ)};
390
391 u64 parameter_register = instr.gpr20.Value();
392 if (lod_bias_enabled) {
393 ++parameter_register;
394 }
388 395
389 const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement( 396 const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement(
390 texture_type, depth_compare, is_array, lod_bias_enabled, 4, 5); 397 texture_type, depth_compare, is_array, lod_bias_enabled, 4, 5);
@@ -404,15 +411,19 @@ Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
404 411
405 const Node array = is_array ? GetRegister(array_register) : nullptr; 412 const Node array = is_array ? GetRegister(array_register) : nullptr;
406 413
414 std::vector<Node> aoffi;
415 if (is_aoffi) {
416 aoffi = GetAoffiCoordinates(GetRegister(parameter_register++), coord_count, false);
417 }
418
407 Node dc{}; 419 Node dc{};
408 if (depth_compare) { 420 if (depth_compare) {
409 // Depth is always stored in the register signaled by gpr20 or in the next register if lod 421 // Depth is always stored in the register signaled by gpr20 or in the next register if lod
410 // or bias are used 422 // or bias are used
411 const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0); 423 dc = GetRegister(parameter_register++);
412 dc = GetRegister(depth_register);
413 } 424 }
414 425
415 return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, 0); 426 return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, 0, aoffi);
416} 427}
417 428
418Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type, 429Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
@@ -448,11 +459,11 @@ Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
448 dc = GetRegister(depth_register); 459 dc = GetRegister(depth_register);
449 } 460 }
450 461
451 return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset); 462 return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset, {});
452} 463}
453 464
454Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool depth_compare, 465Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool depth_compare,
455 bool is_array) { 466 bool is_array, bool is_aoffi) {
456 const std::size_t coord_count = GetCoordCount(texture_type); 467 const std::size_t coord_count = GetCoordCount(texture_type);
457 const std::size_t total_coord_count = coord_count + (is_array ? 1 : 0); 468 const std::size_t total_coord_count = coord_count + (is_array ? 1 : 0);
458 const std::size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0); 469 const std::size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0);
@@ -463,15 +474,27 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de
463 const u64 coord_register = array_register + (is_array ? 1 : 0); 474 const u64 coord_register = array_register + (is_array ? 1 : 0);
464 475
465 std::vector<Node> coords; 476 std::vector<Node> coords;
466 for (size_t i = 0; i < coord_count; ++i) 477 for (std::size_t i = 0; i < coord_count; ++i) {
467 coords.push_back(GetRegister(coord_register + i)); 478 coords.push_back(GetRegister(coord_register + i));
479 }
480
481 u64 parameter_register = instr.gpr20.Value();
482 std::vector<Node> aoffi;
483 if (is_aoffi) {
484 aoffi = GetAoffiCoordinates(GetRegister(parameter_register++), coord_count, true);
485 }
486
487 Node dc{};
488 if (depth_compare) {
489 dc = GetRegister(parameter_register++);
490 }
468 491
469 const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, depth_compare); 492 const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, depth_compare);
470 493
471 Node4 values; 494 Node4 values;
472 for (u32 element = 0; element < values.size(); ++element) { 495 for (u32 element = 0; element < values.size(); ++element) {
473 auto coords_copy = coords; 496 auto coords_copy = coords;
474 MetaTexture meta{sampler, GetRegister(array_register), {}, {}, {}, {}, element}; 497 MetaTexture meta{sampler, GetRegister(array_register), dc, aoffi, {}, {}, {}, element};
475 values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy)); 498 values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
476 } 499 }
477 500
@@ -507,7 +530,7 @@ Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is
507 Node4 values; 530 Node4 values;
508 for (u32 element = 0; element < values.size(); ++element) { 531 for (u32 element = 0; element < values.size(); ++element) {
509 auto coords_copy = coords; 532 auto coords_copy = coords;
510 MetaTexture meta{sampler, array, {}, {}, lod, {}, element}; 533 MetaTexture meta{sampler, array, {}, {}, {}, lod, {}, element};
511 values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy)); 534 values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy));
512 } 535 }
513 return values; 536 return values;
@@ -531,4 +554,45 @@ std::tuple<std::size_t, std::size_t> ShaderIR::ValidateAndGetCoordinateElement(
531 return {coord_count, total_coord_count}; 554 return {coord_count, total_coord_count};
532} 555}
533 556
534} // namespace VideoCommon::Shader \ No newline at end of file 557std::vector<Node> ShaderIR::GetAoffiCoordinates(Node aoffi_reg, std::size_t coord_count,
558 bool is_tld4) {
559 const auto [coord_offsets, size, wrap_value,
560 diff_value] = [is_tld4]() -> std::tuple<std::array<u32, 3>, u32, s32, s32> {
561 if (is_tld4) {
562 return {{0, 8, 16}, 6, 32, 64};
563 } else {
564 return {{0, 4, 8}, 4, 8, 16};
565 }
566 }();
567 const u32 mask = (1U << size) - 1;
568
569 std::vector<Node> aoffi;
570 aoffi.reserve(coord_count);
571
572 const auto aoffi_immediate{
573 TrackImmediate(aoffi_reg, global_code, static_cast<s64>(global_code.size()))};
574 if (!aoffi_immediate) {
575 // Variable access, not supported on AMD.
576 LOG_WARNING(HW_GPU,
577 "AOFFI constant folding failed, some hardware might have graphical issues");
578 for (std::size_t coord = 0; coord < coord_count; ++coord) {
579 const Node value = BitfieldExtract(aoffi_reg, coord_offsets.at(coord), size);
580 const Node condition =
581 Operation(OperationCode::LogicalIGreaterEqual, value, Immediate(wrap_value));
582 const Node negative = Operation(OperationCode::IAdd, value, Immediate(-diff_value));
583 aoffi.push_back(Operation(OperationCode::Select, condition, negative, value));
584 }
585 return aoffi;
586 }
587
588 for (std::size_t coord = 0; coord < coord_count; ++coord) {
589 s32 value = (*aoffi_immediate >> coord_offsets.at(coord)) & mask;
590 if (value >= wrap_value) {
591 value -= diff_value;
592 }
593 aoffi.push_back(Immediate(value));
594 }
595 return aoffi;
596}
597
598} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/xmad.cpp b/src/video_core/shader/decode/xmad.cpp
index c34843307..db15c0718 100644
--- a/src/video_core/shader/decode/xmad.cpp
+++ b/src/video_core/shader/decode/xmad.cpp
@@ -29,39 +29,55 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
29 const bool is_signed_b = instr.xmad.sign_b == 1; 29 const bool is_signed_b = instr.xmad.sign_b == 1;
30 const bool is_signed_c = is_signed_a; 30 const bool is_signed_c = is_signed_a;
31 31
32 auto [is_merge, op_b, op_c] = [&]() -> std::tuple<bool, Node, Node> { 32 auto [is_merge, is_psl, is_high_b, mode, op_b,
33 op_c] = [&]() -> std::tuple<bool, bool, bool, Tegra::Shader::XmadMode, Node, Node> {
33 switch (opcode->get().GetId()) { 34 switch (opcode->get().GetId()) {
34 case OpCode::Id::XMAD_CR: 35 case OpCode::Id::XMAD_CR:
35 return {instr.xmad.merge_56, 36 return {instr.xmad.merge_56,
37 instr.xmad.product_shift_left_second,
38 instr.xmad.high_b,
39 instr.xmad.mode_cbf,
36 GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()), 40 GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()),
37 GetRegister(instr.gpr39)}; 41 GetRegister(instr.gpr39)};
38 case OpCode::Id::XMAD_RR: 42 case OpCode::Id::XMAD_RR:
39 return {instr.xmad.merge_37, GetRegister(instr.gpr20), GetRegister(instr.gpr39)}; 43 return {instr.xmad.merge_37, instr.xmad.product_shift_left, instr.xmad.high_b_rr,
44 instr.xmad.mode, GetRegister(instr.gpr20), GetRegister(instr.gpr39)};
40 case OpCode::Id::XMAD_RC: 45 case OpCode::Id::XMAD_RC:
41 return {false, GetRegister(instr.gpr39), 46 return {false,
47 false,
48 instr.xmad.high_b,
49 instr.xmad.mode_cbf,
50 GetRegister(instr.gpr39),
42 GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())}; 51 GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())};
43 case OpCode::Id::XMAD_IMM: 52 case OpCode::Id::XMAD_IMM:
44 return {instr.xmad.merge_37, Immediate(static_cast<u32>(instr.xmad.imm20_16)), 53 return {instr.xmad.merge_37,
54 instr.xmad.product_shift_left,
55 false,
56 instr.xmad.mode,
57 Immediate(static_cast<u32>(instr.xmad.imm20_16)),
45 GetRegister(instr.gpr39)}; 58 GetRegister(instr.gpr39)};
46 } 59 }
47 UNIMPLEMENTED_MSG("Unhandled XMAD instruction: {}", opcode->get().GetName()); 60 UNIMPLEMENTED_MSG("Unhandled XMAD instruction: {}", opcode->get().GetName());
48 return {false, Immediate(0), Immediate(0)}; 61 return {false, false, false, Tegra::Shader::XmadMode::None, Immediate(0), Immediate(0)};
49 }(); 62 }();
50 63
51 op_a = BitfieldExtract(op_a, instr.xmad.high_a ? 16 : 0, 16); 64 op_a = BitfieldExtract(op_a, instr.xmad.high_a ? 16 : 0, 16);
52 65
53 const Node original_b = op_b; 66 const Node original_b = op_b;
54 op_b = BitfieldExtract(op_b, instr.xmad.high_b ? 16 : 0, 16); 67 op_b = BitfieldExtract(op_b, is_high_b ? 16 : 0, 16);
55 68
56 // TODO(Rodrigo): Use an appropiate sign for this operation 69 // TODO(Rodrigo): Use an appropiate sign for this operation
57 Node product = Operation(OperationCode::IMul, NO_PRECISE, op_a, op_b); 70 Node product = Operation(OperationCode::IMul, NO_PRECISE, op_a, op_b);
58 if (instr.xmad.product_shift_left) { 71 if (is_psl) {
59 product = Operation(OperationCode::ILogicalShiftLeft, NO_PRECISE, product, Immediate(16)); 72 product = Operation(OperationCode::ILogicalShiftLeft, NO_PRECISE, product, Immediate(16));
60 } 73 }
74 SetTemporal(bb, 0, product);
75 product = GetTemporal(0);
61 76
62 const Node original_c = op_c; 77 const Node original_c = op_c;
78 const Tegra::Shader::XmadMode set_mode = mode; // Workaround to clang compile error
63 op_c = [&]() { 79 op_c = [&]() {
64 switch (instr.xmad.mode) { 80 switch (set_mode) {
65 case Tegra::Shader::XmadMode::None: 81 case Tegra::Shader::XmadMode::None:
66 return original_c; 82 return original_c;
67 case Tegra::Shader::XmadMode::CLo: 83 case Tegra::Shader::XmadMode::CLo:
@@ -80,8 +96,13 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
80 } 96 }
81 }(); 97 }();
82 98
99 SetTemporal(bb, 1, op_c);
100 op_c = GetTemporal(1);
101
83 // TODO(Rodrigo): Use an appropiate sign for this operation 102 // TODO(Rodrigo): Use an appropiate sign for this operation
84 Node sum = Operation(OperationCode::IAdd, product, op_c); 103 Node sum = Operation(OperationCode::IAdd, product, op_c);
104 SetTemporal(bb, 2, sum);
105 sum = GetTemporal(2);
85 if (is_merge) { 106 if (is_merge) {
86 const Node a = BitfieldExtract(sum, 0, 16); 107 const Node a = BitfieldExtract(sum, 0, 16);
87 const Node b = 108 const Node b =
@@ -95,4 +116,4 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
95 return pc; 116 return pc;
96} 117}
97 118
98} // namespace VideoCommon::Shader \ No newline at end of file 119} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index 5bc3a3900..4888998d3 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -7,6 +7,7 @@
7#include <array> 7#include <array>
8#include <cstring> 8#include <cstring>
9#include <map> 9#include <map>
10#include <optional>
10#include <set> 11#include <set>
11#include <string> 12#include <string>
12#include <tuple> 13#include <tuple>
@@ -290,6 +291,7 @@ struct MetaTexture {
290 const Sampler& sampler; 291 const Sampler& sampler;
291 Node array{}; 292 Node array{};
292 Node depth_compare{}; 293 Node depth_compare{};
294 std::vector<Node> aoffi;
293 Node bias{}; 295 Node bias{};
294 Node lod{}; 296 Node lod{};
295 Node component{}; 297 Node component{};
@@ -741,14 +743,14 @@ private:
741 743
742 Node4 GetTexCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type, 744 Node4 GetTexCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
743 Tegra::Shader::TextureProcessMode process_mode, bool depth_compare, 745 Tegra::Shader::TextureProcessMode process_mode, bool depth_compare,
744 bool is_array); 746 bool is_array, bool is_aoffi);
745 747
746 Node4 GetTexsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type, 748 Node4 GetTexsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
747 Tegra::Shader::TextureProcessMode process_mode, bool depth_compare, 749 Tegra::Shader::TextureProcessMode process_mode, bool depth_compare,
748 bool is_array); 750 bool is_array);
749 751
750 Node4 GetTld4Code(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type, 752 Node4 GetTld4Code(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
751 bool depth_compare, bool is_array); 753 bool depth_compare, bool is_array, bool is_aoffi);
752 754
753 Node4 GetTldsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type, 755 Node4 GetTldsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
754 bool is_array); 756 bool is_array);
@@ -757,9 +759,11 @@ private:
757 Tegra::Shader::TextureType texture_type, bool depth_compare, bool is_array, 759 Tegra::Shader::TextureType texture_type, bool depth_compare, bool is_array,
758 bool lod_bias_enabled, std::size_t max_coords, std::size_t max_inputs); 760 bool lod_bias_enabled, std::size_t max_coords, std::size_t max_inputs);
759 761
762 std::vector<Node> GetAoffiCoordinates(Node aoffi_reg, std::size_t coord_count, bool is_tld4);
763
760 Node4 GetTextureCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type, 764 Node4 GetTextureCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
761 Tegra::Shader::TextureProcessMode process_mode, std::vector<Node> coords, 765 Tegra::Shader::TextureProcessMode process_mode, std::vector<Node> coords,
762 Node array, Node depth_compare, u32 bias_offset); 766 Node array, Node depth_compare, u32 bias_offset, std::vector<Node> aoffi);
763 767
764 Node GetVideoOperand(Node op, bool is_chunk, bool is_signed, Tegra::Shader::VideoType type, 768 Node GetVideoOperand(Node op, bool is_chunk, bool is_signed, Tegra::Shader::VideoType type,
765 u64 byte_height); 769 u64 byte_height);
@@ -773,6 +777,8 @@ private:
773 777
774 Node TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor); 778 Node TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor);
775 779
780 std::optional<u32> TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor);
781
776 std::pair<Node, s64> TrackRegister(const GprNode* tracked, const NodeBlock& code, s64 cursor); 782 std::pair<Node, s64> TrackRegister(const GprNode* tracked, const NodeBlock& code, s64 cursor);
777 783
778 template <typename... T> 784 template <typename... T>
diff --git a/src/video_core/shader/track.cpp b/src/video_core/shader/track.cpp
index 33b071747..4505667ff 100644
--- a/src/video_core/shader/track.cpp
+++ b/src/video_core/shader/track.cpp
@@ -6,6 +6,7 @@
6#include <utility> 6#include <utility>
7#include <variant> 7#include <variant>
8 8
9#include "common/common_types.h"
9#include "video_core/shader/shader_ir.h" 10#include "video_core/shader/shader_ir.h"
10 11
11namespace VideoCommon::Shader { 12namespace VideoCommon::Shader {
@@ -14,7 +15,7 @@ namespace {
14std::pair<Node, s64> FindOperation(const NodeBlock& code, s64 cursor, 15std::pair<Node, s64> FindOperation(const NodeBlock& code, s64 cursor,
15 OperationCode operation_code) { 16 OperationCode operation_code) {
16 for (; cursor >= 0; --cursor) { 17 for (; cursor >= 0; --cursor) {
17 const Node node = code[cursor]; 18 const Node node = code.at(cursor);
18 if (const auto operation = std::get_if<OperationNode>(node)) { 19 if (const auto operation = std::get_if<OperationNode>(node)) {
19 if (operation->GetCode() == operation_code) 20 if (operation->GetCode() == operation_code)
20 return {node, cursor}; 21 return {node, cursor};
@@ -64,6 +65,20 @@ Node ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) {
64 return nullptr; 65 return nullptr;
65} 66}
66 67
68std::optional<u32> ShaderIR::TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor) {
69 // Reduce the cursor in one to avoid infinite loops when the instruction sets the same register
70 // that it uses as operand
71 const auto [found, found_cursor] =
72 TrackRegister(&std::get<GprNode>(*tracked), code, cursor - 1);
73 if (!found) {
74 return {};
75 }
76 if (const auto immediate = std::get_if<ImmediateNode>(found)) {
77 return immediate->GetValue();
78 }
79 return {};
80}
81
67std::pair<Node, s64> ShaderIR::TrackRegister(const GprNode* tracked, const NodeBlock& code, 82std::pair<Node, s64> ShaderIR::TrackRegister(const GprNode* tracked, const NodeBlock& code,
68 s64 cursor) { 83 s64 cursor) {
69 for (; cursor >= 0; --cursor) { 84 for (; cursor >= 0; --cursor) {
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 4cab599b4..732a1bf89 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -31,6 +31,8 @@ add_executable(yuzu
31 configuration/configure_general.h 31 configuration/configure_general.h
32 configuration/configure_graphics.cpp 32 configuration/configure_graphics.cpp
33 configuration/configure_graphics.h 33 configuration/configure_graphics.h
34 configuration/configure_hotkeys.cpp
35 configuration/configure_hotkeys.h
34 configuration/configure_input.cpp 36 configuration/configure_input.cpp
35 configuration/configure_input.h 37 configuration/configure_input.h
36 configuration/configure_input_player.cpp 38 configuration/configure_input_player.cpp
@@ -78,6 +80,8 @@ add_executable(yuzu
78 ui_settings.h 80 ui_settings.h
79 util/limitable_input_dialog.cpp 81 util/limitable_input_dialog.cpp
80 util/limitable_input_dialog.h 82 util/limitable_input_dialog.h
83 util/sequence_dialog/sequence_dialog.cpp
84 util/sequence_dialog/sequence_dialog.h
81 util/spinbox.cpp 85 util/spinbox.cpp
82 util/spinbox.h 86 util/spinbox.h
83 util/util.cpp 87 util/util.cpp
@@ -95,6 +99,7 @@ set(UIS
95 configuration/configure_gamelist.ui 99 configuration/configure_gamelist.ui
96 configuration/configure_general.ui 100 configuration/configure_general.ui
97 configuration/configure_graphics.ui 101 configuration/configure_graphics.ui
102 configuration/configure_hotkeys.ui
98 configuration/configure_input.ui 103 configuration/configure_input.ui
99 configuration/configure_input_player.ui 104 configuration/configure_input_player.ui
100 configuration/configure_input_simple.ui 105 configuration/configure_input_simple.ui
@@ -105,7 +110,6 @@ set(UIS
105 configuration/configure_touchscreen_advanced.ui 110 configuration/configure_touchscreen_advanced.ui
106 configuration/configure_web.ui 111 configuration/configure_web.ui
107 compatdb.ui 112 compatdb.ui
108 hotkeys.ui
109 loading_screen.ui 113 loading_screen.ui
110 main.ui 114 main.ui
111) 115)
diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp
index f95f7fe3c..743b24d76 100644
--- a/src/yuzu/applets/profile_select.cpp
+++ b/src/yuzu/applets/profile_select.cpp
@@ -4,6 +4,7 @@
4 4
5#include <mutex> 5#include <mutex>
6#include <QDialogButtonBox> 6#include <QDialogButtonBox>
7#include <QHeaderView>
7#include <QLabel> 8#include <QLabel>
8#include <QLineEdit> 9#include <QLineEdit>
9#include <QScrollArea> 10#include <QScrollArea>
diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h
index 868573324..1c2922e54 100644
--- a/src/yuzu/applets/profile_select.h
+++ b/src/yuzu/applets/profile_select.h
@@ -7,6 +7,7 @@
7#include <vector> 7#include <vector>
8#include <QDialog> 8#include <QDialog>
9#include <QList> 9#include <QList>
10#include <QTreeView>
10#include "core/frontend/applets/profile_select.h" 11#include "core/frontend/applets/profile_select.h"
11 12
12class GMainWindow; 13class GMainWindow;
@@ -16,7 +17,6 @@ class QLabel;
16class QScrollArea; 17class QScrollArea;
17class QStandardItem; 18class QStandardItem;
18class QStandardItemModel; 19class QStandardItemModel;
19class QTreeView;
20class QVBoxLayout; 20class QVBoxLayout;
21 21
22class QtProfileSelectionDialog final : public QDialog { 22class QtProfileSelectionDialog final : public QDialog {
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index dead9f807..802db3945 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -2,6 +2,8 @@
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 <array>
6#include <QKeySequence>
5#include <QSettings> 7#include <QSettings>
6#include "common/file_util.h" 8#include "common/file_util.h"
7#include "configure_input_simple.h" 9#include "configure_input_simple.h"
@@ -9,7 +11,6 @@
9#include "core/hle/service/hid/controllers/npad.h" 11#include "core/hle/service/hid/controllers/npad.h"
10#include "input_common/main.h" 12#include "input_common/main.h"
11#include "yuzu/configuration/config.h" 13#include "yuzu/configuration/config.h"
12#include "yuzu/ui_settings.h"
13 14
14Config::Config() { 15Config::Config() {
15 // TODO: Don't hardcode the path; let the frontend decide where to put the config files. 16 // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
@@ -17,7 +18,6 @@ Config::Config() {
17 FileUtil::CreateFullPath(qt_config_loc); 18 FileUtil::CreateFullPath(qt_config_loc);
18 qt_config = 19 qt_config =
19 std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); 20 std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
20
21 Reload(); 21 Reload();
22} 22}
23 23
@@ -205,6 +205,27 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
205 Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight, 205 Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight,
206}; 206};
207 207
208// This shouldn't have anything except static initializers (no functions). So
209// QKeySequnce(...).toString() is NOT ALLOWED HERE.
210// This must be in alphabetical order according to action name as it must have the same order as
211// UISetting::values.shortcuts, which is alphabetically ordered.
212const std::array<UISettings::Shortcut, 15> Config::default_hotkeys{
213 {{"Capture Screenshot", "Main Window", {"Ctrl+P", Qt::ApplicationShortcut}},
214 {"Continue/Pause Emulation", "Main Window", {"F4", Qt::WindowShortcut}},
215 {"Decrease Speed Limit", "Main Window", {"-", Qt::ApplicationShortcut}},
216 {"Exit yuzu", "Main Window", {"Ctrl+Q", Qt::WindowShortcut}},
217 {"Exit Fullscreen", "Main Window", {"Esc", Qt::WindowShortcut}},
218 {"Fullscreen", "Main Window", {"F11", Qt::WindowShortcut}},
219 {"Increase Speed Limit", "Main Window", {"+", Qt::ApplicationShortcut}},
220 {"Load Amiibo", "Main Window", {"F2", Qt::ApplicationShortcut}},
221 {"Load File", "Main Window", {"Ctrl+O", Qt::WindowShortcut}},
222 {"Restart Emulation", "Main Window", {"F6", Qt::WindowShortcut}},
223 {"Stop Emulation", "Main Window", {"F5", Qt::WindowShortcut}},
224 {"Toggle Filter Bar", "Main Window", {"Ctrl+F", Qt::WindowShortcut}},
225 {"Toggle Speed Limit", "Main Window", {"Ctrl+Z", Qt::ApplicationShortcut}},
226 {"Toggle Status Bar", "Main Window", {"Ctrl+S", Qt::WindowShortcut}},
227 {"Change Docked Mode", "Main Window", {"F10", Qt::ApplicationShortcut}}}};
228
208void Config::ReadPlayerValues() { 229void Config::ReadPlayerValues() {
209 for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { 230 for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
210 auto& player = Settings::values.players[p]; 231 auto& player = Settings::values.players[p];
@@ -508,20 +529,15 @@ void Config::ReadValues() {
508 qt_config->endGroup(); 529 qt_config->endGroup();
509 530
510 qt_config->beginGroup("Shortcuts"); 531 qt_config->beginGroup("Shortcuts");
511 QStringList groups = qt_config->childGroups(); 532 for (auto [name, group, shortcut] : default_hotkeys) {
512 for (auto group : groups) { 533 auto [keyseq, context] = shortcut;
513 qt_config->beginGroup(group); 534 qt_config->beginGroup(group);
514 535 qt_config->beginGroup(name);
515 QStringList hotkeys = qt_config->childGroups(); 536 UISettings::values.shortcuts.push_back(
516 for (auto hotkey : hotkeys) { 537 {name,
517 qt_config->beginGroup(hotkey); 538 group,
518 UISettings::values.shortcuts.emplace_back(UISettings::Shortcut( 539 {ReadSetting("KeySeq", keyseq).toString(), ReadSetting("Context", context).toInt()}});
519 group + "/" + hotkey, 540 qt_config->endGroup();
520 UISettings::ContextualShortcut(ReadSetting("KeySeq").toString(),
521 ReadSetting("Context").toInt())));
522 qt_config->endGroup();
523 }
524
525 qt_config->endGroup(); 541 qt_config->endGroup();
526 } 542 }
527 qt_config->endGroup(); 543 qt_config->endGroup();
@@ -758,9 +774,16 @@ void Config::SaveValues() {
758 qt_config->endGroup(); 774 qt_config->endGroup();
759 775
760 qt_config->beginGroup("Shortcuts"); 776 qt_config->beginGroup("Shortcuts");
761 for (auto shortcut : UISettings::values.shortcuts) { 777 // Lengths of UISettings::values.shortcuts & default_hotkeys are same.
762 WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first); 778 // However, their ordering must also be the same.
763 WriteSetting(shortcut.first + "/Context", shortcut.second.second); 779 for (std::size_t i = 0; i < default_hotkeys.size(); i++) {
780 auto [name, group, shortcut] = UISettings::values.shortcuts[i];
781 qt_config->beginGroup(group);
782 qt_config->beginGroup(name);
783 WriteSetting("KeySeq", shortcut.first, default_hotkeys[i].shortcut.first);
784 WriteSetting("Context", shortcut.second, default_hotkeys[i].shortcut.second);
785 qt_config->endGroup();
786 qt_config->endGroup();
764 } 787 }
765 qt_config->endGroup(); 788 qt_config->endGroup();
766 789
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index f4185db18..221d2364c 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -9,6 +9,7 @@
9#include <string> 9#include <string>
10#include <QVariant> 10#include <QVariant>
11#include "core/settings.h" 11#include "core/settings.h"
12#include "yuzu/ui_settings.h"
12 13
13class QSettings; 14class QSettings;
14 15
@@ -47,6 +48,8 @@ private:
47 void WriteSetting(const QString& name, const QVariant& value); 48 void WriteSetting(const QString& name, const QVariant& value);
48 void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value); 49 void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
49 50
51 static const std::array<UISettings::Shortcut, 15> default_hotkeys;
52
50 std::unique_ptr<QSettings> qt_config; 53 std::unique_ptr<QSettings> qt_config;
51 std::string qt_config_loc; 54 std::string qt_config_loc;
52}; 55};
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 3f03f0b77..267717bc9 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -7,9 +7,15 @@
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>382</width> 9 <width>382</width>
10 <height>241</height> 10 <height>650</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
13 <property name="minimumSize">
14 <size>
15 <width>0</width>
16 <height>650</height>
17 </size>
18 </property>
13 <property name="windowTitle"> 19 <property name="windowTitle">
14 <string>yuzu Configuration</string> 20 <string>yuzu Configuration</string>
15 </property> 21 </property>
@@ -62,6 +68,11 @@
62 <string>Input</string> 68 <string>Input</string>
63 </attribute> 69 </attribute>
64 </widget> 70 </widget>
71 <widget class="ConfigureHotkeys" name="hotkeysTab">
72 <attribute name="title">
73 <string>Hotkeys</string>
74 </attribute>
75 </widget>
65 <widget class="ConfigureGraphics" name="graphicsTab"> 76 <widget class="ConfigureGraphics" name="graphicsTab">
66 <attribute name="title"> 77 <attribute name="title">
67 <string>Graphics</string> 78 <string>Graphics</string>
@@ -150,6 +161,12 @@
150 <header>configuration/configure_input_simple.h</header> 161 <header>configuration/configure_input_simple.h</header>
151 <container>1</container> 162 <container>1</container>
152 </customwidget> 163 </customwidget>
164 <customwidget>
165 <class>ConfigureHotkeys</class>
166 <extends>QWidget</extends>
167 <header>configuration/configure_hotkeys.h</header>
168 <container>1</container>
169 </customwidget>
153 </customwidgets> 170 </customwidgets>
154 <resources/> 171 <resources/>
155 <connections> 172 <connections>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 777050405..51bd1f121 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -8,20 +8,22 @@
8#include "ui_configure.h" 8#include "ui_configure.h"
9#include "yuzu/configuration/config.h" 9#include "yuzu/configuration/config.h"
10#include "yuzu/configuration/configure_dialog.h" 10#include "yuzu/configuration/configure_dialog.h"
11#include "yuzu/configuration/configure_input_player.h"
11#include "yuzu/hotkeys.h" 12#include "yuzu/hotkeys.h"
12 13
13ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry) 14ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
14 : QDialog(parent), ui(new Ui::ConfigureDialog) { 15 : QDialog(parent), registry(registry), ui(new Ui::ConfigureDialog) {
15 ui->setupUi(this); 16 ui->setupUi(this);
16 ui->generalTab->PopulateHotkeyList(registry); 17 ui->hotkeysTab->Populate(registry);
17 this->setConfiguration(); 18 this->setConfiguration();
18 this->PopulateSelectionList(); 19 this->PopulateSelectionList();
19 connect(ui->selectorList, &QListWidget::itemSelectionChanged, this, 20 connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
20 &ConfigureDialog::UpdateVisibleTabs); 21 &ConfigureDialog::UpdateVisibleTabs);
21
22 adjustSize(); 22 adjustSize();
23
24 ui->selectorList->setCurrentRow(0); 23 ui->selectorList->setCurrentRow(0);
24
25 // Synchronise lists upon initialisation
26 ui->hotkeysTab->EmitHotkeysChanged();
25} 27}
26 28
27ConfigureDialog::~ConfigureDialog() = default; 29ConfigureDialog::~ConfigureDialog() = default;
@@ -34,6 +36,7 @@ void ConfigureDialog::applyConfiguration() {
34 ui->systemTab->applyConfiguration(); 36 ui->systemTab->applyConfiguration();
35 ui->profileManagerTab->applyConfiguration(); 37 ui->profileManagerTab->applyConfiguration();
36 ui->inputTab->applyConfiguration(); 38 ui->inputTab->applyConfiguration();
39 ui->hotkeysTab->applyConfiguration(registry);
37 ui->graphicsTab->applyConfiguration(); 40 ui->graphicsTab->applyConfiguration();
38 ui->audioTab->applyConfiguration(); 41 ui->audioTab->applyConfiguration();
39 ui->debugTab->applyConfiguration(); 42 ui->debugTab->applyConfiguration();
@@ -47,7 +50,7 @@ void ConfigureDialog::PopulateSelectionList() {
47 {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}}, 50 {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}},
48 {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}}, 51 {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}},
49 {tr("Graphics"), {tr("Graphics")}}, 52 {tr("Graphics"), {tr("Graphics")}},
50 {tr("Controls"), {tr("Input")}}}}; 53 {tr("Controls"), {tr("Input"), tr("Hotkeys")}}}};
51 54
52 for (const auto& entry : items) { 55 for (const auto& entry : items) {
53 auto* const item = new QListWidgetItem(entry.first); 56 auto* const item = new QListWidgetItem(entry.first);
@@ -66,6 +69,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
66 {tr("System"), ui->systemTab}, 69 {tr("System"), ui->systemTab},
67 {tr("Profiles"), ui->profileManagerTab}, 70 {tr("Profiles"), ui->profileManagerTab},
68 {tr("Input"), ui->inputTab}, 71 {tr("Input"), ui->inputTab},
72 {tr("Hotkeys"), ui->hotkeysTab},
69 {tr("Graphics"), ui->graphicsTab}, 73 {tr("Graphics"), ui->graphicsTab},
70 {tr("Audio"), ui->audioTab}, 74 {tr("Audio"), ui->audioTab},
71 {tr("Debug"), ui->debugTab}, 75 {tr("Debug"), ui->debugTab},
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 243d9fa09..2363ba584 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -17,7 +17,7 @@ class ConfigureDialog : public QDialog {
17 Q_OBJECT 17 Q_OBJECT
18 18
19public: 19public:
20 explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry); 20 explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry);
21 ~ConfigureDialog() override; 21 ~ConfigureDialog() override;
22 22
23 void applyConfiguration(); 23 void applyConfiguration();
@@ -28,4 +28,5 @@ private:
28 void PopulateSelectionList(); 28 void PopulateSelectionList();
29 29
30 std::unique_ptr<Ui::ConfigureDialog> ui; 30 std::unique_ptr<Ui::ConfigureDialog> ui;
31 HotkeyRegistry& registry;
31}; 32};
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 389fcf667..eeb038afb 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -35,10 +35,6 @@ void ConfigureGeneral::setConfiguration() {
35 ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit); 35 ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
36} 36}
37 37
38void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
39 ui->widget->Populate(registry);
40}
41
42void ConfigureGeneral::applyConfiguration() { 38void ConfigureGeneral::applyConfiguration() {
43 UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); 39 UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
44 UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); 40 UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h
index 59738af40..df41d995b 100644
--- a/src/yuzu/configuration/configure_general.h
+++ b/src/yuzu/configuration/configure_general.h
@@ -20,7 +20,6 @@ public:
20 explicit ConfigureGeneral(QWidget* parent = nullptr); 20 explicit ConfigureGeneral(QWidget* parent = nullptr);
21 ~ConfigureGeneral() override; 21 ~ConfigureGeneral() override;
22 22
23 void PopulateHotkeyList(const HotkeyRegistry& registry);
24 void applyConfiguration(); 23 void applyConfiguration();
25 24
26private: 25private:
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index 01d1c0b8e..1a5721fe7 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -98,22 +98,6 @@
98 </widget> 98 </widget>
99 </item> 99 </item>
100 <item> 100 <item>
101 <widget class="QGroupBox" name="HotKeysGroupBox">
102 <property name="title">
103 <string>Hotkeys</string>
104 </property>
105 <layout class="QHBoxLayout" name="HotKeysHorizontalLayout">
106 <item>
107 <layout class="QVBoxLayout" name="HotKeysVerticalLayout">
108 <item>
109 <widget class="GHotkeysDialog" name="widget" native="true"/>
110 </item>
111 </layout>
112 </item>
113 </layout>
114 </widget>
115 </item>
116 <item>
117 <spacer name="verticalSpacer"> 101 <spacer name="verticalSpacer">
118 <property name="orientation"> 102 <property name="orientation">
119 <enum>Qt::Vertical</enum> 103 <enum>Qt::Vertical</enum>
@@ -130,14 +114,6 @@
130 </item> 114 </item>
131 </layout> 115 </layout>
132 </widget> 116 </widget>
133 <customwidgets>
134 <customwidget>
135 <class>GHotkeysDialog</class>
136 <extends>QWidget</extends>
137 <header>hotkeys.h</header>
138 <container>1</container>
139 </customwidget>
140 </customwidgets>
141 <resources/> 117 <resources/>
142 <connections/> 118 <connections/>
143</ui> 119</ui>
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
new file mode 100644
index 000000000..bfb562535
--- /dev/null
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -0,0 +1,121 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QMessageBox>
6#include <QStandardItemModel>
7#include "core/settings.h"
8#include "ui_configure_hotkeys.h"
9#include "yuzu/configuration/configure_hotkeys.h"
10#include "yuzu/hotkeys.h"
11#include "yuzu/util/sequence_dialog/sequence_dialog.h"
12
13ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
14 : QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) {
15 ui->setupUi(this);
16 setFocusPolicy(Qt::ClickFocus);
17
18 model = new QStandardItemModel(this);
19 model->setColumnCount(3);
20 model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
21
22 connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure);
23 ui->hotkey_list->setModel(model);
24
25 // TODO(Kloen): Make context configurable as well (hiding the column for now)
26 ui->hotkey_list->hideColumn(2);
27
28 ui->hotkey_list->setColumnWidth(0, 200);
29 ui->hotkey_list->resizeColumnToContents(1);
30}
31
32ConfigureHotkeys::~ConfigureHotkeys() = default;
33
34void ConfigureHotkeys::EmitHotkeysChanged() {
35 emit HotkeysChanged(GetUsedKeyList());
36}
37
38QList<QKeySequence> ConfigureHotkeys::GetUsedKeyList() const {
39 QList<QKeySequence> list;
40 for (int r = 0; r < model->rowCount(); r++) {
41 const QStandardItem* parent = model->item(r, 0);
42 for (int r2 = 0; r2 < parent->rowCount(); r2++) {
43 const QStandardItem* keyseq = parent->child(r2, 1);
44 list << QKeySequence::fromString(keyseq->text(), QKeySequence::NativeText);
45 }
46 }
47 return list;
48}
49
50void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
51 for (const auto& group : registry.hotkey_groups) {
52 auto* parent_item = new QStandardItem(group.first);
53 parent_item->setEditable(false);
54 for (const auto& hotkey : group.second) {
55 auto* action = new QStandardItem(hotkey.first);
56 auto* keyseq =
57 new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
58 action->setEditable(false);
59 keyseq->setEditable(false);
60 parent_item->appendRow({action, keyseq});
61 }
62 model->appendRow(parent_item);
63 }
64
65 ui->hotkey_list->expandAll();
66}
67
68void ConfigureHotkeys::Configure(QModelIndex index) {
69 if (index.parent() == QModelIndex())
70 return;
71
72 index = index.sibling(index.row(), 1);
73 auto* model = ui->hotkey_list->model();
74 auto previous_key = model->data(index);
75
76 auto* hotkey_dialog = new SequenceDialog;
77 int return_code = hotkey_dialog->exec();
78
79 auto key_sequence = hotkey_dialog->GetSequence();
80
81 if (return_code == QDialog::Rejected || key_sequence.isEmpty())
82 return;
83
84 if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) {
85 QMessageBox::critical(this, tr("Error in inputted key"),
86 tr("You're using a key that's already bound."));
87 } else {
88 model->setData(index, key_sequence.toString(QKeySequence::NativeText));
89 EmitHotkeysChanged();
90 }
91}
92
93bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) {
94 return GetUsedKeyList().contains(key_sequence);
95}
96
97void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) {
98 for (int key_id = 0; key_id < model->rowCount(); key_id++) {
99 const QStandardItem* parent = model->item(key_id, 0);
100 for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
101 const QStandardItem* action = parent->child(key_column_id, 0);
102 const QStandardItem* keyseq = parent->child(key_column_id, 1);
103 for (auto& [group, sub_actions] : registry.hotkey_groups) {
104 if (group != parent->text())
105 continue;
106 for (auto& [action_name, hotkey] : sub_actions) {
107 if (action_name != action->text())
108 continue;
109 hotkey.keyseq = QKeySequence(keyseq->text());
110 }
111 }
112 }
113 }
114
115 registry.SaveHotkeys();
116 Settings::Apply();
117}
118
119void ConfigureHotkeys::retranslateUi() {
120 ui->retranslateUi(this);
121}
diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h
new file mode 100644
index 000000000..cd203aad6
--- /dev/null
+++ b/src/yuzu/configuration/configure_hotkeys.h
@@ -0,0 +1,48 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QWidget>
9#include "core/settings.h"
10
11namespace Ui {
12class ConfigureHotkeys;
13}
14
15class HotkeyRegistry;
16class QStandardItemModel;
17
18class ConfigureHotkeys : public QWidget {
19 Q_OBJECT
20
21public:
22 explicit ConfigureHotkeys(QWidget* parent = nullptr);
23 ~ConfigureHotkeys() override;
24
25 void applyConfiguration(HotkeyRegistry& registry);
26 void retranslateUi();
27
28 void EmitHotkeysChanged();
29
30 /**
31 * Populates the hotkey list widget using data from the provided registry.
32 * Called everytime the Configure dialog is opened.
33 * @param registry The HotkeyRegistry whose data is used to populate the list.
34 */
35 void Populate(const HotkeyRegistry& registry);
36
37signals:
38 void HotkeysChanged(QList<QKeySequence> new_key_list);
39
40private:
41 void Configure(QModelIndex index);
42 bool IsUsedKey(QKeySequence key_sequence);
43 QList<QKeySequence> GetUsedKeyList() const;
44
45 std::unique_ptr<Ui::ConfigureHotkeys> ui;
46
47 QStandardItemModel* model;
48};
diff --git a/src/yuzu/configuration/configure_hotkeys.ui b/src/yuzu/configuration/configure_hotkeys.ui
new file mode 100644
index 000000000..0d0b70f38
--- /dev/null
+++ b/src/yuzu/configuration/configure_hotkeys.ui
@@ -0,0 +1,42 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureHotkeys</class>
4 <widget class="QWidget" name="ConfigureHotkeys">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>363</width>
10 <height>388</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Hotkey Settings</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout_2">
19 <item>
20 <widget class="QLabel" name="label_2">
21 <property name="text">
22 <string>Double-click on a binding to change it.</string>
23 </property>
24 </widget>
25 </item>
26 <item>
27 <widget class="QTreeView" name="hotkey_list">
28 <property name="editTriggers">
29 <set>QAbstractItemView::NoEditTriggers</set>
30 </property>
31 <property name="sortingEnabled">
32 <bool>false</bool>
33 </property>
34 </widget>
35 </item>
36 </layout>
37 </item>
38 </layout>
39 </widget>
40 <resources/>
41 <connections/>
42</ui> \ No newline at end of file
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 4422a572b..4b67656ac 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -18,6 +18,7 @@
18#include "common/common_types.h" 18#include "common/common_types.h"
19#include "common/logging/log.h" 19#include "common/logging/log.h"
20#include "core/file_sys/patch_manager.h" 20#include "core/file_sys/patch_manager.h"
21#include "core/file_sys/registered_cache.h"
21#include "yuzu/compatibility_list.h" 22#include "yuzu/compatibility_list.h"
22#include "yuzu/game_list.h" 23#include "yuzu/game_list.h"
23#include "yuzu/game_list_p.h" 24#include "yuzu/game_list_p.h"
@@ -193,8 +194,9 @@ void GameList::onFilterCloseClicked() {
193 main_window->filterBarSetChecked(false); 194 main_window->filterBarSetChecked(false);
194} 195}
195 196
196GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent) 197GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvider* provider,
197 : QWidget{parent}, vfs(std::move(vfs)) { 198 GMainWindow* parent)
199 : QWidget{parent}, vfs(std::move(vfs)), provider(provider) {
198 watcher = new QFileSystemWatcher(this); 200 watcher = new QFileSystemWatcher(this);
199 connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); 201 connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
200 202
@@ -432,7 +434,8 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
432 434
433 emit ShouldCancelWorker(); 435 emit ShouldCancelWorker();
434 436
435 GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan, compatibility_list); 437 GameListWorker* worker =
438 new GameListWorker(vfs, provider, dir_path, deep_scan, compatibility_list);
436 439
437 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); 440 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
438 connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, 441 connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 8ea5cbaaa..56007eef8 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -26,8 +26,9 @@ class GameListSearchField;
26class GMainWindow; 26class GMainWindow;
27 27
28namespace FileSys { 28namespace FileSys {
29class ManualContentProvider;
29class VfsFilesystem; 30class VfsFilesystem;
30} 31} // namespace FileSys
31 32
32enum class GameListOpenTarget { 33enum class GameListOpenTarget {
33 SaveData, 34 SaveData,
@@ -47,7 +48,8 @@ public:
47 COLUMN_COUNT, // Number of columns 48 COLUMN_COUNT, // Number of columns
48 }; 49 };
49 50
50 explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs, GMainWindow* parent = nullptr); 51 explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs,
52 FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr);
51 ~GameList() override; 53 ~GameList() override;
52 54
53 void clearFilter(); 55 void clearFilter();
@@ -86,6 +88,7 @@ private:
86 void RefreshGameDirectory(); 88 void RefreshGameDirectory();
87 89
88 std::shared_ptr<FileSys::VfsFilesystem> vfs; 90 std::shared_ptr<FileSys::VfsFilesystem> vfs;
91 FileSys::ManualContentProvider* provider;
89 GameListSearchField* search_field; 92 GameListSearchField* search_field;
90 GMainWindow* main_window = nullptr; 93 GMainWindow* main_window = nullptr;
91 QVBoxLayout* layout = nullptr; 94 QVBoxLayout* layout = nullptr;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index b37710f59..8687e7c5a 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -12,12 +12,15 @@
12 12
13#include "common/common_paths.h" 13#include "common/common_paths.h"
14#include "common/file_util.h" 14#include "common/file_util.h"
15#include "core/core.h"
16#include "core/file_sys/card_image.h"
15#include "core/file_sys/content_archive.h" 17#include "core/file_sys/content_archive.h"
16#include "core/file_sys/control_metadata.h" 18#include "core/file_sys/control_metadata.h"
17#include "core/file_sys/mode.h" 19#include "core/file_sys/mode.h"
18#include "core/file_sys/nca_metadata.h" 20#include "core/file_sys/nca_metadata.h"
19#include "core/file_sys/patch_manager.h" 21#include "core/file_sys/patch_manager.h"
20#include "core/file_sys/registered_cache.h" 22#include "core/file_sys/registered_cache.h"
23#include "core/file_sys/submission_package.h"
21#include "core/hle/service/filesystem/filesystem.h" 24#include "core/hle/service/filesystem/filesystem.h"
22#include "core/loader/loader.h" 25#include "core/loader/loader.h"
23#include "yuzu/compatibility_list.h" 26#include "yuzu/compatibility_list.h"
@@ -119,20 +122,25 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
119} 122}
120} // Anonymous namespace 123} // Anonymous namespace
121 124
122GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan, 125GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs,
123 const CompatibilityList& compatibility_list) 126 FileSys::ManualContentProvider* provider, QString dir_path,
124 : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan), 127 bool deep_scan, const CompatibilityList& compatibility_list)
128 : vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan),
125 compatibility_list(compatibility_list) {} 129 compatibility_list(compatibility_list) {}
126 130
127GameListWorker::~GameListWorker() = default; 131GameListWorker::~GameListWorker() = default;
128 132
129void GameListWorker::AddInstalledTitlesToGameList() { 133void GameListWorker::AddTitlesToGameList() {
130 const auto cache = Service::FileSystem::GetUnionContents(); 134 const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>(
131 const auto installed_games = cache.ListEntriesFilter(FileSys::TitleType::Application, 135 Core::System::GetInstance().GetContentProvider());
132 FileSys::ContentRecordType::Program); 136 const auto installed_games = cache.ListEntriesFilterOrigin(
137 std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
133 138
134 for (const auto& game : installed_games) { 139 for (const auto& [slot, game] : installed_games) {
135 const auto file = cache.GetEntryUnparsed(game); 140 if (slot == FileSys::ContentProviderUnionSlot::FrontendManual)
141 continue;
142
143 const auto file = cache.GetEntryUnparsed(game.title_id, game.type);
136 std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file); 144 std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
137 if (!loader) 145 if (!loader)
138 continue; 146 continue;
@@ -150,45 +158,13 @@ void GameListWorker::AddInstalledTitlesToGameList() {
150 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id, 158 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
151 compatibility_list, patch)); 159 compatibility_list, patch));
152 } 160 }
153
154 const auto control_data = cache.ListEntriesFilter(FileSys::TitleType::Application,
155 FileSys::ContentRecordType::Control);
156
157 for (const auto& entry : control_data) {
158 auto nca = cache.GetEntry(entry);
159 if (nca != nullptr) {
160 nca_control_map.insert_or_assign(entry.title_id, std::move(nca));
161 }
162 }
163} 161}
164 162
165void GameListWorker::FillControlMap(const std::string& dir_path) { 163void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path,
166 const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory, 164 unsigned int recursion) {
167 const std::string& virtual_name) -> bool { 165 const auto callback = [this, target, recursion](u64* num_entries_out,
168 if (stop_processing) { 166 const std::string& directory,
169 // Breaks the callback loop 167 const std::string& virtual_name) -> bool {
170 return false;
171 }
172
173 const std::string physical_name = directory + DIR_SEP + virtual_name;
174 const QFileInfo file_info(QString::fromStdString(physical_name));
175 if (!file_info.isDir() && file_info.suffix() == QStringLiteral("nca")) {
176 auto nca =
177 std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
178 if (nca->GetType() == FileSys::NCAContentType::Control) {
179 const u64 title_id = nca->GetTitleId();
180 nca_control_map.insert_or_assign(title_id, std::move(nca));
181 }
182 }
183 return true;
184 };
185
186 FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
187}
188
189void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
190 const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
191 const std::string& virtual_name) -> bool {
192 if (stop_processing) { 168 if (stop_processing) {
193 // Breaks the callback loop. 169 // Breaks the callback loop.
194 return false; 170 return false;
@@ -198,7 +174,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
198 const bool is_dir = FileUtil::IsDirectory(physical_name); 174 const bool is_dir = FileUtil::IsDirectory(physical_name);
199 if (!is_dir && 175 if (!is_dir &&
200 (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { 176 (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
201 auto loader = Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); 177 const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
178 auto loader = Loader::GetLoader(file);
202 if (!loader) { 179 if (!loader) {
203 return true; 180 return true;
204 } 181 }
@@ -209,31 +186,42 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
209 return true; 186 return true;
210 } 187 }
211 188
212 std::vector<u8> icon;
213 const auto res1 = loader->ReadIcon(icon);
214
215 u64 program_id = 0; 189 u64 program_id = 0;
216 const auto res2 = loader->ReadProgramId(program_id); 190 const auto res2 = loader->ReadProgramId(program_id);
217 191
218 std::string name = " "; 192 if (target == ScanTarget::FillManualContentProvider) {
219 const auto res3 = loader->ReadTitle(name); 193 if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
194 provider->AddEntry(FileSys::TitleType::Application,
195 FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
196 program_id, file);
197 } else if (res2 == Loader::ResultStatus::Success &&
198 (file_type == Loader::FileType::XCI ||
199 file_type == Loader::FileType::NSP)) {
200 const auto nsp = file_type == Loader::FileType::NSP
201 ? std::make_shared<FileSys::NSP>(file)
202 : FileSys::XCI{file}.GetSecurePartitionNSP();
203 for (const auto& title : nsp->GetNCAs()) {
204 for (const auto& entry : title.second) {
205 provider->AddEntry(entry.first.first, entry.first.second, title.first,
206 entry.second->GetBaseFile());
207 }
208 }
209 }
210 } else {
211 std::vector<u8> icon;
212 const auto res1 = loader->ReadIcon(icon);
220 213
221 const FileSys::PatchManager patch{program_id}; 214 std::string name = " ";
215 const auto res3 = loader->ReadTitle(name);
222 216
223 if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success && 217 const FileSys::PatchManager patch{program_id};
224 res2 == Loader::ResultStatus::Success) {
225 // Use from metadata pool.
226 if (nca_control_map.find(program_id) != nca_control_map.end()) {
227 const auto& nca = nca_control_map[program_id];
228 GetMetadataFromControlNCA(patch, *nca, icon, name);
229 }
230 }
231 218
232 emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, 219 emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
233 compatibility_list, patch)); 220 compatibility_list, patch));
221 }
234 } else if (is_dir && recursion > 0) { 222 } else if (is_dir && recursion > 0) {
235 watch_list.append(QString::fromStdString(physical_name)); 223 watch_list.append(QString::fromStdString(physical_name));
236 AddFstEntriesToGameList(physical_name, recursion - 1); 224 ScanFileSystem(target, physical_name, recursion - 1);
237 } 225 }
238 226
239 return true; 227 return true;
@@ -245,10 +233,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
245void GameListWorker::run() { 233void GameListWorker::run() {
246 stop_processing = false; 234 stop_processing = false;
247 watch_list.append(dir_path); 235 watch_list.append(dir_path);
248 FillControlMap(dir_path.toStdString()); 236 provider->ClearAllEntries();
249 AddInstalledTitlesToGameList(); 237 ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(),
250 AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); 238 deep_scan ? 256 : 0);
251 nca_control_map.clear(); 239 AddTitlesToGameList();
240 ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0);
252 emit Finished(watch_list); 241 emit Finished(watch_list);
253} 242}
254 243
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 0e42d0bde..7c3074af9 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -33,7 +33,8 @@ class GameListWorker : public QObject, public QRunnable {
33 Q_OBJECT 33 Q_OBJECT
34 34
35public: 35public:
36 GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan, 36 GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs,
37 FileSys::ManualContentProvider* provider, QString dir_path, bool deep_scan,
37 const CompatibilityList& compatibility_list); 38 const CompatibilityList& compatibility_list);
38 ~GameListWorker() override; 39 ~GameListWorker() override;
39 40
@@ -58,12 +59,17 @@ signals:
58 void Finished(QStringList watch_list); 59 void Finished(QStringList watch_list);
59 60
60private: 61private:
61 void AddInstalledTitlesToGameList(); 62 void AddTitlesToGameList();
62 void FillControlMap(const std::string& dir_path); 63
63 void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); 64 enum class ScanTarget {
65 FillManualContentProvider,
66 PopulateGameList,
67 };
68
69 void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0);
64 70
65 std::shared_ptr<FileSys::VfsFilesystem> vfs; 71 std::shared_ptr<FileSys::VfsFilesystem> vfs;
66 std::map<u64, std::unique_ptr<FileSys::NCA>> nca_control_map; 72 FileSys::ManualContentProvider* provider;
67 QStringList watch_list; 73 QStringList watch_list;
68 QString dir_path; 74 QString dir_path;
69 bool deep_scan; 75 bool deep_scan;
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp
index dce399774..4582e7f21 100644
--- a/src/yuzu/hotkeys.cpp
+++ b/src/yuzu/hotkeys.cpp
@@ -2,7 +2,6 @@
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 <map>
6#include <QKeySequence> 5#include <QKeySequence>
7#include <QShortcut> 6#include <QShortcut>
8#include <QTreeWidgetItem> 7#include <QTreeWidgetItem>
@@ -13,47 +12,32 @@
13HotkeyRegistry::HotkeyRegistry() = default; 12HotkeyRegistry::HotkeyRegistry() = default;
14HotkeyRegistry::~HotkeyRegistry() = default; 13HotkeyRegistry::~HotkeyRegistry() = default;
15 14
16void HotkeyRegistry::LoadHotkeys() {
17 // Make sure NOT to use a reference here because it would become invalid once we call
18 // beginGroup()
19 for (auto shortcut : UISettings::values.shortcuts) {
20 const QStringList cat = shortcut.first.split('/');
21 Q_ASSERT(cat.size() >= 2);
22
23 // RegisterHotkey assigns default keybindings, so use old values as default parameters
24 Hotkey& hk = hotkey_groups[cat[0]][cat[1]];
25 if (!shortcut.second.first.isEmpty()) {
26 hk.keyseq = QKeySequence::fromString(shortcut.second.first);
27 hk.context = static_cast<Qt::ShortcutContext>(shortcut.second.second);
28 }
29 if (hk.shortcut)
30 hk.shortcut->setKey(hk.keyseq);
31 }
32}
33
34void HotkeyRegistry::SaveHotkeys() { 15void HotkeyRegistry::SaveHotkeys() {
35 UISettings::values.shortcuts.clear(); 16 UISettings::values.shortcuts.clear();
36 for (const auto& group : hotkey_groups) { 17 for (const auto& group : hotkey_groups) {
37 for (const auto& hotkey : group.second) { 18 for (const auto& hotkey : group.second) {
38 UISettings::values.shortcuts.emplace_back( 19 UISettings::values.shortcuts.push_back(
39 UISettings::Shortcut(group.first + '/' + hotkey.first, 20 {hotkey.first, group.first,
40 UISettings::ContextualShortcut(hotkey.second.keyseq.toString(), 21 UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
41 hotkey.second.context))); 22 hotkey.second.context)});
42 } 23 }
43 } 24 }
44} 25}
45 26
46void HotkeyRegistry::RegisterHotkey(const QString& group, const QString& action, 27void HotkeyRegistry::LoadHotkeys() {
47 const QKeySequence& default_keyseq, 28 // Make sure NOT to use a reference here because it would become invalid once we call
48 Qt::ShortcutContext default_context) { 29 // beginGroup()
49 auto& hotkey_group = hotkey_groups[group]; 30 for (auto shortcut : UISettings::values.shortcuts) {
50 if (hotkey_group.find(action) != hotkey_group.end()) { 31 Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
51 return; 32 if (!shortcut.shortcut.first.isEmpty()) {
33 hk.keyseq = QKeySequence::fromString(shortcut.shortcut.first, QKeySequence::NativeText);
34 hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.second);
35 }
36 if (hk.shortcut) {
37 hk.shortcut->disconnect();
38 hk.shortcut->setKey(hk.keyseq);
39 }
52 } 40 }
53
54 auto& hotkey_action = hotkey_groups[group][action];
55 hotkey_action.keyseq = default_keyseq;
56 hotkey_action.context = default_context;
57} 41}
58 42
59QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) { 43QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) {
@@ -65,24 +49,11 @@ QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action
65 return hk.shortcut; 49 return hk.shortcut;
66} 50}
67 51
68GHotkeysDialog::GHotkeysDialog(QWidget* parent) : QWidget(parent) { 52QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) {
69 ui.setupUi(this); 53 return hotkey_groups[group][action].keyseq;
70} 54}
71 55
72void GHotkeysDialog::Populate(const HotkeyRegistry& registry) { 56Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group,
73 for (const auto& group : registry.hotkey_groups) { 57 const QString& action) {
74 QTreeWidgetItem* toplevel_item = new QTreeWidgetItem(QStringList(group.first)); 58 return hotkey_groups[group][action].context;
75 for (const auto& hotkey : group.second) {
76 QStringList columns;
77 columns << hotkey.first << hotkey.second.keyseq.toString();
78 QTreeWidgetItem* item = new QTreeWidgetItem(columns);
79 toplevel_item->addChild(item);
80 }
81 ui.treeWidget->addTopLevelItem(toplevel_item);
82 }
83 // TODO: Make context configurable as well (hiding the column for now)
84 ui.treeWidget->setColumnCount(2);
85
86 ui.treeWidget->resizeColumnToContents(0);
87 ui.treeWidget->resizeColumnToContents(1);
88} 59}
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index f38e6c002..4f526dc7e 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -5,7 +5,6 @@
5#pragma once 5#pragma once
6 6
7#include <map> 7#include <map>
8#include "ui_hotkeys.h"
9 8
10class QDialog; 9class QDialog;
11class QKeySequence; 10class QKeySequence;
@@ -14,7 +13,7 @@ class QShortcut;
14 13
15class HotkeyRegistry final { 14class HotkeyRegistry final {
16public: 15public:
17 friend class GHotkeysDialog; 16 friend class ConfigureHotkeys;
18 17
19 explicit HotkeyRegistry(); 18 explicit HotkeyRegistry();
20 ~HotkeyRegistry(); 19 ~HotkeyRegistry();
@@ -49,22 +48,27 @@ public:
49 QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget); 48 QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
50 49
51 /** 50 /**
52 * Register a hotkey. 51 * Returns a QKeySequence object whose signal can be connected to QAction::setShortcut.
53 * 52 *
54 * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger") 53 * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger").
55 * @param action Name of the action (e.g. "Start Emulation", "Load Image") 54 * @param action Name of the action (e.g. "Start Emulation", "Load Image").
56 * @param default_keyseq Default key sequence to assign if the hotkey wasn't present in the 55 */
57 * settings file before 56 QKeySequence GetKeySequence(const QString& group, const QString& action);
58 * @param default_context Default context to assign if the hotkey wasn't present in the settings 57
59 * file before 58 /**
60 * @warning Both the group and action strings will be displayed in the hotkey settings dialog 59 * Returns a Qt::ShortcutContext object who can be connected to other
60 * QAction::setShortcutContext.
61 *
62 * @param group General group this shortcut context belongs to (e.g. "Main Window",
63 * "Debugger").
64 * @param action Name of the action (e.g. "Start Emulation", "Load Image").
61 */ 65 */
62 void RegisterHotkey(const QString& group, const QString& action, 66 Qt::ShortcutContext GetShortcutContext(const QString& group, const QString& action);
63 const QKeySequence& default_keyseq = {},
64 Qt::ShortcutContext default_context = Qt::WindowShortcut);
65 67
66private: 68private:
67 struct Hotkey { 69 struct Hotkey {
70 Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {}
71
68 QKeySequence keyseq; 72 QKeySequence keyseq;
69 QShortcut* shortcut = nullptr; 73 QShortcut* shortcut = nullptr;
70 Qt::ShortcutContext context = Qt::WindowShortcut; 74 Qt::ShortcutContext context = Qt::WindowShortcut;
@@ -75,15 +79,3 @@ private:
75 79
76 HotkeyGroupMap hotkey_groups; 80 HotkeyGroupMap hotkey_groups;
77}; 81};
78
79class GHotkeysDialog : public QWidget {
80 Q_OBJECT
81
82public:
83 explicit GHotkeysDialog(QWidget* parent = nullptr);
84
85 void Populate(const HotkeyRegistry& registry);
86
87private:
88 Ui::hotkeys ui;
89};
diff --git a/src/yuzu/hotkeys.ui b/src/yuzu/hotkeys.ui
deleted file mode 100644
index 050fe064e..000000000
--- a/src/yuzu/hotkeys.ui
+++ /dev/null
@@ -1,46 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>hotkeys</class>
4 <widget class="QWidget" name="hotkeys">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>363</width>
10 <height>388</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Hotkey Settings</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <widget class="QTreeWidget" name="treeWidget">
19 <property name="selectionBehavior">
20 <enum>QAbstractItemView::SelectItems</enum>
21 </property>
22 <property name="headerHidden">
23 <bool>false</bool>
24 </property>
25 <column>
26 <property name="text">
27 <string>Action</string>
28 </property>
29 </column>
30 <column>
31 <property name="text">
32 <string>Hotkey</string>
33 </property>
34 </column>
35 <column>
36 <property name="text">
37 <string>Context</string>
38 </property>
39 </column>
40 </widget>
41 </item>
42 </layout>
43 </widget>
44 <resources/>
45 <connections/>
46</ui>
diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp
index 86f6d0165..4e2d988cd 100644
--- a/src/yuzu/loading_screen.cpp
+++ b/src/yuzu/loading_screen.cpp
@@ -192,7 +192,12 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size
192 } 192 }
193 193
194 // update labels and progress bar 194 // update labels and progress bar
195 ui->stage->setText(stage_translations[stage].arg(value).arg(total)); 195 if (stage == VideoCore::LoadCallbackStage::Decompile ||
196 stage == VideoCore::LoadCallbackStage::Build) {
197 ui->stage->setText(stage_translations[stage].arg(value).arg(total));
198 } else {
199 ui->stage->setText(stage_translations[stage]);
200 }
196 ui->value->setText(estimate); 201 ui->value->setText(estimate);
197 ui->progress_bar->setValue(static_cast<int>(value)); 202 ui->progress_bar->setValue(static_cast<int>(value));
198 previous_time = now; 203 previous_time = now;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 2b9db69a3..d5a328d92 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -171,7 +171,8 @@ static void InitializeLogging() {
171 171
172GMainWindow::GMainWindow() 172GMainWindow::GMainWindow()
173 : config(new Config()), emu_thread(nullptr), 173 : config(new Config()), emu_thread(nullptr),
174 vfs(std::make_shared<FileSys::RealVfsFilesystem>()) { 174 vfs(std::make_shared<FileSys::RealVfsFilesystem>()),
175 provider(std::make_unique<FileSys::ManualContentProvider>()) {
175 InitializeLogging(); 176 InitializeLogging();
176 177
177 debug_context = Tegra::DebugContext::Construct(); 178 debug_context = Tegra::DebugContext::Construct();
@@ -203,11 +204,15 @@ GMainWindow::GMainWindow()
203 .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc)); 204 .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
204 show(); 205 show();
205 206
207 Core::System::GetInstance().SetContentProvider(
208 std::make_unique<FileSys::ContentProviderUnion>());
209 Core::System::GetInstance().RegisterContentProvider(
210 FileSys::ContentProviderUnionSlot::FrontendManual, provider.get());
211 Service::FileSystem::CreateFactories(*vfs);
212
206 // Gen keys if necessary 213 // Gen keys if necessary
207 OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); 214 OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
208 215
209 // Necessary to load titles from nand in gamelist.
210 Service::FileSystem::CreateFactories(*vfs);
211 game_list->LoadCompatibilityList(); 216 game_list->LoadCompatibilityList();
212 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 217 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
213 218
@@ -419,7 +424,7 @@ void GMainWindow::InitializeWidgets() {
419 render_window = new GRenderWindow(this, emu_thread.get()); 424 render_window = new GRenderWindow(this, emu_thread.get());
420 render_window->hide(); 425 render_window->hide();
421 426
422 game_list = new GameList(vfs, this); 427 game_list = new GameList(vfs, provider.get(), this);
423 ui.horizontalLayout->addWidget(game_list); 428 ui.horizontalLayout->addWidget(game_list);
424 429
425 loading_screen = new LoadingScreen(this); 430 loading_screen = new LoadingScreen(this);
@@ -514,33 +519,34 @@ void GMainWindow::InitializeRecentFileMenuActions() {
514} 519}
515 520
516void GMainWindow::InitializeHotkeys() { 521void GMainWindow::InitializeHotkeys() {
517 hotkey_registry.RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
518 hotkey_registry.RegisterHotkey("Main Window", "Start Emulation");
519 hotkey_registry.RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4));
520 hotkey_registry.RegisterHotkey("Main Window", "Restart", QKeySequence(Qt::Key_F5));
521 hotkey_registry.RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen);
522 hotkey_registry.RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape),
523 Qt::ApplicationShortcut);
524 hotkey_registry.RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"),
525 Qt::ApplicationShortcut);
526 hotkey_registry.RegisterHotkey("Main Window", "Increase Speed Limit", QKeySequence("+"),
527 Qt::ApplicationShortcut);
528 hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"),
529 Qt::ApplicationShortcut);
530 hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2),
531 Qt::ApplicationShortcut);
532 hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot",
533 QKeySequence(QKeySequence::Print));
534 hotkey_registry.RegisterHotkey("Main Window", "Change Docked Mode", QKeySequence(Qt::Key_F10));
535
536 hotkey_registry.LoadHotkeys(); 522 hotkey_registry.LoadHotkeys();
537 523
524 ui.action_Load_File->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Load File"));
525 ui.action_Load_File->setShortcutContext(
526 hotkey_registry.GetShortcutContext("Main Window", "Load File"));
527
528 ui.action_Exit->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Exit yuzu"));
529 ui.action_Exit->setShortcutContext(
530 hotkey_registry.GetShortcutContext("Main Window", "Exit yuzu"));
531
532 ui.action_Stop->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Stop Emulation"));
533 ui.action_Stop->setShortcutContext(
534 hotkey_registry.GetShortcutContext("Main Window", "Stop Emulation"));
535
536 ui.action_Show_Filter_Bar->setShortcut(
537 hotkey_registry.GetKeySequence("Main Window", "Toggle Filter Bar"));
538 ui.action_Show_Filter_Bar->setShortcutContext(
539 hotkey_registry.GetShortcutContext("Main Window", "Toggle Filter Bar"));
540
541 ui.action_Show_Status_Bar->setShortcut(
542 hotkey_registry.GetKeySequence("Main Window", "Toggle Status Bar"));
543 ui.action_Show_Status_Bar->setShortcutContext(
544 hotkey_registry.GetShortcutContext("Main Window", "Toggle Status Bar"));
545
538 connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated, 546 connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
539 this, &GMainWindow::OnMenuLoadFile); 547 this, &GMainWindow::OnMenuLoadFile);
540 connect(hotkey_registry.GetHotkey("Main Window", "Start Emulation", this), 548 connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause Emulation", this),
541 &QShortcut::activated, this, &GMainWindow::OnStartGame); 549 &QShortcut::activated, this, [&] {
542 connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause", this), &QShortcut::activated,
543 this, [&] {
544 if (emulation_running) { 550 if (emulation_running) {
545 if (emu_thread->IsRunning()) { 551 if (emu_thread->IsRunning()) {
546 OnPauseGame(); 552 OnPauseGame();
@@ -549,8 +555,8 @@ void GMainWindow::InitializeHotkeys() {
549 } 555 }
550 } 556 }
551 }); 557 });
552 connect(hotkey_registry.GetHotkey("Main Window", "Restart", this), &QShortcut::activated, this, 558 connect(hotkey_registry.GetHotkey("Main Window", "Restart Emulation", this),
553 [this] { 559 &QShortcut::activated, this, [this] {
554 if (!Core::System::GetInstance().IsPoweredOn()) 560 if (!Core::System::GetInstance().IsPoweredOn())
555 return; 561 return;
556 BootGame(QString(game_path)); 562 BootGame(QString(game_path));
@@ -697,7 +703,6 @@ void GMainWindow::ConnectMenuEvents() {
697 &GMainWindow::ToggleWindowMode); 703 &GMainWindow::ToggleWindowMode);
698 connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, 704 connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this,
699 &GMainWindow::OnDisplayTitleBars); 705 &GMainWindow::OnDisplayTitleBars);
700 ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F"));
701 connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar); 706 connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar);
702 connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); 707 connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
703 708
@@ -1179,7 +1184,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
1179 return; 1184 return;
1180 } 1185 }
1181 1186
1182 const auto installed = Service::FileSystem::GetUnionContents(); 1187 const auto& installed = Core::System::GetInstance().GetContentProvider();
1183 const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id); 1188 const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id);
1184 1189
1185 if (!romfs_title_id) { 1190 if (!romfs_title_id) {
@@ -1662,6 +1667,7 @@ void GMainWindow::OnConfigure() {
1662 auto result = configureDialog.exec(); 1667 auto result = configureDialog.exec();
1663 if (result == QDialog::Accepted) { 1668 if (result == QDialog::Accepted) {
1664 configureDialog.applyConfiguration(); 1669 configureDialog.applyConfiguration();
1670 InitializeHotkeys();
1665 if (UISettings::values.theme != old_theme) 1671 if (UISettings::values.theme != old_theme)
1666 UpdateUITheme(); 1672 UpdateUITheme();
1667 if (UISettings::values.enable_discord_presence != old_discord_presence) 1673 if (UISettings::values.enable_discord_presence != old_discord_presence)
@@ -1924,14 +1930,14 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
1924 } 1930 }
1925} 1931}
1926 1932
1927std::optional<u64> GMainWindow::SelectRomFSDumpTarget( 1933std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed,
1928 const FileSys::RegisteredCacheUnion& installed, u64 program_id) { 1934 u64 program_id) {
1929 const auto dlc_entries = 1935 const auto dlc_entries =
1930 installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); 1936 installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
1931 std::vector<FileSys::RegisteredCacheEntry> dlc_match; 1937 std::vector<FileSys::ContentProviderEntry> dlc_match;
1932 dlc_match.reserve(dlc_entries.size()); 1938 dlc_match.reserve(dlc_entries.size());
1933 std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), 1939 std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
1934 [&program_id, &installed](const FileSys::RegisteredCacheEntry& entry) { 1940 [&program_id, &installed](const FileSys::ContentProviderEntry& entry) {
1935 return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id && 1941 return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id &&
1936 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; 1942 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
1937 }); 1943 });
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 7f3aa998e..c727e942c 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -37,7 +37,8 @@ struct SoftwareKeyboardParameters;
37} // namespace Core::Frontend 37} // namespace Core::Frontend
38 38
39namespace FileSys { 39namespace FileSys {
40class RegisteredCacheUnion; 40class ContentProvider;
41class ManualContentProvider;
41class VfsFilesystem; 42class VfsFilesystem;
42} // namespace FileSys 43} // namespace FileSys
43 44
@@ -120,7 +121,6 @@ private:
120 void InitializeWidgets(); 121 void InitializeWidgets();
121 void InitializeDebugWidgets(); 122 void InitializeDebugWidgets();
122 void InitializeRecentFileMenuActions(); 123 void InitializeRecentFileMenuActions();
123 void InitializeHotkeys();
124 124
125 void SetDefaultUIGeometry(); 125 void SetDefaultUIGeometry();
126 void RestoreUIState(); 126 void RestoreUIState();
@@ -196,6 +196,7 @@ private slots:
196 void OnAbout(); 196 void OnAbout();
197 void OnToggleFilterBar(); 197 void OnToggleFilterBar();
198 void OnDisplayTitleBars(bool); 198 void OnDisplayTitleBars(bool);
199 void InitializeHotkeys();
199 void ToggleFullscreen(); 200 void ToggleFullscreen();
200 void ShowFullscreen(); 201 void ShowFullscreen();
201 void HideFullscreen(); 202 void HideFullscreen();
@@ -205,7 +206,7 @@ private slots:
205 void OnReinitializeKeys(ReinitializeKeyBehavior behavior); 206 void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
206 207
207private: 208private:
208 std::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&, u64 program_id); 209 std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
209 void UpdateStatusBar(); 210 void UpdateStatusBar();
210 211
211 Ui::MainWindow ui; 212 Ui::MainWindow ui;
@@ -233,6 +234,7 @@ private:
233 234
234 // FS 235 // FS
235 std::shared_ptr<FileSys::VfsFilesystem> vfs; 236 std::shared_ptr<FileSys::VfsFilesystem> vfs;
237 std::unique_ptr<FileSys::ManualContentProvider> provider;
236 238
237 // Debugger panes 239 // Debugger panes
238 ProfilerWidget* profilerWidget; 240 ProfilerWidget* profilerWidget;
diff --git a/src/yuzu/ui_settings.cpp b/src/yuzu/ui_settings.cpp
index a314493fc..4bdc302e0 100644
--- a/src/yuzu/ui_settings.cpp
+++ b/src/yuzu/ui_settings.cpp
@@ -12,5 +12,4 @@ const Themes themes{{
12}}; 12}};
13 13
14Values values = {}; 14Values values = {};
15
16} // namespace UISettings 15} // namespace UISettings
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index 82aaeedb0..45e705b61 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -15,7 +15,12 @@
15namespace UISettings { 15namespace UISettings {
16 16
17using ContextualShortcut = std::pair<QString, int>; 17using ContextualShortcut = std::pair<QString, int>;
18using Shortcut = std::pair<QString, ContextualShortcut>; 18
19struct Shortcut {
20 QString name;
21 QString group;
22 ContextualShortcut shortcut;
23};
19 24
20using Themes = std::array<std::pair<const char*, const char*>, 2>; 25using Themes = std::array<std::pair<const char*, const char*>, 2>;
21extern const Themes themes; 26extern const Themes themes;
diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
new file mode 100644
index 000000000..d3edf6ec3
--- /dev/null
+++ b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
@@ -0,0 +1,37 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QDialogButtonBox>
6#include <QKeySequenceEdit>
7#include <QVBoxLayout>
8#include "yuzu/util/sequence_dialog/sequence_dialog.h"
9
10SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) {
11 setWindowTitle(tr("Enter a hotkey"));
12 auto* layout = new QVBoxLayout(this);
13 key_sequence = new QKeySequenceEdit;
14 layout->addWidget(key_sequence);
15 auto* buttons =
16 new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
17 buttons->setCenterButtons(true);
18 layout->addWidget(buttons);
19 connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
20 connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
21 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
22}
23
24SequenceDialog::~SequenceDialog() = default;
25
26QKeySequence SequenceDialog::GetSequence() const {
27 // Only the first key is returned. The other 3, if present, are ignored.
28 return QKeySequence(key_sequence->keySequence()[0]);
29}
30
31bool SequenceDialog::focusNextPrevChild(bool next) {
32 return false;
33}
34
35void SequenceDialog::closeEvent(QCloseEvent*) {
36 reject();
37}
diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.h b/src/yuzu/util/sequence_dialog/sequence_dialog.h
new file mode 100644
index 000000000..969c77740
--- /dev/null
+++ b/src/yuzu/util/sequence_dialog/sequence_dialog.h
@@ -0,0 +1,24 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <QDialog>
8
9class QKeySequenceEdit;
10
11class SequenceDialog : public QDialog {
12 Q_OBJECT
13
14public:
15 explicit SequenceDialog(QWidget* parent = nullptr);
16 ~SequenceDialog() override;
17
18 QKeySequence GetSequence() const;
19 void closeEvent(QCloseEvent*) override;
20
21private:
22 QKeySequenceEdit* key_sequence;
23 bool focusNextPrevChild(bool next) override;
24};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 245f25847..7ea4a1b18 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -33,6 +33,7 @@
33#include "yuzu_cmd/emu_window/emu_window_sdl2.h" 33#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
34 34
35#include <getopt.h> 35#include <getopt.h>
36#include "core/file_sys/registered_cache.h"
36#ifndef _MSC_VER 37#ifndef _MSC_VER
37#include <unistd.h> 38#include <unistd.h>
38#endif 39#endif
@@ -178,6 +179,7 @@ int main(int argc, char** argv) {
178 } 179 }
179 180
180 Core::System& system{Core::System::GetInstance()}; 181 Core::System& system{Core::System::GetInstance()};
182 system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
181 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); 183 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
182 Service::FileSystem::CreateFactories(*system.GetFilesystem()); 184 Service::FileSystem::CreateFactories(*system.GetFilesystem());
183 185