summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt2
-rw-r--r--externals/CMakeLists.txt4
m---------externals/zstd0
-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
83 files changed, 1709 insertions, 979 deletions
diff --git a/.gitmodules b/.gitmodules
index 2558a5ebc..26b4e5272 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -40,3 +40,6 @@
[submodule "Vulkan-Headers"]
path = externals/Vulkan-Headers
url = https://github.com/KhronosGroup/Vulkan-Headers.git
+[submodule "externals/zstd"]
+ path = externals/zstd
+ url = https://github.com/facebook/zstd
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ab18275d3..6a417017c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -309,7 +309,7 @@ if (CLANG_FORMAT)
set(CCOMMENT "Running clang format against all the .h and .cpp files in src/")
if (WIN32)
add_custom_target(clang-format
- COMMAND powershell.exe -Command "Get-ChildItem ${SRCS}/* -Include *.cpp,*.h -Recurse | Foreach {${CLANG_FORMAT} -i $_.fullname}"
+ COMMAND powershell.exe -Command "Get-ChildItem '${SRCS}/*' -Include *.cpp,*.h -Recurse | Foreach {&'${CLANG_FORMAT}' -i $_.fullname}"
COMMENT ${CCOMMENT})
elseif(MINGW)
add_custom_target(clang-format
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index e156bbece..aa3319eb1 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -49,6 +49,10 @@ add_subdirectory(open_source_archives EXCLUDE_FROM_ALL)
add_library(unicorn-headers INTERFACE)
target_include_directories(unicorn-headers INTERFACE ./unicorn/include)
+# Zstandard
+add_subdirectory(zstd/build/cmake EXCLUDE_FROM_ALL)
+target_include_directories(libzstd_static INTERFACE ./zstd/lib)
+
# SoundTouch
add_subdirectory(soundtouch)
diff --git a/externals/zstd b/externals/zstd
new file mode 160000
+Subproject 470344d33e1d52a2ada75d278466da8d4ee2faf
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
uint128.h
vector_math.h
web_result.h
+ zstd_compression.cpp
+ zstd_compression.h
)
if(ARCHITECTURE_x86_64)
@@ -138,4 +140,4 @@ endif()
create_target_directory_groups(common)
target_link_libraries(common PUBLIC Boost::boost fmt microprofile)
-target_link_libraries(common PRIVATE lz4_static)
+target_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)
#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!")
#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__)
+
+// If the assert is ignored, execute _b_
+#define ASSERT_OR_EXECUTE(_a_, _b_) \
+ do { \
+ ASSERT(_a_); \
+ if (!(_a_)) { \
+ _b_ \
+ } \
+ } while (0)
+
+// If the assert is ignored, execute _b_
+#define ASSERT_OR_EXECUTE_MSG(_a_, _b_, ...) \
+ do { \
+ ASSERT_MSG(_a_, __VA_ARGS__); \
+ if (!(_a_)) { \
+ _b_ \
+ } \
+ } 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 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <algorithm>
+#include <zstd.h>
+
+#include "common/assert.h"
+#include "common/zstd_compression.h"
+
+namespace Common::Compression {
+
+std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size, s32 compression_level) {
+ compression_level = std::clamp(compression_level, 1, ZSTD_maxCLevel());
+
+ const std::size_t max_compressed_size = ZSTD_compressBound(source_size);
+ std::vector<u8> compressed(max_compressed_size);
+
+ const std::size_t compressed_size =
+ ZSTD_compress(compressed.data(), compressed.size(), source, source_size, compression_level);
+
+ if (ZSTD_isError(compressed_size)) {
+ // Compression failed
+ return {};
+ }
+
+ compressed.resize(compressed_size);
+
+ return compressed;
+}
+
+std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size) {
+ return CompressDataZSTD(source, source_size, ZSTD_CLEVEL_DEFAULT);
+}
+
+std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed) {
+ const std::size_t decompressed_size =
+ ZSTD_getDecompressedSize(compressed.data(), compressed.size());
+ std::vector<u8> decompressed(decompressed_size);
+
+ const std::size_t uncompressed_result_size = ZSTD_decompress(
+ decompressed.data(), decompressed.size(), compressed.data(), compressed.size());
+
+ if (decompressed_size != uncompressed_result_size || ZSTD_isError(uncompressed_result_size)) {
+ // Decompression failed
+ return {};
+ }
+ return decompressed;
+}
+
+} // 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 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Common::Compression {
+
+/**
+ * Compresses a source memory region with Zstandard and returns the compressed data in a vector.
+ *
+ * @param source the uncompressed source memory region.
+ * @param source_size the size in bytes of the uncompressed source memory region.
+ * @param compression_level the used compression level. Should be between 1 and 22.
+ *
+ * @return the compressed data.
+ */
+std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size, s32 compression_level);
+
+/**
+ * Compresses a source memory region with Zstandard with the default compression level and returns
+ * the compressed data in a vector.
+ *
+ * @param source the uncompressed source memory region.
+ * @param source_size the size in bytes of the uncompressed source memory region.
+ *
+ * @return the compressed data.
+ */
+std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size);
+
+/**
+ * Decompresses a source memory region with Zstandard and returns the uncompressed data in a vector.
+ *
+ * @param compressed the compressed source memory region.
+ *
+ * @return the decompressed data.
+ */
+std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed);
+
+} // 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)
void ARM_Dynarmic::Run() {
MICROPROFILE_SCOPE(ARM_Jit_Dynarmic);
- ASSERT(Memory::GetCurrentPageTable() == current_page_table);
jit->Run();
}
@@ -278,7 +277,6 @@ void ARM_Dynarmic::ClearExclusiveState() {
void ARM_Dynarmic::PageTableChanged() {
jit = MakeJit();
- current_page_table = Memory::GetCurrentPageTable();
}
DynarmicExclusiveMonitor::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 @@
#include "core/arm/exclusive_monitor.h"
#include "core/arm/unicorn/arm_unicorn.h"
-namespace Common {
-struct PageTable;
-}
-
namespace Core::Timing {
class CoreTiming;
}
@@ -69,8 +65,6 @@ private:
std::size_t core_index;
Timing::CoreTiming& core_timing;
DynarmicExclusiveMonitor& exclusive_monitor;
-
- Common::PageTable* current_page_table = nullptr;
};
class 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 @@
#include "core/core_timing.h"
#include "core/cpu_core_manager.h"
#include "core/file_sys/mode.h"
+#include "core/file_sys/registered_cache.h"
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_real.h"
#include "core/gdbstub/gdbstub.h"
@@ -108,6 +109,8 @@ struct System::Impl {
// Create a default fs if one doesn't already exist.
if (virtual_filesystem == nullptr)
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
+ if (content_provider == nullptr)
+ content_provider = std::make_unique<FileSys::ContentProviderUnion>();
/// Create default implementations of applets if one is not provided.
if (profile_selector == nullptr)
@@ -249,6 +252,8 @@ struct System::Impl {
Kernel::KernelCore kernel;
/// RealVfsFilesystem instance
FileSys::VirtualFilesystem virtual_filesystem;
+ /// ContentProviderUnion instance
+ std::unique_ptr<FileSys::ContentProviderUnion> content_provider;
/// AppLoader used to load the current executing application
std::unique_ptr<Loader::AppLoader> app_loader;
std::unique_ptr<VideoCore::RendererBase> renderer;
@@ -488,6 +493,27 @@ const Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const {
return *impl->software_keyboard;
}
+void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) {
+ impl->content_provider = std::move(provider);
+}
+
+FileSys::ContentProvider& System::GetContentProvider() {
+ return *impl->content_provider;
+}
+
+const FileSys::ContentProvider& System::GetContentProvider() const {
+ return *impl->content_provider;
+}
+
+void System::RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
+ FileSys::ContentProvider* provider) {
+ impl->content_provider->SetSlot(slot, provider);
+}
+
+void System::ClearContentProvider(FileSys::ContentProviderUnionSlot slot) {
+ impl->content_provider->ClearSlot(slot);
+}
+
void System::SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet) {
impl->web_browser = std::move(applet);
}
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;
namespace FileSys {
class CheatList;
+class ContentProvider;
+class ContentProviderUnion;
+enum class ContentProviderUnionSlot;
class VfsFilesystem;
} // namespace FileSys
@@ -270,6 +273,17 @@ public:
Frontend::WebBrowserApplet& GetWebBrowser();
const Frontend::WebBrowserApplet& GetWebBrowser() const;
+ void SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider);
+
+ FileSys::ContentProvider& GetContentProvider();
+
+ const FileSys::ContentProvider& GetContentProvider() const;
+
+ void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
+ FileSys::ContentProvider* provider);
+
+ void ClearContentProvider(FileSys::ContentProviderUnionSlot slot);
+
private:
System();
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 @@
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/partition_data_manager.h"
@@ -794,7 +795,7 @@ void KeyManager::DeriveBase() {
void KeyManager::DeriveETicket(PartitionDataManager& data) {
// ETicket keys
- const auto es = Service::FileSystem::GetUnionContents().GetEntry(
+ const auto es = Core::System::GetInstance().GetContentProvider().GetEntry(
0x0100000000000033, FileSys::ContentRecordType::Program);
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 @@
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/ips_layer.h"
@@ -69,7 +70,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
}
}
- const auto installed = Service::FileSystem::GetUnionContents();
+ const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto update_disabled =
@@ -155,7 +156,7 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
return out;
}
-std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
+std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::string& name) const {
if (nso.size() < sizeof(Loader::NSOHeader)) {
return nso;
}
@@ -171,18 +172,19 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
if (Settings::values.dump_nso) {
- LOG_INFO(Loader, "Dumping NSO for build_id={}, title_id={:016X}", build_id, title_id);
+ LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id,
+ title_id);
const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id);
if (dump_dir != nullptr) {
const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso");
- const auto file = nso_dir->CreateFile(fmt::format("{}.nso", build_id));
+ const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id));
file->Resize(nso.size());
file->WriteBytes(nso);
}
}
- LOG_INFO(Loader, "Patching NSO for build_id={}", build_id);
+ LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
auto patch_dirs = load_dir->GetSubdirectories();
@@ -345,7 +347,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
if (romfs == nullptr)
return romfs;
- const auto installed = Service::FileSystem::GetUnionContents();
+ const auto& installed = Core::System::GetInstance().GetContentProvider();
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
@@ -392,7 +394,7 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
VirtualFile update_raw) const {
std::map<std::string, std::string, std::less<>> out;
- const auto installed = Service::FileSystem::GetUnionContents();
+ const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto& disabled = Settings::values.disabled_addons[title_id];
// Game Updates
@@ -466,10 +468,10 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
// DLC
const auto dlc_entries = installed.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
- std::vector<RegisteredCacheEntry> dlc_match;
+ std::vector<ContentProviderEntry> dlc_match;
dlc_match.reserve(dlc_entries.size());
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
- [this, &installed](const RegisteredCacheEntry& entry) {
+ [this, &installed](const ContentProviderEntry& entry) {
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
});
@@ -492,7 +494,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
}
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
- const auto installed{Service::FileSystem::GetUnionContents()};
+ const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control);
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:
// Currently tracked NSO patches:
// - IPS
// - IPSwitch
- std::vector<u8> PatchNSO(const std::vector<u8>& nso) const;
+ std::vector<u8> PatchNSO(const std::vector<u8>& nso, const std::string& name) const;
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
// 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 {
// The size of blocks to use when vfs raw copying into nand.
constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000;
-std::string RegisteredCacheEntry::DebugInfo() const {
+std::string ContentProviderEntry::DebugInfo() const {
return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
}
-bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
+bool operator<(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
}
-bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
+bool operator==(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
return std::tie(lhs.title_id, lhs.type) == std::tie(rhs.title_id, rhs.type);
}
-bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
+bool operator!=(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
return !operator==(lhs, rhs);
}
@@ -84,7 +84,7 @@ static std::string GetCNMTName(TitleType type, u64 title_id) {
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
}
-static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
+ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
switch (type) {
case NCAContentType::Program:
// TODO(DarkLordZach): Differentiate between Program and Patch
@@ -104,6 +104,28 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
}
}
+ContentProvider::~ContentProvider() = default;
+
+bool ContentProvider::HasEntry(ContentProviderEntry entry) const {
+ return HasEntry(entry.title_id, entry.type);
+}
+
+VirtualFile ContentProvider::GetEntryUnparsed(ContentProviderEntry entry) const {
+ return GetEntryUnparsed(entry.title_id, entry.type);
+}
+
+VirtualFile ContentProvider::GetEntryRaw(ContentProviderEntry entry) const {
+ return GetEntryRaw(entry.title_id, entry.type);
+}
+
+std::unique_ptr<NCA> ContentProvider::GetEntry(ContentProviderEntry entry) const {
+ return GetEntry(entry.title_id, entry.type);
+}
+
+std::vector<ContentProviderEntry> ContentProvider::ListEntries() const {
+ return ListEntriesFilter(std::nullopt, std::nullopt, std::nullopt);
+}
+
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
std::string_view path) const {
const auto file = dir->GetFileRelative(path);
@@ -161,8 +183,8 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
return file;
}
-static std::optional<NcaID> CheckMapForContentRecord(
- const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
+static std::optional<NcaID> CheckMapForContentRecord(const std::map<u64, CNMT>& map, u64 title_id,
+ ContentRecordType type) {
if (map.find(title_id) == map.end())
return {};
@@ -268,7 +290,7 @@ void RegisteredCache::Refresh() {
AccumulateYuzuMeta();
}
-RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function)
+RegisteredCache::RegisteredCache(VirtualDir dir_, ContentProviderParsingFunction parsing_function)
: dir(std::move(dir_)), parser(std::move(parsing_function)) {
Refresh();
}
@@ -279,19 +301,11 @@ bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
return GetEntryRaw(title_id, type) != nullptr;
}
-bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
- return GetEntryRaw(entry) != nullptr;
-}
-
VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
const auto id = GetNcaIDFromMetadata(title_id, type);
return id ? GetFileAtID(*id) : nullptr;
}
-VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const {
- return GetEntryUnparsed(entry.title_id, entry.type);
-}
-
std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
const auto meta_iter = meta.find(title_id);
if (meta_iter != meta.end())
@@ -309,10 +323,6 @@ VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) c
return id ? parser(GetFileAtID(*id), *id) : nullptr;
}
-VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
- return GetEntryRaw(entry.title_id, entry.type);
-}
-
std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
const auto raw = GetEntryRaw(title_id, type);
if (raw == nullptr)
@@ -320,10 +330,6 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t
return std::make_unique<NCA>(raw, nullptr, 0, keys);
}
-std::unique_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
- return GetEntry(entry.title_id, entry.type);
-}
-
template <typename T>
void RegisteredCache::IterateAllMetadata(
std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc,
@@ -348,25 +354,14 @@ void RegisteredCache::IterateAllMetadata(
}
}
-std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
- std::vector<RegisteredCacheEntry> out;
- IterateAllMetadata<RegisteredCacheEntry>(
- out,
- [](const CNMT& c, const ContentRecord& r) {
- return RegisteredCacheEntry{c.GetTitleID(), r.type};
- },
- [](const CNMT& c, const ContentRecord& r) { return true; });
- return out;
-}
-
-std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
+std::vector<ContentProviderEntry> RegisteredCache::ListEntriesFilter(
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
std::optional<u64> title_id) const {
- std::vector<RegisteredCacheEntry> out;
- IterateAllMetadata<RegisteredCacheEntry>(
+ std::vector<ContentProviderEntry> out;
+ IterateAllMetadata<ContentProviderEntry>(
out,
[](const CNMT& c, const ContentRecord& r) {
- return RegisteredCacheEntry{c.GetTitleID(), r.type};
+ return ContentProviderEntry{c.GetTitleID(), r.type};
},
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
if (title_type && *title_type != c.GetType())
@@ -521,37 +516,56 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
}) != yuzu_meta.end();
}
-RegisteredCacheUnion::RegisteredCacheUnion(std::vector<RegisteredCache*> caches)
- : caches(std::move(caches)) {}
+ContentProviderUnion::~ContentProviderUnion() = default;
-void RegisteredCacheUnion::Refresh() {
- for (const auto& c : caches)
- c->Refresh();
+void ContentProviderUnion::SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider) {
+ providers[slot] = provider;
}
-bool RegisteredCacheUnion::HasEntry(u64 title_id, ContentRecordType type) const {
- return std::any_of(caches.begin(), caches.end(), [title_id, type](const auto& cache) {
- return cache->HasEntry(title_id, type);
- });
+void ContentProviderUnion::ClearSlot(ContentProviderUnionSlot slot) {
+ providers[slot] = nullptr;
}
-bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const {
- return HasEntry(entry.title_id, entry.type);
+void ContentProviderUnion::Refresh() {
+ for (auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
+
+ provider.second->Refresh();
+ }
}
-std::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
- for (const auto& c : caches) {
- const auto res = c->GetEntryVersion(title_id);
- if (res)
+bool ContentProviderUnion::HasEntry(u64 title_id, ContentRecordType type) const {
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
+
+ if (provider.second->HasEntry(title_id, type))
+ return true;
+ }
+
+ return false;
+}
+
+std::optional<u32> ContentProviderUnion::GetEntryVersion(u64 title_id) const {
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
+
+ const auto res = provider.second->GetEntryVersion(title_id);
+ if (res != std::nullopt)
return res;
}
- return {};
+ return std::nullopt;
}
-VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
- for (const auto& c : caches) {
- const auto res = c->GetEntryUnparsed(title_id, type);
+VirtualFile ContentProviderUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
+
+ const auto res = provider.second->GetEntryUnparsed(title_id, type);
if (res != nullptr)
return res;
}
@@ -559,13 +573,12 @@ VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordTy
return nullptr;
}
-VirtualFile RegisteredCacheUnion::GetEntryUnparsed(RegisteredCacheEntry entry) const {
- return GetEntryUnparsed(entry.title_id, entry.type);
-}
+VirtualFile ContentProviderUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
-VirtualFile RegisteredCacheUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
- for (const auto& c : caches) {
- const auto res = c->GetEntryRaw(title_id, type);
+ const auto res = provider.second->GetEntryRaw(title_id, type);
if (res != nullptr)
return res;
}
@@ -573,30 +586,56 @@ VirtualFile RegisteredCacheUnion::GetEntryRaw(u64 title_id, ContentRecordType ty
return nullptr;
}
-VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const {
- return GetEntryRaw(entry.title_id, entry.type);
-}
+std::unique_ptr<NCA> ContentProviderUnion::GetEntry(u64 title_id, ContentRecordType type) const {
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
-std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
- const auto raw = GetEntryRaw(title_id, type);
- if (raw == nullptr)
- return nullptr;
- return std::make_unique<NCA>(raw);
+ auto res = provider.second->GetEntry(title_id, type);
+ if (res != nullptr)
+ return res;
+ }
+
+ return nullptr;
}
-std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
- return GetEntry(entry.title_id, entry.type);
+std::vector<ContentProviderEntry> ContentProviderUnion::ListEntriesFilter(
+ std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const {
+ std::vector<ContentProviderEntry> out;
+
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
+
+ const auto vec = provider.second->ListEntriesFilter(title_type, record_type, title_id);
+ std::copy(vec.begin(), vec.end(), std::back_inserter(out));
+ }
+
+ std::sort(out.begin(), out.end());
+ out.erase(std::unique(out.begin(), out.end()), out.end());
+ return out;
}
-std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
- std::vector<RegisteredCacheEntry> out;
- for (const auto& c : caches) {
- c->IterateAllMetadata<RegisteredCacheEntry>(
- out,
- [](const CNMT& c, const ContentRecord& r) {
- return RegisteredCacheEntry{c.GetTitleID(), r.type};
- },
- [](const CNMT& c, const ContentRecord& r) { return true; });
+std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>>
+ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnionSlot> origin,
+ std::optional<TitleType> title_type,
+ std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const {
+ std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> out;
+
+ for (const auto& provider : providers) {
+ if (provider.second == nullptr)
+ continue;
+
+ if (origin.has_value() && *origin != provider.first)
+ continue;
+
+ const auto vec = provider.second->ListEntriesFilter(title_type, record_type, title_id);
+ std::transform(vec.begin(), vec.end(), std::back_inserter(out),
+ [&provider](const ContentProviderEntry& entry) {
+ return std::make_pair(provider.first, entry);
+ });
}
std::sort(out.begin(), out.end());
@@ -604,25 +643,61 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
return out;
}
-std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
+ManualContentProvider::~ManualContentProvider() = default;
+
+void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type,
+ u64 title_id, VirtualFile file) {
+ entries.insert_or_assign({title_type, content_type, title_id}, file);
+}
+
+void ManualContentProvider::ClearAllEntries() {
+ entries.clear();
+}
+
+void ManualContentProvider::Refresh() {}
+
+bool ManualContentProvider::HasEntry(u64 title_id, ContentRecordType type) const {
+ return GetEntryRaw(title_id, type) != nullptr;
+}
+
+std::optional<u32> ManualContentProvider::GetEntryVersion(u64 title_id) const {
+ return std::nullopt;
+}
+
+VirtualFile ManualContentProvider::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
+ return GetEntryRaw(title_id, type);
+}
+
+VirtualFile ManualContentProvider::GetEntryRaw(u64 title_id, ContentRecordType type) const {
+ const auto iter =
+ std::find_if(entries.begin(), entries.end(), [title_id, type](const auto& entry) {
+ const auto [title_type, content_type, e_title_id] = entry.first;
+ return content_type == type && e_title_id == title_id;
+ });
+ if (iter == entries.end())
+ return nullptr;
+ return iter->second;
+}
+
+std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecordType type) const {
+ const auto res = GetEntryRaw(title_id, type);
+ if (res == nullptr)
+ return nullptr;
+ return std::make_unique<NCA>(res, nullptr, 0, keys);
+}
+
+std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
std::optional<u64> title_id) const {
- std::vector<RegisteredCacheEntry> out;
- for (const auto& c : caches) {
- c->IterateAllMetadata<RegisteredCacheEntry>(
- out,
- [](const CNMT& c, const ContentRecord& r) {
- return RegisteredCacheEntry{c.GetTitleID(), r.type};
- },
- [&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
- if (title_type && *title_type != c.GetType())
- return false;
- if (record_type && *record_type != r.type)
- return false;
- if (title_id && *title_id != c.GetTitleID())
- return false;
- return true;
- });
+ std::vector<ContentProviderEntry> out;
+
+ for (const auto& entry : entries) {
+ const auto [e_title_type, e_content_type, e_title_id] = entry.first;
+ if ((title_type == std::nullopt || e_title_type == *title_type) &&
+ (record_type == std::nullopt || e_content_type == *record_type) &&
+ (title_id == std::nullopt || e_title_id == *title_id)) {
+ out.emplace_back(ContentProviderEntry{e_title_id, e_content_type});
+ }
}
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;
class XCI;
enum class ContentRecordType : u8;
+enum class NCAContentType : u8;
enum class TitleType : u8;
struct ContentRecord;
using NcaID = std::array<u8, 0x10>;
-using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
+using ContentProviderParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>;
enum class InstallResult {
@@ -36,7 +37,7 @@ enum class InstallResult {
ErrorMetaFailed,
};
-struct RegisteredCacheEntry {
+struct ContentProviderEntry {
u64 title_id;
ContentRecordType type;
@@ -47,12 +48,46 @@ constexpr u64 GetUpdateTitleID(u64 base_title_id) {
return base_title_id | 0x800;
}
+ContentRecordType GetCRTypeFromNCAType(NCAContentType type);
+
// boost flat_map requires operator< for O(log(n)) lookups.
-bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
+bool operator<(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
// std unique requires operator== to identify duplicates.
-bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
-bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
+bool operator==(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
+bool operator!=(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
+
+class ContentProvider {
+public:
+ virtual ~ContentProvider();
+
+ virtual void Refresh() = 0;
+
+ virtual bool HasEntry(u64 title_id, ContentRecordType type) const = 0;
+ virtual bool HasEntry(ContentProviderEntry entry) const;
+
+ virtual std::optional<u32> GetEntryVersion(u64 title_id) const = 0;
+
+ virtual VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const = 0;
+ virtual VirtualFile GetEntryUnparsed(ContentProviderEntry entry) const;
+
+ virtual VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const = 0;
+ virtual VirtualFile GetEntryRaw(ContentProviderEntry entry) const;
+
+ virtual std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const = 0;
+ virtual std::unique_ptr<NCA> GetEntry(ContentProviderEntry entry) const;
+
+ virtual std::vector<ContentProviderEntry> ListEntries() const;
+
+ // If a parameter is not std::nullopt, it will be filtered for from all entries.
+ virtual std::vector<ContentProviderEntry> ListEntriesFilter(
+ std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
+ std::optional<u64> title_id = {}) const = 0;
+
+protected:
+ // A single instance of KeyManager to be used by GetEntry()
+ Core::Crypto::KeyManager keys;
+};
/*
* A class that catalogues NCAs in the registered directory structure.
@@ -67,39 +102,32 @@ bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs
* (This impl also supports substituting the nca dir for an nca file, as that's more convenient
* when 4GB splitting can be ignored.)
*/
-class RegisteredCache {
- friend class RegisteredCacheUnion;
-
+class RegisteredCache : public ContentProvider {
public:
// Parsing function defines the conversion from raw file to NCA. If there are other steps
// besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
// parsing function.
explicit RegisteredCache(VirtualDir dir,
- RegisteredCacheParsingFunction parsing_function =
+ ContentProviderParsingFunction parsing_function =
[](const VirtualFile& file, const NcaID& id) { return file; });
- ~RegisteredCache();
+ ~RegisteredCache() override;
- void Refresh();
+ void Refresh() override;
- bool HasEntry(u64 title_id, ContentRecordType type) const;
- bool HasEntry(RegisteredCacheEntry entry) const;
+ bool HasEntry(u64 title_id, ContentRecordType type) const override;
- std::optional<u32> GetEntryVersion(u64 title_id) const;
+ std::optional<u32> GetEntryVersion(u64 title_id) const override;
- VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
- VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
+ VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
- VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
- VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
+ VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
- std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
- std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
+ std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
- std::vector<RegisteredCacheEntry> ListEntries() const;
// If a parameter is not std::nullopt, it will be filtered for from all entries.
- std::vector<RegisteredCacheEntry> ListEntriesFilter(
+ std::vector<ContentProviderEntry> ListEntriesFilter(
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
- std::optional<u64> title_id = {}) const;
+ std::optional<u64> title_id = {}) const override;
// Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
// there is a meta NCA and all of them are accessible.
@@ -131,46 +159,70 @@ private:
bool RawInstallYuzuMeta(const CNMT& cnmt);
VirtualDir dir;
- RegisteredCacheParsingFunction parser;
- Core::Crypto::KeyManager keys;
+ ContentProviderParsingFunction parser;
// maps tid -> NcaID of meta
- boost::container::flat_map<u64, NcaID> meta_id;
+ std::map<u64, NcaID> meta_id;
// maps tid -> meta
- boost::container::flat_map<u64, CNMT> meta;
+ std::map<u64, CNMT> meta;
// maps tid -> meta for CNMT in yuzu_meta
- boost::container::flat_map<u64, CNMT> yuzu_meta;
+ std::map<u64, CNMT> yuzu_meta;
};
-// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface.
-class RegisteredCacheUnion {
-public:
- explicit RegisteredCacheUnion(std::vector<RegisteredCache*> caches);
-
- void Refresh();
-
- bool HasEntry(u64 title_id, ContentRecordType type) const;
- bool HasEntry(RegisteredCacheEntry entry) const;
-
- std::optional<u32> GetEntryVersion(u64 title_id) const;
-
- VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
- VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
-
- VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
- VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
-
- std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
- std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
+enum class ContentProviderUnionSlot {
+ SysNAND, ///< System NAND
+ UserNAND, ///< User NAND
+ SDMC, ///< SD Card
+ FrontendManual, ///< Frontend-defined game list or similar
+};
- std::vector<RegisteredCacheEntry> ListEntries() const;
- // If a parameter is not std::nullopt, it will be filtered for from all entries.
- std::vector<RegisteredCacheEntry> ListEntriesFilter(
+// Combines multiple ContentProvider(s) (i.e. SysNAND, UserNAND, SDMC) into one interface.
+class ContentProviderUnion : public ContentProvider {
+public:
+ ~ContentProviderUnion() override;
+
+ void SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider);
+ void ClearSlot(ContentProviderUnionSlot slot);
+
+ void Refresh() override;
+ bool HasEntry(u64 title_id, ContentRecordType type) const override;
+ std::optional<u32> GetEntryVersion(u64 title_id) const override;
+ VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
+ VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
+ std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
+ std::vector<ContentProviderEntry> ListEntriesFilter(
+ std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const override;
+
+ std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> ListEntriesFilterOrigin(
+ std::optional<ContentProviderUnionSlot> origin = {},
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
std::optional<u64> title_id = {}) const;
private:
- std::vector<RegisteredCache*> caches;
+ std::map<ContentProviderUnionSlot, ContentProvider*> providers;
+};
+
+class ManualContentProvider : public ContentProvider {
+public:
+ ~ManualContentProvider() override;
+
+ void AddEntry(TitleType title_type, ContentRecordType content_type, u64 title_id,
+ VirtualFile file);
+ void ClearAllEntries();
+
+ void Refresh() override;
+ bool HasEntry(u64 title_id, ContentRecordType type) const override;
+ std::optional<u32> GetEntryVersion(u64 title_id) const override;
+ VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
+ VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
+ std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
+ std::vector<ContentProviderEntry> ListEntriesFilter(
+ std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const override;
+
+private:
+ std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
};
} // 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
switch (storage) {
case StorageId::None:
- res = Service::FileSystem::GetUnionContents().GetEntry(title_id, type);
+ res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type);
break;
case StorageId::NandSystem:
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 {
return out;
}
-std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> NSP::GetNCAs() const {
+std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>>
+NSP::GetNCAs() const {
return ncas;
}
-std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const {
+std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type, TitleType title_type) const {
if (extracted)
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
@@ -155,14 +156,14 @@ std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const {
if (title_id_iter == ncas.end())
return nullptr;
- const auto type_iter = title_id_iter->second.find(type);
+ const auto type_iter = title_id_iter->second.find({title_type, type});
if (type_iter == title_id_iter->second.end())
return nullptr;
return type_iter->second;
}
-VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type) const {
+VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type, TitleType title_type) const {
if (extracted)
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
const auto nca = GetNCA(title_id, type);
@@ -240,7 +241,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
const CNMT cnmt(inner_file);
auto& ncas_title = ncas[cnmt.GetTitleID()];
- ncas_title[ContentRecordType::Meta] = nca;
+ ncas_title[{cnmt.GetType(), ContentRecordType::Meta}] = nca;
for (const auto& rec : cnmt.GetContentRecords()) {
const auto id_string = Common::HexArrayToString(rec.nca_id, false);
const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
@@ -258,7 +259,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
(next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
(cnmt.GetTitleID() & 0x800) != 0)) {
- ncas_title[rec.type] = std::move(next_nca);
+ ncas_title[{cnmt.GetType(), rec.type}] = std::move(next_nca);
}
}
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:
// Type 0 Only (Collection of NCAs + Certificate + Ticket + Meta XML)
std::vector<std::shared_ptr<NCA>> GetNCAsCollapsed() const;
std::multimap<u64, std::shared_ptr<NCA>> GetNCAsByTitleID() const;
- std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> GetNCAs() const;
- std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type) const;
- VirtualFile GetNCAFile(u64 title_id, ContentRecordType type) const;
+ std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> GetNCAs()
+ const;
+ std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type,
+ TitleType title_type = TitleType::Application) const;
+ VirtualFile GetNCAFile(u64 title_id, ContentRecordType type,
+ TitleType title_type = TitleType::Application) const;
std::vector<Core::Crypto::Key128> GetTitlekey() const;
std::vector<VirtualFile> GetFiles() const override;
@@ -67,7 +70,7 @@ private:
std::shared_ptr<PartitionFilesystem> pfs;
// Map title id -> {map type -> NCA}
- std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> ncas;
+ std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas;
std::vector<VirtualFile> ticket_files;
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:
context->AddDomainObject(std::move(iface));
} else {
auto& kernel = Core::System::GetInstance().Kernel();
- auto sessions =
+ auto [server, client] =
Kernel::ServerSession::CreateSessionPair(kernel, iface->GetServiceName());
- auto server = std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions);
- auto client = std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions);
iface->ClientConnected(server);
context->AddMoveObject(std::move(client));
}
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 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <tuple>
-
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/errors.h"
@@ -31,18 +29,18 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
active_sessions++;
// Create a new session pair, let the created sessions inherit the parent port's HLE handler.
- auto sessions = ServerSession::CreateSessionPair(kernel, server_port->GetName(), this);
+ auto [server, client] = ServerSession::CreateSessionPair(kernel, server_port->GetName(), this);
if (server_port->HasHLEHandler()) {
- server_port->GetHLEHandler()->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
+ server_port->GetHLEHandler()->ClientConnected(server);
} else {
- server_port->AppendPendingSession(std::get<SharedPtr<ServerSession>>(sessions));
+ server_port->AppendPendingSession(server);
}
// Wake the threads waiting on the ServerPort
server_port->WakeupAllWaitingThreads();
- return MakeResult(std::get<SharedPtr<ClientSession>>(sessions));
+ return MakeResult(client);
}
void 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 @@
#include "core/hle/kernel/thread.h"
#include "core/hle/lock.h"
#include "core/hle/result.h"
+#include "core/memory.h"
namespace Kernel {
@@ -181,6 +182,7 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) {
void KernelCore::MakeCurrentProcess(Process* process) {
impl->current_process = process;
+ Memory::SetCurrentPageTable(&process->VMManager().page_table);
}
Process* 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 {
* @param priority The priority to give the main thread
*/
void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_point, u32 priority) {
- // Setup page table so we can write to memory
- Memory::SetCurrentPageTable(&owner_process.VMManager().page_table);
-
// Initialize new "main" thread
const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress();
auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0,
@@ -109,6 +106,8 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
is_64bit_process = metadata.Is64BitProgram();
vm_manager.Reset(metadata.GetAddressSpaceType());
+ // Ensure that the potentially resized page table is seen by CPU backends.
+ Memory::SetCurrentPageTable(&vm_manager.page_table);
const auto& caps = metadata.GetKernelCapabilities();
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) {
auto* const thread_owner_process = current_thread->GetOwnerProcess();
if (previous_process != thread_owner_process) {
system.Kernel().MakeCurrentProcess(thread_owner_process);
- Memory::SetCurrentPageTable(&thread_owner_process->VMManager().page_table);
}
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) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
}
-std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair(
- KernelCore& kernel, u32 max_sessions, std::string name) {
-
+ServerPort::PortPair ServerPort::CreatePortPair(KernelCore& kernel, u32 max_sessions,
+ std::string name) {
SharedPtr<ServerPort> server_port(new ServerPort(kernel));
SharedPtr<ClientPort> client_port(new ClientPort(kernel));
@@ -51,7 +50,7 @@ std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortP
client_port->max_sessions = max_sessions;
client_port->active_sessions = 0;
- return std::make_tuple(std::move(server_port), std::move(client_port));
+ return std::make_pair(std::move(server_port), std::move(client_port));
}
} // 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 @@
#include <memory>
#include <string>
-#include <tuple>
+#include <utility>
#include <vector>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
@@ -23,6 +23,7 @@ class SessionRequestHandler;
class ServerPort final : public WaitObject {
public:
using HLEHandler = std::shared_ptr<SessionRequestHandler>;
+ using PortPair = std::pair<SharedPtr<ServerPort>, SharedPtr<ClientPort>>;
/**
* Creates a pair of ServerPort and an associated ClientPort.
@@ -32,8 +33,8 @@ public:
* @param name Optional name of the ports
* @return The created port tuple
*/
- static std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> CreatePortPair(
- KernelCore& kernel, u32 max_sessions, std::string name = "UnknownPort");
+ static PortPair CreatePortPair(KernelCore& kernel, u32 max_sessions,
+ std::string name = "UnknownPort");
std::string GetTypeName() const override {
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,
client_session->parent = parent;
server_session->parent = parent;
- return std::make_tuple(std::move(server_session), std::move(client_session));
+ return std::make_pair(std::move(server_session), std::move(client_session));
}
} // 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 @@
#include <memory>
#include <string>
+#include <utility>
#include <vector>
#include "core/hle/kernel/object.h"
@@ -58,7 +59,7 @@ public:
return parent.get();
}
- using SessionPair = std::tuple<SharedPtr<ServerSession>, SharedPtr<ClientSession>>;
+ using SessionPair = std::pair<SharedPtr<ServerSession>, SharedPtr<ClientSession>>;
/**
* 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() {
if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success)
return out;
- const auto& installed{FileSystem::GetUnionContents()};
+ const auto& installed{Core::System::GetInstance().GetContentProvider()};
const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(),
FileSys::ContentRecordType::Manual);
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) {
static std::vector<u64> AccumulateAOCTitleIDs() {
std::vector<u64> add_on_content;
- const auto rcu = FileSystem::GetUnionContents();
+ const auto& rcu = Core::System::GetInstance().GetContentProvider();
const auto list =
rcu.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
std::transform(list.begin(), list.end(), std::back_inserter(add_on_content),
- [](const FileSys::RegisteredCacheEntry& rce) { return rce.title_id; });
+ [](const FileSys::ContentProviderEntry& rce) { return rce.title_id; });
add_on_content.erase(
std::remove_if(
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,
save_data_factory->WriteSaveDataSize(type, title_id, user_id, new_value);
}
-FileSys::RegisteredCacheUnion GetUnionContents() {
- return FileSys::RegisteredCacheUnion{
- {GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()}};
-}
-
FileSys::RegisteredCache* GetSystemNANDContents() {
LOG_TRACE(Service_FS, "Opening System NAND Contents");
@@ -460,6 +455,10 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
if (bis_factory == nullptr) {
bis_factory =
std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory);
+ Core::System::GetInstance().RegisterContentProvider(
+ FileSys::ContentProviderUnionSlot::SysNAND, bis_factory->GetSystemNANDContents());
+ Core::System::GetInstance().RegisterContentProvider(
+ FileSys::ContentProviderUnionSlot::UserNAND, bis_factory->GetUserNANDContents());
}
if (save_data_factory == nullptr) {
@@ -468,6 +467,8 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
if (sdmc_factory == nullptr) {
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
+ Core::System::GetInstance().RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
+ sdmc_factory->GetSDMCContents());
}
}
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,
void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
FileSys::SaveDataSize new_value);
-FileSys::RegisteredCacheUnion GetUnionContents();
-
FileSys::RegisteredCache* GetSystemNANDContents();
FileSys::RegisteredCache* GetUserNANDContents();
FileSys::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 @@
#include "core/memory.h"
#include "core/settings.h"
+#pragma optimize("", off)
+
namespace Loader {
namespace {
struct MODHeader {
@@ -136,13 +138,13 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
// Apply patches if necessary
if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
- std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
+ std::vector<u8> pi_header;
pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header),
reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader));
pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(),
program_image.end());
- pi_header = pm->PatchNSO(pi_header);
+ pi_header = pm->PatchNSO(pi_header, file.GetName());
std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin());
}
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) {
}
}
-Common::PageTable* GetCurrentPageTable() {
- return current_page_table;
-}
-
static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* memory,
Common::PageType type) {
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;
/// Virtual user-space memory regions
enum : VAddr {
- /// Read-only page containing kernel and system configuration values.
- CONFIG_MEMORY_VADDR = 0x1FF80000,
- CONFIG_MEMORY_SIZE = 0x00001000,
- CONFIG_MEMORY_VADDR_END = CONFIG_MEMORY_VADDR + CONFIG_MEMORY_SIZE,
-
- /// Usually read-only page containing mostly values read from hardware.
- SHARED_PAGE_VADDR = 0x1FF81000,
- SHARED_PAGE_SIZE = 0x00001000,
- SHARED_PAGE_VADDR_END = SHARED_PAGE_VADDR + SHARED_PAGE_SIZE,
-
/// TLS (Thread-Local Storage) related.
TLS_ENTRY_SIZE = 0x200,
@@ -50,9 +40,8 @@ enum : VAddr {
KERNEL_REGION_END = KERNEL_REGION_VADDR + KERNEL_REGION_SIZE,
};
-/// Currently active page table
+/// Changes the currently active page table.
void SetCurrentPageTable(Common::PageTable* page_table);
-Common::PageTable* GetCurrentPageTable();
/// Determines if the given VAddr is valid for the specified process.
bool 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_)
: mutable_memory(mutable_memory_),
test_memory(std::make_shared<TestMemory>(this)), kernel{Core::System::GetInstance()} {
auto process = Kernel::Process::Create(Core::System::GetInstance(), "");
- kernel.MakeCurrentProcess(process.get());
page_table = &process->VMManager().page_table;
std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr);
@@ -28,7 +27,7 @@ TestEnvironment::TestEnvironment(bool mutable_memory_)
Memory::MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory);
Memory::MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory);
- Memory::SetCurrentPageTable(page_table);
+ kernel.MakeCurrentProcess(process.get());
}
TestEnvironment::~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() {
const u32 sync_point = regs.sync_info.sync_point.Value();
const u32 increment = regs.sync_info.increment.Value();
const u32 cache_flush = regs.sync_info.unknown.Value();
- UNIMPLEMENTED_MSG("Syncpoint Set {}, increment: {}, unk: {}", sync_point, increment,
- cache_flush);
+ LOG_DEBUG(HW_GPU, "Syncpoint set {}, increment: {}, unk: {}", sync_point, increment,
+ cache_flush);
}
void 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 {
union {
BitField<20, 16, u64> imm20_16;
+ BitField<35, 1, u64> high_b_rr; // used on RR
BitField<36, 1, u64> product_shift_left;
BitField<37, 1, u64> merge_37;
BitField<48, 1, u64> sign_a;
BitField<49, 1, u64> sign_b;
+ BitField<50, 2, XmadMode> mode_cbf; // used by CR, RC
BitField<50, 3, XmadMode> mode;
BitField<52, 1, u64> high_b;
BitField<53, 1, u64> high_a;
+ BitField<55, 1, u64> product_shift_left_second; // used on CR
BitField<56, 1, u64> merge_56;
} xmad;
@@ -1662,7 +1665,7 @@ private:
INST("0011011-11110---", Id::BFI_IMM_R, Type::Bfi, "BFI_IMM_R"),
INST("0100110001000---", Id::LOP_C, Type::ArithmeticInteger, "LOP_C"),
INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"),
- INST("0011100001000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"),
+ INST("0011100-01000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"),
INST("000001----------", Id::LOP32I, Type::ArithmeticIntegerImmediate, "LOP32I"),
INST("0000001---------", Id::LOP3_C, Type::ArithmeticInteger, "LOP3_C"),
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) {
GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer) : renderer{renderer} {
auto& rasterizer{renderer.Rasterizer()};
- memory_manager = std::make_unique<Tegra::MemoryManager>();
+ memory_manager = std::make_unique<Tegra::MemoryManager>(rasterizer);
dma_pusher = std::make_unique<Tegra::DmaPusher>(*this);
maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, rasterizer, *memory_manager);
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 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
-#include "core/core.h"
#include "core/memory.h"
-#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
-#include "video_core/renderer_base.h"
namespace Tegra {
-MemoryManager::MemoryManager() {
+MemoryManager::MemoryManager(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {
std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr);
std::fill(page_table.attributes.begin(), page_table.attributes.end(),
Common::PageType::Unmapped);
@@ -70,8 +67,7 @@ GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
const u64 aligned_size{Common::AlignUp(size, page_size)};
const CacheAddr cache_addr{ToCacheAddr(GetPointer(gpu_addr))};
- Core::System::GetInstance().Renderer().Rasterizer().FlushAndInvalidateRegion(cache_addr,
- aligned_size);
+ rasterizer.FlushAndInvalidateRegion(cache_addr, aligned_size);
UnmapRange(gpu_addr, aligned_size);
return gpu_addr;
@@ -204,14 +200,85 @@ const u8* MemoryManager::GetPointer(GPUVAddr addr) const {
}
void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size) const {
- std::memcpy(dest_buffer, GetPointer(src_addr), size);
+ std::size_t remaining_size{size};
+ std::size_t page_index{src_addr >> page_bits};
+ std::size_t page_offset{src_addr & page_mask};
+
+ while (remaining_size > 0) {
+ const std::size_t copy_amount{
+ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
+
+ switch (page_table.attributes[page_index]) {
+ case Common::PageType::Memory: {
+ const u8* src_ptr{page_table.pointers[page_index] + page_offset};
+ rasterizer.FlushRegion(ToCacheAddr(src_ptr), copy_amount);
+ std::memcpy(dest_buffer, src_ptr, copy_amount);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ page_index++;
+ page_offset = 0;
+ dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
+ remaining_size -= copy_amount;
+ }
}
+
void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size) {
- std::memcpy(GetPointer(dest_addr), src_buffer, size);
+ std::size_t remaining_size{size};
+ std::size_t page_index{dest_addr >> page_bits};
+ std::size_t page_offset{dest_addr & page_mask};
+
+ while (remaining_size > 0) {
+ const std::size_t copy_amount{
+ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
+
+ switch (page_table.attributes[page_index]) {
+ case Common::PageType::Memory: {
+ u8* dest_ptr{page_table.pointers[page_index] + page_offset};
+ rasterizer.InvalidateRegion(ToCacheAddr(dest_ptr), copy_amount);
+ std::memcpy(dest_ptr, src_buffer, copy_amount);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ page_index++;
+ page_offset = 0;
+ src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
+ remaining_size -= copy_amount;
+ }
}
void MemoryManager::CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size) {
- std::memcpy(GetPointer(dest_addr), GetPointer(src_addr), size);
+ std::size_t remaining_size{size};
+ std::size_t page_index{src_addr >> page_bits};
+ std::size_t page_offset{src_addr & page_mask};
+
+ while (remaining_size > 0) {
+ const std::size_t copy_amount{
+ std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
+
+ switch (page_table.attributes[page_index]) {
+ case Common::PageType::Memory: {
+ const u8* src_ptr{page_table.pointers[page_index] + page_offset};
+ rasterizer.FlushRegion(ToCacheAddr(src_ptr), copy_amount);
+ WriteBlock(dest_addr, src_ptr, copy_amount);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ page_index++;
+ page_offset = 0;
+ dest_addr += static_cast<VAddr>(copy_amount);
+ src_addr += static_cast<VAddr>(copy_amount);
+ remaining_size -= copy_amount;
+ }
}
void MemoryManager::MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type,
@@ -351,7 +418,7 @@ MemoryManager::VMAIter MemoryManager::CarveVMA(GPUVAddr base, u64 size) {
const VirtualMemoryArea& vma{vma_handle->second};
if (vma.type == VirtualMemoryArea::Type::Mapped) {
// Region is already allocated
- return {};
+ return vma_handle;
}
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 @@
#include "common/common_types.h"
#include "common/page_table.h"
+namespace VideoCore {
+class RasterizerInterface;
+}
+
namespace Tegra {
/**
@@ -43,7 +47,7 @@ struct VirtualMemoryArea {
class MemoryManager final {
public:
- MemoryManager();
+ MemoryManager(VideoCore::RasterizerInterface& rasterizer);
GPUVAddr AllocateSpace(u64 size, u64 align);
GPUVAddr AllocateSpace(GPUVAddr addr, u64 size, u64 align);
@@ -144,6 +148,7 @@ private:
Common::PageTable page_table{page_bits};
VMAMap vma_map;
+ VideoCore::RasterizerInterface& rasterizer;
};
} // 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() {
gl_buffer[0].resize(GetSizeInBytes());
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
- // Ensure no bad interactions with GL_UNPACK_ALIGNMENT
- ASSERT(params.width * GetBytesPerPixel(params.pixel_format) % 4 == 0);
+ const u32 align = std::clamp(params.RowAlign(0), 1U, 8U);
+ glPixelStorei(GL_PACK_ALIGNMENT, align);
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width));
ASSERT(!tuple.compressed);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
@@ -708,8 +708,8 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
- // Ensure no bad interactions with GL_UNPACK_ALIGNMENT
- ASSERT(params.MipWidth(mip_map) * GetBytesPerPixel(params.pixel_format) % 4 == 0);
+ const u32 align = std::clamp(params.RowAlign(mip_map), 1U, 8U);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, align);
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.MipWidth(mip_map)));
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 @@
#include <vector>
#include "common/alignment.h"
+#include "common/bit_util.h"
#include "common/common_types.h"
#include "common/hash.h"
#include "common/math_util.h"
@@ -205,6 +206,13 @@ struct SurfaceParams {
return bd;
}
+ u32 RowAlign(u32 mip_level) const {
+ const u32 m_width = MipWidth(mip_level);
+ const u32 bytes_per_pixel = GetBytesPerPixel(pixel_format);
+ const u32 l2 = Common::CountTrailingZeroes32(m_width * bytes_per_pixel);
+ return (1U << l2);
+ }
+
/// Creates SurfaceParams from a texture configuration
static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config,
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) {
/// Gets the shader program code from memory for the specified address
ProgramCode GetShaderCode(const u8* host_ptr) {
ProgramCode program_code(VideoCommon::Shader::MAX_PROGRAM_LENGTH);
+ ASSERT_OR_EXECUTE(host_ptr != nullptr, {
+ std::fill(program_code.begin(), program_code.end(), 0);
+ return program_code;
+ });
std::memcpy(program_code.data(), host_ptr, program_code.size() * sizeof(u64));
return program_code;
}
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 @@
namespace OpenGL::GLShader {
+namespace {
+
using Tegra::Shader::Attribute;
using Tegra::Shader::AttributeUse;
using Tegra::Shader::Header;
@@ -34,14 +36,18 @@ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
using ShaderStage = Tegra::Engines::Maxwell3D::Regs::ShaderStage;
using Operation = const OperationNode&;
+enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat };
+
+struct TextureAoffi {};
+using TextureArgument = std::pair<Type, Node>;
+using TextureIR = std::variant<TextureAoffi, TextureArgument>;
+
enum : u32 { POSITION_VARYING_LOCATION = 0, GENERIC_VARYING_START_LOCATION = 1 };
constexpr u32 MAX_CONSTBUFFER_ELEMENTS =
static_cast<u32>(RasterizerOpenGL::MaxConstbufferSize) / (4 * sizeof(float));
constexpr u32 MAX_GLOBALMEMORY_ELEMENTS =
static_cast<u32>(RasterizerOpenGL::MaxGlobalMemorySize) / sizeof(float);
-enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat };
-
class ShaderWriter {
public:
void AddExpression(std::string_view text) {
@@ -91,7 +97,7 @@ private:
};
/// Generates code to use for a swizzle operation.
-static std::string GetSwizzle(u32 elem) {
+std::string GetSwizzle(u32 elem) {
ASSERT(elem <= 3);
std::string swizzle = ".";
swizzle += "xyzw"[elem];
@@ -99,7 +105,7 @@ static std::string GetSwizzle(u32 elem) {
}
/// Translate topology
-static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
+std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
switch (topology) {
case Tegra::Shader::OutputTopology::PointList:
return "points";
@@ -114,7 +120,7 @@ static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
}
/// Returns true if an object has to be treated as precise
-static bool IsPrecise(Operation operand) {
+bool IsPrecise(Operation operand) {
const auto& meta = operand.GetMeta();
if (const auto arithmetic = std::get_if<MetaArithmetic>(&meta)) {
@@ -126,7 +132,7 @@ static bool IsPrecise(Operation operand) {
return false;
}
-static bool IsPrecise(Node node) {
+bool IsPrecise(Node node) {
if (const auto operation = std::get_if<OperationNode>(node)) {
return IsPrecise(*operation);
}
@@ -723,8 +729,8 @@ private:
result_type));
}
- std::string GenerateTexture(Operation operation, const std::string& func,
- const std::vector<std::pair<Type, Node>>& extras) {
+ std::string GenerateTexture(Operation operation, const std::string& function_suffix,
+ const std::vector<TextureIR>& extras) {
constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"};
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
@@ -734,11 +740,11 @@ private:
const bool has_array = meta->sampler.IsArray();
const bool has_shadow = meta->sampler.IsShadow();
- std::string expr = func;
- expr += '(';
- expr += GetSampler(meta->sampler);
- expr += ", ";
-
+ std::string expr = "texture" + function_suffix;
+ if (!meta->aoffi.empty()) {
+ expr += "Offset";
+ }
+ expr += '(' + GetSampler(meta->sampler) + ", ";
expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1);
expr += '(';
for (std::size_t i = 0; i < count; ++i) {
@@ -756,36 +762,74 @@ private:
}
expr += ')';
- for (const auto& extra_pair : extras) {
- const auto [type, operand] = extra_pair;
- if (operand == nullptr) {
- continue;
+ for (const auto& variant : extras) {
+ if (const auto argument = std::get_if<TextureArgument>(&variant)) {
+ expr += GenerateTextureArgument(*argument);
+ } else if (std::get_if<TextureAoffi>(&variant)) {
+ expr += GenerateTextureAoffi(meta->aoffi);
+ } else {
+ UNREACHABLE();
}
- expr += ", ";
+ }
- switch (type) {
- case Type::Int:
- if (const auto immediate = std::get_if<ImmediateNode>(operand)) {
- // Inline the string as an immediate integer in GLSL (some extra arguments are
- // required to be constant)
- expr += std::to_string(static_cast<s32>(immediate->GetValue()));
- } else {
- expr += "ftoi(" + Visit(operand) + ')';
- }
- break;
- case Type::Float:
- expr += Visit(operand);
- break;
- default: {
- const auto type_int = static_cast<u32>(type);
- UNIMPLEMENTED_MSG("Unimplemented extra type={}", type_int);
- expr += '0';
- break;
+ return expr + ')';
+ }
+
+ std::string GenerateTextureArgument(TextureArgument argument) {
+ const auto [type, operand] = argument;
+ if (operand == nullptr) {
+ return {};
+ }
+
+ std::string expr = ", ";
+ switch (type) {
+ case Type::Int:
+ if (const auto immediate = std::get_if<ImmediateNode>(operand)) {
+ // Inline the string as an immediate integer in GLSL (some extra arguments are
+ // required to be constant)
+ expr += std::to_string(static_cast<s32>(immediate->GetValue()));
+ } else {
+ expr += "ftoi(" + Visit(operand) + ')';
+ }
+ break;
+ case Type::Float:
+ expr += Visit(operand);
+ break;
+ default: {
+ const auto type_int = static_cast<u32>(type);
+ UNIMPLEMENTED_MSG("Unimplemented extra type={}", type_int);
+ expr += '0';
+ break;
+ }
+ }
+ return expr;
+ }
+
+ std::string GenerateTextureAoffi(const std::vector<Node>& aoffi) {
+ if (aoffi.empty()) {
+ return {};
+ }
+ constexpr std::array<const char*, 3> coord_constructors = {"int", "ivec2", "ivec3"};
+ std::string expr = ", ";
+ expr += coord_constructors.at(aoffi.size() - 1);
+ expr += '(';
+
+ for (std::size_t index = 0; index < aoffi.size(); ++index) {
+ const auto operand{aoffi.at(index)};
+ if (const auto immediate = std::get_if<ImmediateNode>(operand)) {
+ // Inline the string as an immediate integer in GLSL (AOFFI arguments are required
+ // to be constant by the standard).
+ expr += std::to_string(static_cast<s32>(immediate->GetValue()));
+ } else {
+ expr += "ftoi(" + Visit(operand) + ')';
}
+ if (index + 1 < aoffi.size()) {
+ expr += ", ";
}
}
+ expr += ')';
- return expr + ')';
+ return expr;
}
std::string Assign(Operation operation) {
@@ -1164,7 +1208,8 @@ private:
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
ASSERT(meta);
- std::string expr = GenerateTexture(operation, "texture", {{Type::Float, meta->bias}});
+ std::string expr = GenerateTexture(
+ operation, "", {TextureAoffi{}, TextureArgument{Type::Float, meta->bias}});
if (meta->sampler.IsShadow()) {
expr = "vec4(" + expr + ')';
}
@@ -1175,7 +1220,8 @@ private:
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
ASSERT(meta);
- std::string expr = GenerateTexture(operation, "textureLod", {{Type::Float, meta->lod}});
+ std::string expr = GenerateTexture(
+ operation, "Lod", {TextureArgument{Type::Float, meta->lod}, TextureAoffi{}});
if (meta->sampler.IsShadow()) {
expr = "vec4(" + expr + ')';
}
@@ -1187,7 +1233,8 @@ private:
ASSERT(meta);
const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int;
- return GenerateTexture(operation, "textureGather", {{type, meta->component}}) +
+ return GenerateTexture(operation, "Gather",
+ {TextureArgument{type, meta->component}, TextureAoffi{}}) +
GetSwizzle(meta->element);
}
@@ -1217,8 +1264,8 @@ private:
ASSERT(meta);
if (meta->element < 2) {
- return "itof(int((" + GenerateTexture(operation, "textureQueryLod", {}) +
- " * vec2(256))" + GetSwizzle(meta->element) + "))";
+ return "itof(int((" + GenerateTexture(operation, "QueryLod", {}) + " * vec2(256))" +
+ GetSwizzle(meta->element) + "))";
}
return "0";
}
@@ -1571,6 +1618,8 @@ private:
ShaderWriter code;
};
+} // Anonymous namespace
+
std::string GetCommonDeclarations() {
const auto cbuf = std::to_string(MAX_CONSTBUFFER_ELEMENTS);
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 @@
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/logging/log.h"
-#include "common/lz4_compression.h"
#include "common/scm_rev.h"
+#include "common/zstd_compression.h"
#include "core/core.h"
#include "core/hle/kernel/process.h"
@@ -259,7 +259,7 @@ ShaderDiskCacheOpenGL::LoadPrecompiledFile(FileUtil::IOFile& file) {
return {};
}
- dump.binary = Common::Compression::DecompressDataLZ4(compressed_binary, binary_length);
+ dump.binary = Common::Compression::DecompressDataZSTD(compressed_binary);
if (dump.binary.empty()) {
return {};
}
@@ -288,7 +288,7 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
return {};
}
- const std::vector<u8> code = Common::Compression::DecompressDataLZ4(compressed_code, code_size);
+ const std::vector<u8> code = Common::Compression::DecompressDataZSTD(compressed_code);
if (code.empty()) {
return {};
}
@@ -474,8 +474,8 @@ void ShaderDiskCacheOpenGL::SaveDecompiled(u64 unique_identifier, const std::str
if (!IsUsable())
return;
- const std::vector<u8> compressed_code{Common::Compression::CompressDataLZ4HC(
- reinterpret_cast<const u8*>(code.data()), code.size(), 9)};
+ const std::vector<u8> compressed_code{Common::Compression::CompressDataZSTDDefault(
+ reinterpret_cast<const u8*>(code.data()), code.size())};
if (compressed_code.empty()) {
LOG_ERROR(Render_OpenGL, "Failed to compress GLSL code - skipping shader {:016x}",
unique_identifier);
@@ -506,7 +506,7 @@ void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint p
glGetProgramBinary(program, binary_length, nullptr, &binary_format, binary.data());
const std::vector<u8> compressed_binary =
- Common::Compression::CompressDataLZ4HC(binary.data(), binary.size(), 9);
+ Common::Compression::CompressDataZSTDDefault(binary.data(), binary.size());
if (compressed_binary.empty()) {
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:
UpdatePipeline();
state.draw.shader_program = 0;
state.draw.program_pipeline = pipeline.handle;
- state.geometry_shaders.enabled = (gs != 0);
}
private:
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 @@
namespace OpenGL {
-OpenGLState OpenGLState::cur_state;
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+OpenGLState OpenGLState::cur_state;
bool OpenGLState::s_rgb_used;
+namespace {
+
+template <typename T>
+bool UpdateValue(T& current_value, const T new_value) {
+ const bool changed = current_value != new_value;
+ current_value = new_value;
+ return changed;
+}
+
+template <typename T1, typename T2>
+bool UpdateTie(T1 current_value, const T2 new_value) {
+ const bool changed = current_value != new_value;
+ current_value = new_value;
+ return changed;
+}
+
+void Enable(GLenum cap, bool enable) {
+ if (enable) {
+ glEnable(cap);
+ } else {
+ glDisable(cap);
+ }
+}
+
+void Enable(GLenum cap, GLuint index, bool enable) {
+ if (enable) {
+ glEnablei(cap, index);
+ } else {
+ glDisablei(cap, index);
+ }
+}
+
+void Enable(GLenum cap, bool& current_value, bool new_value) {
+ if (UpdateValue(current_value, new_value))
+ Enable(cap, new_value);
+}
+
+void Enable(GLenum cap, GLuint index, bool& current_value, bool new_value) {
+ if (UpdateValue(current_value, new_value))
+ Enable(cap, index, new_value);
+}
+
+} // namespace
+
OpenGLState::OpenGLState() {
// These all match default OpenGL values
- geometry_shaders.enabled = false;
framebuffer_srgb.enabled = false;
+
multisample_control.alpha_to_coverage = false;
multisample_control.alpha_to_one = false;
+
cull.enabled = false;
cull.mode = GL_BACK;
cull.front_face = GL_CCW;
@@ -30,14 +76,15 @@ OpenGLState::OpenGLState() {
primitive_restart.enabled = false;
primitive_restart.index = 0;
+
for (auto& item : color_mask) {
item.red_enabled = GL_TRUE;
item.green_enabled = GL_TRUE;
item.blue_enabled = GL_TRUE;
item.alpha_enabled = GL_TRUE;
}
- stencil.test_enabled = false;
- auto reset_stencil = [](auto& config) {
+
+ const auto ResetStencil = [](auto& config) {
config.test_func = GL_ALWAYS;
config.test_ref = 0;
config.test_mask = 0xFFFFFFFF;
@@ -46,8 +93,10 @@ OpenGLState::OpenGLState() {
config.action_depth_pass = GL_KEEP;
config.action_stencil_fail = GL_KEEP;
};
- reset_stencil(stencil.front);
- reset_stencil(stencil.back);
+ stencil.test_enabled = false;
+ ResetStencil(stencil.front);
+ ResetStencil(stencil.back);
+
for (auto& item : viewports) {
item.x = 0;
item.y = 0;
@@ -61,6 +110,7 @@ OpenGLState::OpenGLState() {
item.scissor.width = 0;
item.scissor.height = 0;
}
+
for (auto& item : blend) {
item.enabled = true;
item.rgb_equation = GL_FUNC_ADD;
@@ -70,11 +120,14 @@ OpenGLState::OpenGLState() {
item.src_a_func = GL_ONE;
item.dst_a_func = GL_ZERO;
}
+
independant_blend.enabled = false;
+
blend_color.red = 0.0f;
blend_color.green = 0.0f;
blend_color.blue = 0.0f;
blend_color.alpha = 0.0f;
+
logic_op.enabled = false;
logic_op.operation = GL_COPY;
@@ -91,9 +144,12 @@ OpenGLState::OpenGLState() {
clip_distance = {};
point.size = 1;
+
fragment_color_clamp.enabled = false;
+
depth_clamp.far_plane = false;
depth_clamp.near_plane = false;
+
polygon_offset.fill_enable = false;
polygon_offset.line_enable = false;
polygon_offset.point_enable = false;
@@ -103,260 +159,255 @@ OpenGLState::OpenGLState() {
}
void OpenGLState::ApplyDefaultState() {
+ glEnable(GL_BLEND);
glDisable(GL_FRAMEBUFFER_SRGB);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glDisable(GL_PRIMITIVE_RESTART);
glDisable(GL_STENCIL_TEST);
- glEnable(GL_BLEND);
glDisable(GL_COLOR_LOGIC_OP);
glDisable(GL_SCISSOR_TEST);
}
+void OpenGLState::ApplyFramebufferState() const {
+ if (UpdateValue(cur_state.draw.read_framebuffer, draw.read_framebuffer)) {
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer);
+ }
+ if (UpdateValue(cur_state.draw.draw_framebuffer, draw.draw_framebuffer)) {
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw.draw_framebuffer);
+ }
+}
+
+void OpenGLState::ApplyVertexArrayState() const {
+ if (UpdateValue(cur_state.draw.vertex_array, draw.vertex_array)) {
+ glBindVertexArray(draw.vertex_array);
+ }
+}
+
+void OpenGLState::ApplyShaderProgram() const {
+ if (UpdateValue(cur_state.draw.shader_program, draw.shader_program)) {
+ glUseProgram(draw.shader_program);
+ }
+}
+
+void OpenGLState::ApplyProgramPipeline() const {
+ if (UpdateValue(cur_state.draw.program_pipeline, draw.program_pipeline)) {
+ glBindProgramPipeline(draw.program_pipeline);
+ }
+}
+
+void OpenGLState::ApplyClipDistances() const {
+ for (std::size_t i = 0; i < clip_distance.size(); ++i) {
+ Enable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i), cur_state.clip_distance[i],
+ clip_distance[i]);
+ }
+}
+
+void OpenGLState::ApplyPointSize() const {
+ if (UpdateValue(cur_state.point.size, point.size)) {
+ glPointSize(point.size);
+ }
+}
+
+void OpenGLState::ApplyFragmentColorClamp() const {
+ if (UpdateValue(cur_state.fragment_color_clamp.enabled, fragment_color_clamp.enabled)) {
+ glClampColor(GL_CLAMP_FRAGMENT_COLOR_ARB,
+ fragment_color_clamp.enabled ? GL_TRUE : GL_FALSE);
+ }
+}
+
+void OpenGLState::ApplyMultisample() const {
+ Enable(GL_SAMPLE_ALPHA_TO_COVERAGE, cur_state.multisample_control.alpha_to_coverage,
+ multisample_control.alpha_to_coverage);
+ Enable(GL_SAMPLE_ALPHA_TO_ONE, cur_state.multisample_control.alpha_to_one,
+ multisample_control.alpha_to_one);
+}
+
+void OpenGLState::ApplyDepthClamp() const {
+ if (depth_clamp.far_plane == cur_state.depth_clamp.far_plane &&
+ depth_clamp.near_plane == cur_state.depth_clamp.near_plane) {
+ return;
+ }
+ cur_state.depth_clamp = depth_clamp;
+
+ UNIMPLEMENTED_IF_MSG(depth_clamp.far_plane != depth_clamp.near_plane,
+ "Unimplemented Depth Clamp Separation!");
+
+ Enable(GL_DEPTH_CLAMP, depth_clamp.far_plane || depth_clamp.near_plane);
+}
+
void OpenGLState::ApplySRgb() const {
- if (framebuffer_srgb.enabled != cur_state.framebuffer_srgb.enabled) {
- if (framebuffer_srgb.enabled) {
- // Track if sRGB is used
- s_rgb_used = true;
- glEnable(GL_FRAMEBUFFER_SRGB);
- } else {
- glDisable(GL_FRAMEBUFFER_SRGB);
- }
+ if (cur_state.framebuffer_srgb.enabled == framebuffer_srgb.enabled)
+ return;
+ cur_state.framebuffer_srgb.enabled = framebuffer_srgb.enabled;
+ if (framebuffer_srgb.enabled) {
+ // Track if sRGB is used
+ s_rgb_used = true;
+ glEnable(GL_FRAMEBUFFER_SRGB);
+ } else {
+ glDisable(GL_FRAMEBUFFER_SRGB);
}
}
void OpenGLState::ApplyCulling() const {
- if (cull.enabled != cur_state.cull.enabled) {
- if (cull.enabled) {
- glEnable(GL_CULL_FACE);
- } else {
- glDisable(GL_CULL_FACE);
- }
- }
+ Enable(GL_CULL_FACE, cur_state.cull.enabled, cull.enabled);
- if (cull.mode != cur_state.cull.mode) {
+ if (UpdateValue(cur_state.cull.mode, cull.mode)) {
glCullFace(cull.mode);
}
- if (cull.front_face != cur_state.cull.front_face) {
+ if (UpdateValue(cur_state.cull.front_face, cull.front_face)) {
glFrontFace(cull.front_face);
}
}
void OpenGLState::ApplyColorMask() const {
- if (independant_blend.enabled) {
- for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) {
- const auto& updated = color_mask[i];
- const auto& current = cur_state.color_mask[i];
- if (updated.red_enabled != current.red_enabled ||
- updated.green_enabled != current.green_enabled ||
- updated.blue_enabled != current.blue_enabled ||
- updated.alpha_enabled != current.alpha_enabled) {
- glColorMaski(static_cast<GLuint>(i), updated.red_enabled, updated.green_enabled,
- updated.blue_enabled, updated.alpha_enabled);
- }
- }
- } else {
- const auto& updated = color_mask[0];
- const auto& current = cur_state.color_mask[0];
+ for (std::size_t i = 0; i < Maxwell::NumRenderTargets; ++i) {
+ const auto& updated = color_mask[i];
+ auto& current = cur_state.color_mask[i];
if (updated.red_enabled != current.red_enabled ||
updated.green_enabled != current.green_enabled ||
updated.blue_enabled != current.blue_enabled ||
updated.alpha_enabled != current.alpha_enabled) {
- glColorMask(updated.red_enabled, updated.green_enabled, updated.blue_enabled,
- updated.alpha_enabled);
+ current = updated;
+ glColorMaski(static_cast<GLuint>(i), updated.red_enabled, updated.green_enabled,
+ updated.blue_enabled, updated.alpha_enabled);
}
}
}
void OpenGLState::ApplyDepth() const {
- if (depth.test_enabled != cur_state.depth.test_enabled) {
- if (depth.test_enabled) {
- glEnable(GL_DEPTH_TEST);
- } else {
- glDisable(GL_DEPTH_TEST);
- }
- }
+ Enable(GL_DEPTH_TEST, cur_state.depth.test_enabled, depth.test_enabled);
- if (depth.test_func != cur_state.depth.test_func) {
+ if (cur_state.depth.test_func != depth.test_func) {
+ cur_state.depth.test_func = depth.test_func;
glDepthFunc(depth.test_func);
}
- if (depth.write_mask != cur_state.depth.write_mask) {
+ if (cur_state.depth.write_mask != depth.write_mask) {
+ cur_state.depth.write_mask = depth.write_mask;
glDepthMask(depth.write_mask);
}
}
void OpenGLState::ApplyPrimitiveRestart() const {
- if (primitive_restart.enabled != cur_state.primitive_restart.enabled) {
- if (primitive_restart.enabled) {
- glEnable(GL_PRIMITIVE_RESTART);
- } else {
- glDisable(GL_PRIMITIVE_RESTART);
- }
- }
+ Enable(GL_PRIMITIVE_RESTART, cur_state.primitive_restart.enabled, primitive_restart.enabled);
- if (primitive_restart.index != cur_state.primitive_restart.index) {
+ if (cur_state.primitive_restart.index != primitive_restart.index) {
+ cur_state.primitive_restart.index = primitive_restart.index;
glPrimitiveRestartIndex(primitive_restart.index);
}
}
void OpenGLState::ApplyStencilTest() const {
- if (stencil.test_enabled != cur_state.stencil.test_enabled) {
- if (stencil.test_enabled) {
- glEnable(GL_STENCIL_TEST);
- } else {
- glDisable(GL_STENCIL_TEST);
- }
- }
-
- const auto ConfigStencil = [](GLenum face, const auto& config, const auto& prev_config) {
- if (config.test_func != prev_config.test_func || config.test_ref != prev_config.test_ref ||
- config.test_mask != prev_config.test_mask) {
+ Enable(GL_STENCIL_TEST, cur_state.stencil.test_enabled, stencil.test_enabled);
+
+ const auto ConfigStencil = [](GLenum face, const auto& config, auto& current) {
+ if (current.test_func != config.test_func || current.test_ref != config.test_ref ||
+ current.test_mask != config.test_mask) {
+ current.test_func = config.test_func;
+ current.test_ref = config.test_ref;
+ current.test_mask = config.test_mask;
glStencilFuncSeparate(face, config.test_func, config.test_ref, config.test_mask);
}
- if (config.action_depth_fail != prev_config.action_depth_fail ||
- config.action_depth_pass != prev_config.action_depth_pass ||
- config.action_stencil_fail != prev_config.action_stencil_fail) {
+ if (current.action_depth_fail != config.action_depth_fail ||
+ current.action_depth_pass != config.action_depth_pass ||
+ current.action_stencil_fail != config.action_stencil_fail) {
+ current.action_depth_fail = config.action_depth_fail;
+ current.action_depth_pass = config.action_depth_pass;
+ current.action_stencil_fail = config.action_stencil_fail;
glStencilOpSeparate(face, config.action_stencil_fail, config.action_depth_fail,
config.action_depth_pass);
}
- if (config.write_mask != prev_config.write_mask) {
+ if (current.write_mask != config.write_mask) {
+ current.write_mask = config.write_mask;
glStencilMaskSeparate(face, config.write_mask);
}
};
ConfigStencil(GL_FRONT, stencil.front, cur_state.stencil.front);
ConfigStencil(GL_BACK, stencil.back, cur_state.stencil.back);
}
-// Viewport does not affects glClearBuffer so emulate viewport using scissor test
-void OpenGLState::EmulateViewportWithScissor() {
- auto& current = viewports[0];
- if (current.scissor.enabled) {
- const GLint left = std::max(current.x, current.scissor.x);
- const GLint right =
- std::max(current.x + current.width, current.scissor.x + current.scissor.width);
- const GLint bottom = std::max(current.y, current.scissor.y);
- const GLint top =
- std::max(current.y + current.height, current.scissor.y + current.scissor.height);
- current.scissor.x = std::max(left, 0);
- current.scissor.y = std::max(bottom, 0);
- current.scissor.width = std::max(right - left, 0);
- current.scissor.height = std::max(top - bottom, 0);
- } else {
- current.scissor.enabled = true;
- current.scissor.x = current.x;
- current.scissor.y = current.y;
- current.scissor.width = current.width;
- current.scissor.height = current.height;
- }
-}
void OpenGLState::ApplyViewport() const {
- if (geometry_shaders.enabled) {
- for (GLuint i = 0; i < static_cast<GLuint>(Tegra::Engines::Maxwell3D::Regs::NumViewports);
- i++) {
- const auto& current = cur_state.viewports[i];
- const auto& updated = viewports[i];
- if (updated.x != current.x || updated.y != current.y ||
- updated.width != current.width || updated.height != current.height) {
- glViewportIndexedf(
- i, static_cast<GLfloat>(updated.x), static_cast<GLfloat>(updated.y),
- static_cast<GLfloat>(updated.width), static_cast<GLfloat>(updated.height));
- }
- if (updated.depth_range_near != current.depth_range_near ||
- updated.depth_range_far != current.depth_range_far) {
- glDepthRangeIndexed(i, updated.depth_range_near, updated.depth_range_far);
- }
-
- if (updated.scissor.enabled != current.scissor.enabled) {
- if (updated.scissor.enabled) {
- glEnablei(GL_SCISSOR_TEST, i);
- } else {
- glDisablei(GL_SCISSOR_TEST, i);
- }
- }
-
- if (updated.scissor.x != current.scissor.x || updated.scissor.y != current.scissor.y ||
- updated.scissor.width != current.scissor.width ||
- updated.scissor.height != current.scissor.height) {
- glScissorIndexed(i, updated.scissor.x, updated.scissor.y, updated.scissor.width,
- updated.scissor.height);
- }
- }
- } else {
- const auto& current = cur_state.viewports[0];
- const auto& updated = viewports[0];
- if (updated.x != current.x || updated.y != current.y || updated.width != current.width ||
- updated.height != current.height) {
- glViewport(updated.x, updated.y, updated.width, updated.height);
- }
-
- if (updated.depth_range_near != current.depth_range_near ||
- updated.depth_range_far != current.depth_range_far) {
- glDepthRange(updated.depth_range_near, updated.depth_range_far);
+ for (GLuint i = 0; i < static_cast<GLuint>(Maxwell::NumViewports); ++i) {
+ const auto& updated = viewports[i];
+ auto& current = cur_state.viewports[i];
+
+ if (current.x != updated.x || current.y != updated.y || current.width != updated.width ||
+ current.height != updated.height) {
+ current.x = updated.x;
+ current.y = updated.y;
+ current.width = updated.width;
+ current.height = updated.height;
+ glViewportIndexedf(i, static_cast<GLfloat>(updated.x), static_cast<GLfloat>(updated.y),
+ static_cast<GLfloat>(updated.width),
+ static_cast<GLfloat>(updated.height));
}
-
- if (updated.scissor.enabled != current.scissor.enabled) {
- if (updated.scissor.enabled) {
- glEnable(GL_SCISSOR_TEST);
- } else {
- glDisable(GL_SCISSOR_TEST);
- }
+ if (current.depth_range_near != updated.depth_range_near ||
+ current.depth_range_far != updated.depth_range_far) {
+ current.depth_range_near = updated.depth_range_near;
+ current.depth_range_far = updated.depth_range_far;
+ glDepthRangeIndexed(i, updated.depth_range_near, updated.depth_range_far);
}
- if (updated.scissor.x != current.scissor.x || updated.scissor.y != current.scissor.y ||
- updated.scissor.width != current.scissor.width ||
- updated.scissor.height != current.scissor.height) {
- glScissor(updated.scissor.x, updated.scissor.y, updated.scissor.width,
- updated.scissor.height);
+ Enable(GL_SCISSOR_TEST, i, current.scissor.enabled, updated.scissor.enabled);
+
+ if (current.scissor.x != updated.scissor.x || current.scissor.y != updated.scissor.y ||
+ current.scissor.width != updated.scissor.width ||
+ current.scissor.height != updated.scissor.height) {
+ current.scissor.x = updated.scissor.x;
+ current.scissor.y = updated.scissor.y;
+ current.scissor.width = updated.scissor.width;
+ current.scissor.height = updated.scissor.height;
+ glScissorIndexed(i, updated.scissor.x, updated.scissor.y, updated.scissor.width,
+ updated.scissor.height);
}
}
}
void OpenGLState::ApplyGlobalBlending() const {
- const Blend& current = cur_state.blend[0];
const Blend& updated = blend[0];
- if (updated.enabled != current.enabled) {
- if (updated.enabled) {
- glEnable(GL_BLEND);
- } else {
- glDisable(GL_BLEND);
- }
- }
- if (!updated.enabled) {
- return;
- }
- if (updated.src_rgb_func != current.src_rgb_func ||
- updated.dst_rgb_func != current.dst_rgb_func || updated.src_a_func != current.src_a_func ||
- updated.dst_a_func != current.dst_a_func) {
+ Blend& current = cur_state.blend[0];
+
+ Enable(GL_BLEND, current.enabled, updated.enabled);
+
+ if (current.src_rgb_func != updated.src_rgb_func ||
+ current.dst_rgb_func != updated.dst_rgb_func || current.src_a_func != updated.src_a_func ||
+ current.dst_a_func != updated.dst_a_func) {
+ current.src_rgb_func = updated.src_rgb_func;
+ current.dst_rgb_func = updated.dst_rgb_func;
+ current.src_a_func = updated.src_a_func;
+ current.dst_a_func = updated.dst_a_func;
glBlendFuncSeparate(updated.src_rgb_func, updated.dst_rgb_func, updated.src_a_func,
updated.dst_a_func);
}
- if (updated.rgb_equation != current.rgb_equation || updated.a_equation != current.a_equation) {
+ if (current.rgb_equation != updated.rgb_equation || current.a_equation != updated.a_equation) {
+ current.rgb_equation = updated.rgb_equation;
+ current.a_equation = updated.a_equation;
glBlendEquationSeparate(updated.rgb_equation, updated.a_equation);
}
}
void OpenGLState::ApplyTargetBlending(std::size_t target, bool force) const {
const Blend& updated = blend[target];
- const Blend& current = cur_state.blend[target];
- if (updated.enabled != current.enabled || force) {
- if (updated.enabled) {
- glEnablei(GL_BLEND, static_cast<GLuint>(target));
- } else {
- glDisablei(GL_BLEND, static_cast<GLuint>(target));
- }
+ Blend& current = cur_state.blend[target];
+
+ if (current.enabled != updated.enabled || force) {
+ current.enabled = updated.enabled;
+ Enable(GL_BLEND, static_cast<GLuint>(target), updated.enabled);
}
- if (updated.src_rgb_func != current.src_rgb_func ||
- updated.dst_rgb_func != current.dst_rgb_func || updated.src_a_func != current.src_a_func ||
- updated.dst_a_func != current.dst_a_func) {
+ if (UpdateTie(std::tie(current.src_rgb_func, current.dst_rgb_func, current.src_a_func,
+ current.dst_a_func),
+ std::tie(updated.src_rgb_func, updated.dst_rgb_func, updated.src_a_func,
+ updated.dst_a_func))) {
glBlendFuncSeparatei(static_cast<GLuint>(target), updated.src_rgb_func,
updated.dst_rgb_func, updated.src_a_func, updated.dst_a_func);
}
- if (updated.rgb_equation != current.rgb_equation || updated.a_equation != current.a_equation) {
+ if (UpdateTie(std::tie(current.rgb_equation, current.a_equation),
+ std::tie(updated.rgb_equation, updated.a_equation))) {
glBlendEquationSeparatei(static_cast<GLuint>(target), updated.rgb_equation,
updated.a_equation);
}
@@ -364,77 +415,48 @@ void OpenGLState::ApplyTargetBlending(std::size_t target, bool force) const {
void OpenGLState::ApplyBlending() const {
if (independant_blend.enabled) {
- for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) {
- ApplyTargetBlending(i,
- independant_blend.enabled != cur_state.independant_blend.enabled);
+ const bool force = independant_blend.enabled != cur_state.independant_blend.enabled;
+ for (std::size_t target = 0; target < Maxwell::NumRenderTargets; ++target) {
+ ApplyTargetBlending(target, force);
}
} else {
ApplyGlobalBlending();
}
- if (blend_color.red != cur_state.blend_color.red ||
- blend_color.green != cur_state.blend_color.green ||
- blend_color.blue != cur_state.blend_color.blue ||
- blend_color.alpha != cur_state.blend_color.alpha) {
+ cur_state.independant_blend.enabled = independant_blend.enabled;
+
+ if (UpdateTie(
+ std::tie(cur_state.blend_color.red, cur_state.blend_color.green,
+ cur_state.blend_color.blue, cur_state.blend_color.alpha),
+ std::tie(blend_color.red, blend_color.green, blend_color.blue, blend_color.alpha))) {
glBlendColor(blend_color.red, blend_color.green, blend_color.blue, blend_color.alpha);
}
}
void OpenGLState::ApplyLogicOp() const {
- if (logic_op.enabled != cur_state.logic_op.enabled) {
- if (logic_op.enabled) {
- glEnable(GL_COLOR_LOGIC_OP);
- } else {
- glDisable(GL_COLOR_LOGIC_OP);
- }
- }
+ Enable(GL_COLOR_LOGIC_OP, cur_state.logic_op.enabled, logic_op.enabled);
- if (logic_op.operation != cur_state.logic_op.operation) {
+ if (UpdateValue(cur_state.logic_op.operation, logic_op.operation)) {
glLogicOp(logic_op.operation);
}
}
void OpenGLState::ApplyPolygonOffset() const {
- const bool fill_enable_changed =
- polygon_offset.fill_enable != cur_state.polygon_offset.fill_enable;
- const bool line_enable_changed =
- polygon_offset.line_enable != cur_state.polygon_offset.line_enable;
- const bool point_enable_changed =
- polygon_offset.point_enable != cur_state.polygon_offset.point_enable;
- const bool factor_changed = polygon_offset.factor != cur_state.polygon_offset.factor;
- const bool units_changed = polygon_offset.units != cur_state.polygon_offset.units;
- const bool clamp_changed = polygon_offset.clamp != cur_state.polygon_offset.clamp;
-
- if (fill_enable_changed) {
- if (polygon_offset.fill_enable) {
- glEnable(GL_POLYGON_OFFSET_FILL);
- } else {
- glDisable(GL_POLYGON_OFFSET_FILL);
- }
- }
-
- if (line_enable_changed) {
- if (polygon_offset.line_enable) {
- glEnable(GL_POLYGON_OFFSET_LINE);
- } else {
- glDisable(GL_POLYGON_OFFSET_LINE);
- }
- }
-
- if (point_enable_changed) {
- if (polygon_offset.point_enable) {
- glEnable(GL_POLYGON_OFFSET_POINT);
- } else {
- glDisable(GL_POLYGON_OFFSET_POINT);
- }
- }
-
- if (factor_changed || units_changed || clamp_changed) {
+ Enable(GL_POLYGON_OFFSET_FILL, cur_state.polygon_offset.fill_enable,
+ polygon_offset.fill_enable);
+ Enable(GL_POLYGON_OFFSET_LINE, cur_state.polygon_offset.line_enable,
+ polygon_offset.line_enable);
+ Enable(GL_POLYGON_OFFSET_POINT, cur_state.polygon_offset.point_enable,
+ polygon_offset.point_enable);
+
+ if (UpdateTie(std::tie(cur_state.polygon_offset.factor, cur_state.polygon_offset.units,
+ cur_state.polygon_offset.clamp),
+ std::tie(polygon_offset.factor, polygon_offset.units, polygon_offset.clamp))) {
if (GLAD_GL_EXT_polygon_offset_clamp && polygon_offset.clamp != 0) {
glPolygonOffsetClamp(polygon_offset.factor, polygon_offset.units, polygon_offset.clamp);
} else {
- glPolygonOffset(polygon_offset.factor, polygon_offset.units);
UNIMPLEMENTED_IF_MSG(polygon_offset.clamp != 0,
"Unimplemented Depth polygon offset clamp.");
+ glPolygonOffset(polygon_offset.factor, polygon_offset.units);
}
}
}
@@ -443,22 +465,21 @@ void OpenGLState::ApplyTextures() const {
bool has_delta{};
std::size_t first{};
std::size_t last{};
- std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> textures;
+ std::array<GLuint, Maxwell::NumTextureSamplers> textures;
for (std::size_t i = 0; i < std::size(texture_units); ++i) {
const auto& texture_unit = texture_units[i];
- const auto& cur_state_texture_unit = cur_state.texture_units[i];
+ auto& cur_state_texture_unit = cur_state.texture_units[i];
textures[i] = texture_unit.texture;
-
- if (textures[i] != cur_state_texture_unit.texture) {
- if (!has_delta) {
- first = i;
- has_delta = true;
- }
- last = i;
+ if (cur_state_texture_unit.texture == textures[i])
+ continue;
+ cur_state_texture_unit.texture = textures[i];
+ if (!has_delta) {
+ first = i;
+ has_delta = true;
}
+ last = i;
}
-
if (has_delta) {
glBindTextures(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1),
textures.data() + first);
@@ -469,16 +490,18 @@ void OpenGLState::ApplySamplers() const {
bool has_delta{};
std::size_t first{};
std::size_t last{};
- std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> samplers;
+ std::array<GLuint, Maxwell::NumTextureSamplers> samplers;
+
for (std::size_t i = 0; i < std::size(samplers); ++i) {
+ if (cur_state.texture_units[i].sampler == texture_units[i].sampler)
+ continue;
+ cur_state.texture_units[i].sampler = texture_units[i].sampler;
samplers[i] = texture_units[i].sampler;
- if (samplers[i] != cur_state.texture_units[i].sampler) {
- if (!has_delta) {
- first = i;
- has_delta = true;
- }
- last = i;
+ if (!has_delta) {
+ first = i;
+ has_delta = true;
}
+ last = i;
}
if (has_delta) {
glBindSamplers(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1),
@@ -486,81 +509,15 @@ void OpenGLState::ApplySamplers() const {
}
}
-void OpenGLState::ApplyFramebufferState() const {
- if (draw.read_framebuffer != cur_state.draw.read_framebuffer) {
- glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer);
- }
- if (draw.draw_framebuffer != cur_state.draw.draw_framebuffer) {
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw.draw_framebuffer);
- }
-}
-
-void OpenGLState::ApplyVertexArrayState() const {
- if (draw.vertex_array != cur_state.draw.vertex_array) {
- glBindVertexArray(draw.vertex_array);
- }
-}
-
-void OpenGLState::ApplyDepthClamp() const {
- if (depth_clamp.far_plane == cur_state.depth_clamp.far_plane &&
- depth_clamp.near_plane == cur_state.depth_clamp.near_plane) {
- return;
- }
- UNIMPLEMENTED_IF_MSG(depth_clamp.far_plane != depth_clamp.near_plane,
- "Unimplemented Depth Clamp Separation!");
-
- if (depth_clamp.far_plane || depth_clamp.near_plane) {
- glEnable(GL_DEPTH_CLAMP);
- } else {
- glDisable(GL_DEPTH_CLAMP);
- }
-}
-
void OpenGLState::Apply() const {
ApplyFramebufferState();
ApplyVertexArrayState();
-
- // Shader program
- if (draw.shader_program != cur_state.draw.shader_program) {
- glUseProgram(draw.shader_program);
- }
-
- // Program pipeline
- if (draw.program_pipeline != cur_state.draw.program_pipeline) {
- glBindProgramPipeline(draw.program_pipeline);
- }
- // Clip distance
- for (std::size_t i = 0; i < clip_distance.size(); ++i) {
- if (clip_distance[i] != cur_state.clip_distance[i]) {
- if (clip_distance[i]) {
- glEnable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i));
- } else {
- glDisable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i));
- }
- }
- }
- // Point
- if (point.size != cur_state.point.size) {
- glPointSize(point.size);
- }
- if (fragment_color_clamp.enabled != cur_state.fragment_color_clamp.enabled) {
- glClampColor(GL_CLAMP_FRAGMENT_COLOR_ARB,
- fragment_color_clamp.enabled ? GL_TRUE : GL_FALSE);
- }
- if (multisample_control.alpha_to_coverage != cur_state.multisample_control.alpha_to_coverage) {
- if (multisample_control.alpha_to_coverage) {
- glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
- } else {
- glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
- }
- }
- if (multisample_control.alpha_to_one != cur_state.multisample_control.alpha_to_one) {
- if (multisample_control.alpha_to_one) {
- glEnable(GL_SAMPLE_ALPHA_TO_ONE);
- } else {
- glDisable(GL_SAMPLE_ALPHA_TO_ONE);
- }
- }
+ ApplyShaderProgram();
+ ApplyProgramPipeline();
+ ApplyClipDistances();
+ ApplyPointSize();
+ ApplyFragmentColorClamp();
+ ApplyMultisample();
ApplyDepthClamp();
ApplyColorMask();
ApplyViewport();
@@ -574,7 +531,28 @@ void OpenGLState::Apply() const {
ApplyTextures();
ApplySamplers();
ApplyPolygonOffset();
- cur_state = *this;
+}
+
+void OpenGLState::EmulateViewportWithScissor() {
+ auto& current = viewports[0];
+ if (current.scissor.enabled) {
+ const GLint left = std::max(current.x, current.scissor.x);
+ const GLint right =
+ std::max(current.x + current.width, current.scissor.x + current.scissor.width);
+ const GLint bottom = std::max(current.y, current.scissor.y);
+ const GLint top =
+ std::max(current.y + current.height, current.scissor.y + current.scissor.height);
+ current.scissor.x = std::max(left, 0);
+ current.scissor.y = std::max(bottom, 0);
+ current.scissor.width = std::max(right - left, 0);
+ current.scissor.height = std::max(top - bottom, 0);
+ } else {
+ current.scissor.enabled = true;
+ current.scissor.x = current.x;
+ current.scissor.y = current.y;
+ current.scissor.width = current.width;
+ current.scissor.height = current.height;
+ }
}
OpenGLState& 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:
} depth_clamp; // GL_DEPTH_CLAMP
struct {
- bool enabled; // viewports arrays are only supported when geometry shaders are enabled.
- } geometry_shaders;
-
- struct {
bool enabled; // GL_CULL_FACE
GLenum mode; // GL_CULL_FACE_MODE
GLenum front_face; // GL_FRONT_FACE
@@ -184,34 +180,26 @@ public:
static OpenGLState GetCurState() {
return cur_state;
}
+
static bool GetsRGBUsed() {
return s_rgb_used;
}
+
static void ClearsRGBUsed() {
s_rgb_used = false;
}
+
/// Apply this state as the current OpenGL state
void Apply() const;
- /// Apply only the state affecting the framebuffer
+
void ApplyFramebufferState() const;
- /// Apply only the state affecting the vertex array
void ApplyVertexArrayState() const;
- /// Set the initial OpenGL state
- static void ApplyDefaultState();
- /// Resets any references to the given resource
- OpenGLState& UnbindTexture(GLuint handle);
- OpenGLState& ResetSampler(GLuint handle);
- OpenGLState& ResetProgram(GLuint handle);
- OpenGLState& ResetPipeline(GLuint handle);
- OpenGLState& ResetVertexArray(GLuint handle);
- OpenGLState& ResetFramebuffer(GLuint handle);
- void EmulateViewportWithScissor();
-
-private:
- static OpenGLState cur_state;
- // Workaround for sRGB problems caused by
- // QT not supporting srgb output
- static bool s_rgb_used;
+ void ApplyShaderProgram() const;
+ void ApplyProgramPipeline() const;
+ void ApplyClipDistances() const;
+ void ApplyPointSize() const;
+ void ApplyFragmentColorClamp() const;
+ void ApplyMultisample() const;
void ApplySRgb() const;
void ApplyCulling() const;
void ApplyColorMask() const;
@@ -227,6 +215,26 @@ private:
void ApplySamplers() const;
void ApplyDepthClamp() const;
void ApplyPolygonOffset() const;
+
+ /// Set the initial OpenGL state
+ static void ApplyDefaultState();
+
+ /// Resets any references to the given resource
+ OpenGLState& UnbindTexture(GLuint handle);
+ OpenGLState& ResetSampler(GLuint handle);
+ OpenGLState& ResetProgram(GLuint handle);
+ OpenGLState& ResetPipeline(GLuint handle);
+ OpenGLState& ResetVertexArray(GLuint handle);
+ OpenGLState& ResetFramebuffer(GLuint handle);
+
+ /// Viewport does not affects glClearBuffer so emulate viewport using scissor test
+ void EmulateViewportWithScissor();
+
+private:
+ static OpenGLState cur_state;
+
+ // Workaround for sRGB problems caused by QT not supporting srgb output
+ static bool s_rgb_used;
};
} // 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 @@
#include <fmt/format.h>
#include "common/assert.h"
+#include "common/bit_field.h"
#include "common/common_types.h"
+#include "common/logging/log.h"
#include "video_core/engines/shader_bytecode.h"
#include "video_core/shader/shader_ir.h"
@@ -41,19 +43,18 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
switch (opcode->get().GetId()) {
case OpCode::Id::TEX: {
- UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(TextureMiscMode::AOFFI),
- "AOFFI is not implemented");
-
if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) {
LOG_WARNING(HW_GPU, "TEX.NODEP implementation is incomplete");
}
const TextureType texture_type{instr.tex.texture_type};
const bool is_array = instr.tex.array != 0;
+ const bool is_aoffi = instr.tex.UsesMiscMode(TextureMiscMode::AOFFI);
const bool depth_compare = instr.tex.UsesMiscMode(TextureMiscMode::DC);
const auto process_mode = instr.tex.GetTextureProcessMode();
WriteTexInstructionFloat(
- bb, instr, GetTexCode(instr, texture_type, process_mode, depth_compare, is_array));
+ bb, instr,
+ GetTexCode(instr, texture_type, process_mode, depth_compare, is_array, is_aoffi));
break;
}
case OpCode::Id::TEXS: {
@@ -78,8 +79,6 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
}
case OpCode::Id::TLD4: {
ASSERT(instr.tld4.array == 0);
- UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::AOFFI),
- "AOFFI is not implemented");
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::NDV),
"NDV is not implemented");
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::PTP),
@@ -92,8 +91,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
const auto texture_type = instr.tld4.texture_type.Value();
const bool depth_compare = instr.tld4.UsesMiscMode(TextureMiscMode::DC);
const bool is_array = instr.tld4.array != 0;
- WriteTexInstructionFloat(bb, instr,
- GetTld4Code(instr, texture_type, depth_compare, is_array));
+ const bool is_aoffi = instr.tld4.UsesMiscMode(TextureMiscMode::AOFFI);
+ WriteTexInstructionFloat(
+ bb, instr, GetTld4Code(instr, texture_type, depth_compare, is_array, is_aoffi));
break;
}
case OpCode::Id::TLD4S: {
@@ -127,7 +127,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
Node4 values;
for (u32 element = 0; element < values.size(); ++element) {
auto coords_copy = coords;
- MetaTexture meta{sampler, {}, {}, {}, {}, component, element};
+ MetaTexture meta{sampler, {}, {}, {}, {}, {}, component, element};
values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
}
@@ -152,7 +152,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
if (!instr.txq.IsComponentEnabled(element)) {
continue;
}
- MetaTexture meta{sampler, {}, {}, {}, {}, {}, element};
+ MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, element};
const Node value =
Operation(OperationCode::TextureQueryDimensions, meta, GetRegister(instr.gpr8));
SetTemporal(bb, indexer++, value);
@@ -202,7 +202,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
for (u32 element = 0; element < 2; ++element) {
auto params = coords;
- MetaTexture meta{sampler, {}, {}, {}, {}, {}, element};
+ MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, element};
const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params));
SetTemporal(bb, element, value);
}
@@ -325,7 +325,8 @@ void ShaderIR::WriteTexsInstructionHalfFloat(NodeBlock& bb, Instruction instr,
Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
TextureProcessMode process_mode, std::vector<Node> coords,
- Node array, Node depth_compare, u32 bias_offset) {
+ Node array, Node depth_compare, u32 bias_offset,
+ std::vector<Node> aoffi) {
const bool is_array = array;
const bool is_shadow = depth_compare;
@@ -374,7 +375,7 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
Node4 values;
for (u32 element = 0; element < values.size(); ++element) {
auto copy_coords = coords;
- MetaTexture meta{sampler, array, depth_compare, bias, lod, {}, element};
+ MetaTexture meta{sampler, array, depth_compare, aoffi, bias, lod, {}, element};
values[element] = Operation(read_method, meta, std::move(copy_coords));
}
@@ -382,9 +383,15 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
}
Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
- TextureProcessMode process_mode, bool depth_compare, bool is_array) {
- const bool lod_bias_enabled =
- (process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ);
+ TextureProcessMode process_mode, bool depth_compare, bool is_array,
+ bool is_aoffi) {
+ const bool lod_bias_enabled{
+ (process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ)};
+
+ u64 parameter_register = instr.gpr20.Value();
+ if (lod_bias_enabled) {
+ ++parameter_register;
+ }
const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement(
texture_type, depth_compare, is_array, lod_bias_enabled, 4, 5);
@@ -404,15 +411,19 @@ Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
const Node array = is_array ? GetRegister(array_register) : nullptr;
+ std::vector<Node> aoffi;
+ if (is_aoffi) {
+ aoffi = GetAoffiCoordinates(GetRegister(parameter_register++), coord_count, false);
+ }
+
Node dc{};
if (depth_compare) {
// Depth is always stored in the register signaled by gpr20 or in the next register if lod
// or bias are used
- const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
- dc = GetRegister(depth_register);
+ dc = GetRegister(parameter_register++);
}
- return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, 0);
+ return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, 0, aoffi);
}
Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
@@ -448,11 +459,11 @@ Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
dc = GetRegister(depth_register);
}
- return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset);
+ return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset, {});
}
Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool depth_compare,
- bool is_array) {
+ bool is_array, bool is_aoffi) {
const std::size_t coord_count = GetCoordCount(texture_type);
const std::size_t total_coord_count = coord_count + (is_array ? 1 : 0);
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
const u64 coord_register = array_register + (is_array ? 1 : 0);
std::vector<Node> coords;
- for (size_t i = 0; i < coord_count; ++i)
+ for (std::size_t i = 0; i < coord_count; ++i) {
coords.push_back(GetRegister(coord_register + i));
+ }
+
+ u64 parameter_register = instr.gpr20.Value();
+ std::vector<Node> aoffi;
+ if (is_aoffi) {
+ aoffi = GetAoffiCoordinates(GetRegister(parameter_register++), coord_count, true);
+ }
+
+ Node dc{};
+ if (depth_compare) {
+ dc = GetRegister(parameter_register++);
+ }
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, depth_compare);
Node4 values;
for (u32 element = 0; element < values.size(); ++element) {
auto coords_copy = coords;
- MetaTexture meta{sampler, GetRegister(array_register), {}, {}, {}, {}, element};
+ MetaTexture meta{sampler, GetRegister(array_register), dc, aoffi, {}, {}, {}, element};
values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
}
@@ -507,7 +530,7 @@ Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is
Node4 values;
for (u32 element = 0; element < values.size(); ++element) {
auto coords_copy = coords;
- MetaTexture meta{sampler, array, {}, {}, lod, {}, element};
+ MetaTexture meta{sampler, array, {}, {}, {}, lod, {}, element};
values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy));
}
return values;
@@ -531,4 +554,45 @@ std::tuple<std::size_t, std::size_t> ShaderIR::ValidateAndGetCoordinateElement(
return {coord_count, total_coord_count};
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+std::vector<Node> ShaderIR::GetAoffiCoordinates(Node aoffi_reg, std::size_t coord_count,
+ bool is_tld4) {
+ const auto [coord_offsets, size, wrap_value,
+ diff_value] = [is_tld4]() -> std::tuple<std::array<u32, 3>, u32, s32, s32> {
+ if (is_tld4) {
+ return {{0, 8, 16}, 6, 32, 64};
+ } else {
+ return {{0, 4, 8}, 4, 8, 16};
+ }
+ }();
+ const u32 mask = (1U << size) - 1;
+
+ std::vector<Node> aoffi;
+ aoffi.reserve(coord_count);
+
+ const auto aoffi_immediate{
+ TrackImmediate(aoffi_reg, global_code, static_cast<s64>(global_code.size()))};
+ if (!aoffi_immediate) {
+ // Variable access, not supported on AMD.
+ LOG_WARNING(HW_GPU,
+ "AOFFI constant folding failed, some hardware might have graphical issues");
+ for (std::size_t coord = 0; coord < coord_count; ++coord) {
+ const Node value = BitfieldExtract(aoffi_reg, coord_offsets.at(coord), size);
+ const Node condition =
+ Operation(OperationCode::LogicalIGreaterEqual, value, Immediate(wrap_value));
+ const Node negative = Operation(OperationCode::IAdd, value, Immediate(-diff_value));
+ aoffi.push_back(Operation(OperationCode::Select, condition, negative, value));
+ }
+ return aoffi;
+ }
+
+ for (std::size_t coord = 0; coord < coord_count; ++coord) {
+ s32 value = (*aoffi_immediate >> coord_offsets.at(coord)) & mask;
+ if (value >= wrap_value) {
+ value -= diff_value;
+ }
+ aoffi.push_back(Immediate(value));
+ }
+ return aoffi;
+}
+
+} // 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) {
const bool is_signed_b = instr.xmad.sign_b == 1;
const bool is_signed_c = is_signed_a;
- auto [is_merge, op_b, op_c] = [&]() -> std::tuple<bool, Node, Node> {
+ auto [is_merge, is_psl, is_high_b, mode, op_b,
+ op_c] = [&]() -> std::tuple<bool, bool, bool, Tegra::Shader::XmadMode, Node, Node> {
switch (opcode->get().GetId()) {
case OpCode::Id::XMAD_CR:
return {instr.xmad.merge_56,
+ instr.xmad.product_shift_left_second,
+ instr.xmad.high_b,
+ instr.xmad.mode_cbf,
GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()),
GetRegister(instr.gpr39)};
case OpCode::Id::XMAD_RR:
- return {instr.xmad.merge_37, GetRegister(instr.gpr20), GetRegister(instr.gpr39)};
+ return {instr.xmad.merge_37, instr.xmad.product_shift_left, instr.xmad.high_b_rr,
+ instr.xmad.mode, GetRegister(instr.gpr20), GetRegister(instr.gpr39)};
case OpCode::Id::XMAD_RC:
- return {false, GetRegister(instr.gpr39),
+ return {false,
+ false,
+ instr.xmad.high_b,
+ instr.xmad.mode_cbf,
+ GetRegister(instr.gpr39),
GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())};
case OpCode::Id::XMAD_IMM:
- return {instr.xmad.merge_37, Immediate(static_cast<u32>(instr.xmad.imm20_16)),
+ return {instr.xmad.merge_37,
+ instr.xmad.product_shift_left,
+ false,
+ instr.xmad.mode,
+ Immediate(static_cast<u32>(instr.xmad.imm20_16)),
GetRegister(instr.gpr39)};
}
UNIMPLEMENTED_MSG("Unhandled XMAD instruction: {}", opcode->get().GetName());
- return {false, Immediate(0), Immediate(0)};
+ return {false, false, false, Tegra::Shader::XmadMode::None, Immediate(0), Immediate(0)};
}();
op_a = BitfieldExtract(op_a, instr.xmad.high_a ? 16 : 0, 16);
const Node original_b = op_b;
- op_b = BitfieldExtract(op_b, instr.xmad.high_b ? 16 : 0, 16);
+ op_b = BitfieldExtract(op_b, is_high_b ? 16 : 0, 16);
// TODO(Rodrigo): Use an appropiate sign for this operation
Node product = Operation(OperationCode::IMul, NO_PRECISE, op_a, op_b);
- if (instr.xmad.product_shift_left) {
+ if (is_psl) {
product = Operation(OperationCode::ILogicalShiftLeft, NO_PRECISE, product, Immediate(16));
}
+ SetTemporal(bb, 0, product);
+ product = GetTemporal(0);
const Node original_c = op_c;
+ const Tegra::Shader::XmadMode set_mode = mode; // Workaround to clang compile error
op_c = [&]() {
- switch (instr.xmad.mode) {
+ switch (set_mode) {
case Tegra::Shader::XmadMode::None:
return original_c;
case Tegra::Shader::XmadMode::CLo:
@@ -80,8 +96,13 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
}
}();
+ SetTemporal(bb, 1, op_c);
+ op_c = GetTemporal(1);
+
// TODO(Rodrigo): Use an appropiate sign for this operation
Node sum = Operation(OperationCode::IAdd, product, op_c);
+ SetTemporal(bb, 2, sum);
+ sum = GetTemporal(2);
if (is_merge) {
const Node a = BitfieldExtract(sum, 0, 16);
const Node b =
@@ -95,4 +116,4 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
return pc;
}
-} // namespace VideoCommon::Shader \ No newline at end of file
+} // 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 @@
#include <array>
#include <cstring>
#include <map>
+#include <optional>
#include <set>
#include <string>
#include <tuple>
@@ -290,6 +291,7 @@ struct MetaTexture {
const Sampler& sampler;
Node array{};
Node depth_compare{};
+ std::vector<Node> aoffi;
Node bias{};
Node lod{};
Node component{};
@@ -741,14 +743,14 @@ private:
Node4 GetTexCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
Tegra::Shader::TextureProcessMode process_mode, bool depth_compare,
- bool is_array);
+ bool is_array, bool is_aoffi);
Node4 GetTexsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
Tegra::Shader::TextureProcessMode process_mode, bool depth_compare,
bool is_array);
Node4 GetTld4Code(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
- bool depth_compare, bool is_array);
+ bool depth_compare, bool is_array, bool is_aoffi);
Node4 GetTldsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
bool is_array);
@@ -757,9 +759,11 @@ private:
Tegra::Shader::TextureType texture_type, bool depth_compare, bool is_array,
bool lod_bias_enabled, std::size_t max_coords, std::size_t max_inputs);
+ std::vector<Node> GetAoffiCoordinates(Node aoffi_reg, std::size_t coord_count, bool is_tld4);
+
Node4 GetTextureCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
Tegra::Shader::TextureProcessMode process_mode, std::vector<Node> coords,
- Node array, Node depth_compare, u32 bias_offset);
+ Node array, Node depth_compare, u32 bias_offset, std::vector<Node> aoffi);
Node GetVideoOperand(Node op, bool is_chunk, bool is_signed, Tegra::Shader::VideoType type,
u64 byte_height);
@@ -773,6 +777,8 @@ private:
Node TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor);
+ std::optional<u32> TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor);
+
std::pair<Node, s64> TrackRegister(const GprNode* tracked, const NodeBlock& code, s64 cursor);
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 @@
#include <utility>
#include <variant>
+#include "common/common_types.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -14,7 +15,7 @@ namespace {
std::pair<Node, s64> FindOperation(const NodeBlock& code, s64 cursor,
OperationCode operation_code) {
for (; cursor >= 0; --cursor) {
- const Node node = code[cursor];
+ const Node node = code.at(cursor);
if (const auto operation = std::get_if<OperationNode>(node)) {
if (operation->GetCode() == operation_code)
return {node, cursor};
@@ -64,6 +65,20 @@ Node ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) {
return nullptr;
}
+std::optional<u32> ShaderIR::TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor) {
+ // Reduce the cursor in one to avoid infinite loops when the instruction sets the same register
+ // that it uses as operand
+ const auto [found, found_cursor] =
+ TrackRegister(&std::get<GprNode>(*tracked), code, cursor - 1);
+ if (!found) {
+ return {};
+ }
+ if (const auto immediate = std::get_if<ImmediateNode>(found)) {
+ return immediate->GetValue();
+ }
+ return {};
+}
+
std::pair<Node, s64> ShaderIR::TrackRegister(const GprNode* tracked, const NodeBlock& code,
s64 cursor) {
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
configuration/configure_general.h
configuration/configure_graphics.cpp
configuration/configure_graphics.h
+ configuration/configure_hotkeys.cpp
+ configuration/configure_hotkeys.h
configuration/configure_input.cpp
configuration/configure_input.h
configuration/configure_input_player.cpp
@@ -78,6 +80,8 @@ add_executable(yuzu
ui_settings.h
util/limitable_input_dialog.cpp
util/limitable_input_dialog.h
+ util/sequence_dialog/sequence_dialog.cpp
+ util/sequence_dialog/sequence_dialog.h
util/spinbox.cpp
util/spinbox.h
util/util.cpp
@@ -95,6 +99,7 @@ set(UIS
configuration/configure_gamelist.ui
configuration/configure_general.ui
configuration/configure_graphics.ui
+ configuration/configure_hotkeys.ui
configuration/configure_input.ui
configuration/configure_input_player.ui
configuration/configure_input_simple.ui
@@ -105,7 +110,6 @@ set(UIS
configuration/configure_touchscreen_advanced.ui
configuration/configure_web.ui
compatdb.ui
- hotkeys.ui
loading_screen.ui
main.ui
)
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 @@
#include <mutex>
#include <QDialogButtonBox>
+#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#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 @@
#include <vector>
#include <QDialog>
#include <QList>
+#include <QTreeView>
#include "core/frontend/applets/profile_select.h"
class GMainWindow;
@@ -16,7 +17,6 @@ class QLabel;
class QScrollArea;
class QStandardItem;
class QStandardItemModel;
-class QTreeView;
class QVBoxLayout;
class 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 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include <QKeySequence>
#include <QSettings>
#include "common/file_util.h"
#include "configure_input_simple.h"
@@ -9,7 +11,6 @@
#include "core/hle/service/hid/controllers/npad.h"
#include "input_common/main.h"
#include "yuzu/configuration/config.h"
-#include "yuzu/ui_settings.h"
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
@@ -17,7 +18,6 @@ Config::Config() {
FileUtil::CreateFullPath(qt_config_loc);
qt_config =
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
-
Reload();
}
@@ -205,6 +205,27 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight,
};
+// This shouldn't have anything except static initializers (no functions). So
+// QKeySequnce(...).toString() is NOT ALLOWED HERE.
+// This must be in alphabetical order according to action name as it must have the same order as
+// UISetting::values.shortcuts, which is alphabetically ordered.
+const std::array<UISettings::Shortcut, 15> Config::default_hotkeys{
+ {{"Capture Screenshot", "Main Window", {"Ctrl+P", Qt::ApplicationShortcut}},
+ {"Continue/Pause Emulation", "Main Window", {"F4", Qt::WindowShortcut}},
+ {"Decrease Speed Limit", "Main Window", {"-", Qt::ApplicationShortcut}},
+ {"Exit yuzu", "Main Window", {"Ctrl+Q", Qt::WindowShortcut}},
+ {"Exit Fullscreen", "Main Window", {"Esc", Qt::WindowShortcut}},
+ {"Fullscreen", "Main Window", {"F11", Qt::WindowShortcut}},
+ {"Increase Speed Limit", "Main Window", {"+", Qt::ApplicationShortcut}},
+ {"Load Amiibo", "Main Window", {"F2", Qt::ApplicationShortcut}},
+ {"Load File", "Main Window", {"Ctrl+O", Qt::WindowShortcut}},
+ {"Restart Emulation", "Main Window", {"F6", Qt::WindowShortcut}},
+ {"Stop Emulation", "Main Window", {"F5", Qt::WindowShortcut}},
+ {"Toggle Filter Bar", "Main Window", {"Ctrl+F", Qt::WindowShortcut}},
+ {"Toggle Speed Limit", "Main Window", {"Ctrl+Z", Qt::ApplicationShortcut}},
+ {"Toggle Status Bar", "Main Window", {"Ctrl+S", Qt::WindowShortcut}},
+ {"Change Docked Mode", "Main Window", {"F10", Qt::ApplicationShortcut}}}};
+
void Config::ReadPlayerValues() {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
auto& player = Settings::values.players[p];
@@ -508,20 +529,15 @@ void Config::ReadValues() {
qt_config->endGroup();
qt_config->beginGroup("Shortcuts");
- QStringList groups = qt_config->childGroups();
- for (auto group : groups) {
+ for (auto [name, group, shortcut] : default_hotkeys) {
+ auto [keyseq, context] = shortcut;
qt_config->beginGroup(group);
-
- QStringList hotkeys = qt_config->childGroups();
- for (auto hotkey : hotkeys) {
- qt_config->beginGroup(hotkey);
- UISettings::values.shortcuts.emplace_back(UISettings::Shortcut(
- group + "/" + hotkey,
- UISettings::ContextualShortcut(ReadSetting("KeySeq").toString(),
- ReadSetting("Context").toInt())));
- qt_config->endGroup();
- }
-
+ qt_config->beginGroup(name);
+ UISettings::values.shortcuts.push_back(
+ {name,
+ group,
+ {ReadSetting("KeySeq", keyseq).toString(), ReadSetting("Context", context).toInt()}});
+ qt_config->endGroup();
qt_config->endGroup();
}
qt_config->endGroup();
@@ -758,9 +774,16 @@ void Config::SaveValues() {
qt_config->endGroup();
qt_config->beginGroup("Shortcuts");
- for (auto shortcut : UISettings::values.shortcuts) {
- WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first);
- WriteSetting(shortcut.first + "/Context", shortcut.second.second);
+ // Lengths of UISettings::values.shortcuts & default_hotkeys are same.
+ // However, their ordering must also be the same.
+ for (std::size_t i = 0; i < default_hotkeys.size(); i++) {
+ auto [name, group, shortcut] = UISettings::values.shortcuts[i];
+ qt_config->beginGroup(group);
+ qt_config->beginGroup(name);
+ WriteSetting("KeySeq", shortcut.first, default_hotkeys[i].shortcut.first);
+ WriteSetting("Context", shortcut.second, default_hotkeys[i].shortcut.second);
+ qt_config->endGroup();
+ qt_config->endGroup();
}
qt_config->endGroup();
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 @@
#include <string>
#include <QVariant>
#include "core/settings.h"
+#include "yuzu/ui_settings.h"
class QSettings;
@@ -47,6 +48,8 @@ private:
void WriteSetting(const QString& name, const QVariant& value);
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
+ static const std::array<UISettings::Shortcut, 15> default_hotkeys;
+
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
};
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 @@
<x>0</x>
<y>0</y>
<width>382</width>
- <height>241</height>
+ <height>650</height>
</rect>
</property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>650</height>
+ </size>
+ </property>
<property name="windowTitle">
<string>yuzu Configuration</string>
</property>
@@ -62,6 +68,11 @@
<string>Input</string>
</attribute>
</widget>
+ <widget class="ConfigureHotkeys" name="hotkeysTab">
+ <attribute name="title">
+ <string>Hotkeys</string>
+ </attribute>
+ </widget>
<widget class="ConfigureGraphics" name="graphicsTab">
<attribute name="title">
<string>Graphics</string>
@@ -150,6 +161,12 @@
<header>configuration/configure_input_simple.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>ConfigureHotkeys</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_hotkeys.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<resources/>
<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 @@
#include "ui_configure.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_dialog.h"
+#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/hotkeys.h"
-ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry)
- : QDialog(parent), ui(new Ui::ConfigureDialog) {
+ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
+ : QDialog(parent), registry(registry), ui(new Ui::ConfigureDialog) {
ui->setupUi(this);
- ui->generalTab->PopulateHotkeyList(registry);
+ ui->hotkeysTab->Populate(registry);
this->setConfiguration();
this->PopulateSelectionList();
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
&ConfigureDialog::UpdateVisibleTabs);
-
adjustSize();
-
ui->selectorList->setCurrentRow(0);
+
+ // Synchronise lists upon initialisation
+ ui->hotkeysTab->EmitHotkeysChanged();
}
ConfigureDialog::~ConfigureDialog() = default;
@@ -34,6 +36,7 @@ void ConfigureDialog::applyConfiguration() {
ui->systemTab->applyConfiguration();
ui->profileManagerTab->applyConfiguration();
ui->inputTab->applyConfiguration();
+ ui->hotkeysTab->applyConfiguration(registry);
ui->graphicsTab->applyConfiguration();
ui->audioTab->applyConfiguration();
ui->debugTab->applyConfiguration();
@@ -47,7 +50,7 @@ void ConfigureDialog::PopulateSelectionList() {
{{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}},
{tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}},
{tr("Graphics"), {tr("Graphics")}},
- {tr("Controls"), {tr("Input")}}}};
+ {tr("Controls"), {tr("Input"), tr("Hotkeys")}}}};
for (const auto& entry : items) {
auto* const item = new QListWidgetItem(entry.first);
@@ -66,6 +69,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
{tr("System"), ui->systemTab},
{tr("Profiles"), ui->profileManagerTab},
{tr("Input"), ui->inputTab},
+ {tr("Hotkeys"), ui->hotkeysTab},
{tr("Graphics"), ui->graphicsTab},
{tr("Audio"), ui->audioTab},
{tr("Debug"), ui->debugTab},
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 {
Q_OBJECT
public:
- explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry);
+ explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry);
~ConfigureDialog() override;
void applyConfiguration();
@@ -28,4 +28,5 @@ private:
void PopulateSelectionList();
std::unique_ptr<Ui::ConfigureDialog> ui;
+ HotkeyRegistry& registry;
};
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() {
ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
}
-void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
- ui->widget->Populate(registry);
-}
-
void ConfigureGeneral::applyConfiguration() {
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
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:
explicit ConfigureGeneral(QWidget* parent = nullptr);
~ConfigureGeneral() override;
- void PopulateHotkeyList(const HotkeyRegistry& registry);
void applyConfiguration();
private:
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 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="HotKeysGroupBox">
- <property name="title">
- <string>Hotkeys</string>
- </property>
- <layout class="QHBoxLayout" name="HotKeysHorizontalLayout">
- <item>
- <layout class="QVBoxLayout" name="HotKeysVerticalLayout">
- <item>
- <widget class="GHotkeysDialog" name="widget" native="true"/>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -130,14 +114,6 @@
</item>
</layout>
</widget>
- <customwidgets>
- <customwidget>
- <class>GHotkeysDialog</class>
- <extends>QWidget</extends>
- <header>hotkeys.h</header>
- <container>1</container>
- </customwidget>
- </customwidgets>
<resources/>
<connections/>
</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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QMessageBox>
+#include <QStandardItemModel>
+#include "core/settings.h"
+#include "ui_configure_hotkeys.h"
+#include "yuzu/configuration/configure_hotkeys.h"
+#include "yuzu/hotkeys.h"
+#include "yuzu/util/sequence_dialog/sequence_dialog.h"
+
+ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) {
+ ui->setupUi(this);
+ setFocusPolicy(Qt::ClickFocus);
+
+ model = new QStandardItemModel(this);
+ model->setColumnCount(3);
+ model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
+
+ connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure);
+ ui->hotkey_list->setModel(model);
+
+ // TODO(Kloen): Make context configurable as well (hiding the column for now)
+ ui->hotkey_list->hideColumn(2);
+
+ ui->hotkey_list->setColumnWidth(0, 200);
+ ui->hotkey_list->resizeColumnToContents(1);
+}
+
+ConfigureHotkeys::~ConfigureHotkeys() = default;
+
+void ConfigureHotkeys::EmitHotkeysChanged() {
+ emit HotkeysChanged(GetUsedKeyList());
+}
+
+QList<QKeySequence> ConfigureHotkeys::GetUsedKeyList() const {
+ QList<QKeySequence> list;
+ for (int r = 0; r < model->rowCount(); r++) {
+ const QStandardItem* parent = model->item(r, 0);
+ for (int r2 = 0; r2 < parent->rowCount(); r2++) {
+ const QStandardItem* keyseq = parent->child(r2, 1);
+ list << QKeySequence::fromString(keyseq->text(), QKeySequence::NativeText);
+ }
+ }
+ return list;
+}
+
+void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
+ for (const auto& group : registry.hotkey_groups) {
+ auto* parent_item = new QStandardItem(group.first);
+ parent_item->setEditable(false);
+ for (const auto& hotkey : group.second) {
+ auto* action = new QStandardItem(hotkey.first);
+ auto* keyseq =
+ new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
+ action->setEditable(false);
+ keyseq->setEditable(false);
+ parent_item->appendRow({action, keyseq});
+ }
+ model->appendRow(parent_item);
+ }
+
+ ui->hotkey_list->expandAll();
+}
+
+void ConfigureHotkeys::Configure(QModelIndex index) {
+ if (index.parent() == QModelIndex())
+ return;
+
+ index = index.sibling(index.row(), 1);
+ auto* model = ui->hotkey_list->model();
+ auto previous_key = model->data(index);
+
+ auto* hotkey_dialog = new SequenceDialog;
+ int return_code = hotkey_dialog->exec();
+
+ auto key_sequence = hotkey_dialog->GetSequence();
+
+ if (return_code == QDialog::Rejected || key_sequence.isEmpty())
+ return;
+
+ if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) {
+ QMessageBox::critical(this, tr("Error in inputted key"),
+ tr("You're using a key that's already bound."));
+ } else {
+ model->setData(index, key_sequence.toString(QKeySequence::NativeText));
+ EmitHotkeysChanged();
+ }
+}
+
+bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) {
+ return GetUsedKeyList().contains(key_sequence);
+}
+
+void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) {
+ for (int key_id = 0; key_id < model->rowCount(); key_id++) {
+ const QStandardItem* parent = model->item(key_id, 0);
+ for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
+ const QStandardItem* action = parent->child(key_column_id, 0);
+ const QStandardItem* keyseq = parent->child(key_column_id, 1);
+ for (auto& [group, sub_actions] : registry.hotkey_groups) {
+ if (group != parent->text())
+ continue;
+ for (auto& [action_name, hotkey] : sub_actions) {
+ if (action_name != action->text())
+ continue;
+ hotkey.keyseq = QKeySequence(keyseq->text());
+ }
+ }
+ }
+ }
+
+ registry.SaveHotkeys();
+ Settings::Apply();
+}
+
+void ConfigureHotkeys::retranslateUi() {
+ ui->retranslateUi(this);
+}
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QWidget>
+#include "core/settings.h"
+
+namespace Ui {
+class ConfigureHotkeys;
+}
+
+class HotkeyRegistry;
+class QStandardItemModel;
+
+class ConfigureHotkeys : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureHotkeys(QWidget* parent = nullptr);
+ ~ConfigureHotkeys() override;
+
+ void applyConfiguration(HotkeyRegistry& registry);
+ void retranslateUi();
+
+ void EmitHotkeysChanged();
+
+ /**
+ * Populates the hotkey list widget using data from the provided registry.
+ * Called everytime the Configure dialog is opened.
+ * @param registry The HotkeyRegistry whose data is used to populate the list.
+ */
+ void Populate(const HotkeyRegistry& registry);
+
+signals:
+ void HotkeysChanged(QList<QKeySequence> new_key_list);
+
+private:
+ void Configure(QModelIndex index);
+ bool IsUsedKey(QKeySequence key_sequence);
+ QList<QKeySequence> GetUsedKeyList() const;
+
+ std::unique_ptr<Ui::ConfigureHotkeys> ui;
+
+ QStandardItemModel* model;
+};
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureHotkeys</class>
+ <widget class="QWidget" name="ConfigureHotkeys">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>363</width>
+ <height>388</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Hotkey Settings</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Double-click on a binding to change it.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="hotkey_list">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="sortingEnabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</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 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/registered_cache.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/game_list.h"
#include "yuzu/game_list_p.h"
@@ -193,8 +194,9 @@ void GameList::onFilterCloseClicked() {
main_window->filterBarSetChecked(false);
}
-GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)
- : QWidget{parent}, vfs(std::move(vfs)) {
+GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvider* provider,
+ GMainWindow* parent)
+ : QWidget{parent}, vfs(std::move(vfs)), provider(provider) {
watcher = new QFileSystemWatcher(this);
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
@@ -432,7 +434,8 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
emit ShouldCancelWorker();
- GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan, compatibility_list);
+ GameListWorker* worker =
+ new GameListWorker(vfs, provider, dir_path, deep_scan, compatibility_list);
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
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;
class GMainWindow;
namespace FileSys {
+class ManualContentProvider;
class VfsFilesystem;
-}
+} // namespace FileSys
enum class GameListOpenTarget {
SaveData,
@@ -47,7 +48,8 @@ public:
COLUMN_COUNT, // Number of columns
};
- explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs, GMainWindow* parent = nullptr);
+ explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs,
+ FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr);
~GameList() override;
void clearFilter();
@@ -86,6 +88,7 @@ private:
void RefreshGameDirectory();
std::shared_ptr<FileSys::VfsFilesystem> vfs;
+ FileSys::ManualContentProvider* provider;
GameListSearchField* search_field;
GMainWindow* main_window = nullptr;
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 @@
#include "common/common_paths.h"
#include "common/file_util.h"
+#include "core/core.h"
+#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/submission_package.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "yuzu/compatibility_list.h"
@@ -119,20 +122,25 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
}
} // Anonymous namespace
-GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
- const CompatibilityList& compatibility_list)
- : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
+GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs,
+ FileSys::ManualContentProvider* provider, QString dir_path,
+ bool deep_scan, const CompatibilityList& compatibility_list)
+ : vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan),
compatibility_list(compatibility_list) {}
GameListWorker::~GameListWorker() = default;
-void GameListWorker::AddInstalledTitlesToGameList() {
- const auto cache = Service::FileSystem::GetUnionContents();
- const auto installed_games = cache.ListEntriesFilter(FileSys::TitleType::Application,
- FileSys::ContentRecordType::Program);
+void GameListWorker::AddTitlesToGameList() {
+ const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>(
+ Core::System::GetInstance().GetContentProvider());
+ const auto installed_games = cache.ListEntriesFilterOrigin(
+ std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
- for (const auto& game : installed_games) {
- const auto file = cache.GetEntryUnparsed(game);
+ for (const auto& [slot, game] : installed_games) {
+ if (slot == FileSys::ContentProviderUnionSlot::FrontendManual)
+ continue;
+
+ const auto file = cache.GetEntryUnparsed(game.title_id, game.type);
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
if (!loader)
continue;
@@ -150,45 +158,13 @@ void GameListWorker::AddInstalledTitlesToGameList() {
emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
compatibility_list, patch));
}
-
- const auto control_data = cache.ListEntriesFilter(FileSys::TitleType::Application,
- FileSys::ContentRecordType::Control);
-
- for (const auto& entry : control_data) {
- auto nca = cache.GetEntry(entry);
- if (nca != nullptr) {
- nca_control_map.insert_or_assign(entry.title_id, std::move(nca));
- }
- }
}
-void GameListWorker::FillControlMap(const std::string& dir_path) {
- const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
- if (stop_processing) {
- // Breaks the callback loop
- return false;
- }
-
- const std::string physical_name = directory + DIR_SEP + virtual_name;
- const QFileInfo file_info(QString::fromStdString(physical_name));
- if (!file_info.isDir() && file_info.suffix() == QStringLiteral("nca")) {
- auto nca =
- std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
- if (nca->GetType() == FileSys::NCAContentType::Control) {
- const u64 title_id = nca->GetTitleId();
- nca_control_map.insert_or_assign(title_id, std::move(nca));
- }
- }
- return true;
- };
-
- FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
-}
-
-void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
- const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
+void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path,
+ unsigned int recursion) {
+ const auto callback = [this, target, recursion](u64* num_entries_out,
+ const std::string& directory,
+ const std::string& virtual_name) -> bool {
if (stop_processing) {
// Breaks the callback loop.
return false;
@@ -198,7 +174,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
const bool is_dir = FileUtil::IsDirectory(physical_name);
if (!is_dir &&
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
- auto loader = Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
+ const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
+ auto loader = Loader::GetLoader(file);
if (!loader) {
return true;
}
@@ -209,31 +186,42 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
return true;
}
- std::vector<u8> icon;
- const auto res1 = loader->ReadIcon(icon);
-
u64 program_id = 0;
const auto res2 = loader->ReadProgramId(program_id);
- std::string name = " ";
- const auto res3 = loader->ReadTitle(name);
+ if (target == ScanTarget::FillManualContentProvider) {
+ if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
+ provider->AddEntry(FileSys::TitleType::Application,
+ FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
+ program_id, file);
+ } else if (res2 == Loader::ResultStatus::Success &&
+ (file_type == Loader::FileType::XCI ||
+ file_type == Loader::FileType::NSP)) {
+ const auto nsp = file_type == Loader::FileType::NSP
+ ? std::make_shared<FileSys::NSP>(file)
+ : FileSys::XCI{file}.GetSecurePartitionNSP();
+ for (const auto& title : nsp->GetNCAs()) {
+ for (const auto& entry : title.second) {
+ provider->AddEntry(entry.first.first, entry.first.second, title.first,
+ entry.second->GetBaseFile());
+ }
+ }
+ }
+ } else {
+ std::vector<u8> icon;
+ const auto res1 = loader->ReadIcon(icon);
- const FileSys::PatchManager patch{program_id};
+ std::string name = " ";
+ const auto res3 = loader->ReadTitle(name);
- if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
- res2 == Loader::ResultStatus::Success) {
- // Use from metadata pool.
- if (nca_control_map.find(program_id) != nca_control_map.end()) {
- const auto& nca = nca_control_map[program_id];
- GetMetadataFromControlNCA(patch, *nca, icon, name);
- }
- }
+ const FileSys::PatchManager patch{program_id};
- emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
- compatibility_list, patch));
+ emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
+ compatibility_list, patch));
+ }
} else if (is_dir && recursion > 0) {
watch_list.append(QString::fromStdString(physical_name));
- AddFstEntriesToGameList(physical_name, recursion - 1);
+ ScanFileSystem(target, physical_name, recursion - 1);
}
return true;
@@ -245,10 +233,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
void GameListWorker::run() {
stop_processing = false;
watch_list.append(dir_path);
- FillControlMap(dir_path.toStdString());
- AddInstalledTitlesToGameList();
- AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
- nca_control_map.clear();
+ provider->ClearAllEntries();
+ ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(),
+ deep_scan ? 256 : 0);
+ AddTitlesToGameList();
+ ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0);
emit Finished(watch_list);
}
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 {
Q_OBJECT
public:
- GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
+ GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs,
+ FileSys::ManualContentProvider* provider, QString dir_path, bool deep_scan,
const CompatibilityList& compatibility_list);
~GameListWorker() override;
@@ -58,12 +59,17 @@ signals:
void Finished(QStringList watch_list);
private:
- void AddInstalledTitlesToGameList();
- void FillControlMap(const std::string& dir_path);
- void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
+ void AddTitlesToGameList();
+
+ enum class ScanTarget {
+ FillManualContentProvider,
+ PopulateGameList,
+ };
+
+ void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0);
std::shared_ptr<FileSys::VfsFilesystem> vfs;
- std::map<u64, std::unique_ptr<FileSys::NCA>> nca_control_map;
+ FileSys::ManualContentProvider* provider;
QStringList watch_list;
QString dir_path;
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 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <map>
#include <QKeySequence>
#include <QShortcut>
#include <QTreeWidgetItem>
@@ -13,47 +12,32 @@
HotkeyRegistry::HotkeyRegistry() = default;
HotkeyRegistry::~HotkeyRegistry() = default;
-void HotkeyRegistry::LoadHotkeys() {
- // Make sure NOT to use a reference here because it would become invalid once we call
- // beginGroup()
- for (auto shortcut : UISettings::values.shortcuts) {
- const QStringList cat = shortcut.first.split('/');
- Q_ASSERT(cat.size() >= 2);
-
- // RegisterHotkey assigns default keybindings, so use old values as default parameters
- Hotkey& hk = hotkey_groups[cat[0]][cat[1]];
- if (!shortcut.second.first.isEmpty()) {
- hk.keyseq = QKeySequence::fromString(shortcut.second.first);
- hk.context = static_cast<Qt::ShortcutContext>(shortcut.second.second);
- }
- if (hk.shortcut)
- hk.shortcut->setKey(hk.keyseq);
- }
-}
-
void HotkeyRegistry::SaveHotkeys() {
UISettings::values.shortcuts.clear();
for (const auto& group : hotkey_groups) {
for (const auto& hotkey : group.second) {
- UISettings::values.shortcuts.emplace_back(
- UISettings::Shortcut(group.first + '/' + hotkey.first,
- UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
- hotkey.second.context)));
+ UISettings::values.shortcuts.push_back(
+ {hotkey.first, group.first,
+ UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
+ hotkey.second.context)});
}
}
}
-void HotkeyRegistry::RegisterHotkey(const QString& group, const QString& action,
- const QKeySequence& default_keyseq,
- Qt::ShortcutContext default_context) {
- auto& hotkey_group = hotkey_groups[group];
- if (hotkey_group.find(action) != hotkey_group.end()) {
- return;
+void HotkeyRegistry::LoadHotkeys() {
+ // Make sure NOT to use a reference here because it would become invalid once we call
+ // beginGroup()
+ for (auto shortcut : UISettings::values.shortcuts) {
+ Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
+ if (!shortcut.shortcut.first.isEmpty()) {
+ hk.keyseq = QKeySequence::fromString(shortcut.shortcut.first, QKeySequence::NativeText);
+ hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.second);
+ }
+ if (hk.shortcut) {
+ hk.shortcut->disconnect();
+ hk.shortcut->setKey(hk.keyseq);
+ }
}
-
- auto& hotkey_action = hotkey_groups[group][action];
- hotkey_action.keyseq = default_keyseq;
- hotkey_action.context = default_context;
}
QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) {
@@ -65,24 +49,11 @@ QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action
return hk.shortcut;
}
-GHotkeysDialog::GHotkeysDialog(QWidget* parent) : QWidget(parent) {
- ui.setupUi(this);
+QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) {
+ return hotkey_groups[group][action].keyseq;
}
-void GHotkeysDialog::Populate(const HotkeyRegistry& registry) {
- for (const auto& group : registry.hotkey_groups) {
- QTreeWidgetItem* toplevel_item = new QTreeWidgetItem(QStringList(group.first));
- for (const auto& hotkey : group.second) {
- QStringList columns;
- columns << hotkey.first << hotkey.second.keyseq.toString();
- QTreeWidgetItem* item = new QTreeWidgetItem(columns);
- toplevel_item->addChild(item);
- }
- ui.treeWidget->addTopLevelItem(toplevel_item);
- }
- // TODO: Make context configurable as well (hiding the column for now)
- ui.treeWidget->setColumnCount(2);
-
- ui.treeWidget->resizeColumnToContents(0);
- ui.treeWidget->resizeColumnToContents(1);
+Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group,
+ const QString& action) {
+ return hotkey_groups[group][action].context;
}
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 @@
#pragma once
#include <map>
-#include "ui_hotkeys.h"
class QDialog;
class QKeySequence;
@@ -14,7 +13,7 @@ class QShortcut;
class HotkeyRegistry final {
public:
- friend class GHotkeysDialog;
+ friend class ConfigureHotkeys;
explicit HotkeyRegistry();
~HotkeyRegistry();
@@ -49,22 +48,27 @@ public:
QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
/**
- * Register a hotkey.
+ * Returns a QKeySequence object whose signal can be connected to QAction::setShortcut.
*
- * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger")
- * @param action Name of the action (e.g. "Start Emulation", "Load Image")
- * @param default_keyseq Default key sequence to assign if the hotkey wasn't present in the
- * settings file before
- * @param default_context Default context to assign if the hotkey wasn't present in the settings
- * file before
- * @warning Both the group and action strings will be displayed in the hotkey settings dialog
+ * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger").
+ * @param action Name of the action (e.g. "Start Emulation", "Load Image").
+ */
+ QKeySequence GetKeySequence(const QString& group, const QString& action);
+
+ /**
+ * Returns a Qt::ShortcutContext object who can be connected to other
+ * QAction::setShortcutContext.
+ *
+ * @param group General group this shortcut context belongs to (e.g. "Main Window",
+ * "Debugger").
+ * @param action Name of the action (e.g. "Start Emulation", "Load Image").
*/
- void RegisterHotkey(const QString& group, const QString& action,
- const QKeySequence& default_keyseq = {},
- Qt::ShortcutContext default_context = Qt::WindowShortcut);
+ Qt::ShortcutContext GetShortcutContext(const QString& group, const QString& action);
private:
struct Hotkey {
+ Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {}
+
QKeySequence keyseq;
QShortcut* shortcut = nullptr;
Qt::ShortcutContext context = Qt::WindowShortcut;
@@ -75,15 +79,3 @@ private:
HotkeyGroupMap hotkey_groups;
};
-
-class GHotkeysDialog : public QWidget {
- Q_OBJECT
-
-public:
- explicit GHotkeysDialog(QWidget* parent = nullptr);
-
- void Populate(const HotkeyRegistry& registry);
-
-private:
- Ui::hotkeys ui;
-};
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 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>hotkeys</class>
- <widget class="QWidget" name="hotkeys">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>363</width>
- <height>388</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>Hotkey Settings</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QTreeWidget" name="treeWidget">
- <property name="selectionBehavior">
- <enum>QAbstractItemView::SelectItems</enum>
- </property>
- <property name="headerHidden">
- <bool>false</bool>
- </property>
- <column>
- <property name="text">
- <string>Action</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Hotkey</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Context</string>
- </property>
- </column>
- </widget>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</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
}
// update labels and progress bar
- ui->stage->setText(stage_translations[stage].arg(value).arg(total));
+ if (stage == VideoCore::LoadCallbackStage::Decompile ||
+ stage == VideoCore::LoadCallbackStage::Build) {
+ ui->stage->setText(stage_translations[stage].arg(value).arg(total));
+ } else {
+ ui->stage->setText(stage_translations[stage]);
+ }
ui->value->setText(estimate);
ui->progress_bar->setValue(static_cast<int>(value));
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() {
GMainWindow::GMainWindow()
: config(new Config()), emu_thread(nullptr),
- vfs(std::make_shared<FileSys::RealVfsFilesystem>()) {
+ vfs(std::make_shared<FileSys::RealVfsFilesystem>()),
+ provider(std::make_unique<FileSys::ManualContentProvider>()) {
InitializeLogging();
debug_context = Tegra::DebugContext::Construct();
@@ -203,11 +204,15 @@ GMainWindow::GMainWindow()
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
show();
+ Core::System::GetInstance().SetContentProvider(
+ std::make_unique<FileSys::ContentProviderUnion>());
+ Core::System::GetInstance().RegisterContentProvider(
+ FileSys::ContentProviderUnionSlot::FrontendManual, provider.get());
+ Service::FileSystem::CreateFactories(*vfs);
+
// Gen keys if necessary
OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
- // Necessary to load titles from nand in gamelist.
- Service::FileSystem::CreateFactories(*vfs);
game_list->LoadCompatibilityList();
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
@@ -419,7 +424,7 @@ void GMainWindow::InitializeWidgets() {
render_window = new GRenderWindow(this, emu_thread.get());
render_window->hide();
- game_list = new GameList(vfs, this);
+ game_list = new GameList(vfs, provider.get(), this);
ui.horizontalLayout->addWidget(game_list);
loading_screen = new LoadingScreen(this);
@@ -514,33 +519,34 @@ void GMainWindow::InitializeRecentFileMenuActions() {
}
void GMainWindow::InitializeHotkeys() {
- hotkey_registry.RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
- hotkey_registry.RegisterHotkey("Main Window", "Start Emulation");
- hotkey_registry.RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4));
- hotkey_registry.RegisterHotkey("Main Window", "Restart", QKeySequence(Qt::Key_F5));
- hotkey_registry.RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen);
- hotkey_registry.RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Increase Speed Limit", QKeySequence("+"),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot",
- QKeySequence(QKeySequence::Print));
- hotkey_registry.RegisterHotkey("Main Window", "Change Docked Mode", QKeySequence(Qt::Key_F10));
-
hotkey_registry.LoadHotkeys();
+ ui.action_Load_File->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Load File"));
+ ui.action_Load_File->setShortcutContext(
+ hotkey_registry.GetShortcutContext("Main Window", "Load File"));
+
+ ui.action_Exit->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Exit yuzu"));
+ ui.action_Exit->setShortcutContext(
+ hotkey_registry.GetShortcutContext("Main Window", "Exit yuzu"));
+
+ ui.action_Stop->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Stop Emulation"));
+ ui.action_Stop->setShortcutContext(
+ hotkey_registry.GetShortcutContext("Main Window", "Stop Emulation"));
+
+ ui.action_Show_Filter_Bar->setShortcut(
+ hotkey_registry.GetKeySequence("Main Window", "Toggle Filter Bar"));
+ ui.action_Show_Filter_Bar->setShortcutContext(
+ hotkey_registry.GetShortcutContext("Main Window", "Toggle Filter Bar"));
+
+ ui.action_Show_Status_Bar->setShortcut(
+ hotkey_registry.GetKeySequence("Main Window", "Toggle Status Bar"));
+ ui.action_Show_Status_Bar->setShortcutContext(
+ hotkey_registry.GetShortcutContext("Main Window", "Toggle Status Bar"));
+
connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
this, &GMainWindow::OnMenuLoadFile);
- connect(hotkey_registry.GetHotkey("Main Window", "Start Emulation", this),
- &QShortcut::activated, this, &GMainWindow::OnStartGame);
- connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause", this), &QShortcut::activated,
- this, [&] {
+ connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause Emulation", this),
+ &QShortcut::activated, this, [&] {
if (emulation_running) {
if (emu_thread->IsRunning()) {
OnPauseGame();
@@ -549,8 +555,8 @@ void GMainWindow::InitializeHotkeys() {
}
}
});
- connect(hotkey_registry.GetHotkey("Main Window", "Restart", this), &QShortcut::activated, this,
- [this] {
+ connect(hotkey_registry.GetHotkey("Main Window", "Restart Emulation", this),
+ &QShortcut::activated, this, [this] {
if (!Core::System::GetInstance().IsPoweredOn())
return;
BootGame(QString(game_path));
@@ -697,7 +703,6 @@ void GMainWindow::ConnectMenuEvents() {
&GMainWindow::ToggleWindowMode);
connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this,
&GMainWindow::OnDisplayTitleBars);
- ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F"));
connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar);
connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
@@ -1179,7 +1184,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- const auto installed = Service::FileSystem::GetUnionContents();
+ const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id);
if (!romfs_title_id) {
@@ -1662,6 +1667,7 @@ void GMainWindow::OnConfigure() {
auto result = configureDialog.exec();
if (result == QDialog::Accepted) {
configureDialog.applyConfiguration();
+ InitializeHotkeys();
if (UISettings::values.theme != old_theme)
UpdateUITheme();
if (UISettings::values.enable_discord_presence != old_discord_presence)
@@ -1924,14 +1930,14 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
}
}
-std::optional<u64> GMainWindow::SelectRomFSDumpTarget(
- const FileSys::RegisteredCacheUnion& installed, u64 program_id) {
+std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed,
+ u64 program_id) {
const auto dlc_entries =
installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
- std::vector<FileSys::RegisteredCacheEntry> dlc_match;
+ std::vector<FileSys::ContentProviderEntry> dlc_match;
dlc_match.reserve(dlc_entries.size());
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
- [&program_id, &installed](const FileSys::RegisteredCacheEntry& entry) {
+ [&program_id, &installed](const FileSys::ContentProviderEntry& entry) {
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id &&
installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
});
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;
} // namespace Core::Frontend
namespace FileSys {
-class RegisteredCacheUnion;
+class ContentProvider;
+class ManualContentProvider;
class VfsFilesystem;
} // namespace FileSys
@@ -120,7 +121,6 @@ private:
void InitializeWidgets();
void InitializeDebugWidgets();
void InitializeRecentFileMenuActions();
- void InitializeHotkeys();
void SetDefaultUIGeometry();
void RestoreUIState();
@@ -196,6 +196,7 @@ private slots:
void OnAbout();
void OnToggleFilterBar();
void OnDisplayTitleBars(bool);
+ void InitializeHotkeys();
void ToggleFullscreen();
void ShowFullscreen();
void HideFullscreen();
@@ -205,7 +206,7 @@ private slots:
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
private:
- std::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&, u64 program_id);
+ std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
void UpdateStatusBar();
Ui::MainWindow ui;
@@ -233,6 +234,7 @@ private:
// FS
std::shared_ptr<FileSys::VfsFilesystem> vfs;
+ std::unique_ptr<FileSys::ManualContentProvider> provider;
// Debugger panes
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{{
}};
Values values = {};
-
} // 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 @@
namespace UISettings {
using ContextualShortcut = std::pair<QString, int>;
-using Shortcut = std::pair<QString, ContextualShortcut>;
+
+struct Shortcut {
+ QString name;
+ QString group;
+ ContextualShortcut shortcut;
+};
using Themes = std::array<std::pair<const char*, const char*>, 2>;
extern 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 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QDialogButtonBox>
+#include <QKeySequenceEdit>
+#include <QVBoxLayout>
+#include "yuzu/util/sequence_dialog/sequence_dialog.h"
+
+SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) {
+ setWindowTitle(tr("Enter a hotkey"));
+ auto* layout = new QVBoxLayout(this);
+ key_sequence = new QKeySequenceEdit;
+ layout->addWidget(key_sequence);
+ auto* buttons =
+ new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
+ buttons->setCenterButtons(true);
+ layout->addWidget(buttons);
+ connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+}
+
+SequenceDialog::~SequenceDialog() = default;
+
+QKeySequence SequenceDialog::GetSequence() const {
+ // Only the first key is returned. The other 3, if present, are ignored.
+ return QKeySequence(key_sequence->keySequence()[0]);
+}
+
+bool SequenceDialog::focusNextPrevChild(bool next) {
+ return false;
+}
+
+void SequenceDialog::closeEvent(QCloseEvent*) {
+ reject();
+}
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 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QDialog>
+
+class QKeySequenceEdit;
+
+class SequenceDialog : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit SequenceDialog(QWidget* parent = nullptr);
+ ~SequenceDialog() override;
+
+ QKeySequence GetSequence() const;
+ void closeEvent(QCloseEvent*) override;
+
+private:
+ QKeySequenceEdit* key_sequence;
+ bool focusNextPrevChild(bool next) override;
+};
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 @@
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
#include <getopt.h>
+#include "core/file_sys/registered_cache.h"
#ifndef _MSC_VER
#include <unistd.h>
#endif
@@ -178,6 +179,7 @@ int main(int argc, char** argv) {
}
Core::System& system{Core::System::GetInstance()};
+ system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
Service::FileSystem::CreateFactories(*system.GetFilesystem());