diff options
77 files changed, 2021 insertions, 1177 deletions
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/kernel/client_port.h b/src/core/hle/kernel/client_port.h index 6cd607206..4921ad4f0 100644 --- a/src/core/hle/kernel/client_port.h +++ b/src/core/hle/kernel/client_port.h @@ -25,7 +25,7 @@ public: return name; } - static const HandleType HANDLE_TYPE = HandleType::ClientPort; + static constexpr HandleType HANDLE_TYPE = HandleType::ClientPort; HandleType GetHandleType() const override { return HANDLE_TYPE; } diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h index b1f39aad7..09cdff588 100644 --- a/src/core/hle/kernel/client_session.h +++ b/src/core/hle/kernel/client_session.h @@ -29,7 +29,7 @@ public: return name; } - static const HandleType HANDLE_TYPE = HandleType::ClientSession; + static constexpr HandleType HANDLE_TYPE = HandleType::ClientSession; HandleType GetHandleType() const override { return HANDLE_TYPE; } diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 26c6b95ab..4e94048da 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -106,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/process.h b/src/core/hle/kernel/process.h index f060f2a3b..dda52f4c0 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -85,7 +85,7 @@ public: return name; } - static const HandleType HANDLE_TYPE = HandleType::Process; + static constexpr HandleType HANDLE_TYPE = HandleType::Process; HandleType GetHandleType() const override { return HANDLE_TYPE; } diff --git a/src/core/hle/kernel/readable_event.h b/src/core/hle/kernel/readable_event.h index 2eb9dcbb7..84215f572 100644 --- a/src/core/hle/kernel/readable_event.h +++ b/src/core/hle/kernel/readable_event.h @@ -31,7 +31,7 @@ public: return reset_type; } - static const HandleType HANDLE_TYPE = HandleType::ReadableEvent; + static constexpr HandleType HANDLE_TYPE = HandleType::ReadableEvent; HandleType GetHandleType() const override { return HANDLE_TYPE; } diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h index 70e09858a..2613a6bb5 100644 --- a/src/core/hle/kernel/resource_limit.h +++ b/src/core/hle/kernel/resource_limit.h @@ -41,7 +41,7 @@ public: return GetTypeName(); } - static const HandleType HANDLE_TYPE = HandleType::ResourceLimit; + static constexpr HandleType HANDLE_TYPE = HandleType::ResourceLimit; HandleType GetHandleType() const override { return HANDLE_TYPE; } diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index fef573b71..dc88a1ebd 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -43,7 +43,7 @@ public: return name; } - static const HandleType HANDLE_TYPE = HandleType::ServerPort; + static constexpr HandleType HANDLE_TYPE = HandleType::ServerPort; HandleType GetHandleType() const override { return HANDLE_TYPE; } diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index a6b2cf06a..696a82cd9 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -28,11 +28,9 @@ ServerSession::~ServerSession() { // the emulated application. // Decrease the port's connection count. - if (parent->port) + if (parent->port) { parent->port->ConnectionClosed(); - - // TODO(Subv): Wake up all the ClientSession's waiting threads and set - // the SendSyncRequest result to 0xC920181A. + } parent->server = nullptr; } @@ -74,9 +72,6 @@ void ServerSession::ClientDisconnected() { handler->ClientDisconnected(this); } - // TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set - // their WaitSynchronization result to 0xC920181A. - // Clean up the list of client threads with pending requests, they are unneeded now that the // client endpoint is closed. pending_requesting_threads.clear(); diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index 09b835ff8..738df30f8 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -46,7 +46,7 @@ public: return name; } - static const HandleType HANDLE_TYPE = HandleType::ServerSession; + static constexpr HandleType HANDLE_TYPE = HandleType::ServerSession; HandleType GetHandleType() const override { return HANDLE_TYPE; } diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h index 37e18c443..c2b6155e1 100644 --- a/src/core/hle/kernel/shared_memory.h +++ b/src/core/hle/kernel/shared_memory.h @@ -76,7 +76,7 @@ public: return name; } - static const HandleType HANDLE_TYPE = HandleType::SharedMemory; + static constexpr HandleType HANDLE_TYPE = HandleType::SharedMemory; HandleType GetHandleType() const override { return HANDLE_TYPE; } diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 73e5d1bb4..83c83e45a 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -106,7 +106,7 @@ public: return "Thread"; } - static const HandleType HANDLE_TYPE = HandleType::Thread; + static constexpr HandleType HANDLE_TYPE = HandleType::Thread; HandleType GetHandleType() const override { return HANDLE_TYPE; } diff --git a/src/core/hle/kernel/writable_event.h b/src/core/hle/kernel/writable_event.h index c9068dd3d..d00c92a6b 100644 --- a/src/core/hle/kernel/writable_event.h +++ b/src/core/hle/kernel/writable_event.h @@ -37,7 +37,7 @@ public: return name; } - static const HandleType HANDLE_TYPE = HandleType::WritableEvent; + static constexpr HandleType HANDLE_TYPE = HandleType::WritableEvent; HandleType GetHandleType() const override { return HANDLE_TYPE; } 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/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 242a0d1cd..5c8ca429e 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -106,6 +106,8 @@ add_library(video_core STATIC textures/decoders.cpp textures/decoders.h textures/texture.h + texture_cache.cpp + texture_cache.h video_core.cpp video_core.h ) diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index 8b1bea1ae..046d047cb 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -8,6 +8,7 @@ #include "video_core/dma_pusher.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/gpu.h" +#include "video_core/memory_manager.h" namespace Tegra { 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/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index 7989ec11b..25652e794 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -7,6 +7,7 @@ #include "common/alignment.h" #include "core/core.h" +#include "video_core/memory_manager.h" #include "video_core/renderer_opengl/gl_buffer_cache.h" #include "video_core/renderer_opengl/gl_rasterizer.h" diff --git a/src/video_core/renderer_opengl/gl_global_cache.cpp b/src/video_core/renderer_opengl/gl_global_cache.cpp index 5842d6213..8d9ee81f1 100644 --- a/src/video_core/renderer_opengl/gl_global_cache.cpp +++ b/src/video_core/renderer_opengl/gl_global_cache.cpp @@ -6,6 +6,7 @@ #include "common/logging/log.h" #include "core/core.h" +#include "video_core/memory_manager.h" #include "video_core/renderer_opengl/gl_global_cache.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 7ff1e6737..d250d5cbb 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -299,6 +299,10 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { BaseBindings base_bindings; std::array<bool, Maxwell::NumClipDistances> clip_distances{}; + // Prepare packed bindings + bind_ubo_pushbuffer.Setup(base_bindings.cbuf); + bind_ssbo_pushbuffer.Setup(base_bindings.gmem); + for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { const auto& shader_config = gpu.regs.shader_config[index]; const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)}; @@ -321,8 +325,8 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { &ubo, sizeof(ubo), static_cast<std::size_t>(uniform_buffer_alignment)); // Bind the emulation info buffer - glBindBufferRange(GL_UNIFORM_BUFFER, base_bindings.cbuf, buffer_cache.GetHandle(), offset, - static_cast<GLsizeiptr>(sizeof(ubo))); + bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), offset, + static_cast<GLsizeiptr>(sizeof(ubo))); Shader shader{shader_cache.GetStageProgram(program)}; const auto [program_handle, next_bindings] = @@ -366,6 +370,9 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { base_bindings = next_bindings; } + bind_ubo_pushbuffer.Bind(); + bind_ssbo_pushbuffer.Bind(); + SyncClipEnabled(clip_distances); gpu.dirty_flags.shaders = false; @@ -900,23 +907,14 @@ void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::Shader const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<std::size_t>(stage)]; const auto& entries = shader->GetShaderEntries().const_buffers; - constexpr u64 max_binds = Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers; - std::array<GLuint, max_binds> bind_buffers; - std::array<GLintptr, max_binds> bind_offsets; - std::array<GLsizeiptr, max_binds> bind_sizes; - - ASSERT_MSG(entries.size() <= max_binds, "Exceeded expected number of binding points."); - // Upload only the enabled buffers from the 16 constbuffers of each shader stage for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { const auto& used_buffer = entries[bindpoint]; const auto& buffer = shader_stage.const_buffers[used_buffer.GetIndex()]; if (!buffer.enabled) { - // With disabled buffers set values as zero to unbind them - bind_buffers[bindpoint] = 0; - bind_offsets[bindpoint] = 0; - bind_sizes[bindpoint] = 0; + // Set values to zero to unbind buffers + bind_ubo_pushbuffer.Push(0, 0, 0); continue; } @@ -944,30 +942,19 @@ void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::Shader const GLintptr const_buffer_offset = buffer_cache.UploadMemory( buffer.address, size, static_cast<std::size_t>(uniform_buffer_alignment)); - // Prepare values for multibind - bind_buffers[bindpoint] = buffer_cache.GetHandle(); - bind_offsets[bindpoint] = const_buffer_offset; - bind_sizes[bindpoint] = size; + bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), const_buffer_offset, size); } - - // The first binding is reserved for emulation values - const GLuint ubo_base_binding = base_bindings.cbuf + 1; - glBindBuffersRange(GL_UNIFORM_BUFFER, ubo_base_binding, static_cast<GLsizei>(entries.size()), - bind_buffers.data(), bind_offsets.data(), bind_sizes.data()); } void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, const Shader& shader, GLenum primitive_mode, BaseBindings base_bindings) { - // TODO(Rodrigo): Use ARB_multi_bind here const auto& entries = shader->GetShaderEntries().global_memory_entries; - - for (u32 bindpoint = 0; bindpoint < static_cast<u32>(entries.size()); ++bindpoint) { - const auto& entry = entries[bindpoint]; - const u32 current_bindpoint = base_bindings.gmem + bindpoint; - const auto& region = global_cache.GetGlobalRegion(entry, stage); - - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, current_bindpoint, region->GetBufferHandle()); + for (std::size_t bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { + const auto& entry{entries[bindpoint]}; + const auto& region{global_cache.GetGlobalRegion(entry, stage)}; + bind_ssbo_pushbuffer.Push(region->GetBufferHandle(), 0, + static_cast<GLsizeiptr>(region->GetSizeInBytes())); } } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 54fbf48aa..e4c64ae71 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -28,6 +28,7 @@ #include "video_core/renderer_opengl/gl_shader_cache.h" #include "video_core/renderer_opengl/gl_shader_manager.h" #include "video_core/renderer_opengl/gl_state.h" +#include "video_core/renderer_opengl/utils.h" namespace Core { class System; @@ -229,6 +230,9 @@ private: PrimitiveAssembler primitive_assembler{buffer_cache}; GLint uniform_buffer_alignment; + BindBuffersRangePushBuffer bind_ubo_pushbuffer{GL_UNIFORM_BUFFER}; + BindBuffersRangePushBuffer bind_ssbo_pushbuffer{GL_SHADER_STORAGE_BUFFER}; + std::size_t CalculateVertexArraysSize() const; std::size_t CalculateIndexBufferSize() const; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 7a3280620..55b6d8591 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -15,6 +15,7 @@ #include "core/hle/kernel/process.h" #include "core/settings.h" #include "video_core/engines/maxwell_3d.h" +#include "video_core/memory_manager.h" #include "video_core/morton.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" @@ -265,6 +266,10 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only, params.component_type = ComponentTypeFromRenderTarget(config.format); params.type = GetFormatType(params.pixel_format); params.width = config.width; + if (!params.is_tiled) { + const u32 bpp = params.GetFormatBpp() / 8; + params.pitch = config.width * bpp; + } params.height = config.height; params.unaligned_height = config.height; params.target = SurfaceTarget::Texture2D; @@ -661,8 +666,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); @@ -707,8 +712,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)); @@ -1174,10 +1179,16 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, return new_surface; } + const bool old_compressed = + GetFormatTuple(old_params.pixel_format, old_params.component_type).compressed; + const bool new_compressed = + GetFormatTuple(new_params.pixel_format, new_params.component_type).compressed; + const bool compatible_formats = + GetFormatBpp(old_params.pixel_format) == GetFormatBpp(new_params.pixel_format) && + !(old_compressed || new_compressed); // For compatible surfaces, we can just do fast glCopyImageSubData based copy - if (old_params.target == new_params.target && old_params.type == new_params.type && - old_params.depth == new_params.depth && old_params.depth == 1 && - GetFormatBpp(old_params.pixel_format) == GetFormatBpp(new_params.pixel_format)) { + if (old_params.target == new_params.target && old_params.depth == new_params.depth && + old_params.depth == 1 && compatible_formats) { FastCopySurface(old_surface, new_surface); return new_surface; } @@ -1192,7 +1203,7 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, case SurfaceTarget::TextureCubemap: case SurfaceTarget::Texture2DArray: case SurfaceTarget::TextureCubeArray: - if (old_params.pixel_format == new_params.pixel_format) + if (compatible_formats) FastLayeredCopySurface(old_surface, new_surface); else { AccurateCopySurface(old_surface, new_surface); 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 ab381932c..99f67494c 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -7,6 +7,7 @@ #include "common/hash.h" #include "core/core.h" #include "video_core/engines/maxwell_3d.h" +#include "video_core/memory_manager.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_cache.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 3ea08ef7b..28e490b3c 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -552,8 +552,7 @@ private: } else if (std::holds_alternative<OperationNode>(*offset)) { // Indirect access const std::string final_offset = code.GenerateTemporary(); - code.AddLine("uint " + final_offset + " = (ftou(" + Visit(offset) + ") / 4) & " + - std::to_string(MAX_CONSTBUFFER_ELEMENTS - 1) + ';'); + code.AddLine("uint " + final_offset + " = (ftou(" + Visit(offset) + ") / 4);"); return fmt::format("{}[{} / 4][{} % 4]", GetConstBuffer(cbuf->GetIndex()), final_offset, final_offset); diff --git a/src/video_core/renderer_opengl/utils.cpp b/src/video_core/renderer_opengl/utils.cpp index d84634cb3..84a987371 100644 --- a/src/video_core/renderer_opengl/utils.cpp +++ b/src/video_core/renderer_opengl/utils.cpp @@ -5,11 +5,39 @@ #include <string> #include <fmt/format.h> #include <glad/glad.h> +#include "common/assert.h" #include "common/common_types.h" #include "video_core/renderer_opengl/utils.h" namespace OpenGL { +BindBuffersRangePushBuffer::BindBuffersRangePushBuffer(GLenum target) : target{target} {} + +BindBuffersRangePushBuffer::~BindBuffersRangePushBuffer() = default; + +void BindBuffersRangePushBuffer::Setup(GLuint first_) { + first = first_; + buffers.clear(); + offsets.clear(); + sizes.clear(); +} + +void BindBuffersRangePushBuffer::Push(GLuint buffer, GLintptr offset, GLsizeiptr size) { + buffers.push_back(buffer); + offsets.push_back(offset); + sizes.push_back(size); +} + +void BindBuffersRangePushBuffer::Bind() const { + const std::size_t count{buffers.size()}; + DEBUG_ASSERT(count == offsets.size() && count == sizes.size()); + if (count == 0) { + return; + } + glBindBuffersRange(target, first, static_cast<GLsizei>(count), buffers.data(), offsets.data(), + sizes.data()); +} + void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string extra_info) { if (!GLAD_GL_KHR_debug) { return; // We don't need to throw an error as this is just for debugging diff --git a/src/video_core/renderer_opengl/utils.h b/src/video_core/renderer_opengl/utils.h index 1fcb6fc11..aef45c9dc 100644 --- a/src/video_core/renderer_opengl/utils.h +++ b/src/video_core/renderer_opengl/utils.h @@ -5,11 +5,31 @@ #pragma once #include <string> +#include <vector> #include <glad/glad.h> #include "common/common_types.h" namespace OpenGL { +class BindBuffersRangePushBuffer { +public: + BindBuffersRangePushBuffer(GLenum target); + ~BindBuffersRangePushBuffer(); + + void Setup(GLuint first_); + + void Push(GLuint buffer, GLintptr offset, GLsizeiptr size); + + void Bind() const; + +private: + GLenum target; + GLuint first; + std::vector<GLuint> buffers; + std::vector<GLintptr> offsets; + std::vector<GLsizeiptr> sizes; +}; + void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string extra_info = ""); } // namespace OpenGL
\ No newline at end of file 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/texture_cache.cpp b/src/video_core/texture_cache.cpp new file mode 100644 index 000000000..e96eba7cc --- /dev/null +++ b/src/video_core/texture_cache.cpp @@ -0,0 +1,386 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/alignment.h" +#include "common/assert.h" +#include "common/cityhash.h" +#include "common/common_types.h" +#include "core/core.h" +#include "video_core/surface.h" +#include "video_core/texture_cache.h" +#include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" + +namespace VideoCommon { + +using VideoCore::Surface::SurfaceTarget; + +using VideoCore::Surface::ComponentTypeFromDepthFormat; +using VideoCore::Surface::ComponentTypeFromRenderTarget; +using VideoCore::Surface::ComponentTypeFromTexture; +using VideoCore::Surface::PixelFormatFromDepthFormat; +using VideoCore::Surface::PixelFormatFromRenderTargetFormat; +using VideoCore::Surface::PixelFormatFromTextureFormat; +using VideoCore::Surface::SurfaceTargetFromTextureType; + +constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { + return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); +} + +SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, + const Tegra::Texture::FullTextureInfo& config) { + SurfaceParams params; + params.is_tiled = config.tic.IsTiled(); + params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, + params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, + params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, + params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; + params.pixel_format = + PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), false); + params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); + params.type = GetFormatType(params.pixel_format); + params.target = SurfaceTargetFromTextureType(config.tic.texture_type); + params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format)); + params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format)); + params.depth = config.tic.Depth(); + if (params.target == SurfaceTarget::TextureCubemap || + params.target == SurfaceTarget::TextureCubeArray) { + params.depth *= 6; + } + params.pitch = params.is_tiled ? 0 : config.tic.Pitch(); + params.unaligned_height = config.tic.Height(); + params.num_levels = config.tic.max_mip_level + 1; + + params.CalculateCachedValues(); + return params; +} + +SurfaceParams SurfaceParams::CreateForDepthBuffer( + Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, + u32 block_width, u32 block_height, u32 block_depth, + Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) { + SurfaceParams params; + params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; + params.block_width = 1 << std::min(block_width, 5U); + params.block_height = 1 << std::min(block_height, 5U); + params.block_depth = 1 << std::min(block_depth, 5U); + params.tile_width_spacing = 1; + params.pixel_format = PixelFormatFromDepthFormat(format); + params.component_type = ComponentTypeFromDepthFormat(format); + params.type = GetFormatType(params.pixel_format); + params.width = zeta_width; + params.height = zeta_height; + params.unaligned_height = zeta_height; + params.target = SurfaceTarget::Texture2D; + params.depth = 1; + params.num_levels = 1; + + params.CalculateCachedValues(); + return params; +} + +SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) { + const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; + SurfaceParams params; + params.is_tiled = + config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; + params.block_width = 1 << config.memory_layout.block_width; + params.block_height = 1 << config.memory_layout.block_height; + params.block_depth = 1 << config.memory_layout.block_depth; + params.tile_width_spacing = 1; + params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); + params.component_type = ComponentTypeFromRenderTarget(config.format); + params.type = GetFormatType(params.pixel_format); + if (params.is_tiled) { + params.width = config.width; + } else { + const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT; + params.pitch = config.width; + params.width = params.pitch / bpp; + } + params.height = config.height; + params.depth = 1; + params.unaligned_height = config.height; + params.target = SurfaceTarget::Texture2D; + params.num_levels = 1; + + params.CalculateCachedValues(); + return params; +} + +SurfaceParams SurfaceParams::CreateForFermiCopySurface( + const Tegra::Engines::Fermi2D::Regs::Surface& config) { + SurfaceParams params{}; + params.is_tiled = !config.linear; + params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0, + params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0, + params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0, + params.tile_width_spacing = 1; + params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); + params.component_type = ComponentTypeFromRenderTarget(config.format); + params.type = GetFormatType(params.pixel_format); + params.width = config.width; + params.height = config.height; + params.unaligned_height = config.height; + // TODO(Rodrigo): Try to guess the surface target from depth and layer parameters + params.target = SurfaceTarget::Texture2D; + params.depth = 1; + params.num_levels = 1; + + params.CalculateCachedValues(); + return params; +} + +u32 SurfaceParams::GetMipWidth(u32 level) const { + return std::max(1U, width >> level); +} + +u32 SurfaceParams::GetMipHeight(u32 level) const { + return std::max(1U, height >> level); +} + +u32 SurfaceParams::GetMipDepth(u32 level) const { + return IsLayered() ? depth : std::max(1U, depth >> level); +} + +bool SurfaceParams::IsLayered() const { + switch (target) { + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubeArray: + case SurfaceTarget::TextureCubemap: + return true; + default: + return false; + } +} + +u32 SurfaceParams::GetMipBlockHeight(u32 level) const { + // Auto block resizing algorithm from: + // https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c + if (level == 0) { + return block_height; + } + const u32 height{GetMipHeight(level)}; + const u32 default_block_height{GetDefaultBlockHeight(pixel_format)}; + const u32 blocks_in_y{(height + default_block_height - 1) / default_block_height}; + u32 block_height = 16; + while (block_height > 1 && blocks_in_y <= block_height * 4) { + block_height >>= 1; + } + return block_height; +} + +u32 SurfaceParams::GetMipBlockDepth(u32 level) const { + if (level == 0) + return block_depth; + if (target != SurfaceTarget::Texture3D) + return 1; + + const u32 depth{GetMipDepth(level)}; + u32 block_depth = 32; + while (block_depth > 1 && depth * 2 <= block_depth) { + block_depth >>= 1; + } + if (block_depth == 32 && GetMipBlockHeight(level) >= 4) { + return 16; + } + return block_depth; +} + +std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const { + std::size_t offset = 0; + for (u32 i = 0; i < level; i++) { + offset += GetInnerMipmapMemorySize(i, false, IsLayered(), false); + } + return offset; +} + +std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level) const { + std::size_t offset = 0; + for (u32 i = 0; i < level; i++) { + offset += GetInnerMipmapMemorySize(i, true, false, false); + } + return offset; +} + +std::size_t SurfaceParams::GetGuestLayerSize() const { + return GetInnerMemorySize(false, true, false); +} + +std::size_t SurfaceParams::GetHostLayerSize(u32 level) const { + return GetInnerMipmapMemorySize(level, true, IsLayered(), false); +} + +bool SurfaceParams::IsFamiliar(const SurfaceParams& view_params) const { + if (std::tie(is_tiled, tile_width_spacing, pixel_format, component_type, type) != + std::tie(view_params.is_tiled, view_params.tile_width_spacing, view_params.pixel_format, + view_params.component_type, view_params.type)) { + return false; + } + + const SurfaceTarget view_target{view_params.target}; + if (view_target == target) { + return true; + } + + switch (target) { + case SurfaceTarget::Texture1D: + case SurfaceTarget::Texture2D: + case SurfaceTarget::Texture3D: + return false; + case SurfaceTarget::Texture1DArray: + return view_target == SurfaceTarget::Texture1D; + case SurfaceTarget::Texture2DArray: + return view_target == SurfaceTarget::Texture2D; + case SurfaceTarget::TextureCubemap: + return view_target == SurfaceTarget::Texture2D || + view_target == SurfaceTarget::Texture2DArray; + case SurfaceTarget::TextureCubeArray: + return view_target == SurfaceTarget::Texture2D || + view_target == SurfaceTarget::Texture2DArray || + view_target == SurfaceTarget::TextureCubemap; + default: + UNIMPLEMENTED_MSG("Unimplemented texture family={}", static_cast<u32>(target)); + return false; + } +} + +bool SurfaceParams::IsPixelFormatZeta() const { + return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat && + pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat; +} + +void SurfaceParams::CalculateCachedValues() { + guest_size_in_bytes = GetInnerMemorySize(false, false, false); + + // ASTC is uncompressed in software, in emulated as RGBA8 + if (IsPixelFormatASTC(pixel_format)) { + host_size_in_bytes = width * height * depth * 4; + } else { + host_size_in_bytes = GetInnerMemorySize(true, false, false); + } + + switch (target) { + case SurfaceTarget::Texture1D: + case SurfaceTarget::Texture2D: + case SurfaceTarget::Texture3D: + num_layers = 1; + break; + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubemap: + case SurfaceTarget::TextureCubeArray: + num_layers = depth; + break; + default: + UNREACHABLE(); + } +} + +std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only, + bool uncompressed) const { + const bool tiled{as_host_size ? false : is_tiled}; + const u32 tile_x{GetDefaultBlockWidth(pixel_format)}; + const u32 tile_y{GetDefaultBlockHeight(pixel_format)}; + const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), tile_x)}; + const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), tile_y)}; + const u32 depth{layer_only ? 1U : GetMipDepth(level)}; + return Tegra::Texture::CalculateSize(tiled, GetBytesPerPixel(pixel_format), width, height, + depth, GetMipBlockHeight(level), GetMipBlockDepth(level)); +} + +std::size_t SurfaceParams::GetInnerMemorySize(bool as_host_size, bool layer_only, + bool uncompressed) const { + std::size_t size = 0; + for (u32 level = 0; level < num_levels; ++level) { + size += GetInnerMipmapMemorySize(level, as_host_size, layer_only, uncompressed); + } + if (!as_host_size && is_tiled) { + size = Common::AlignUp(size, Tegra::Texture::GetGOBSize() * block_height * block_depth); + } + return size; +} + +std::map<u64, std::pair<u32, u32>> SurfaceParams::CreateViewOffsetMap() const { + std::map<u64, std::pair<u32, u32>> view_offset_map; + switch (target) { + case SurfaceTarget::Texture1D: + case SurfaceTarget::Texture2D: + case SurfaceTarget::Texture3D: { + constexpr u32 layer = 0; + for (u32 level = 0; level < num_levels; ++level) { + const std::size_t offset{GetGuestMipmapLevelOffset(level)}; + view_offset_map.insert({offset, {layer, level}}); + } + break; + } + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubemap: + case SurfaceTarget::TextureCubeArray: { + const std::size_t layer_size{GetGuestLayerSize()}; + for (u32 level = 0; level < num_levels; ++level) { + const std::size_t level_offset{GetGuestMipmapLevelOffset(level)}; + for (u32 layer = 0; layer < num_layers; ++layer) { + const auto layer_offset{static_cast<std::size_t>(layer_size * layer)}; + const std::size_t offset{level_offset + layer_offset}; + view_offset_map.insert({offset, {layer, level}}); + } + } + break; + } + default: + UNIMPLEMENTED_MSG("Unimplemented surface target {}", static_cast<u32>(target)); + } + return view_offset_map; +} + +bool SurfaceParams::IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const { + return IsDimensionValid(view_params, level) && IsDepthValid(view_params, level) && + IsInBounds(view_params, layer, level); +} + +bool SurfaceParams::IsDimensionValid(const SurfaceParams& view_params, u32 level) const { + return view_params.width == GetMipWidth(level) && view_params.height == GetMipHeight(level); +} + +bool SurfaceParams::IsDepthValid(const SurfaceParams& view_params, u32 level) const { + if (view_params.target != SurfaceTarget::Texture3D) { + return true; + } + return view_params.depth == GetMipDepth(level); +} + +bool SurfaceParams::IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const { + return layer + view_params.num_layers <= num_layers && + level + view_params.num_levels <= num_levels; +} + +std::size_t HasheableSurfaceParams::Hash() const { + return static_cast<std::size_t>( + Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this))); +} + +bool HasheableSurfaceParams::operator==(const HasheableSurfaceParams& rhs) const { + return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width, + height, depth, pitch, unaligned_height, num_levels, pixel_format, + component_type, type, target) == + std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth, + rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch, + rhs.unaligned_height, rhs.num_levels, rhs.pixel_format, rhs.component_type, + rhs.type, rhs.target); +} + +std::size_t ViewKey::Hash() const { + return static_cast<std::size_t>( + Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this))); +} + +bool ViewKey::operator==(const ViewKey& rhs) const { + return std::tie(base_layer, num_layers, base_level, num_levels) == + std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels); +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache.h b/src/video_core/texture_cache.h new file mode 100644 index 000000000..041551691 --- /dev/null +++ b/src/video_core/texture_cache.h @@ -0,0 +1,586 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <list> +#include <memory> +#include <set> +#include <tuple> +#include <type_traits> +#include <unordered_map> + +#include <boost/icl/interval_map.hpp> +#include <boost/range/iterator_range.hpp> + +#include "common/assert.h" +#include "common/common_types.h" +#include "core/memory.h" +#include "video_core/engines/fermi_2d.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/gpu.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/surface.h" + +namespace Core { +class System; +} + +namespace Tegra::Texture { +struct FullTextureInfo; +} + +namespace VideoCore { +class RasterizerInterface; +} + +namespace VideoCommon { + +class HasheableSurfaceParams { +public: + std::size_t Hash() const; + + bool operator==(const HasheableSurfaceParams& rhs) const; + +protected: + // Avoid creation outside of a managed environment. + HasheableSurfaceParams() = default; + + bool is_tiled; + u32 block_width; + u32 block_height; + u32 block_depth; + u32 tile_width_spacing; + u32 width; + u32 height; + u32 depth; + u32 pitch; + u32 unaligned_height; + u32 num_levels; + VideoCore::Surface::PixelFormat pixel_format; + VideoCore::Surface::ComponentType component_type; + VideoCore::Surface::SurfaceType type; + VideoCore::Surface::SurfaceTarget target; +}; + +class SurfaceParams final : public HasheableSurfaceParams { +public: + /// Creates SurfaceCachedParams from a texture configuration. + static SurfaceParams CreateForTexture(Core::System& system, + const Tegra::Texture::FullTextureInfo& config); + + /// Creates SurfaceCachedParams for a depth buffer configuration. + static SurfaceParams CreateForDepthBuffer( + Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, + u32 block_width, u32 block_height, u32 block_depth, + Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type); + + /// Creates SurfaceCachedParams from a framebuffer configuration. + static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index); + + /// Creates SurfaceCachedParams from a Fermi2D surface configuration. + static SurfaceParams CreateForFermiCopySurface( + const Tegra::Engines::Fermi2D::Regs::Surface& config); + + bool IsTiled() const { + return is_tiled; + } + + u32 GetBlockWidth() const { + return block_width; + } + + u32 GetTileWidthSpacing() const { + return tile_width_spacing; + } + + u32 GetWidth() const { + return width; + } + + u32 GetHeight() const { + return height; + } + + u32 GetDepth() const { + return depth; + } + + u32 GetPitch() const { + return pitch; + } + + u32 GetNumLevels() const { + return num_levels; + } + + VideoCore::Surface::PixelFormat GetPixelFormat() const { + return pixel_format; + } + + VideoCore::Surface::ComponentType GetComponentType() const { + return component_type; + } + + VideoCore::Surface::SurfaceTarget GetTarget() const { + return target; + } + + VideoCore::Surface::SurfaceType GetType() const { + return type; + } + + std::size_t GetGuestSizeInBytes() const { + return guest_size_in_bytes; + } + + std::size_t GetHostSizeInBytes() const { + return host_size_in_bytes; + } + + u32 GetNumLayers() const { + return num_layers; + } + + /// Returns the width of a given mipmap level. + u32 GetMipWidth(u32 level) const; + + /// Returns the height of a given mipmap level. + u32 GetMipHeight(u32 level) const; + + /// Returns the depth of a given mipmap level. + u32 GetMipDepth(u32 level) const; + + /// Returns true if these parameters are from a layered surface. + bool IsLayered() const; + + /// Returns the block height of a given mipmap level. + u32 GetMipBlockHeight(u32 level) const; + + /// Returns the block depth of a given mipmap level. + u32 GetMipBlockDepth(u32 level) const; + + /// Returns the offset in bytes in guest memory of a given mipmap level. + std::size_t GetGuestMipmapLevelOffset(u32 level) const; + + /// Returns the offset in bytes in host memory (linear) of a given mipmap level. + std::size_t GetHostMipmapLevelOffset(u32 level) const; + + /// Returns the size of a layer in bytes in guest memory. + std::size_t GetGuestLayerSize() const; + + /// Returns the size of a layer in bytes in host memory for a given mipmap level. + std::size_t GetHostLayerSize(u32 level) const; + + /// Returns true if another surface can be familiar with this. This is a loosely defined term + /// that reflects the possibility of these two surface parameters potentially being part of a + /// bigger superset. + bool IsFamiliar(const SurfaceParams& view_params) const; + + /// Returns true if the pixel format is a depth and/or stencil format. + bool IsPixelFormatZeta() const; + + /// Creates a map that redirects an address difference to a layer and mipmap level. + std::map<u64, std::pair<u32, u32>> CreateViewOffsetMap() const; + + /// Returns true if the passed surface view parameters is equal or a valid subset of this. + bool IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const; + +private: + /// Calculates values that can be deduced from HasheableSurfaceParams. + void CalculateCachedValues(); + + /// Returns the size of a given mipmap level. + std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only, + bool uncompressed) const; + + /// Returns the size of all mipmap levels and aligns as needed. + std::size_t GetInnerMemorySize(bool as_host_size, bool layer_only, bool uncompressed) const; + + /// Returns true if the passed view width and height match the size of this params in a given + /// mipmap level. + bool IsDimensionValid(const SurfaceParams& view_params, u32 level) const; + + /// Returns true if the passed view depth match the size of this params in a given mipmap level. + bool IsDepthValid(const SurfaceParams& view_params, u32 level) const; + + /// Returns true if the passed view layers and mipmap levels are in bounds. + bool IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const; + + std::size_t guest_size_in_bytes; + std::size_t host_size_in_bytes; + u32 num_layers; +}; + +struct ViewKey { + std::size_t Hash() const; + + bool operator==(const ViewKey& rhs) const; + + u32 base_layer{}; + u32 num_layers{}; + u32 base_level{}; + u32 num_levels{}; +}; + +} // namespace VideoCommon + +namespace std { + +template <> +struct hash<VideoCommon::SurfaceParams> { + std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept { + return k.Hash(); + } +}; + +template <> +struct hash<VideoCommon::ViewKey> { + std::size_t operator()(const VideoCommon::ViewKey& k) const noexcept { + return k.Hash(); + } +}; + +} // namespace std + +namespace VideoCommon { + +template <typename TView, typename TExecutionContext> +class SurfaceBase { + static_assert(std::is_trivially_copyable_v<TExecutionContext>); + +public: + virtual void LoadBuffer() = 0; + + virtual TExecutionContext FlushBuffer(TExecutionContext exctx) = 0; + + virtual TExecutionContext UploadTexture(TExecutionContext exctx) = 0; + + TView* TryGetView(VAddr view_addr, const SurfaceParams& view_params) { + if (view_addr < cpu_addr || !params.IsFamiliar(view_params)) { + // It can't be a view if it's in a prior address. + return {}; + } + + const auto relative_offset{static_cast<u64>(view_addr - cpu_addr)}; + const auto it{view_offset_map.find(relative_offset)}; + if (it == view_offset_map.end()) { + // Couldn't find an aligned view. + return {}; + } + const auto [layer, level] = it->second; + + if (!params.IsViewValid(view_params, layer, level)) { + return {}; + } + + return GetView(layer, view_params.GetNumLayers(), level, view_params.GetNumLevels()); + } + + VAddr GetCpuAddr() const { + ASSERT(is_registered); + return cpu_addr; + } + + u8* GetHostPtr() const { + ASSERT(is_registered); + return host_ptr; + } + + CacheAddr GetCacheAddr() const { + ASSERT(is_registered); + return cache_addr; + } + + std::size_t GetSizeInBytes() const { + return params.GetGuestSizeInBytes(); + } + + void MarkAsModified(bool is_modified_) { + is_modified = is_modified_; + } + + const SurfaceParams& GetSurfaceParams() const { + return params; + } + + TView* GetView(VAddr view_addr, const SurfaceParams& view_params) { + TView* view{TryGetView(view_addr, view_params)}; + ASSERT(view != nullptr); + return view; + } + + void Register(VAddr cpu_addr_, u8* host_ptr_) { + ASSERT(!is_registered); + is_registered = true; + cpu_addr = cpu_addr_; + host_ptr = host_ptr_; + cache_addr = ToCacheAddr(host_ptr_); + } + + void Register(VAddr cpu_addr_) { + Register(cpu_addr_, Memory::GetPointer(cpu_addr_)); + } + + void Unregister() { + ASSERT(is_registered); + is_registered = false; + } + + bool IsRegistered() const { + return is_registered; + } + +protected: + explicit SurfaceBase(const SurfaceParams& params) + : params{params}, view_offset_map{params.CreateViewOffsetMap()} {} + + ~SurfaceBase() = default; + + virtual std::unique_ptr<TView> CreateView(const ViewKey& view_key) = 0; + + bool IsModified() const { + return is_modified; + } + + const SurfaceParams params; + +private: + TView* GetView(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels) { + const ViewKey key{base_layer, num_layers, base_level, num_levels}; + const auto [entry, is_cache_miss] = views.try_emplace(key); + auto& view{entry->second}; + if (is_cache_miss) { + view = CreateView(key); + } + return view.get(); + } + + const std::map<u64, std::pair<u32, u32>> view_offset_map; + + VAddr cpu_addr{}; + u8* host_ptr{}; + CacheAddr cache_addr{}; + bool is_modified{}; + bool is_registered{}; + std::unordered_map<ViewKey, std::unique_ptr<TView>> views; +}; + +template <typename TSurface, typename TView, typename TExecutionContext> +class TextureCache { + static_assert(std::is_trivially_copyable_v<TExecutionContext>); + using ResultType = std::tuple<TView*, TExecutionContext>; + using IntervalMap = boost::icl::interval_map<CacheAddr, std::set<TSurface*>>; + using IntervalType = typename IntervalMap::interval_type; + +public: + void InvalidateRegion(CacheAddr addr, std::size_t size) { + for (TSurface* surface : GetSurfacesInRegion(addr, size)) { + if (!surface->IsRegistered()) { + // Skip duplicates + continue; + } + Unregister(surface); + } + } + + ResultType GetTextureSurface(TExecutionContext exctx, + const Tegra::Texture::FullTextureInfo& config) { + auto& memory_manager{system.GPU().MemoryManager()}; + const auto cpu_addr{memory_manager.GpuToCpuAddress(config.tic.Address())}; + if (!cpu_addr) { + return {{}, exctx}; + } + const auto params{SurfaceParams::CreateForTexture(system, config)}; + return GetSurfaceView(exctx, *cpu_addr, params, true); + } + + ResultType GetDepthBufferSurface(TExecutionContext exctx, bool preserve_contents) { + const auto& regs{system.GPU().Maxwell3D().regs}; + if (!regs.zeta.Address() || !regs.zeta_enable) { + return {{}, exctx}; + } + + auto& memory_manager{system.GPU().MemoryManager()}; + const auto cpu_addr{memory_manager.GpuToCpuAddress(regs.zeta.Address())}; + if (!cpu_addr) { + return {{}, exctx}; + } + + const auto depth_params{SurfaceParams::CreateForDepthBuffer( + system, regs.zeta_width, regs.zeta_height, regs.zeta.format, + regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height, + regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)}; + return GetSurfaceView(exctx, *cpu_addr, depth_params, preserve_contents); + } + + ResultType GetColorBufferSurface(TExecutionContext exctx, std::size_t index, + bool preserve_contents) { + ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); + + const auto& regs{system.GPU().Maxwell3D().regs}; + if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 || + regs.rt[index].format == Tegra::RenderTargetFormat::NONE) { + return {{}, exctx}; + } + + auto& memory_manager{system.GPU().MemoryManager()}; + const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; + const auto cpu_addr{memory_manager.GpuToCpuAddress( + config.Address() + config.base_layer * config.layer_stride * sizeof(u32))}; + if (!cpu_addr) { + return {{}, exctx}; + } + + return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index), + preserve_contents); + } + + ResultType GetFermiSurface(TExecutionContext exctx, + const Tegra::Engines::Fermi2D::Regs::Surface& config) { + const auto cpu_addr{system.GPU().MemoryManager().GpuToCpuAddress(config.Address())}; + ASSERT(cpu_addr); + return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFermiCopySurface(config), + true); + } + + TSurface* TryFindFramebufferSurface(const u8* host_ptr) const { + const auto it{registered_surfaces.find(ToCacheAddr(host_ptr))}; + return it != registered_surfaces.end() ? *it->second.begin() : nullptr; + } + +protected: + TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer) + : system{system}, rasterizer{rasterizer} {} + + ~TextureCache() = default; + + virtual ResultType TryFastGetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr, + const SurfaceParams& params, bool preserve_contents, + const std::vector<TSurface*>& overlaps) = 0; + + virtual std::unique_ptr<TSurface> CreateSurface(const SurfaceParams& params) = 0; + + void Register(TSurface* surface, VAddr cpu_addr, u8* host_ptr) { + surface->Register(cpu_addr, host_ptr); + registered_surfaces.add({GetSurfaceInterval(surface), {surface}}); + rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), 1); + } + + void Unregister(TSurface* surface) { + registered_surfaces.subtract({GetSurfaceInterval(surface), {surface}}); + rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), -1); + surface->Unregister(); + } + + TSurface* GetUncachedSurface(const SurfaceParams& params) { + if (TSurface* surface = TryGetReservedSurface(params); surface) + return surface; + // No reserved surface available, create a new one and reserve it + auto new_surface{CreateSurface(params)}; + TSurface* surface{new_surface.get()}; + ReserveSurface(params, std::move(new_surface)); + return surface; + } + + Core::System& system; + +private: + ResultType GetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, const SurfaceParams& params, + bool preserve_contents) { + const auto host_ptr{Memory::GetPointer(cpu_addr)}; + const auto cache_addr{ToCacheAddr(host_ptr)}; + const auto overlaps{GetSurfacesInRegion(cache_addr, params.GetGuestSizeInBytes())}; + if (overlaps.empty()) { + return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents); + } + + if (overlaps.size() == 1) { + if (TView* view = overlaps[0]->TryGetView(cpu_addr, params); view) + return {view, exctx}; + } + + TView* fast_view; + std::tie(fast_view, exctx) = + TryFastGetSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents, overlaps); + + for (TSurface* surface : overlaps) { + if (!fast_view) { + // Flush even when we don't care about the contents, to preserve memory not written + // by the new surface. + exctx = surface->FlushBuffer(exctx); + } + Unregister(surface); + } + + if (fast_view) { + return {fast_view, exctx}; + } + + return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents); + } + + ResultType LoadSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr, + const SurfaceParams& params, bool preserve_contents) { + TSurface* new_surface{GetUncachedSurface(params)}; + Register(new_surface, cpu_addr, host_ptr); + if (preserve_contents) { + exctx = LoadSurface(exctx, new_surface); + } + return {new_surface->GetView(cpu_addr, params), exctx}; + } + + TExecutionContext LoadSurface(TExecutionContext exctx, TSurface* surface) { + surface->LoadBuffer(); + exctx = surface->UploadTexture(exctx); + surface->MarkAsModified(false); + return exctx; + } + + std::vector<TSurface*> GetSurfacesInRegion(CacheAddr cache_addr, std::size_t size) const { + if (size == 0) { + return {}; + } + const IntervalType interval{cache_addr, cache_addr + size}; + + std::vector<TSurface*> surfaces; + for (auto& pair : boost::make_iterator_range(registered_surfaces.equal_range(interval))) { + surfaces.push_back(*pair.second.begin()); + } + return surfaces; + } + + void ReserveSurface(const SurfaceParams& params, std::unique_ptr<TSurface> surface) { + surface_reserve[params].push_back(std::move(surface)); + } + + TSurface* TryGetReservedSurface(const SurfaceParams& params) { + auto search{surface_reserve.find(params)}; + if (search == surface_reserve.end()) { + return {}; + } + for (auto& surface : search->second) { + if (!surface->IsRegistered()) { + return surface.get(); + } + } + return {}; + } + + IntervalType GetSurfaceInterval(TSurface* surface) const { + return IntervalType::right_open(surface->GetCacheAddr(), + surface->GetCacheAddr() + surface->GetSizeInBytes()); + } + + VideoCore::RasterizerInterface& rasterizer; + + IntervalMap registered_surfaces; + + /// The surface reserve is a "backup" cache, this is where we put unique surfaces that have + /// previously been used. This is to prevent surfaces from being constantly created and + /// destroyed when used with different surface parameters. + std::unordered_map<SurfaceParams, std::list<std::unique_ptr<TSurface>>> surface_reserve; +}; + +} // namespace VideoCommon diff --git a/src/video_core/textures/convert.cpp b/src/video_core/textures/convert.cpp index 5e439f036..82050bd51 100644 --- a/src/video_core/textures/convert.cpp +++ b/src/video_core/textures/convert.cpp @@ -10,6 +10,7 @@ #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" +#include "video_core/surface.h" #include "video_core/textures/astc.h" #include "video_core/textures/convert.h" diff --git a/src/video_core/textures/convert.h b/src/video_core/textures/convert.h index 07cd8b5da..12542e71c 100644 --- a/src/video_core/textures/convert.h +++ b/src/video_core/textures/convert.h @@ -5,7 +5,10 @@ #pragma once #include "common/common_types.h" -#include "video_core/surface.h" + +namespace VideoCore::Surface { +enum class PixelFormat; +} namespace Tegra::Texture { diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index 93ecc6e31..bea0d5bc2 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -7,9 +7,7 @@ #include <array> #include "common/assert.h" #include "common/bit_field.h" -#include "common/common_funcs.h" #include "common/common_types.h" -#include "video_core/memory_manager.h" namespace Tegra::Texture { diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 4cab599b4..2eb86d6e5 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 @@ -54,8 +56,6 @@ add_executable(yuzu debugger/graphics/graphics_breakpoints.cpp debugger/graphics/graphics_breakpoints.h debugger/graphics/graphics_breakpoints_p.h - debugger/graphics/graphics_surface.cpp - debugger/graphics/graphics_surface.h debugger/console.cpp debugger/console.h debugger/profiler.cpp @@ -78,6 +78,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 +97,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 +108,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/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp deleted file mode 100644 index f2d14becf..000000000 --- a/src/yuzu/debugger/graphics/graphics_surface.cpp +++ /dev/null @@ -1,516 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <QBoxLayout> -#include <QComboBox> -#include <QDebug> -#include <QFileDialog> -#include <QLabel> -#include <QMessageBox> -#include <QMouseEvent> -#include <QPushButton> -#include <QScrollArea> -#include <QSpinBox> -#include "common/vector_math.h" -#include "core/core.h" -#include "core/memory.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/gpu.h" -#include "video_core/textures/decoders.h" -#include "video_core/textures/texture.h" -#include "yuzu/debugger/graphics/graphics_surface.h" -#include "yuzu/util/spinbox.h" - -static Tegra::Texture::TextureFormat ConvertToTextureFormat( - Tegra::RenderTargetFormat render_target_format) { - switch (render_target_format) { - case Tegra::RenderTargetFormat::RGBA8_UNORM: - return Tegra::Texture::TextureFormat::A8R8G8B8; - case Tegra::RenderTargetFormat::RGB10_A2_UNORM: - return Tegra::Texture::TextureFormat::A2B10G10R10; - default: - UNIMPLEMENTED_MSG("Unimplemented RT format"); - return Tegra::Texture::TextureFormat::A8R8G8B8; - } -} - -SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) - : QLabel(parent), surface_widget(surface_widget_) {} - -SurfacePicture::~SurfacePicture() = default; - -void SurfacePicture::mousePressEvent(QMouseEvent* event) { - // Only do something while the left mouse button is held down - if (!(event->buttons() & Qt::LeftButton)) - return; - - if (pixmap() == nullptr) - return; - - if (surface_widget) - surface_widget->Pick(event->x() * pixmap()->width() / width(), - event->y() * pixmap()->height() / height()); -} - -void SurfacePicture::mouseMoveEvent(QMouseEvent* event) { - // We also want to handle the event if the user moves the mouse while holding down the LMB - mousePressEvent(event); -} - -GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, - QWidget* parent) - : BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent), - surface_source(Source::RenderTarget0) { - setObjectName("MaxwellSurface"); - - surface_source_list = new QComboBox; - surface_source_list->addItem(tr("Render Target 0")); - surface_source_list->addItem(tr("Render Target 1")); - surface_source_list->addItem(tr("Render Target 2")); - surface_source_list->addItem(tr("Render Target 3")); - surface_source_list->addItem(tr("Render Target 4")); - surface_source_list->addItem(tr("Render Target 5")); - surface_source_list->addItem(tr("Render Target 6")); - surface_source_list->addItem(tr("Render Target 7")); - surface_source_list->addItem(tr("Z Buffer")); - surface_source_list->addItem(tr("Custom")); - surface_source_list->setCurrentIndex(static_cast<int>(surface_source)); - - surface_address_control = new CSpinBox; - surface_address_control->SetBase(16); - surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF); - surface_address_control->SetPrefix("0x"); - - unsigned max_dimension = 16384; // TODO: Find actual maximum - - surface_width_control = new QSpinBox; - surface_width_control->setRange(0, max_dimension); - - surface_height_control = new QSpinBox; - surface_height_control->setRange(0, max_dimension); - - surface_picker_x_control = new QSpinBox; - surface_picker_x_control->setRange(0, max_dimension - 1); - - surface_picker_y_control = new QSpinBox; - surface_picker_y_control->setRange(0, max_dimension - 1); - - // clang-format off - // Color formats sorted by Maxwell texture format index - const QStringList surface_formats{ - tr("None"), - QStringLiteral("R32_G32_B32_A32"), - QStringLiteral("R32_G32_B32"), - QStringLiteral("R16_G16_B16_A16"), - QStringLiteral("R32_G32"), - QStringLiteral("R32_B24G8"), - QStringLiteral("ETC2_RGB"), - QStringLiteral("X8B8G8R8"), - QStringLiteral("A8R8G8B8"), - QStringLiteral("A2B10G10R10"), - QStringLiteral("ETC2_RGB_PTA"), - QStringLiteral("ETC2_RGBA"), - QStringLiteral("R16_G16"), - QStringLiteral("G8R24"), - QStringLiteral("G24R8"), - QStringLiteral("R32"), - QStringLiteral("BC6H_SF16"), - QStringLiteral("BC6H_UF16"), - QStringLiteral("A4B4G4R4"), - QStringLiteral("A5B5G5R1"), - QStringLiteral("A1B5G5R5"), - QStringLiteral("B5G6R5"), - QStringLiteral("B6G5R5"), - QStringLiteral("BC7U"), - QStringLiteral("G8R8"), - QStringLiteral("EAC"), - QStringLiteral("EACX2"), - QStringLiteral("R16"), - QStringLiteral("Y8_VIDEO"), - QStringLiteral("R8"), - QStringLiteral("G4R4"), - QStringLiteral("R1"), - QStringLiteral("E5B9G9R9_SHAREDEXP"), - QStringLiteral("BF10GF11RF11"), - QStringLiteral("G8B8G8R8"), - QStringLiteral("B8G8R8G8"), - QStringLiteral("DXT1"), - QStringLiteral("DXT23"), - QStringLiteral("DXT45"), - QStringLiteral("DXN1"), - QStringLiteral("DXN2"), - QStringLiteral("Z24S8"), - QStringLiteral("X8Z24"), - QStringLiteral("S8Z24"), - QStringLiteral("X4V4Z24__COV4R4V"), - QStringLiteral("X4V4Z24__COV8R8V"), - QStringLiteral("V8Z24__COV4R12V"), - QStringLiteral("ZF32"), - QStringLiteral("ZF32_X24S8"), - QStringLiteral("X8Z24_X20V4S8__COV4R4V"), - QStringLiteral("X8Z24_X20V4S8__COV8R8V"), - QStringLiteral("ZF32_X20V4X8__COV4R4V"), - QStringLiteral("ZF32_X20V4X8__COV8R8V"), - QStringLiteral("ZF32_X20V4S8__COV4R4V"), - QStringLiteral("ZF32_X20V4S8__COV8R8V"), - QStringLiteral("X8Z24_X16V8S8__COV4R12V"), - QStringLiteral("ZF32_X16V8X8__COV4R12V"), - QStringLiteral("ZF32_X16V8S8__COV4R12V"), - QStringLiteral("Z16"), - QStringLiteral("V8Z24__COV8R24V"), - QStringLiteral("X8Z24_X16V8S8__COV8R24V"), - QStringLiteral("ZF32_X16V8X8__COV8R24V"), - QStringLiteral("ZF32_X16V8S8__COV8R24V"), - QStringLiteral("ASTC_2D_4X4"), - QStringLiteral("ASTC_2D_5X5"), - QStringLiteral("ASTC_2D_6X6"), - QStringLiteral("ASTC_2D_8X8"), - QStringLiteral("ASTC_2D_10X10"), - QStringLiteral("ASTC_2D_12X12"), - QStringLiteral("ASTC_2D_5X4"), - QStringLiteral("ASTC_2D_6X5"), - QStringLiteral("ASTC_2D_8X6"), - QStringLiteral("ASTC_2D_10X8"), - QStringLiteral("ASTC_2D_12X10"), - QStringLiteral("ASTC_2D_8X5"), - QStringLiteral("ASTC_2D_10X5"), - QStringLiteral("ASTC_2D_10X6"), - }; - // clang-format on - - surface_format_control = new QComboBox; - surface_format_control->addItems(surface_formats); - - surface_info_label = new QLabel(); - surface_info_label->setWordWrap(true); - - surface_picture_label = new SurfacePicture(0, this); - surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); - surface_picture_label->setScaledContents(false); - - auto scroll_area = new QScrollArea(); - scroll_area->setBackgroundRole(QPalette::Dark); - scroll_area->setWidgetResizable(false); - scroll_area->setWidget(surface_picture_label); - - save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save")); - - // Connections - connect(this, &GraphicsSurfaceWidget::Update, this, &GraphicsSurfaceWidget::OnUpdate); - connect(surface_source_list, qOverload<int>(&QComboBox::currentIndexChanged), this, - &GraphicsSurfaceWidget::OnSurfaceSourceChanged); - connect(surface_address_control, &CSpinBox::ValueChanged, this, - &GraphicsSurfaceWidget::OnSurfaceAddressChanged); - connect(surface_width_control, qOverload<int>(&QSpinBox::valueChanged), this, - &GraphicsSurfaceWidget::OnSurfaceWidthChanged); - connect(surface_height_control, qOverload<int>(&QSpinBox::valueChanged), this, - &GraphicsSurfaceWidget::OnSurfaceHeightChanged); - connect(surface_format_control, qOverload<int>(&QComboBox::currentIndexChanged), this, - &GraphicsSurfaceWidget::OnSurfaceFormatChanged); - connect(surface_picker_x_control, qOverload<int>(&QSpinBox::valueChanged), this, - &GraphicsSurfaceWidget::OnSurfacePickerXChanged); - connect(surface_picker_y_control, qOverload<int>(&QSpinBox::valueChanged), this, - &GraphicsSurfaceWidget::OnSurfacePickerYChanged); - connect(save_surface, &QPushButton::clicked, this, &GraphicsSurfaceWidget::SaveSurface); - - auto main_widget = new QWidget; - auto main_layout = new QVBoxLayout; - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Source:"))); - sub_layout->addWidget(surface_source_list); - main_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("GPU Address:"))); - sub_layout->addWidget(surface_address_control); - main_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Width:"))); - sub_layout->addWidget(surface_width_control); - main_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Height:"))); - sub_layout->addWidget(surface_height_control); - main_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Format:"))); - sub_layout->addWidget(surface_format_control); - main_layout->addLayout(sub_layout); - } - main_layout->addWidget(scroll_area); - - auto info_layout = new QHBoxLayout; - { - auto xy_layout = new QVBoxLayout; - { - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("X:"))); - sub_layout->addWidget(surface_picker_x_control); - xy_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Y:"))); - sub_layout->addWidget(surface_picker_y_control); - xy_layout->addLayout(sub_layout); - } - } - info_layout->addLayout(xy_layout); - surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); - info_layout->addWidget(surface_info_label); - } - main_layout->addLayout(info_layout); - - main_layout->addWidget(save_surface); - main_widget->setLayout(main_layout); - setWidget(main_widget); - - // Load current data - TODO: Make sure this works when emulation is not running - if (debug_context && debug_context->at_breakpoint) { - emit Update(); - widget()->setEnabled(debug_context->at_breakpoint); - } else { - widget()->setEnabled(false); - } -} - -void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { - emit Update(); - widget()->setEnabled(true); -} - -void GraphicsSurfaceWidget::OnResumed() { - widget()->setEnabled(false); -} - -void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) { - surface_source = static_cast<Source>(new_value); - emit Update(); -} - -void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) { - if (surface_address != new_value) { - surface_address = static_cast<GPUVAddr>(new_value); - - surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); - emit Update(); - } -} - -void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) { - if (surface_width != static_cast<unsigned>(new_value)) { - surface_width = static_cast<unsigned>(new_value); - - surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); - emit Update(); - } -} - -void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) { - if (surface_height != static_cast<unsigned>(new_value)) { - surface_height = static_cast<unsigned>(new_value); - - surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); - emit Update(); - } -} - -void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) { - if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) { - surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value); - - surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); - emit Update(); - } -} - -void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) { - if (surface_picker_x != new_value) { - surface_picker_x = new_value; - Pick(surface_picker_x, surface_picker_y); - } -} - -void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) { - if (surface_picker_y != new_value) { - surface_picker_y = new_value; - Pick(surface_picker_x, surface_picker_y); - } -} - -void GraphicsSurfaceWidget::Pick(int x, int y) { - surface_picker_x_control->setValue(x); - surface_picker_y_control->setValue(y); - - if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 || - y >= static_cast<int>(surface_height)) { - surface_info_label->setText(tr("Pixel out of bounds")); - surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); - return; - } - - surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>")); - surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); -} - -void GraphicsSurfaceWidget::OnUpdate() { - auto& gpu = Core::System::GetInstance().GPU(); - - QPixmap pixmap; - - switch (surface_source) { - case Source::RenderTarget0: - case Source::RenderTarget1: - case Source::RenderTarget2: - case Source::RenderTarget3: - case Source::RenderTarget4: - case Source::RenderTarget5: - case Source::RenderTarget6: - case Source::RenderTarget7: { - // TODO: Store a reference to the registers in the debug context instead of accessing them - // directly... - - const auto& registers = gpu.Maxwell3D().regs; - const auto& rt = registers.rt[static_cast<std::size_t>(surface_source) - - static_cast<std::size_t>(Source::RenderTarget0)]; - - surface_address = rt.Address(); - surface_width = rt.width; - surface_height = rt.height; - if (rt.format != Tegra::RenderTargetFormat::NONE) { - surface_format = ConvertToTextureFormat(rt.format); - } - - break; - } - - case Source::Custom: { - // Keep user-specified values - break; - } - - default: - qDebug() << "Unknown surface source " << static_cast<int>(surface_source); - break; - } - - surface_address_control->SetValue(surface_address); - surface_width_control->setValue(surface_width); - surface_height_control->setValue(surface_height); - surface_format_control->setCurrentIndex(static_cast<int>(surface_format)); - - if (surface_address == 0) { - surface_picture_label->hide(); - surface_info_label->setText(tr("(invalid surface address)")); - surface_info_label->setAlignment(Qt::AlignCenter); - surface_picker_x_control->setEnabled(false); - surface_picker_y_control->setEnabled(false); - save_surface->setEnabled(false); - return; - } - - // TODO: Implement a good way to visualize alpha components! - - QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); - - // TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles. - // Needs to be fixed if we plan to use this feature more, otherwise we may remove it. - auto unswizzled_data = Tegra::Texture::UnswizzleTexture( - gpu.MemoryManager().GetPointer(surface_address), 1, 1, - Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height, 1U); - - auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format, - surface_width, surface_height); - - surface_picture_label->show(); - - for (unsigned int y = 0; y < surface_height; ++y) { - for (unsigned int x = 0; x < surface_width; ++x) { - Common::Vec4<u8> color; - color[0] = texture_data[x + y * surface_width + 0]; - color[1] = texture_data[x + y * surface_width + 1]; - color[2] = texture_data[x + y * surface_width + 2]; - color[3] = texture_data[x + y * surface_width + 3]; - decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); - } - } - - pixmap = QPixmap::fromImage(decoded_image); - surface_picture_label->setPixmap(pixmap); - surface_picture_label->resize(pixmap.size()); - - // Update the info with pixel data - surface_picker_x_control->setEnabled(true); - surface_picker_y_control->setEnabled(true); - Pick(surface_picker_x, surface_picker_y); - - // Enable saving the converted pixmap to file - save_surface->setEnabled(true); -} - -void GraphicsSurfaceWidget::SaveSurface() { - const QString png_filter = tr("Portable Network Graphic (*.png)"); - const QString bin_filter = tr("Binary data (*.bin)"); - - QString selected_filter; - const QString filename = QFileDialog::getSaveFileName( - this, tr("Save Surface"), - QStringLiteral("texture-0x%1.png").arg(QString::number(surface_address, 16)), - QStringLiteral("%1;;%2").arg(png_filter, bin_filter), &selected_filter); - - if (filename.isEmpty()) { - // If the user canceled the dialog, don't save anything. - return; - } - - if (selected_filter == png_filter) { - const QPixmap* const pixmap = surface_picture_label->pixmap(); - ASSERT_MSG(pixmap != nullptr, "No pixmap set"); - - QFile file{filename}; - if (!file.open(QIODevice::WriteOnly)) { - QMessageBox::warning(this, tr("Error"), tr("Failed to open file '%1'").arg(filename)); - return; - } - - if (!pixmap->save(&file, "PNG")) { - QMessageBox::warning(this, tr("Error"), - tr("Failed to save surface data to file '%1'").arg(filename)); - } - } else if (selected_filter == bin_filter) { - auto& gpu = Core::System::GetInstance().GPU(); - const std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address); - - const u8* const buffer = Memory::GetPointer(*address); - ASSERT_MSG(buffer != nullptr, "Memory not accessible"); - - QFile file{filename}; - if (!file.open(QIODevice::WriteOnly)) { - QMessageBox::warning(this, tr("Error"), tr("Failed to open file '%1'").arg(filename)); - return; - } - - const int size = - surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format); - const QByteArray data(reinterpret_cast<const char*>(buffer), size); - if (file.write(data) != data.size()) { - QMessageBox::warning( - this, tr("Error"), - tr("Failed to completely write surface data to file. The saved data will " - "likely be corrupt.")); - } - } else { - UNREACHABLE_MSG("Unhandled filter selected"); - } -} diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h deleted file mode 100644 index 89445b18f..000000000 --- a/src/yuzu/debugger/graphics/graphics_surface.h +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <QLabel> -#include <QPushButton> -#include "video_core/memory_manager.h" -#include "video_core/textures/texture.h" -#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" - -class QComboBox; -class QSpinBox; -class CSpinBox; - -class GraphicsSurfaceWidget; - -class SurfacePicture : public QLabel { - Q_OBJECT - -public: - explicit SurfacePicture(QWidget* parent = nullptr, - GraphicsSurfaceWidget* surface_widget = nullptr); - ~SurfacePicture() override; - -protected slots: - void mouseMoveEvent(QMouseEvent* event) override; - void mousePressEvent(QMouseEvent* event) override; - -private: - GraphicsSurfaceWidget* surface_widget; -}; - -class GraphicsSurfaceWidget : public BreakPointObserverDock { - Q_OBJECT - - using Event = Tegra::DebugContext::Event; - - enum class Source { - RenderTarget0 = 0, - RenderTarget1 = 1, - RenderTarget2 = 2, - RenderTarget3 = 3, - RenderTarget4 = 4, - RenderTarget5 = 5, - RenderTarget6 = 6, - RenderTarget7 = 7, - ZBuffer = 8, - Custom = 9, - }; - -public: - explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, - QWidget* parent = nullptr); - void Pick(int x, int y); - -public slots: - void OnSurfaceSourceChanged(int new_value); - void OnSurfaceAddressChanged(qint64 new_value); - void OnSurfaceWidthChanged(int new_value); - void OnSurfaceHeightChanged(int new_value); - void OnSurfaceFormatChanged(int new_value); - void OnSurfacePickerXChanged(int new_value); - void OnSurfacePickerYChanged(int new_value); - void OnUpdate(); - -signals: - void Update(); - -private: - void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override; - void OnResumed() override; - - void SaveSurface(); - - QComboBox* surface_source_list; - CSpinBox* surface_address_control; - QSpinBox* surface_width_control; - QSpinBox* surface_height_control; - QComboBox* surface_format_control; - - SurfacePicture* surface_picture_label; - QSpinBox* surface_picker_x_control; - QSpinBox* surface_picker_y_control; - QLabel* surface_info_label; - QPushButton* save_surface; - - Source surface_source; - GPUVAddr surface_address; - unsigned surface_width; - unsigned surface_height; - Tegra::Texture::TextureFormat surface_format; - int surface_picker_x = 0; - int surface_picker_y = 0; -}; 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..ca231d710 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -90,7 +90,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/configuration/configure_dialog.h" #include "yuzu/debugger/console.h" #include "yuzu/debugger/graphics/graphics_breakpoints.h" -#include "yuzu/debugger/graphics/graphics_surface.h" #include "yuzu/debugger/profiler.h" #include "yuzu/debugger/wait_tree.h" #include "yuzu/discord.h" @@ -171,7 +170,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 +203,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 +423,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); @@ -478,11 +482,6 @@ void GMainWindow::InitializeDebugWidgets() { graphicsBreakpointsWidget->hide(); debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); - graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this); - addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget); - graphicsSurfaceWidget->hide(); - debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction()); - waitTreeWidget = new WaitTreeWidget(this); addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); waitTreeWidget->hide(); @@ -514,33 +513,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 +549,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 +697,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 +1178,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 +1661,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 +1924,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..85e3810f2 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -23,7 +23,6 @@ class EmuThread; class GameList; class GImageInfo; class GraphicsBreakPointsWidget; -class GraphicsSurfaceWidget; class GRenderWindow; class LoadingScreen; class MicroProfileDialog; @@ -37,7 +36,8 @@ struct SoftwareKeyboardParameters; } // namespace Core::Frontend namespace FileSys { -class RegisteredCacheUnion; +class ContentProvider; +class ManualContentProvider; class VfsFilesystem; } // namespace FileSys @@ -120,7 +120,6 @@ private: void InitializeWidgets(); void InitializeDebugWidgets(); void InitializeRecentFileMenuActions(); - void InitializeHotkeys(); void SetDefaultUIGeometry(); void RestoreUIState(); @@ -196,6 +195,7 @@ private slots: void OnAbout(); void OnToggleFilterBar(); void OnDisplayTitleBars(bool); + void InitializeHotkeys(); void ToggleFullscreen(); void ShowFullscreen(); void HideFullscreen(); @@ -205,7 +205,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,12 +233,12 @@ private: // FS std::shared_ptr<FileSys::VfsFilesystem> vfs; + std::unique_ptr<FileSys::ManualContentProvider> provider; // Debugger panes ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; GraphicsBreakPointsWidget* graphicsBreakpointsWidget; - GraphicsSurfaceWidget* graphicsSurfaceWidget; WaitTreeWidget* waitTreeWidget; QAction* actions_recent_files[max_recent_files_item]; 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()); |