summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt3
-rw-r--r--src/common/memory_detect.cpp23
-rw-r--r--src/core/crypto/key_manager.h13
-rw-r--r--src/core/file_sys/bis_factory.cpp2
-rw-r--r--src/core/file_sys/card_image.cpp4
-rw-r--r--src/core/file_sys/card_image.h2
-rw-r--r--src/core/file_sys/content_archive.cpp5
-rw-r--r--src/core/file_sys/content_archive.h5
-rw-r--r--src/core/file_sys/registered_cache.cpp6
-rw-r--r--src/core/file_sys/registered_cache.h2
-rw-r--r--src/core/file_sys/submission_package.cpp4
-rw-r--r--src/core/file_sys/submission_package.h2
-rw-r--r--src/core/file_sys/xts_archive.h2
-rw-r--r--src/core/hle/service/es/es.cpp2
-rw-r--r--src/core/hle/service/prepo/prepo.cpp7
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/compatible_formats.cpp162
-rw-r--r--src/video_core/compatible_formats.h32
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp20
-rw-r--r--src/video_core/texture_cache/texture_cache.h25
-rw-r--r--src/yuzu/main.cpp52
-rw-r--r--src/yuzu/main.h6
-rw-r--r--src/yuzu/main.ui12
23 files changed, 324 insertions, 69 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b71071271..73405ce4b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -214,6 +214,9 @@ if(ENABLE_QT)
214 set(QT_PREFIX_HINT HINTS "${QT_PREFIX}") 214 set(QT_PREFIX_HINT HINTS "${QT_PREFIX}")
215 endif() 215 endif()
216 find_package(Qt5 5.9 COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT}) 216 find_package(Qt5 5.9 COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
217 if (YUZU_USE_QT_WEB_ENGINE)
218 find_package(Qt5 COMPONENTS WebEngineCore WebEngineWidgets)
219 endif()
217 if (NOT Qt5_FOUND) 220 if (NOT Qt5_FOUND)
218 list(APPEND CONAN_REQUIRED_LIBS "qt/5.14.1@bincrafters/stable") 221 list(APPEND CONAN_REQUIRED_LIBS "qt/5.14.1@bincrafters/stable")
219 endif() 222 endif()
diff --git a/src/common/memory_detect.cpp b/src/common/memory_detect.cpp
index 3fdc309a2..8cff6ec37 100644
--- a/src/common/memory_detect.cpp
+++ b/src/common/memory_detect.cpp
@@ -9,10 +9,12 @@
9// clang-format on 9// clang-format on
10#else 10#else
11#include <sys/types.h> 11#include <sys/types.h>
12#ifdef __APPLE__ 12#if defined(__APPLE__) || defined(__FreeBSD__)
13#include <sys/sysctl.h> 13#include <sys/sysctl.h>
14#else 14#elif defined(__linux__)
15#include <sys/sysinfo.h> 15#include <sys/sysinfo.h>
16#else
17#include <unistd.h>
16#endif 18#endif
17#endif 19#endif
18 20
@@ -38,15 +40,26 @@ static MemoryInfo Detect() {
38 // hw and vm are defined in sysctl.h 40 // hw and vm are defined in sysctl.h
39 // https://github.com/apple/darwin-xnu/blob/master/bsd/sys/sysctl.h#L471 41 // https://github.com/apple/darwin-xnu/blob/master/bsd/sys/sysctl.h#L471
40 // sysctlbyname(const char *, void *, size_t *, void *, size_t); 42 // sysctlbyname(const char *, void *, size_t *, void *, size_t);
41 sysctlbyname("hw.memsize", &ramsize, &sizeof_ramsize, NULL, 0); 43 sysctlbyname("hw.memsize", &ramsize, &sizeof_ramsize, nullptr, 0);
42 sysctlbyname("vm.swapusage", &vmusage, &sizeof_vmusage, NULL, 0); 44 sysctlbyname("vm.swapusage", &vmusage, &sizeof_vmusage, nullptr, 0);
43 mem_info.TotalPhysicalMemory = ramsize; 45 mem_info.TotalPhysicalMemory = ramsize;
44 mem_info.TotalSwapMemory = vmusage.xsu_total; 46 mem_info.TotalSwapMemory = vmusage.xsu_total;
45#else 47#elif defined(__FreeBSD__)
48 u_long physmem, swap_total;
49 std::size_t sizeof_u_long = sizeof(u_long);
50 // sysctlbyname(const char *, void *, size_t *, const void *, size_t);
51 sysctlbyname("hw.physmem", &physmem, &sizeof_u_long, nullptr, 0);
52 sysctlbyname("vm.swap_total", &swap_total, &sizeof_u_long, nullptr, 0);
53 mem_info.TotalPhysicalMemory = physmem;
54 mem_info.TotalSwapMemory = swap_total;
55#elif defined(__linux__)
46 struct sysinfo meminfo; 56 struct sysinfo meminfo;
47 sysinfo(&meminfo); 57 sysinfo(&meminfo);
48 mem_info.TotalPhysicalMemory = meminfo.totalram; 58 mem_info.TotalPhysicalMemory = meminfo.totalram;
49 mem_info.TotalSwapMemory = meminfo.totalswap; 59 mem_info.TotalSwapMemory = meminfo.totalswap;
60#else
61 mem_info.TotalPhysicalMemory = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE);
62 mem_info.TotalSwapMemory = 0;
50#endif 63#endif
51 64
52 return mem_info; 65 return mem_info;
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 7265c4171..9269a73f2 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -223,7 +223,16 @@ bool operator<(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
223 223
224class KeyManager { 224class KeyManager {
225public: 225public:
226 KeyManager(); 226 static KeyManager& Instance() {
227 static KeyManager instance;
228 return instance;
229 }
230
231 KeyManager(const KeyManager&) = delete;
232 KeyManager& operator=(const KeyManager&) = delete;
233
234 KeyManager(KeyManager&&) = delete;
235 KeyManager& operator=(KeyManager&&) = delete;
227 236
228 bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const; 237 bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
229 bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const; 238 bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
@@ -257,6 +266,8 @@ public:
257 bool AddTicketPersonalized(Ticket raw); 266 bool AddTicketPersonalized(Ticket raw);
258 267
259private: 268private:
269 KeyManager();
270
260 std::map<KeyIndex<S128KeyType>, Key128> s128_keys; 271 std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
261 std::map<KeyIndex<S256KeyType>, Key256> s256_keys; 272 std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
262 273
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 0af44f340..8935a62c3 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -79,7 +79,7 @@ VirtualDir BISFactory::OpenPartition(BisPartitionId id) const {
79} 79}
80 80
81VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const { 81VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const {
82 Core::Crypto::KeyManager keys; 82 auto& keys = Core::Crypto::KeyManager::Instance();
83 Core::Crypto::PartitionDataManager pdm{ 83 Core::Crypto::PartitionDataManager pdm{
84 Core::System::GetInstance().GetFilesystem()->OpenDirectory( 84 Core::System::GetInstance().GetFilesystem()->OpenDirectory(
85 FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), Mode::Read)}; 85 FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), Mode::Read)};
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 07d0c8d5d..664a47e7f 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -178,7 +178,7 @@ u32 XCI::GetSystemUpdateVersion() {
178 return 0; 178 return 0;
179 179
180 for (const auto& file : update->GetFiles()) { 180 for (const auto& file : update->GetFiles()) {
181 NCA nca{file, nullptr, 0, keys}; 181 NCA nca{file, nullptr, 0};
182 182
183 if (nca.GetStatus() != Loader::ResultStatus::Success) 183 if (nca.GetStatus() != Loader::ResultStatus::Success)
184 continue; 184 continue;
@@ -286,7 +286,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
286 continue; 286 continue;
287 } 287 }
288 288
289 auto nca = std::make_shared<NCA>(file, nullptr, 0, keys); 289 auto nca = std::make_shared<NCA>(file, nullptr, 0);
290 if (nca->IsUpdate()) { 290 if (nca->IsUpdate()) {
291 continue; 291 continue;
292 } 292 }
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index c2ee0ea99..e1b136426 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -140,6 +140,6 @@ private:
140 140
141 u64 update_normal_partition_end; 141 u64 update_normal_partition_end;
142 142
143 Core::Crypto::KeyManager keys; 143 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
144}; 144};
145} // namespace FileSys 145} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index b8bbdd1ef..473245d5a 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -118,9 +118,8 @@ static bool IsValidNCA(const NCAHeader& header) {
118 return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); 118 return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
119} 119}
120 120
121NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset, 121NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
122 Core::Crypto::KeyManager keys_) 122 : file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)) {
123 : file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)), keys(std::move(keys_)) {
124 if (file == nullptr) { 123 if (file == nullptr) {
125 status = Loader::ResultStatus::ErrorNullFile; 124 status = Loader::ResultStatus::ErrorNullFile;
126 return; 125 return;
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index e249079b5..d25cbcf91 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -99,8 +99,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
99class NCA : public ReadOnlyVfsDirectory { 99class NCA : public ReadOnlyVfsDirectory {
100public: 100public:
101 explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr, 101 explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr,
102 u64 bktr_base_ivfc_offset = 0, 102 u64 bktr_base_ivfc_offset = 0);
103 Core::Crypto::KeyManager keys = Core::Crypto::KeyManager());
104 ~NCA() override; 103 ~NCA() override;
105 104
106 Loader::ResultStatus GetStatus() const; 105 Loader::ResultStatus GetStatus() const;
@@ -159,7 +158,7 @@ private:
159 bool encrypted = false; 158 bool encrypted = false;
160 bool is_update = false; 159 bool is_update = false;
161 160
162 Core::Crypto::KeyManager keys; 161 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
163}; 162};
164 163
165} // namespace FileSys 164} // namespace FileSys
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index ba5f76288..27c1b0233 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -408,7 +408,7 @@ void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
408 408
409 if (file == nullptr) 409 if (file == nullptr)
410 continue; 410 continue;
411 const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0, keys); 411 const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0);
412 if (nca->GetStatus() != Loader::ResultStatus::Success || 412 if (nca->GetStatus() != Loader::ResultStatus::Success ||
413 nca->GetType() != NCAContentType::Meta) { 413 nca->GetType() != NCAContentType::Meta) {
414 continue; 414 continue;
@@ -486,7 +486,7 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t
486 const auto raw = GetEntryRaw(title_id, type); 486 const auto raw = GetEntryRaw(title_id, type);
487 if (raw == nullptr) 487 if (raw == nullptr)
488 return nullptr; 488 return nullptr;
489 return std::make_unique<NCA>(raw, nullptr, 0, keys); 489 return std::make_unique<NCA>(raw, nullptr, 0);
490} 490}
491 491
492template <typename T> 492template <typename T>
@@ -865,7 +865,7 @@ std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecord
865 const auto res = GetEntryRaw(title_id, type); 865 const auto res = GetEntryRaw(title_id, type);
866 if (res == nullptr) 866 if (res == nullptr)
867 return nullptr; 867 return nullptr;
868 return std::make_unique<NCA>(res, nullptr, 0, keys); 868 return std::make_unique<NCA>(res, nullptr, 0);
869} 869}
870 870
871std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( 871std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index d1eec240e..f339cd17b 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -88,7 +88,7 @@ public:
88 88
89protected: 89protected:
90 // A single instance of KeyManager to be used by GetEntry() 90 // A single instance of KeyManager to be used by GetEntry()
91 Core::Crypto::KeyManager keys; 91 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
92}; 92};
93 93
94class PlaceholderCache { 94class PlaceholderCache {
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index ef3084681..175a8266a 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -21,7 +21,7 @@
21namespace FileSys { 21namespace FileSys {
22namespace { 22namespace {
23void SetTicketKeys(const std::vector<VirtualFile>& files) { 23void SetTicketKeys(const std::vector<VirtualFile>& files) {
24 Core::Crypto::KeyManager keys; 24 auto& keys = Core::Crypto::KeyManager::Instance();
25 25
26 for (const auto& ticket_file : files) { 26 for (const auto& ticket_file : files) {
27 if (ticket_file == nullptr) { 27 if (ticket_file == nullptr) {
@@ -285,7 +285,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
285 continue; 285 continue;
286 } 286 }
287 287
288 auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0, keys); 288 auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0);
289 if (next_nca->GetType() == NCAContentType::Program) { 289 if (next_nca->GetType() == NCAContentType::Program) {
290 program_status[cnmt.GetTitleID()] = next_nca->GetStatus(); 290 program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
291 } 291 }
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index ee9b6ce17..cf89de6a9 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -73,7 +73,7 @@ private:
73 std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas; 73 std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas;
74 std::vector<VirtualFile> ticket_files; 74 std::vector<VirtualFile> ticket_files;
75 75
76 Core::Crypto::KeyManager keys; 76 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
77 77
78 VirtualFile romfs; 78 VirtualFile romfs;
79 VirtualDir exefs; 79 VirtualDir exefs;
diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h
index 7704dee90..563531bb6 100644
--- a/src/core/file_sys/xts_archive.h
+++ b/src/core/file_sys/xts_archive.h
@@ -62,6 +62,6 @@ private:
62 62
63 VirtualFile dec_file; 63 VirtualFile dec_file;
64 64
65 Core::Crypto::KeyManager keys; 65 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
66}; 66};
67} // namespace FileSys 67} // namespace FileSys
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp
index 9365f27e1..a41c73c48 100644
--- a/src/core/hle/service/es/es.cpp
+++ b/src/core/hle/service/es/es.cpp
@@ -302,7 +302,7 @@ private:
302 rb.Push<u64>(write_size); 302 rb.Push<u64>(write_size);
303 } 303 }
304 304
305 Core::Crypto::KeyManager keys; 305 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
306}; 306};
307 307
308void InstallInterfaces(SM::ServiceManager& service_manager) { 308void InstallInterfaces(SM::ServiceManager& service_manager) {
diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp
index 14309c679..67833d9af 100644
--- a/src/core/hle/service/prepo/prepo.cpp
+++ b/src/core/hle/service/prepo/prepo.cpp
@@ -75,8 +75,13 @@ private:
75 const auto user_id = rp.PopRaw<u128>(); 75 const auto user_id = rp.PopRaw<u128>();
76 const auto process_id = rp.PopRaw<u64>(); 76 const auto process_id = rp.PopRaw<u64>();
77 std::vector<std::vector<u8>> data{ctx.ReadBuffer(0)}; 77 std::vector<std::vector<u8>> data{ctx.ReadBuffer(0)};
78
78 if constexpr (Type == Core::Reporter::PlayReportType::Old2) { 79 if constexpr (Type == Core::Reporter::PlayReportType::Old2) {
79 data.emplace_back(ctx.ReadBuffer(1)); 80 const auto read_buffer_count =
81 ctx.BufferDescriptorX().size() + ctx.BufferDescriptorA().size();
82 if (read_buffer_count > 1) {
83 data.emplace_back(ctx.ReadBuffer(1));
84 }
80 } 85 }
81 86
82 LOG_DEBUG( 87 LOG_DEBUG(
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 2dc752aa9..21c46a567 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -3,6 +3,8 @@ add_library(video_core STATIC
3 buffer_cache/buffer_cache.h 3 buffer_cache/buffer_cache.h
4 buffer_cache/map_interval.cpp 4 buffer_cache/map_interval.cpp
5 buffer_cache/map_interval.h 5 buffer_cache/map_interval.h
6 compatible_formats.cpp
7 compatible_formats.h
6 dirty_flags.cpp 8 dirty_flags.cpp
7 dirty_flags.h 9 dirty_flags.h
8 dma_pusher.cpp 10 dma_pusher.cpp
diff --git a/src/video_core/compatible_formats.cpp b/src/video_core/compatible_formats.cpp
new file mode 100644
index 000000000..6c426b035
--- /dev/null
+++ b/src/video_core/compatible_formats.cpp
@@ -0,0 +1,162 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <array>
6#include <bitset>
7#include <cstddef>
8
9#include "video_core/compatible_formats.h"
10#include "video_core/surface.h"
11
12namespace VideoCore::Surface {
13
14namespace {
15
16// Compatibility table taken from Table 3.X.2 in:
17// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_view.txt
18
19constexpr std::array VIEW_CLASS_128_BITS = {
20 PixelFormat::RGBA32F,
21 PixelFormat::RGBA32UI,
22};
23// Missing formats:
24// PixelFormat::RGBA32I
25
26constexpr std::array VIEW_CLASS_96_BITS = {
27 PixelFormat::RGB32F,
28};
29// Missing formats:
30// PixelFormat::RGB32UI,
31// PixelFormat::RGB32I,
32
33constexpr std::array VIEW_CLASS_64_BITS = {
34 PixelFormat::RGBA16F, PixelFormat::RG32F, PixelFormat::RGBA16UI, PixelFormat::RG32UI,
35 PixelFormat::RGBA16U, PixelFormat::RGBA16F, PixelFormat::RGBA16S,
36};
37// Missing formats:
38// PixelFormat::RGBA16I
39// PixelFormat::RG32I
40
41// TODO: How should we handle 48 bits?
42
43constexpr std::array VIEW_CLASS_32_BITS = {
44 PixelFormat::RG16F, PixelFormat::R11FG11FB10F, PixelFormat::R32F,
45 PixelFormat::A2B10G10R10U, PixelFormat::RG16UI, PixelFormat::R32UI,
46 PixelFormat::RG16I, PixelFormat::R32I, PixelFormat::ABGR8U,
47 PixelFormat::RG16, PixelFormat::ABGR8S, PixelFormat::RG16S,
48 PixelFormat::RGBA8_SRGB, PixelFormat::E5B9G9R9F, PixelFormat::BGRA8,
49 PixelFormat::BGRA8_SRGB,
50};
51// Missing formats:
52// PixelFormat::RGBA8UI
53// PixelFormat::RGBA8I
54// PixelFormat::RGB10_A2_UI
55
56// TODO: How should we handle 24 bits?
57
58constexpr std::array VIEW_CLASS_16_BITS = {
59 PixelFormat::R16F, PixelFormat::RG8UI, PixelFormat::R16UI, PixelFormat::R16I,
60 PixelFormat::RG8U, PixelFormat::R16U, PixelFormat::RG8S, PixelFormat::R16S,
61};
62// Missing formats:
63// PixelFormat::RG8I
64
65constexpr std::array VIEW_CLASS_8_BITS = {
66 PixelFormat::R8UI,
67 PixelFormat::R8U,
68};
69// Missing formats:
70// PixelFormat::R8I
71// PixelFormat::R8S
72
73constexpr std::array VIEW_CLASS_RGTC1_RED = {
74 PixelFormat::DXN1,
75};
76// Missing formats:
77// COMPRESSED_SIGNED_RED_RGTC1
78
79constexpr std::array VIEW_CLASS_RGTC2_RG = {
80 PixelFormat::DXN2UNORM,
81 PixelFormat::DXN2SNORM,
82};
83
84constexpr std::array VIEW_CLASS_BPTC_UNORM = {
85 PixelFormat::BC7U,
86 PixelFormat::BC7U_SRGB,
87};
88
89constexpr std::array VIEW_CLASS_BPTC_FLOAT = {
90 PixelFormat::BC6H_SF16,
91 PixelFormat::BC6H_UF16,
92};
93
94// Compatibility table taken from Table 4.X.1 in:
95// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_copy_image.txt
96
97constexpr std::array COPY_CLASS_128_BITS = {
98 PixelFormat::RGBA32UI, PixelFormat::RGBA32F, PixelFormat::DXT23,
99 PixelFormat::DXT23_SRGB, PixelFormat::DXT45, PixelFormat::DXT45_SRGB,
100 PixelFormat::DXN2SNORM, PixelFormat::BC7U, PixelFormat::BC7U_SRGB,
101 PixelFormat::BC6H_SF16, PixelFormat::BC6H_UF16,
102};
103// Missing formats:
104// PixelFormat::RGBA32I
105// COMPRESSED_RG_RGTC2
106
107constexpr std::array COPY_CLASS_64_BITS = {
108 PixelFormat::RGBA16F, PixelFormat::RG32F, PixelFormat::RGBA16UI, PixelFormat::RG32UI,
109 PixelFormat::RGBA16U, PixelFormat::RGBA16S, PixelFormat::DXT1_SRGB, PixelFormat::DXT1,
110
111};
112// Missing formats:
113// PixelFormat::RGBA16I
114// PixelFormat::RG32I,
115// COMPRESSED_RGB_S3TC_DXT1_EXT
116// COMPRESSED_SRGB_S3TC_DXT1_EXT
117// COMPRESSED_RGBA_S3TC_DXT1_EXT
118// COMPRESSED_SIGNED_RED_RGTC1
119
120void Enable(FormatCompatibility::Table& compatiblity, size_t format_a, size_t format_b) {
121 compatiblity[format_a][format_b] = true;
122 compatiblity[format_b][format_a] = true;
123}
124
125void Enable(FormatCompatibility::Table& compatibility, PixelFormat format_a, PixelFormat format_b) {
126 Enable(compatibility, static_cast<size_t>(format_a), static_cast<size_t>(format_b));
127}
128
129template <typename Range>
130void EnableRange(FormatCompatibility::Table& compatibility, const Range& range) {
131 for (auto it_a = range.begin(); it_a != range.end(); ++it_a) {
132 for (auto it_b = it_a; it_b != range.end(); ++it_b) {
133 Enable(compatibility, *it_a, *it_b);
134 }
135 }
136}
137
138} // Anonymous namespace
139
140FormatCompatibility::FormatCompatibility() {
141 for (size_t i = 0; i < MaxPixelFormat; ++i) {
142 // Identity is allowed
143 Enable(view, i, i);
144 }
145
146 EnableRange(view, VIEW_CLASS_128_BITS);
147 EnableRange(view, VIEW_CLASS_96_BITS);
148 EnableRange(view, VIEW_CLASS_64_BITS);
149 EnableRange(view, VIEW_CLASS_32_BITS);
150 EnableRange(view, VIEW_CLASS_16_BITS);
151 EnableRange(view, VIEW_CLASS_8_BITS);
152 EnableRange(view, VIEW_CLASS_RGTC1_RED);
153 EnableRange(view, VIEW_CLASS_RGTC2_RG);
154 EnableRange(view, VIEW_CLASS_BPTC_UNORM);
155 EnableRange(view, VIEW_CLASS_BPTC_FLOAT);
156
157 copy = view;
158 EnableRange(copy, COPY_CLASS_128_BITS);
159 EnableRange(copy, COPY_CLASS_64_BITS);
160}
161
162} // namespace VideoCore::Surface
diff --git a/src/video_core/compatible_formats.h b/src/video_core/compatible_formats.h
new file mode 100644
index 000000000..d1082566d
--- /dev/null
+++ b/src/video_core/compatible_formats.h
@@ -0,0 +1,32 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <array>
6#include <bitset>
7#include <cstddef>
8
9#include "video_core/surface.h"
10
11namespace VideoCore::Surface {
12
13class FormatCompatibility {
14public:
15 using Table = std::array<std::bitset<MaxPixelFormat>, MaxPixelFormat>;
16
17 explicit FormatCompatibility();
18
19 bool TestView(PixelFormat format_a, PixelFormat format_b) const noexcept {
20 return view[static_cast<size_t>(format_a)][static_cast<size_t>(format_b)];
21 }
22
23 bool TestCopy(PixelFormat format_a, PixelFormat format_b) const noexcept {
24 return copy[static_cast<size_t>(format_a)][static_cast<size_t>(format_b)];
25 }
26
27private:
28 Table view;
29 Table copy;
30};
31
32} // namespace VideoCore::Surface
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index b6b6659c1..208fc6167 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -188,20 +188,6 @@ bool IsASTCSupported() {
188 return true; 188 return true;
189} 189}
190 190
191/// @brief Returns true when a GL_RENDERER is a Turing GPU
192/// @param renderer GL_RENDERER string
193bool IsTuring(std::string_view renderer) {
194 static constexpr std::array<std::string_view, 12> TURING_GPUS = {
195 "GTX 1650", "GTX 1660", "RTX 2060", "RTX 2070",
196 "RTX 2080", "TITAN RTX", "Quadro RTX 3000", "Quadro RTX 4000",
197 "Quadro RTX 5000", "Quadro RTX 6000", "Quadro RTX 8000", "Tesla T4",
198 };
199 return std::any_of(TURING_GPUS.begin(), TURING_GPUS.end(),
200 [renderer](std::string_view candidate) {
201 return renderer.find(candidate) != std::string_view::npos;
202 });
203}
204
205} // Anonymous namespace 191} // Anonymous namespace
206 192
207Device::Device() 193Device::Device()
@@ -213,7 +199,6 @@ Device::Device()
213 199
214 const bool is_nvidia = vendor == "NVIDIA Corporation"; 200 const bool is_nvidia = vendor == "NVIDIA Corporation";
215 const bool is_amd = vendor == "ATI Technologies Inc."; 201 const bool is_amd = vendor == "ATI Technologies Inc.";
216 const bool is_turing = is_nvidia && IsTuring(renderer);
217 202
218 bool disable_fast_buffer_sub_data = false; 203 bool disable_fast_buffer_sub_data = false;
219 if (is_nvidia && version == "4.6.0 NVIDIA 443.24") { 204 if (is_nvidia && version == "4.6.0 NVIDIA 443.24") {
@@ -238,15 +223,12 @@ Device::Device()
238 has_component_indexing_bug = is_amd; 223 has_component_indexing_bug = is_amd;
239 has_precise_bug = TestPreciseBug(); 224 has_precise_bug = TestPreciseBug();
240 has_nv_viewport_array2 = GLAD_GL_NV_viewport_array2; 225 has_nv_viewport_array2 = GLAD_GL_NV_viewport_array2;
226 has_vertex_buffer_unified_memory = GLAD_GL_NV_vertex_buffer_unified_memory;
241 227
242 // At the moment of writing this, only Nvidia's driver optimizes BufferSubData on exclusive 228 // At the moment of writing this, only Nvidia's driver optimizes BufferSubData on exclusive
243 // uniform buffers as "push constants" 229 // uniform buffers as "push constants"
244 has_fast_buffer_sub_data = is_nvidia && !disable_fast_buffer_sub_data; 230 has_fast_buffer_sub_data = is_nvidia && !disable_fast_buffer_sub_data;
245 231
246 // Nvidia's driver on Turing GPUs randomly crashes when the buffer is made resident, or on
247 // DeleteBuffers. Disable unified memory on these devices.
248 has_vertex_buffer_unified_memory = GLAD_GL_NV_vertex_buffer_unified_memory && !is_turing;
249
250 use_assembly_shaders = Settings::values.use_assembly_shaders && GLAD_GL_NV_gpu_program5 && 232 use_assembly_shaders = Settings::values.use_assembly_shaders && GLAD_GL_NV_gpu_program5 &&
251 GLAD_GL_NV_compute_program5 && GLAD_GL_NV_transform_feedback && 233 GLAD_GL_NV_compute_program5 && GLAD_GL_NV_transform_feedback &&
252 GLAD_GL_NV_transform_feedback2; 234 GLAD_GL_NV_transform_feedback2;
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 85075e868..6207d8dfe 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -24,6 +24,7 @@
24#include "core/core.h" 24#include "core/core.h"
25#include "core/memory.h" 25#include "core/memory.h"
26#include "core/settings.h" 26#include "core/settings.h"
27#include "video_core/compatible_formats.h"
27#include "video_core/dirty_flags.h" 28#include "video_core/dirty_flags.h"
28#include "video_core/engines/fermi_2d.h" 29#include "video_core/engines/fermi_2d.h"
29#include "video_core/engines/maxwell_3d.h" 30#include "video_core/engines/maxwell_3d.h"
@@ -47,8 +48,8 @@ class RasterizerInterface;
47 48
48namespace VideoCommon { 49namespace VideoCommon {
49 50
51using VideoCore::Surface::FormatCompatibility;
50using VideoCore::Surface::PixelFormat; 52using VideoCore::Surface::PixelFormat;
51
52using VideoCore::Surface::SurfaceTarget; 53using VideoCore::Surface::SurfaceTarget;
53using RenderTargetConfig = Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig; 54using RenderTargetConfig = Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig;
54 55
@@ -595,7 +596,7 @@ private:
595 } else { 596 } else {
596 new_surface = GetUncachedSurface(gpu_addr, params); 597 new_surface = GetUncachedSurface(gpu_addr, params);
597 } 598 }
598 const auto& final_params = new_surface->GetSurfaceParams(); 599 const SurfaceParams& final_params = new_surface->GetSurfaceParams();
599 if (cr_params.type != final_params.type) { 600 if (cr_params.type != final_params.type) {
600 if (Settings::IsGPULevelExtreme()) { 601 if (Settings::IsGPULevelExtreme()) {
601 BufferCopy(current_surface, new_surface); 602 BufferCopy(current_surface, new_surface);
@@ -603,7 +604,7 @@ private:
603 } else { 604 } else {
604 std::vector<CopyParams> bricks = current_surface->BreakDown(final_params); 605 std::vector<CopyParams> bricks = current_surface->BreakDown(final_params);
605 for (auto& brick : bricks) { 606 for (auto& brick : bricks) {
606 ImageCopy(current_surface, new_surface, brick); 607 TryCopyImage(current_surface, new_surface, brick);
607 } 608 }
608 } 609 }
609 Unregister(current_surface); 610 Unregister(current_surface);
@@ -694,7 +695,7 @@ private:
694 } 695 }
695 const CopyParams copy_params(0, 0, 0, 0, 0, base_layer, 0, mipmap, width, height, 696 const CopyParams copy_params(0, 0, 0, 0, 0, base_layer, 0, mipmap, width, height,
696 src_params.depth); 697 src_params.depth);
697 ImageCopy(surface, new_surface, copy_params); 698 TryCopyImage(surface, new_surface, copy_params);
698 } 699 }
699 } 700 }
700 if (passed_tests == 0) { 701 if (passed_tests == 0) {
@@ -791,7 +792,7 @@ private:
791 const u32 width = params.width; 792 const u32 width = params.width;
792 const u32 height = params.height; 793 const u32 height = params.height;
793 const CopyParams copy_params(0, 0, 0, 0, 0, slice, 0, 0, width, height, 1); 794 const CopyParams copy_params(0, 0, 0, 0, 0, slice, 0, 0, width, height, 1);
794 ImageCopy(surface, new_surface, copy_params); 795 TryCopyImage(surface, new_surface, copy_params);
795 } 796 }
796 for (const auto& surface : overlaps) { 797 for (const auto& surface : overlaps) {
797 Unregister(surface); 798 Unregister(surface);
@@ -1192,6 +1193,19 @@ private:
1192 return {}; 1193 return {};
1193 } 1194 }
1194 1195
1196 /// Try to do an image copy logging when formats are incompatible.
1197 void TryCopyImage(TSurface& src, TSurface& dst, const CopyParams& copy) {
1198 const SurfaceParams& src_params = src->GetSurfaceParams();
1199 const SurfaceParams& dst_params = dst->GetSurfaceParams();
1200 if (!format_compatibility.TestCopy(src_params.pixel_format, dst_params.pixel_format)) {
1201 LOG_ERROR(HW_GPU, "Illegal copy between formats={{{}, {}}}",
1202 static_cast<int>(dst_params.pixel_format),
1203 static_cast<int>(src_params.pixel_format));
1204 return;
1205 }
1206 ImageCopy(src, dst, copy);
1207 }
1208
1195 constexpr PixelFormat GetSiblingFormat(PixelFormat format) const { 1209 constexpr PixelFormat GetSiblingFormat(PixelFormat format) const {
1196 return siblings_table[static_cast<std::size_t>(format)]; 1210 return siblings_table[static_cast<std::size_t>(format)];
1197 } 1211 }
@@ -1241,6 +1255,7 @@ private:
1241 VideoCore::RasterizerInterface& rasterizer; 1255 VideoCore::RasterizerInterface& rasterizer;
1242 1256
1243 FormatLookupTable format_lookup_table; 1257 FormatLookupTable format_lookup_table;
1258 FormatCompatibility format_compatibility;
1244 1259
1245 u64 ticks{}; 1260 u64 ticks{};
1246 1261
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 880a6a06e..fb299a39b 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -868,6 +868,9 @@ void GMainWindow::ConnectMenuEvents() {
868 connect(ui.action_Report_Compatibility, &QAction::triggered, this, 868 connect(ui.action_Report_Compatibility, &QAction::triggered, this,
869 &GMainWindow::OnMenuReportCompatibility); 869 &GMainWindow::OnMenuReportCompatibility);
870 connect(ui.action_Open_Mods_Page, &QAction::triggered, this, &GMainWindow::OnOpenModsPage); 870 connect(ui.action_Open_Mods_Page, &QAction::triggered, this, &GMainWindow::OnOpenModsPage);
871 connect(ui.action_Open_Quickstart_Guide, &QAction::triggered, this,
872 &GMainWindow::OnOpenQuickstartGuide);
873 connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ);
871 connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); 874 connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); });
872 connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); 875 connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
873 876
@@ -1078,17 +1081,19 @@ void GMainWindow::BootGame(const QString& filename) {
1078 const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); 1081 const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
1079 1082
1080 std::string title_name; 1083 std::string title_name;
1084 std::string title_version;
1081 const auto res = Core::System::GetInstance().GetGameName(title_name); 1085 const auto res = Core::System::GetInstance().GetGameName(title_name);
1082 if (res != Loader::ResultStatus::Success) {
1083 const auto metadata = FileSys::PatchManager(title_id).GetControlMetadata();
1084 if (metadata.first != nullptr)
1085 title_name = metadata.first->GetApplicationName();
1086 1086
1087 if (title_name.empty()) 1087 const auto metadata = FileSys::PatchManager(title_id).GetControlMetadata();
1088 title_name = FileUtil::GetFilename(filename.toStdString()); 1088 if (metadata.first != nullptr) {
1089 title_version = metadata.first->GetVersionString();
1090 title_name = metadata.first->GetApplicationName();
1089 } 1091 }
1090 LOG_INFO(Frontend, "Booting game: {:016X} | {}", title_id, title_name); 1092 if (res != Loader::ResultStatus::Success || title_name.empty()) {
1091 UpdateWindowTitle(QString::fromStdString(title_name)); 1093 title_name = FileUtil::GetFilename(filename.toStdString());
1094 }
1095 LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version);
1096 UpdateWindowTitle(title_name, title_version);
1092 1097
1093 loading_screen->Prepare(Core::System::GetInstance().GetAppLoader()); 1098 loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
1094 loading_screen->show(); 1099 loading_screen->show();
@@ -1839,16 +1844,26 @@ void GMainWindow::OnMenuReportCompatibility() {
1839 } 1844 }
1840} 1845}
1841 1846
1842void GMainWindow::OnOpenModsPage() { 1847void GMainWindow::OpenURL(const QUrl& url) {
1843 const auto mods_page_url = QStringLiteral("https://github.com/yuzu-emu/yuzu/wiki/Switch-Mods"); 1848 const bool open = QDesktopServices::openUrl(url);
1844 const QUrl mods_page(mods_page_url);
1845 const bool open = QDesktopServices::openUrl(mods_page);
1846 if (!open) { 1849 if (!open) {
1847 QMessageBox::warning(this, tr("Error opening URL"), 1850 QMessageBox::warning(this, tr("Error opening URL"),
1848 tr("Unable to open the URL \"%1\".").arg(mods_page_url)); 1851 tr("Unable to open the URL \"%1\".").arg(url.toString()));
1849 } 1852 }
1850} 1853}
1851 1854
1855void GMainWindow::OnOpenModsPage() {
1856 OpenURL(QUrl(QStringLiteral("https://github.com/yuzu-emu/yuzu/wiki/Switch-Mods")));
1857}
1858
1859void GMainWindow::OnOpenQuickstartGuide() {
1860 OpenURL(QUrl(QStringLiteral("https://yuzu-emu.org/help/quickstart/")));
1861}
1862
1863void GMainWindow::OnOpenFAQ() {
1864 OpenURL(QUrl(QStringLiteral("https://yuzu-emu.org/wiki/faq/")));
1865}
1866
1852void GMainWindow::ToggleFullscreen() { 1867void GMainWindow::ToggleFullscreen() {
1853 if (!emulation_running) { 1868 if (!emulation_running) {
1854 return; 1869 return;
@@ -2051,7 +2066,8 @@ void GMainWindow::OnCaptureScreenshot() {
2051 OnStartGame(); 2066 OnStartGame();
2052} 2067}
2053 2068
2054void GMainWindow::UpdateWindowTitle(const QString& title_name) { 2069void GMainWindow::UpdateWindowTitle(const std::string& title_name,
2070 const std::string& title_version) {
2055 const auto full_name = std::string(Common::g_build_fullname); 2071 const auto full_name = std::string(Common::g_build_fullname);
2056 const auto branch_name = std::string(Common::g_scm_branch); 2072 const auto branch_name = std::string(Common::g_scm_branch);
2057 const auto description = std::string(Common::g_scm_desc); 2073 const auto description = std::string(Common::g_scm_desc);
@@ -2060,7 +2076,7 @@ void GMainWindow::UpdateWindowTitle(const QString& title_name) {
2060 const auto date = 2076 const auto date =
2061 QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd")).toStdString(); 2077 QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd")).toStdString();
2062 2078
2063 if (title_name.isEmpty()) { 2079 if (title_name.empty()) {
2064 const auto fmt = std::string(Common::g_title_bar_format_idle); 2080 const auto fmt = std::string(Common::g_title_bar_format_idle);
2065 setWindowTitle(QString::fromStdString(fmt::format(fmt.empty() ? "yuzu {0}| {1}-{2}" : fmt, 2081 setWindowTitle(QString::fromStdString(fmt::format(fmt.empty() ? "yuzu {0}| {1}-{2}" : fmt,
2066 full_name, branch_name, description, 2082 full_name, branch_name, description,
@@ -2068,8 +2084,8 @@ void GMainWindow::UpdateWindowTitle(const QString& title_name) {
2068 } else { 2084 } else {
2069 const auto fmt = std::string(Common::g_title_bar_format_running); 2085 const auto fmt = std::string(Common::g_title_bar_format_running);
2070 setWindowTitle(QString::fromStdString( 2086 setWindowTitle(QString::fromStdString(
2071 fmt::format(fmt.empty() ? "yuzu {0}| {3} | {1}-{2}" : fmt, full_name, branch_name, 2087 fmt::format(fmt.empty() ? "yuzu {0}| {3} | {6} | {1}-{2}" : fmt, full_name, branch_name,
2072 description, title_name.toStdString(), date, build_id))); 2088 description, title_name, date, build_id, title_version)));
2073 } 2089 }
2074} 2090}
2075 2091
@@ -2210,7 +2226,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
2210 "title.keys_autogenerated"); 2226 "title.keys_autogenerated");
2211 } 2227 }
2212 2228
2213 Core::Crypto::KeyManager keys{}; 2229 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
2214 if (keys.BaseDeriveNecessary()) { 2230 if (keys.BaseDeriveNecessary()) {
2215 Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory( 2231 Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory(
2216 FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), FileSys::Mode::Read)}; 2232 FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), FileSys::Mode::Read)};
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 5581874ed..66c84e5c0 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -182,6 +182,8 @@ private slots:
182 void OnStopGame(); 182 void OnStopGame();
183 void OnMenuReportCompatibility(); 183 void OnMenuReportCompatibility();
184 void OnOpenModsPage(); 184 void OnOpenModsPage();
185 void OnOpenQuickstartGuide();
186 void OnOpenFAQ();
185 /// Called whenever a user selects a game in the game list widget. 187 /// Called whenever a user selects a game in the game list widget.
186 void OnGameListLoadFile(QString game_path); 188 void OnGameListLoadFile(QString game_path);
187 void OnGameListOpenFolder(GameListOpenTarget target, const std::string& game_path); 189 void OnGameListOpenFolder(GameListOpenTarget target, const std::string& game_path);
@@ -216,10 +218,12 @@ private slots:
216 218
217private: 219private:
218 std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); 220 std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
219 void UpdateWindowTitle(const QString& title_name = {}); 221 void UpdateWindowTitle(const std::string& title_name = {},
222 const std::string& title_version = {});
220 void UpdateStatusBar(); 223 void UpdateStatusBar();
221 void HideMouseCursor(); 224 void HideMouseCursor();
222 void ShowMouseCursor(); 225 void ShowMouseCursor();
226 void OpenURL(const QUrl& url);
223 227
224 Ui::MainWindow ui; 228 Ui::MainWindow ui;
225 229
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index b5745dfd5..bee6e107e 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -114,6 +114,8 @@
114 </property> 114 </property>
115 <addaction name="action_Report_Compatibility"/> 115 <addaction name="action_Report_Compatibility"/>
116 <addaction name="action_Open_Mods_Page"/> 116 <addaction name="action_Open_Mods_Page"/>
117 <addaction name="action_Open_Quickstart_Guide"/>
118 <addaction name="action_Open_FAQ"/>
117 <addaction name="separator"/> 119 <addaction name="separator"/>
118 <addaction name="action_About"/> 120 <addaction name="action_About"/>
119 </widget> 121 </widget>
@@ -262,6 +264,16 @@
262 <string>Open Mods Page</string> 264 <string>Open Mods Page</string>
263 </property> 265 </property>
264 </action> 266 </action>
267 <action name="action_Open_Quickstart_Guide">
268 <property name="text">
269 <string>Open Quickstart Guide</string>
270 </property>
271 </action>
272 <action name="action_Open_FAQ">
273 <property name="text">
274 <string>FAQ</string>
275 </property>
276 </action>
265 <action name="action_Open_yuzu_Folder"> 277 <action name="action_Open_yuzu_Folder">
266 <property name="text"> 278 <property name="text">
267 <string>Open yuzu Folder</string> 279 <string>Open yuzu Folder</string>