summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/common/CMakeLists.txt5
-rw-r--r--src/common/detached_tasks.cpp41
-rw-r--r--src/common/detached_tasks.h40
-rw-r--r--src/common/logging/text_formatter.cpp2
-rw-r--r--src/common/param_package.cpp18
-rw-r--r--src/common/param_package.h2
-rw-r--r--src/common/web_result.h24
-rw-r--r--src/core/CMakeLists.txt4
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp2
-rw-r--r--src/core/arm/unicorn/arm_unicorn.cpp2
-rw-r--r--src/core/file_sys/patch_manager.cpp16
-rw-r--r--src/core/file_sys/patch_manager.h6
-rw-r--r--src/core/file_sys/romfs_factory.cpp7
-rw-r--r--src/core/file_sys/romfs_factory.h2
-rw-r--r--src/core/file_sys/submission_package.cpp5
-rw-r--r--src/core/gdbstub/gdbstub.cpp44
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp20
-rw-r--r--src/core/hle/kernel/client_port.cpp4
-rw-r--r--src/core/hle/kernel/client_port.h2
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp14
-rw-r--r--src/core/hle/kernel/kernel.cpp36
-rw-r--r--src/core/hle/kernel/mutex.cpp34
-rw-r--r--src/core/hle/kernel/process.cpp6
-rw-r--r--src/core/hle/kernel/scheduler.cpp36
-rw-r--r--src/core/hle/kernel/server_port.h1
-rw-r--r--src/core/hle/kernel/server_session.cpp4
-rw-r--r--src/core/hle/kernel/svc.cpp121
-rw-r--r--src/core/hle/kernel/thread.cpp38
-rw-r--r--src/core/hle/kernel/thread.h218
-rw-r--r--src/core/hle/kernel/wait_object.cpp31
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp10
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp9
-rw-r--r--src/core/hle/service/filesystem/filesystem.h1
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp7
-rw-r--r--src/core/hle/service/sm/sm.h19
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp4
-rw-r--r--src/core/loader/loader.cpp3
-rw-r--r--src/core/loader/loader.h14
-rw-r--r--src/core/loader/nax.cpp4
-rw-r--r--src/core/loader/nax.h1
-rw-r--r--src/core/loader/nro.cpp15
-rw-r--r--src/core/loader/nso.cpp17
-rw-r--r--src/core/loader/nso.h11
-rw-r--r--src/core/loader/nsp.cpp32
-rw-r--r--src/core/loader/nsp.h4
-rw-r--r--src/core/loader/xci.cpp36
-rw-r--r--src/core/loader/xci.h4
-rw-r--r--src/core/settings.h7
-rw-r--r--src/core/telemetry_session.cpp52
-rw-r--r--src/core/telemetry_session.h5
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/engines/fermi_2d.cpp50
-rw-r--r--src/video_core/engines/fermi_2d.h8
-rw-r--r--src/video_core/engines/maxwell_3d.h6
-rw-r--r--src/video_core/gpu.cpp2
-rw-r--r--src/video_core/rasterizer_interface.h11
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp17
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h7
-rw-r--r--src/video_core/renderer_opengl/gl_primitive_assembler.cpp64
-rw-r--r--src/video_core/renderer_opengl/gl_primitive_assembler.h33
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp175
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h13
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp63
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h9
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp262
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.h14
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h25
-rw-r--r--src/video_core/textures/texture.h13
-rw-r--r--src/web_service/CMakeLists.txt16
-rw-r--r--src/web_service/telemetry_json.cpp99
-rw-r--r--src/web_service/telemetry_json.h58
-rw-r--r--src/web_service/verify_login.cpp27
-rw-r--r--src/web_service/verify_login.h22
-rw-r--r--src/web_service/web_backend.cpp149
-rw-r--r--src/web_service/web_backend.h92
-rw-r--r--src/yuzu/CMakeLists.txt20
-rw-r--r--src/yuzu/compatdb.cpp65
-rw-r--r--src/yuzu/compatdb.h26
-rw-r--r--src/yuzu/compatdb.ui215
-rw-r--r--src/yuzu/configuration/config.cpp20
-rw-r--r--src/yuzu/configuration/configure.ui11
-rw-r--r--src/yuzu/configuration/configure_debug.cpp2
-rw-r--r--src/yuzu/configuration/configure_debug.ui23
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp1
-rw-r--r--src/yuzu/configuration/configure_general.cpp2
-rw-r--r--src/yuzu/configuration/configure_input.cpp87
-rw-r--r--src/yuzu/configuration/configure_input.h3
-rw-r--r--src/yuzu/configuration/configure_input.ui28
-rw-r--r--src/yuzu/configuration/configure_web.cpp119
-rw-r--r--src/yuzu/configuration/configure_web.h38
-rw-r--r--src/yuzu/configuration/configure_web.ui206
-rw-r--r--src/yuzu/debugger/wait_tree.cpp47
-rw-r--r--src/yuzu/discord.h25
-rw-r--r--src/yuzu/discord_impl.cpp52
-rw-r--r--src/yuzu/discord_impl.h20
-rw-r--r--src/yuzu/game_list_worker.cpp20
-rw-r--r--src/yuzu/main.cpp84
-rw-r--r--src/yuzu/main.h10
-rw-r--r--src/yuzu/main.ui16
-rw-r--r--src/yuzu/ui_settings.cpp8
-rw-r--r--src/yuzu/ui_settings.h8
-rw-r--r--src/yuzu_cmd/config.cpp9
-rw-r--r--src/yuzu_cmd/default_ini.h8
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp2
-rw-r--r--src/yuzu_cmd/yuzu.cpp22
106 files changed, 2992 insertions, 485 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a88551fbc..f69d00a2b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -13,3 +13,6 @@ endif()
13if (ENABLE_QT) 13if (ENABLE_QT)
14 add_subdirectory(yuzu) 14 add_subdirectory(yuzu)
15endif() 15endif()
16if (ENABLE_WEB_SERVICE)
17 add_subdirectory(web_service)
18endif()
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 6a3f1fe08..d0e506689 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -29,7 +29,7 @@ if ($ENV{CI})
29 if (BUILD_VERSION) 29 if (BUILD_VERSION)
30 # This leaves a trailing space on the last word, but we actually want that 30 # This leaves a trailing space on the last word, but we actually want that
31 # because of how it's styled in the title bar. 31 # because of how it's styled in the title bar.
32 set(BUILD_FULLNAME "${REPO_NAME} #${BUILD_VERSION} ") 32 set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
33 else() 33 else()
34 set(BUILD_FULLNAME "") 34 set(BUILD_FULLNAME "")
35 endif() 35 endif()
@@ -41,6 +41,8 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU
41add_library(common STATIC 41add_library(common STATIC
42 alignment.h 42 alignment.h
43 assert.h 43 assert.h
44 detached_tasks.cpp
45 detached_tasks.h
44 bit_field.h 46 bit_field.h
45 bit_set.h 47 bit_set.h
46 cityhash.cpp 48 cityhash.cpp
@@ -87,6 +89,7 @@ add_library(common STATIC
87 timer.cpp 89 timer.cpp
88 timer.h 90 timer.h
89 vector_math.h 91 vector_math.h
92 web_result.h
90) 93)
91 94
92if(ARCHITECTURE_x86_64) 95if(ARCHITECTURE_x86_64)
diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp
new file mode 100644
index 000000000..a347d9e02
--- /dev/null
+++ b/src/common/detached_tasks.cpp
@@ -0,0 +1,41 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <thread>
6#include "common/assert.h"
7#include "common/detached_tasks.h"
8
9namespace Common {
10
11DetachedTasks* DetachedTasks::instance = nullptr;
12
13DetachedTasks::DetachedTasks() {
14 ASSERT(instance == nullptr);
15 instance = this;
16}
17
18void DetachedTasks::WaitForAllTasks() {
19 std::unique_lock<std::mutex> lock(mutex);
20 cv.wait(lock, [this]() { return count == 0; });
21}
22
23DetachedTasks::~DetachedTasks() {
24 std::unique_lock<std::mutex> lock(mutex);
25 ASSERT(count == 0);
26 instance = nullptr;
27}
28
29void DetachedTasks::AddTask(std::function<void()> task) {
30 std::unique_lock<std::mutex> lock(instance->mutex);
31 ++instance->count;
32 std::thread([task{std::move(task)}]() {
33 task();
34 std::unique_lock<std::mutex> lock(instance->mutex);
35 --instance->count;
36 std::notify_all_at_thread_exit(instance->cv, std::move(lock));
37 })
38 .detach();
39}
40
41} // namespace Common
diff --git a/src/common/detached_tasks.h b/src/common/detached_tasks.h
new file mode 100644
index 000000000..5dd8fc27b
--- /dev/null
+++ b/src/common/detached_tasks.h
@@ -0,0 +1,40 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <condition_variable>
8#include <functional>
9
10namespace Common {
11
12/**
13 * A background manager which ensures that all detached task is finished before program exits.
14 *
15 * Some tasks, telemetry submission for example, prefer executing asynchronously and don't care
16 * about the result. These tasks are suitable for std::thread::detach(). However, this is unsafe if
17 * the task is launched just before the program exits (which is a common case for telemetry), so we
18 * need to block on these tasks on program exit.
19 *
20 * To make detached task safe, a single DetachedTasks object should be placed in the main(), and
21 * call WaitForAllTasks() after all program execution but before global/static variable destruction.
22 * Any potentially unsafe detached task should be executed via DetachedTasks::AddTask.
23 */
24class DetachedTasks {
25public:
26 DetachedTasks();
27 ~DetachedTasks();
28 void WaitForAllTasks();
29
30 static void AddTask(std::function<void()> task);
31
32private:
33 static DetachedTasks* instance;
34
35 std::condition_variable cv;
36 std::mutex mutex;
37 int count = 0;
38};
39
40} // namespace Common
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp
index 05437c137..6a0605c63 100644
--- a/src/common/logging/text_formatter.cpp
+++ b/src/common/logging/text_formatter.cpp
@@ -31,7 +31,7 @@ std::string FormatLogMessage(const Entry& entry) {
31} 31}
32 32
33void PrintMessage(const Entry& entry) { 33void PrintMessage(const Entry& entry) {
34 auto str = FormatLogMessage(entry) + '\n'; 34 const auto str = FormatLogMessage(entry).append(1, '\n');
35 fputs(str.c_str(), stderr); 35 fputs(str.c_str(), stderr);
36} 36}
37 37
diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp
index 9526ca0c6..b916b4866 100644
--- a/src/common/param_package.cpp
+++ b/src/common/param_package.cpp
@@ -20,7 +20,15 @@ constexpr char KEY_VALUE_SEPARATOR_ESCAPE[] = "$0";
20constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1"; 20constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1";
21constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2"; 21constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2";
22 22
23/// A placeholder for empty param packages to avoid empty strings
24/// (they may be recognized as "not set" by some frontend libraries like qt)
25constexpr char EMPTY_PLACEHOLDER[] = "[empty]";
26
23ParamPackage::ParamPackage(const std::string& serialized) { 27ParamPackage::ParamPackage(const std::string& serialized) {
28 if (serialized == EMPTY_PLACEHOLDER) {
29 return;
30 }
31
24 std::vector<std::string> pairs; 32 std::vector<std::string> pairs;
25 Common::SplitString(serialized, PARAM_SEPARATOR, pairs); 33 Common::SplitString(serialized, PARAM_SEPARATOR, pairs);
26 34
@@ -46,7 +54,7 @@ ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : d
46 54
47std::string ParamPackage::Serialize() const { 55std::string ParamPackage::Serialize() const {
48 if (data.empty()) 56 if (data.empty())
49 return ""; 57 return EMPTY_PLACEHOLDER;
50 58
51 std::string result; 59 std::string result;
52 60
@@ -120,4 +128,12 @@ bool ParamPackage::Has(const std::string& key) const {
120 return data.find(key) != data.end(); 128 return data.find(key) != data.end();
121} 129}
122 130
131void ParamPackage::Erase(const std::string& key) {
132 data.erase(key);
133}
134
135void ParamPackage::Clear() {
136 data.clear();
137}
138
123} // namespace Common 139} // namespace Common
diff --git a/src/common/param_package.h b/src/common/param_package.h
index 7842cd4ef..6a0a9b656 100644
--- a/src/common/param_package.h
+++ b/src/common/param_package.h
@@ -32,6 +32,8 @@ public:
32 void Set(const std::string& key, int value); 32 void Set(const std::string& key, int value);
33 void Set(const std::string& key, float value); 33 void Set(const std::string& key, float value);
34 bool Has(const std::string& key) const; 34 bool Has(const std::string& key) const;
35 void Erase(const std::string& key);
36 void Clear();
35 37
36private: 38private:
37 DataType data; 39 DataType data;
diff --git a/src/common/web_result.h b/src/common/web_result.h
new file mode 100644
index 000000000..969926674
--- /dev/null
+++ b/src/common/web_result.h
@@ -0,0 +1,24 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <string>
8
9namespace Common {
10struct WebResult {
11 enum class Code : u32 {
12 Success,
13 InvalidURL,
14 CredentialsMissing,
15 LibError,
16 HttpError,
17 WrongContent,
18 NoWebservice,
19 };
20 Code result_code;
21 std::string result_string;
22 std::string returned_data;
23};
24} // namespace Common
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index a98dbb9ab..e4a676e91 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -396,6 +396,10 @@ create_target_directory_groups(core)
396 396
397target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) 397target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
398target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives) 398target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives)
399if (ENABLE_WEB_SERVICE)
400 add_definitions(-DENABLE_WEB_SERVICE)
401 target_link_libraries(core PUBLIC json-headers web_service)
402endif()
399 403
400if (ARCHITECTURE_x86_64) 404if (ARCHITECTURE_x86_64)
401 target_sources(core PRIVATE 405 target_sources(core PRIVATE
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 05cc84458..7e978cf7a 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -86,7 +86,7 @@ public:
86 parent.jit->HaltExecution(); 86 parent.jit->HaltExecution();
87 parent.SetPC(pc); 87 parent.SetPC(pc);
88 Kernel::Thread* thread = Kernel::GetCurrentThread(); 88 Kernel::Thread* thread = Kernel::GetCurrentThread();
89 parent.SaveContext(thread->context); 89 parent.SaveContext(thread->GetContext());
90 GDBStub::Break(); 90 GDBStub::Break();
91 GDBStub::SendTrap(thread, 5); 91 GDBStub::SendTrap(thread, 5);
92 return; 92 return;
diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp
index e218a0b15..ded4dd359 100644
--- a/src/core/arm/unicorn/arm_unicorn.cpp
+++ b/src/core/arm/unicorn/arm_unicorn.cpp
@@ -195,7 +195,7 @@ void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
195 uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address); 195 uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
196 } 196 }
197 Kernel::Thread* thread = Kernel::GetCurrentThread(); 197 Kernel::Thread* thread = Kernel::GetCurrentThread();
198 SaveContext(thread->context); 198 SaveContext(thread->GetContext());
199 if (last_bkpt_hit || GDBStub::GetCpuStepFlag()) { 199 if (last_bkpt_hit || GDBStub::GetCpuStepFlag()) {
200 last_bkpt_hit = false; 200 last_bkpt_hit = false;
201 GDBStub::Break(); 201 GDBStub::Break();
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index ab2e5e43d..7b31a57a4 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -204,8 +204,8 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
204 romfs = std::move(packed); 204 romfs = std::move(packed);
205} 205}
206 206
207VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, 207VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
208 ContentRecordType type) const { 208 VirtualFile update_raw) const {
209 LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id, 209 LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
210 static_cast<u8>(type)); 210 static_cast<u8>(type));
211 211
@@ -225,6 +225,13 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
225 FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0))); 225 FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
226 romfs = new_nca->GetRomFS(); 226 romfs = new_nca->GetRomFS();
227 } 227 }
228 } else if (update_raw != nullptr) {
229 const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
230 if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
231 new_nca->GetRomFS() != nullptr) {
232 LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
233 romfs = new_nca->GetRomFS();
234 }
228 } 235 }
229 236
230 // LayeredFS 237 // LayeredFS
@@ -244,7 +251,8 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
244 return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty()); 251 return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
245} 252}
246 253
247std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames() const { 254std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
255 VirtualFile update_raw) const {
248 std::map<std::string, std::string, std::less<>> out; 256 std::map<std::string, std::string, std::less<>> out;
249 const auto installed = Service::FileSystem::GetUnionContents(); 257 const auto installed = Service::FileSystem::GetUnionContents();
250 258
@@ -265,6 +273,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
265 "Update", 273 "Update",
266 FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements)); 274 FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
267 } 275 }
276 } else if (update_raw != nullptr) {
277 out.insert_or_assign("Update", "PACKED");
268 } 278 }
269 } 279 }
270 280
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 66fdba148..eb6fc4607 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -47,11 +47,13 @@ public:
47 // - Game Updates 47 // - Game Updates
48 // - LayeredFS 48 // - LayeredFS
49 VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, 49 VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
50 ContentRecordType type = ContentRecordType::Program) const; 50 ContentRecordType type = ContentRecordType::Program,
51 VirtualFile update_raw = nullptr) const;
51 52
52 // Returns a vector of pairs between patch names and patch versions. 53 // Returns a vector of pairs between patch names and patch versions.
53 // i.e. Update 3.2.2 will return {"Update", "3.2.2"} 54 // i.e. Update 3.2.2 will return {"Update", "3.2.2"}
54 std::map<std::string, std::string, std::less<>> GetPatchVersionNames() const; 55 std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
56 VirtualFile update_raw = nullptr) const;
55 57
56 // Given title_id of the program, attempts to get the control data of the update and parse it, 58 // Given title_id of the program, attempts to get the control data of the update and parse it,
57 // falling back to the base control data. 59 // falling back to the base control data.
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 4994c2532..0b645b106 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -30,12 +30,17 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
30 30
31RomFSFactory::~RomFSFactory() = default; 31RomFSFactory::~RomFSFactory() = default;
32 32
33void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
34 this->update_raw = std::move(update_raw);
35}
36
33ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() { 37ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
34 if (!updatable) 38 if (!updatable)
35 return MakeResult<VirtualFile>(file); 39 return MakeResult<VirtualFile>(file);
36 40
37 const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID()); 41 const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID());
38 return MakeResult<VirtualFile>(patch_manager.PatchRomFS(file, ivfc_offset)); 42 return MakeResult<VirtualFile>(
43 patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
39} 44}
40 45
41ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) { 46ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index 2cace8180..7724c0b23 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -32,11 +32,13 @@ public:
32 explicit RomFSFactory(Loader::AppLoader& app_loader); 32 explicit RomFSFactory(Loader::AppLoader& app_loader);
33 ~RomFSFactory(); 33 ~RomFSFactory();
34 34
35 void SetPackedUpdate(VirtualFile update_raw);
35 ResultVal<VirtualFile> OpenCurrentProcess(); 36 ResultVal<VirtualFile> OpenCurrentProcess();
36 ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type); 37 ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type);
37 38
38private: 39private:
39 VirtualFile file; 40 VirtualFile file;
41 VirtualFile update_raw;
40 bool updatable; 42 bool updatable;
41 u64 ivfc_offset; 43 u64 ivfc_offset;
42}; 44};
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index 09bf077cd..ab5dc900c 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -259,8 +259,11 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
259 auto next_nca = std::make_shared<NCA>(next_file); 259 auto next_nca = std::make_shared<NCA>(next_file);
260 if (next_nca->GetType() == NCAContentType::Program) 260 if (next_nca->GetType() == NCAContentType::Program)
261 program_status[cnmt.GetTitleID()] = next_nca->GetStatus(); 261 program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
262 if (next_nca->GetStatus() == Loader::ResultStatus::Success) 262 if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
263 (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
264 (cnmt.GetTitleID() & 0x800) != 0)) {
263 ncas_title[rec.type] = std::move(next_nca); 265 ncas_title[rec.type] = std::move(next_nca);
266 }
264 } 267 }
265 268
266 break; 269 break;
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index 5bc947010..e961ef121 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -209,7 +209,7 @@ static Kernel::Thread* FindThreadById(int id) {
209 for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) { 209 for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
210 const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList(); 210 const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
211 for (auto& thread : threads) { 211 for (auto& thread : threads) {
212 if (thread->GetThreadId() == static_cast<u32>(id)) { 212 if (thread->GetThreadID() == static_cast<u32>(id)) {
213 current_core = core; 213 current_core = core;
214 return thread.get(); 214 return thread.get();
215 } 215 }
@@ -223,16 +223,18 @@ static u64 RegRead(std::size_t id, Kernel::Thread* thread = nullptr) {
223 return 0; 223 return 0;
224 } 224 }
225 225
226 const auto& thread_context = thread->GetContext();
227
226 if (id < SP_REGISTER) { 228 if (id < SP_REGISTER) {
227 return thread->context.cpu_registers[id]; 229 return thread_context.cpu_registers[id];
228 } else if (id == SP_REGISTER) { 230 } else if (id == SP_REGISTER) {
229 return thread->context.sp; 231 return thread_context.sp;
230 } else if (id == PC_REGISTER) { 232 } else if (id == PC_REGISTER) {
231 return thread->context.pc; 233 return thread_context.pc;
232 } else if (id == PSTATE_REGISTER) { 234 } else if (id == PSTATE_REGISTER) {
233 return thread->context.pstate; 235 return thread_context.pstate;
234 } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) { 236 } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
235 return thread->context.vector_registers[id - UC_ARM64_REG_Q0][0]; 237 return thread_context.vector_registers[id - UC_ARM64_REG_Q0][0];
236 } else { 238 } else {
237 return 0; 239 return 0;
238 } 240 }
@@ -243,16 +245,18 @@ static void RegWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr)
243 return; 245 return;
244 } 246 }
245 247
248 auto& thread_context = thread->GetContext();
249
246 if (id < SP_REGISTER) { 250 if (id < SP_REGISTER) {
247 thread->context.cpu_registers[id] = val; 251 thread_context.cpu_registers[id] = val;
248 } else if (id == SP_REGISTER) { 252 } else if (id == SP_REGISTER) {
249 thread->context.sp = val; 253 thread_context.sp = val;
250 } else if (id == PC_REGISTER) { 254 } else if (id == PC_REGISTER) {
251 thread->context.pc = val; 255 thread_context.pc = val;
252 } else if (id == PSTATE_REGISTER) { 256 } else if (id == PSTATE_REGISTER) {
253 thread->context.pstate = static_cast<u32>(val); 257 thread_context.pstate = static_cast<u32>(val);
254 } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) { 258 } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
255 thread->context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val; 259 thread_context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val;
256 } 260 }
257} 261}
258 262
@@ -595,7 +599,7 @@ static void HandleQuery() {
595 for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) { 599 for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
596 const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList(); 600 const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
597 for (const auto& thread : threads) { 601 for (const auto& thread : threads) {
598 val += fmt::format("{:x}", thread->GetThreadId()); 602 val += fmt::format("{:x}", thread->GetThreadID());
599 val += ","; 603 val += ",";
600 } 604 }
601 } 605 }
@@ -612,7 +616,7 @@ static void HandleQuery() {
612 for (const auto& thread : threads) { 616 for (const auto& thread : threads) {
613 buffer += 617 buffer +=
614 fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*", 618 fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
615 thread->GetThreadId(), core, thread->GetThreadId()); 619 thread->GetThreadID(), core, thread->GetThreadID());
616 } 620 }
617 } 621 }
618 buffer += "</threads>"; 622 buffer += "</threads>";
@@ -693,7 +697,7 @@ static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
693 } 697 }
694 698
695 if (thread) { 699 if (thread) {
696 buffer += fmt::format(";thread:{:x};", thread->GetThreadId()); 700 buffer += fmt::format(";thread:{:x};", thread->GetThreadID());
697 } 701 }
698 702
699 SendReply(buffer.c_str()); 703 SendReply(buffer.c_str());
@@ -857,7 +861,9 @@ static void WriteRegister() {
857 } 861 }
858 862
859 // Update Unicorn context skipping scheduler, no running threads at this point 863 // Update Unicorn context skipping scheduler, no running threads at this point
860 Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context); 864 Core::System::GetInstance()
865 .ArmInterface(current_core)
866 .LoadContext(current_thread->GetContext());
861 867
862 SendReply("OK"); 868 SendReply("OK");
863} 869}
@@ -886,7 +892,9 @@ static void WriteRegisters() {
886 } 892 }
887 893
888 // Update Unicorn context skipping scheduler, no running threads at this point 894 // Update Unicorn context skipping scheduler, no running threads at this point
889 Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context); 895 Core::System::GetInstance()
896 .ArmInterface(current_core)
897 .LoadContext(current_thread->GetContext());
890 898
891 SendReply("OK"); 899 SendReply("OK");
892} 900}
@@ -960,7 +968,9 @@ static void Step() {
960 if (command_length > 1) { 968 if (command_length > 1) {
961 RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread); 969 RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread);
962 // Update Unicorn context skipping scheduler, no running threads at this point 970 // Update Unicorn context skipping scheduler, no running threads at this point
963 Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context); 971 Core::System::GetInstance()
972 .ArmInterface(current_core)
973 .LoadContext(current_thread->GetContext());
964 } 974 }
965 step_loop = true; 975 step_loop = true;
966 halt_loop = true; 976 halt_loop = true;
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
index 93577591f..ebf193930 100644
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ b/src/core/hle/kernel/address_arbiter.cpp
@@ -23,13 +23,13 @@ namespace AddressArbiter {
23// Performs actual address waiting logic. 23// Performs actual address waiting logic.
24static ResultCode WaitForAddress(VAddr address, s64 timeout) { 24static ResultCode WaitForAddress(VAddr address, s64 timeout) {
25 SharedPtr<Thread> current_thread = GetCurrentThread(); 25 SharedPtr<Thread> current_thread = GetCurrentThread();
26 current_thread->arb_wait_address = address; 26 current_thread->SetArbiterWaitAddress(address);
27 current_thread->status = ThreadStatus::WaitArb; 27 current_thread->SetStatus(ThreadStatus::WaitArb);
28 current_thread->wakeup_callback = nullptr; 28 current_thread->InvalidateWakeupCallback();
29 29
30 current_thread->WakeAfterDelay(timeout); 30 current_thread->WakeAfterDelay(timeout);
31 31
32 Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule(); 32 Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
33 return RESULT_TIMEOUT; 33 return RESULT_TIMEOUT;
34} 34}
35 35
@@ -39,10 +39,10 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
39 std::vector<SharedPtr<Thread>>& waiting_threads, 39 std::vector<SharedPtr<Thread>>& waiting_threads,
40 VAddr arb_addr) { 40 VAddr arb_addr) {
41 const auto& scheduler = Core::System::GetInstance().Scheduler(core_index); 41 const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
42 auto& thread_list = scheduler->GetThreadList(); 42 const auto& thread_list = scheduler->GetThreadList();
43 43
44 for (auto& thread : thread_list) { 44 for (const auto& thread : thread_list) {
45 if (thread->arb_wait_address == arb_addr) 45 if (thread->GetArbiterWaitAddress() == arb_addr)
46 waiting_threads.push_back(thread); 46 waiting_threads.push_back(thread);
47 } 47 }
48 }; 48 };
@@ -57,7 +57,7 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
57 // Sort them by priority, such that the highest priority ones come first. 57 // Sort them by priority, such that the highest priority ones come first.
58 std::sort(threads.begin(), threads.end(), 58 std::sort(threads.begin(), threads.end(),
59 [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) { 59 [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
60 return lhs->current_priority < rhs->current_priority; 60 return lhs->GetPriority() < rhs->GetPriority();
61 }); 61 });
62 62
63 return threads; 63 return threads;
@@ -73,9 +73,9 @@ static void WakeThreads(std::vector<SharedPtr<Thread>>& waiting_threads, s32 num
73 73
74 // Signal the waiting threads. 74 // Signal the waiting threads.
75 for (std::size_t i = 0; i < last; i++) { 75 for (std::size_t i = 0; i < last; i++) {
76 ASSERT(waiting_threads[i]->status == ThreadStatus::WaitArb); 76 ASSERT(waiting_threads[i]->GetStatus() == ThreadStatus::WaitArb);
77 waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS); 77 waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
78 waiting_threads[i]->arb_wait_address = 0; 78 waiting_threads[i]->SetArbiterWaitAddress(0);
79 waiting_threads[i]->ResumeFromWait(); 79 waiting_threads[i]->ResumeFromWait();
80 } 80 }
81} 81}
diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp
index 873d6c516..d4c91d529 100644
--- a/src/core/hle/kernel/client_port.cpp
+++ b/src/core/hle/kernel/client_port.cpp
@@ -17,6 +17,10 @@ namespace Kernel {
17ClientPort::ClientPort(KernelCore& kernel) : Object{kernel} {} 17ClientPort::ClientPort(KernelCore& kernel) : Object{kernel} {}
18ClientPort::~ClientPort() = default; 18ClientPort::~ClientPort() = default;
19 19
20SharedPtr<ServerPort> ClientPort::GetServerPort() const {
21 return server_port;
22}
23
20ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { 24ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
21 // Note: Threads do not wait for the server endpoint to call 25 // Note: Threads do not wait for the server endpoint to call
22 // AcceptSession before returning from this call. 26 // AcceptSession before returning from this call.
diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h
index f3dfebbb1..6cd607206 100644
--- a/src/core/hle/kernel/client_port.h
+++ b/src/core/hle/kernel/client_port.h
@@ -30,6 +30,8 @@ public:
30 return HANDLE_TYPE; 30 return HANDLE_TYPE;
31 } 31 }
32 32
33 SharedPtr<ServerPort> GetServerPort() const;
34
33 /** 35 /**
34 * Creates a new Session pair, adds the created ServerSession to the associated ServerPort's 36 * Creates a new Session pair, adds the created ServerSession to the associated ServerPort's
35 * list of pending sessions, and signals the ServerPort, causing any threads 37 * list of pending sessions, and signals the ServerPort, causing any threads
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 72fb9d250..edad5f1b1 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -42,14 +42,14 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
42 Kernel::SharedPtr<Kernel::Event> event) { 42 Kernel::SharedPtr<Kernel::Event> event) {
43 43
44 // Put the client thread to sleep until the wait event is signaled or the timeout expires. 44 // Put the client thread to sleep until the wait event is signaled or the timeout expires.
45 thread->wakeup_callback = [context = *this, callback]( 45 thread->SetWakeupCallback([context = *this, callback](
46 ThreadWakeupReason reason, SharedPtr<Thread> thread, 46 ThreadWakeupReason reason, SharedPtr<Thread> thread,
47 SharedPtr<WaitObject> object, std::size_t index) mutable -> bool { 47 SharedPtr<WaitObject> object, std::size_t index) mutable -> bool {
48 ASSERT(thread->status == ThreadStatus::WaitHLEEvent); 48 ASSERT(thread->GetStatus() == ThreadStatus::WaitHLEEvent);
49 callback(thread, context, reason); 49 callback(thread, context, reason);
50 context.WriteToOutgoingCommandBuffer(*thread); 50 context.WriteToOutgoingCommandBuffer(*thread);
51 return true; 51 return true;
52 }; 52 });
53 53
54 if (!event) { 54 if (!event) {
55 // Create event if not provided 55 // Create event if not provided
@@ -59,8 +59,8 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
59 } 59 }
60 60
61 event->Clear(); 61 event->Clear();
62 thread->status = ThreadStatus::WaitHLEEvent; 62 thread->SetStatus(ThreadStatus::WaitHLEEvent);
63 thread->wait_objects = {event}; 63 thread->SetWaitObjects({event});
64 event->AddWaitingThread(thread); 64 event->AddWaitingThread(thread);
65 65
66 if (timeout > 0) { 66 if (timeout > 0) {
@@ -209,7 +209,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb
209 209
210ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) { 210ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) {
211 std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf; 211 std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf;
212 Memory::ReadBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(), 212 Memory::ReadBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
213 dst_cmdbuf.size() * sizeof(u32)); 213 dst_cmdbuf.size() * sizeof(u32));
214 214
215 // The header was already built in the internal command buffer. Attempt to parse it to verify 215 // The header was already built in the internal command buffer. Attempt to parse it to verify
@@ -268,7 +268,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread)
268 } 268 }
269 269
270 // Copy the translated command buffer back into the thread's command buffer area. 270 // Copy the translated command buffer back into the thread's command buffer area.
271 Memory::WriteBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(), 271 Memory::WriteBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
272 dst_cmdbuf.size() * sizeof(u32)); 272 dst_cmdbuf.size() * sizeof(u32));
273 273
274 return RESULT_SUCCESS; 274 return RESULT_SUCCESS;
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 3e0800a71..98eb74298 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -46,40 +46,40 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
46 46
47 bool resume = true; 47 bool resume = true;
48 48
49 if (thread->status == ThreadStatus::WaitSynchAny || 49 if (thread->GetStatus() == ThreadStatus::WaitSynchAny ||
50 thread->status == ThreadStatus::WaitSynchAll || 50 thread->GetStatus() == ThreadStatus::WaitSynchAll ||
51 thread->status == ThreadStatus::WaitHLEEvent) { 51 thread->GetStatus() == ThreadStatus::WaitHLEEvent) {
52 // Remove the thread from each of its waiting objects' waitlists 52 // Remove the thread from each of its waiting objects' waitlists
53 for (auto& object : thread->wait_objects) { 53 for (const auto& object : thread->GetWaitObjects()) {
54 object->RemoveWaitingThread(thread.get()); 54 object->RemoveWaitingThread(thread.get());
55 } 55 }
56 thread->wait_objects.clear(); 56 thread->ClearWaitObjects();
57 57
58 // Invoke the wakeup callback before clearing the wait objects 58 // Invoke the wakeup callback before clearing the wait objects
59 if (thread->wakeup_callback) { 59 if (thread->HasWakeupCallback()) {
60 resume = thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr, 0); 60 resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
61 } 61 }
62 } 62 }
63 63
64 if (thread->mutex_wait_address != 0 || thread->condvar_wait_address != 0 || 64 if (thread->GetMutexWaitAddress() != 0 || thread->GetCondVarWaitAddress() != 0 ||
65 thread->wait_handle) { 65 thread->GetWaitHandle() != 0) {
66 ASSERT(thread->status == ThreadStatus::WaitMutex); 66 ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
67 thread->mutex_wait_address = 0; 67 thread->SetMutexWaitAddress(0);
68 thread->condvar_wait_address = 0; 68 thread->SetCondVarWaitAddress(0);
69 thread->wait_handle = 0; 69 thread->SetWaitHandle(0);
70 70
71 auto lock_owner = thread->lock_owner; 71 auto* const lock_owner = thread->GetLockOwner();
72 // Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance 72 // Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
73 // and don't have a lock owner unless SignalProcessWideKey was called first and the thread 73 // and don't have a lock owner unless SignalProcessWideKey was called first and the thread
74 // wasn't awakened due to the mutex already being acquired. 74 // wasn't awakened due to the mutex already being acquired.
75 if (lock_owner) { 75 if (lock_owner != nullptr) {
76 lock_owner->RemoveMutexWaiter(thread); 76 lock_owner->RemoveMutexWaiter(thread);
77 } 77 }
78 } 78 }
79 79
80 if (thread->arb_wait_address != 0) { 80 if (thread->GetArbiterWaitAddress() != 0) {
81 ASSERT(thread->status == ThreadStatus::WaitArb); 81 ASSERT(thread->GetStatus() == ThreadStatus::WaitArb);
82 thread->arb_wait_address = 0; 82 thread->SetArbiterWaitAddress(0);
83 } 83 }
84 84
85 if (resume) { 85 if (resume) {
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
index 81675eac5..dd541ffcc 100644
--- a/src/core/hle/kernel/mutex.cpp
+++ b/src/core/hle/kernel/mutex.cpp
@@ -28,11 +28,11 @@ static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
28 SharedPtr<Thread> highest_priority_thread; 28 SharedPtr<Thread> highest_priority_thread;
29 u32 num_waiters = 0; 29 u32 num_waiters = 0;
30 30
31 for (auto& thread : current_thread->wait_mutex_threads) { 31 for (const auto& thread : current_thread->GetMutexWaitingThreads()) {
32 if (thread->mutex_wait_address != mutex_addr) 32 if (thread->GetMutexWaitAddress() != mutex_addr)
33 continue; 33 continue;
34 34
35 ASSERT(thread->status == ThreadStatus::WaitMutex); 35 ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
36 36
37 ++num_waiters; 37 ++num_waiters;
38 if (highest_priority_thread == nullptr || 38 if (highest_priority_thread == nullptr ||
@@ -47,12 +47,12 @@ static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
47/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner. 47/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
48static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread, 48static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread,
49 SharedPtr<Thread> new_owner) { 49 SharedPtr<Thread> new_owner) {
50 auto threads = current_thread->wait_mutex_threads; 50 const auto threads = current_thread->GetMutexWaitingThreads();
51 for (auto& thread : threads) { 51 for (const auto& thread : threads) {
52 if (thread->mutex_wait_address != mutex_addr) 52 if (thread->GetMutexWaitAddress() != mutex_addr)
53 continue; 53 continue;
54 54
55 ASSERT(thread->lock_owner == current_thread); 55 ASSERT(thread->GetLockOwner() == current_thread);
56 current_thread->RemoveMutexWaiter(thread); 56 current_thread->RemoveMutexWaiter(thread);
57 if (new_owner != thread) 57 if (new_owner != thread)
58 new_owner->AddMutexWaiter(thread); 58 new_owner->AddMutexWaiter(thread);
@@ -84,11 +84,11 @@ ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle ho
84 return ERR_INVALID_HANDLE; 84 return ERR_INVALID_HANDLE;
85 85
86 // Wait until the mutex is released 86 // Wait until the mutex is released
87 GetCurrentThread()->mutex_wait_address = address; 87 GetCurrentThread()->SetMutexWaitAddress(address);
88 GetCurrentThread()->wait_handle = requesting_thread_handle; 88 GetCurrentThread()->SetWaitHandle(requesting_thread_handle);
89 89
90 GetCurrentThread()->status = ThreadStatus::WaitMutex; 90 GetCurrentThread()->SetStatus(ThreadStatus::WaitMutex);
91 GetCurrentThread()->wakeup_callback = nullptr; 91 GetCurrentThread()->InvalidateWakeupCallback();
92 92
93 // Update the lock holder thread's priority to prevent priority inversion. 93 // Update the lock holder thread's priority to prevent priority inversion.
94 holding_thread->AddMutexWaiter(GetCurrentThread()); 94 holding_thread->AddMutexWaiter(GetCurrentThread());
@@ -115,7 +115,7 @@ ResultCode Mutex::Release(VAddr address) {
115 // Transfer the ownership of the mutex from the previous owner to the new one. 115 // Transfer the ownership of the mutex from the previous owner to the new one.
116 TransferMutexOwnership(address, GetCurrentThread(), thread); 116 TransferMutexOwnership(address, GetCurrentThread(), thread);
117 117
118 u32 mutex_value = thread->wait_handle; 118 u32 mutex_value = thread->GetWaitHandle();
119 119
120 if (num_waiters >= 2) { 120 if (num_waiters >= 2) {
121 // Notify the guest that there are still some threads waiting for the mutex 121 // Notify the guest that there are still some threads waiting for the mutex
@@ -125,13 +125,13 @@ ResultCode Mutex::Release(VAddr address) {
125 // Grant the mutex to the next waiting thread and resume it. 125 // Grant the mutex to the next waiting thread and resume it.
126 Memory::Write32(address, mutex_value); 126 Memory::Write32(address, mutex_value);
127 127
128 ASSERT(thread->status == ThreadStatus::WaitMutex); 128 ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
129 thread->ResumeFromWait(); 129 thread->ResumeFromWait();
130 130
131 thread->lock_owner = nullptr; 131 thread->SetLockOwner(nullptr);
132 thread->condvar_wait_address = 0; 132 thread->SetCondVarWaitAddress(0);
133 thread->mutex_wait_address = 0; 133 thread->SetMutexWaitAddress(0);
134 thread->wait_handle = 0; 134 thread->SetWaitHandle(0);
135 135
136 return RESULT_SUCCESS; 136 return RESULT_SUCCESS;
137} 137}
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index dc9fc8470..fb0027a71 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -144,15 +144,15 @@ void Process::PrepareForTermination() {
144 144
145 const auto stop_threads = [this](const std::vector<SharedPtr<Thread>>& thread_list) { 145 const auto stop_threads = [this](const std::vector<SharedPtr<Thread>>& thread_list) {
146 for (auto& thread : thread_list) { 146 for (auto& thread : thread_list) {
147 if (thread->owner_process != this) 147 if (thread->GetOwnerProcess() != this)
148 continue; 148 continue;
149 149
150 if (thread == GetCurrentThread()) 150 if (thread == GetCurrentThread())
151 continue; 151 continue;
152 152
153 // TODO(Subv): When are the other running/ready threads terminated? 153 // TODO(Subv): When are the other running/ready threads terminated?
154 ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny || 154 ASSERT_MSG(thread->GetStatus() == ThreadStatus::WaitSynchAny ||
155 thread->status == ThreadStatus::WaitSynchAll, 155 thread->GetStatus() == ThreadStatus::WaitSynchAll,
156 "Exiting processes with non-waiting threads is currently unimplemented"); 156 "Exiting processes with non-waiting threads is currently unimplemented");
157 157
158 thread->Stop(); 158 thread->Stop();
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index 1e82cfffb..cfd6e1bad 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -38,10 +38,10 @@ Thread* Scheduler::PopNextReadyThread() {
38 Thread* next = nullptr; 38 Thread* next = nullptr;
39 Thread* thread = GetCurrentThread(); 39 Thread* thread = GetCurrentThread();
40 40
41 if (thread && thread->status == ThreadStatus::Running) { 41 if (thread && thread->GetStatus() == ThreadStatus::Running) {
42 // We have to do better than the current thread. 42 // We have to do better than the current thread.
43 // This call returns null when that's not possible. 43 // This call returns null when that's not possible.
44 next = ready_queue.pop_first_better(thread->current_priority); 44 next = ready_queue.pop_first_better(thread->GetPriority());
45 if (!next) { 45 if (!next) {
46 // Otherwise just keep going with the current thread 46 // Otherwise just keep going with the current thread
47 next = thread; 47 next = thread;
@@ -58,22 +58,21 @@ void Scheduler::SwitchContext(Thread* new_thread) {
58 58
59 // Save context for previous thread 59 // Save context for previous thread
60 if (previous_thread) { 60 if (previous_thread) {
61 previous_thread->last_running_ticks = CoreTiming::GetTicks(); 61 cpu_core.SaveContext(previous_thread->GetContext());
62 cpu_core.SaveContext(previous_thread->context);
63 // Save the TPIDR_EL0 system register in case it was modified. 62 // Save the TPIDR_EL0 system register in case it was modified.
64 previous_thread->tpidr_el0 = cpu_core.GetTPIDR_EL0(); 63 previous_thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
65 64
66 if (previous_thread->status == ThreadStatus::Running) { 65 if (previous_thread->GetStatus() == ThreadStatus::Running) {
67 // This is only the case when a reschedule is triggered without the current thread 66 // This is only the case when a reschedule is triggered without the current thread
68 // yielding execution (i.e. an event triggered, system core time-sliced, etc) 67 // yielding execution (i.e. an event triggered, system core time-sliced, etc)
69 ready_queue.push_front(previous_thread->current_priority, previous_thread); 68 ready_queue.push_front(previous_thread->GetPriority(), previous_thread);
70 previous_thread->status = ThreadStatus::Ready; 69 previous_thread->SetStatus(ThreadStatus::Ready);
71 } 70 }
72 } 71 }
73 72
74 // Load context of new thread 73 // Load context of new thread
75 if (new_thread) { 74 if (new_thread) {
76 ASSERT_MSG(new_thread->status == ThreadStatus::Ready, 75 ASSERT_MSG(new_thread->GetStatus() == ThreadStatus::Ready,
77 "Thread must be ready to become running."); 76 "Thread must be ready to become running.");
78 77
79 // Cancel any outstanding wakeup events for this thread 78 // Cancel any outstanding wakeup events for this thread
@@ -83,15 +82,16 @@ void Scheduler::SwitchContext(Thread* new_thread) {
83 82
84 current_thread = new_thread; 83 current_thread = new_thread;
85 84
86 ready_queue.remove(new_thread->current_priority, new_thread); 85 ready_queue.remove(new_thread->GetPriority(), new_thread);
87 new_thread->status = ThreadStatus::Running; 86 new_thread->SetStatus(ThreadStatus::Running);
88 87
89 if (previous_process != current_thread->owner_process) { 88 const auto thread_owner_process = current_thread->GetOwnerProcess();
90 Core::CurrentProcess() = current_thread->owner_process; 89 if (previous_process != thread_owner_process) {
90 Core::CurrentProcess() = thread_owner_process;
91 SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table); 91 SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table);
92 } 92 }
93 93
94 cpu_core.LoadContext(new_thread->context); 94 cpu_core.LoadContext(new_thread->GetContext());
95 cpu_core.SetTlsAddress(new_thread->GetTLSAddress()); 95 cpu_core.SetTlsAddress(new_thread->GetTLSAddress());
96 cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0()); 96 cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0());
97 cpu_core.ClearExclusiveState(); 97 cpu_core.ClearExclusiveState();
@@ -136,14 +136,14 @@ void Scheduler::RemoveThread(Thread* thread) {
136void Scheduler::ScheduleThread(Thread* thread, u32 priority) { 136void Scheduler::ScheduleThread(Thread* thread, u32 priority) {
137 std::lock_guard<std::mutex> lock(scheduler_mutex); 137 std::lock_guard<std::mutex> lock(scheduler_mutex);
138 138
139 ASSERT(thread->status == ThreadStatus::Ready); 139 ASSERT(thread->GetStatus() == ThreadStatus::Ready);
140 ready_queue.push_back(priority, thread); 140 ready_queue.push_back(priority, thread);
141} 141}
142 142
143void Scheduler::UnscheduleThread(Thread* thread, u32 priority) { 143void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
144 std::lock_guard<std::mutex> lock(scheduler_mutex); 144 std::lock_guard<std::mutex> lock(scheduler_mutex);
145 145
146 ASSERT(thread->status == ThreadStatus::Ready); 146 ASSERT(thread->GetStatus() == ThreadStatus::Ready);
147 ready_queue.remove(priority, thread); 147 ready_queue.remove(priority, thread);
148} 148}
149 149
@@ -151,8 +151,8 @@ void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
151 std::lock_guard<std::mutex> lock(scheduler_mutex); 151 std::lock_guard<std::mutex> lock(scheduler_mutex);
152 152
153 // If thread was ready, adjust queues 153 // If thread was ready, adjust queues
154 if (thread->status == ThreadStatus::Ready) 154 if (thread->GetStatus() == ThreadStatus::Ready)
155 ready_queue.move(thread, thread->current_priority, priority); 155 ready_queue.move(thread, thread->GetPriority(), priority);
156 else 156 else
157 ready_queue.prepare(priority); 157 ready_queue.prepare(priority);
158} 158}
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index 62fb51349..e52f8245f 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -11,6 +11,7 @@
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "core/hle/kernel/object.h" 12#include "core/hle/kernel/object.h"
13#include "core/hle/kernel/wait_object.h" 13#include "core/hle/kernel/wait_object.h"
14#include "core/hle/result.h"
14 15
15namespace Kernel { 16namespace Kernel {
16 17
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index aba0cab96..1ece691c7 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -120,10 +120,10 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
120 result = hle_handler->HandleSyncRequest(context); 120 result = hle_handler->HandleSyncRequest(context);
121 } 121 }
122 122
123 if (thread->status == ThreadStatus::Running) { 123 if (thread->GetStatus() == ThreadStatus::Running) {
124 // Put the thread to sleep until the server replies, it will be awoken in 124 // Put the thread to sleep until the server replies, it will be awoken in
125 // svcReplyAndReceive for LLE servers. 125 // svcReplyAndReceive for LLE servers.
126 thread->status = ThreadStatus::WaitIPC; 126 thread->SetStatus(ThreadStatus::WaitIPC);
127 127
128 if (hle_handler != nullptr) { 128 if (hle_handler != nullptr) {
129 // For HLE services, we put the request threads to sleep for a short duration to 129 // For HLE services, we put the request threads to sleep for a short duration to
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 1cdaa740a..6c4af7e47 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -156,7 +156,7 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
156 return ERR_INVALID_HANDLE; 156 return ERR_INVALID_HANDLE;
157 } 157 }
158 158
159 *thread_id = thread->GetThreadId(); 159 *thread_id = thread->GetThreadID();
160 return RESULT_SUCCESS; 160 return RESULT_SUCCESS;
161} 161}
162 162
@@ -177,7 +177,7 @@ static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
177/// Default thread wakeup callback for WaitSynchronization 177/// Default thread wakeup callback for WaitSynchronization
178static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread, 178static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
179 SharedPtr<WaitObject> object, std::size_t index) { 179 SharedPtr<WaitObject> object, std::size_t index) {
180 ASSERT(thread->status == ThreadStatus::WaitSynchAny); 180 ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny);
181 181
182 if (reason == ThreadWakeupReason::Timeout) { 182 if (reason == ThreadWakeupReason::Timeout) {
183 thread->SetWaitSynchronizationResult(RESULT_TIMEOUT); 183 thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
@@ -204,10 +204,10 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
204 if (handle_count > MaxHandles) 204 if (handle_count > MaxHandles)
205 return ResultCode(ErrorModule::Kernel, ErrCodes::TooLarge); 205 return ResultCode(ErrorModule::Kernel, ErrCodes::TooLarge);
206 206
207 auto thread = GetCurrentThread(); 207 auto* const thread = GetCurrentThread();
208 208
209 using ObjectPtr = SharedPtr<WaitObject>; 209 using ObjectPtr = Thread::ThreadWaitObjects::value_type;
210 std::vector<ObjectPtr> objects(handle_count); 210 Thread::ThreadWaitObjects objects(handle_count);
211 auto& kernel = Core::System::GetInstance().Kernel(); 211 auto& kernel = Core::System::GetInstance().Kernel();
212 212
213 for (u64 i = 0; i < handle_count; ++i) { 213 for (u64 i = 0; i < handle_count; ++i) {
@@ -244,14 +244,14 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
244 for (auto& object : objects) 244 for (auto& object : objects)
245 object->AddWaitingThread(thread); 245 object->AddWaitingThread(thread);
246 246
247 thread->wait_objects = std::move(objects); 247 thread->SetWaitObjects(std::move(objects));
248 thread->status = ThreadStatus::WaitSynchAny; 248 thread->SetStatus(ThreadStatus::WaitSynchAny);
249 249
250 // Create an event to wake the thread up after the specified nanosecond delay has passed 250 // Create an event to wake the thread up after the specified nanosecond delay has passed
251 thread->WakeAfterDelay(nano_seconds); 251 thread->WakeAfterDelay(nano_seconds);
252 thread->wakeup_callback = DefaultThreadWakeupCallback; 252 thread->SetWakeupCallback(DefaultThreadWakeupCallback);
253 253
254 Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); 254 Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
255 255
256 return RESULT_TIMEOUT; 256 return RESULT_TIMEOUT;
257} 257}
@@ -266,7 +266,7 @@ static ResultCode CancelSynchronization(Handle thread_handle) {
266 return ERR_INVALID_HANDLE; 266 return ERR_INVALID_HANDLE;
267 } 267 }
268 268
269 ASSERT(thread->status == ThreadStatus::WaitSynchAny); 269 ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny);
270 thread->SetWaitSynchronizationResult( 270 thread->SetWaitSynchronizationResult(
271 ResultCode(ErrorModule::Kernel, ErrCodes::SynchronizationCanceled)); 271 ResultCode(ErrorModule::Kernel, ErrCodes::SynchronizationCanceled));
272 thread->ResumeFromWait(); 272 thread->ResumeFromWait();
@@ -425,7 +425,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
425 } 425 }
426 426
427 const auto current_process = Core::CurrentProcess(); 427 const auto current_process = Core::CurrentProcess();
428 if (thread->owner_process != current_process) { 428 if (thread->GetOwnerProcess() != current_process) {
429 return ERR_INVALID_HANDLE; 429 return ERR_INVALID_HANDLE;
430 } 430 }
431 431
@@ -433,7 +433,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
433 return ERR_ALREADY_REGISTERED; 433 return ERR_ALREADY_REGISTERED;
434 } 434 }
435 435
436 Core::ARM_Interface::ThreadContext ctx = thread->context; 436 Core::ARM_Interface::ThreadContext ctx = thread->GetContext();
437 // Mask away mode bits, interrupt bits, IL bit, and other reserved bits. 437 // Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
438 ctx.pstate &= 0xFF0FFE20; 438 ctx.pstate &= 0xFF0FFE20;
439 439
@@ -479,14 +479,14 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
479 479
480 thread->SetPriority(priority); 480 thread->SetPriority(priority);
481 481
482 Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); 482 Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
483 return RESULT_SUCCESS; 483 return RESULT_SUCCESS;
484} 484}
485 485
486/// Get which CPU core is executing the current thread 486/// Get which CPU core is executing the current thread
487static u32 GetCurrentProcessorNumber() { 487static u32 GetCurrentProcessorNumber() {
488 LOG_TRACE(Kernel_SVC, "called"); 488 LOG_TRACE(Kernel_SVC, "called");
489 return GetCurrentThread()->processor_id; 489 return GetCurrentThread()->GetProcessorID();
490} 490}
491 491
492static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size, 492static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size,
@@ -622,10 +622,14 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
622 CASCADE_RESULT(SharedPtr<Thread> thread, 622 CASCADE_RESULT(SharedPtr<Thread> thread,
623 Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top, 623 Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top,
624 Core::CurrentProcess())); 624 Core::CurrentProcess()));
625 CASCADE_RESULT(thread->guest_handle, kernel.HandleTable().Create(thread)); 625 const auto new_guest_handle = kernel.HandleTable().Create(thread);
626 *out_handle = thread->guest_handle; 626 if (new_guest_handle.Failed()) {
627 return new_guest_handle.Code();
628 }
629 thread->SetGuestHandle(*new_guest_handle);
630 *out_handle = *new_guest_handle;
627 631
628 Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); 632 Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
629 633
630 LOG_TRACE(Kernel_SVC, 634 LOG_TRACE(Kernel_SVC,
631 "called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, " 635 "called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, "
@@ -645,10 +649,10 @@ static ResultCode StartThread(Handle thread_handle) {
645 return ERR_INVALID_HANDLE; 649 return ERR_INVALID_HANDLE;
646 } 650 }
647 651
648 ASSERT(thread->status == ThreadStatus::Dormant); 652 ASSERT(thread->GetStatus() == ThreadStatus::Dormant);
649 653
650 thread->ResumeFromWait(); 654 thread->ResumeFromWait();
651 Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); 655 Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
652 656
653 return RESULT_SUCCESS; 657 return RESULT_SUCCESS;
654} 658}
@@ -694,17 +698,17 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
694 CASCADE_CODE(Mutex::Release(mutex_addr)); 698 CASCADE_CODE(Mutex::Release(mutex_addr));
695 699
696 SharedPtr<Thread> current_thread = GetCurrentThread(); 700 SharedPtr<Thread> current_thread = GetCurrentThread();
697 current_thread->condvar_wait_address = condition_variable_addr; 701 current_thread->SetCondVarWaitAddress(condition_variable_addr);
698 current_thread->mutex_wait_address = mutex_addr; 702 current_thread->SetMutexWaitAddress(mutex_addr);
699 current_thread->wait_handle = thread_handle; 703 current_thread->SetWaitHandle(thread_handle);
700 current_thread->status = ThreadStatus::WaitMutex; 704 current_thread->SetStatus(ThreadStatus::WaitMutex);
701 current_thread->wakeup_callback = nullptr; 705 current_thread->InvalidateWakeupCallback();
702 706
703 current_thread->WakeAfterDelay(nano_seconds); 707 current_thread->WakeAfterDelay(nano_seconds);
704 708
705 // Note: Deliberately don't attempt to inherit the lock owner's priority. 709 // Note: Deliberately don't attempt to inherit the lock owner's priority.
706 710
707 Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule(); 711 Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
708 return RESULT_SUCCESS; 712 return RESULT_SUCCESS;
709} 713}
710 714
@@ -713,14 +717,14 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
713 LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}", 717 LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}",
714 condition_variable_addr, target); 718 condition_variable_addr, target);
715 719
716 auto RetrieveWaitingThreads = [](std::size_t core_index, 720 const auto RetrieveWaitingThreads = [](std::size_t core_index,
717 std::vector<SharedPtr<Thread>>& waiting_threads, 721 std::vector<SharedPtr<Thread>>& waiting_threads,
718 VAddr condvar_addr) { 722 VAddr condvar_addr) {
719 const auto& scheduler = Core::System::GetInstance().Scheduler(core_index); 723 const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
720 auto& thread_list = scheduler->GetThreadList(); 724 const auto& thread_list = scheduler->GetThreadList();
721 725
722 for (auto& thread : thread_list) { 726 for (const auto& thread : thread_list) {
723 if (thread->condvar_wait_address == condvar_addr) 727 if (thread->GetCondVarWaitAddress() == condvar_addr)
724 waiting_threads.push_back(thread); 728 waiting_threads.push_back(thread);
725 } 729 }
726 }; 730 };
@@ -734,7 +738,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
734 // Sort them by priority, such that the highest priority ones come first. 738 // Sort them by priority, such that the highest priority ones come first.
735 std::sort(waiting_threads.begin(), waiting_threads.end(), 739 std::sort(waiting_threads.begin(), waiting_threads.end(),
736 [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) { 740 [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
737 return lhs->current_priority < rhs->current_priority; 741 return lhs->GetPriority() < rhs->GetPriority();
738 }); 742 });
739 743
740 // Only process up to 'target' threads, unless 'target' is -1, in which case process 744 // Only process up to 'target' threads, unless 'target' is -1, in which case process
@@ -750,7 +754,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
750 for (std::size_t index = 0; index < last; ++index) { 754 for (std::size_t index = 0; index < last; ++index) {
751 auto& thread = waiting_threads[index]; 755 auto& thread = waiting_threads[index];
752 756
753 ASSERT(thread->condvar_wait_address == condition_variable_addr); 757 ASSERT(thread->GetCondVarWaitAddress() == condition_variable_addr);
754 758
755 std::size_t current_core = Core::System::GetInstance().CurrentCoreIndex(); 759 std::size_t current_core = Core::System::GetInstance().CurrentCoreIndex();
756 760
@@ -759,42 +763,43 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
759 // Atomically read the value of the mutex. 763 // Atomically read the value of the mutex.
760 u32 mutex_val = 0; 764 u32 mutex_val = 0;
761 do { 765 do {
762 monitor.SetExclusive(current_core, thread->mutex_wait_address); 766 monitor.SetExclusive(current_core, thread->GetMutexWaitAddress());
763 767
764 // If the mutex is not yet acquired, acquire it. 768 // If the mutex is not yet acquired, acquire it.
765 mutex_val = Memory::Read32(thread->mutex_wait_address); 769 mutex_val = Memory::Read32(thread->GetMutexWaitAddress());
766 770
767 if (mutex_val != 0) { 771 if (mutex_val != 0) {
768 monitor.ClearExclusive(); 772 monitor.ClearExclusive();
769 break; 773 break;
770 } 774 }
771 } while (!monitor.ExclusiveWrite32(current_core, thread->mutex_wait_address, 775 } while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
772 thread->wait_handle)); 776 thread->GetWaitHandle()));
773 777
774 if (mutex_val == 0) { 778 if (mutex_val == 0) {
775 // We were able to acquire the mutex, resume this thread. 779 // We were able to acquire the mutex, resume this thread.
776 ASSERT(thread->status == ThreadStatus::WaitMutex); 780 ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
777 thread->ResumeFromWait(); 781 thread->ResumeFromWait();
778 782
779 auto lock_owner = thread->lock_owner; 783 auto* const lock_owner = thread->GetLockOwner();
780 if (lock_owner) 784 if (lock_owner != nullptr) {
781 lock_owner->RemoveMutexWaiter(thread); 785 lock_owner->RemoveMutexWaiter(thread);
786 }
782 787
783 thread->lock_owner = nullptr; 788 thread->SetLockOwner(nullptr);
784 thread->mutex_wait_address = 0; 789 thread->SetMutexWaitAddress(0);
785 thread->condvar_wait_address = 0; 790 thread->SetCondVarWaitAddress(0);
786 thread->wait_handle = 0; 791 thread->SetWaitHandle(0);
787 } else { 792 } else {
788 // Atomically signal that the mutex now has a waiting thread. 793 // Atomically signal that the mutex now has a waiting thread.
789 do { 794 do {
790 monitor.SetExclusive(current_core, thread->mutex_wait_address); 795 monitor.SetExclusive(current_core, thread->GetMutexWaitAddress());
791 796
792 // Ensure that the mutex value is still what we expect. 797 // Ensure that the mutex value is still what we expect.
793 u32 value = Memory::Read32(thread->mutex_wait_address); 798 u32 value = Memory::Read32(thread->GetMutexWaitAddress());
794 // TODO(Subv): When this happens, the kernel just clears the exclusive state and 799 // TODO(Subv): When this happens, the kernel just clears the exclusive state and
795 // retries the initial read for this thread. 800 // retries the initial read for this thread.
796 ASSERT_MSG(mutex_val == value, "Unhandled synchronization primitive case"); 801 ASSERT_MSG(mutex_val == value, "Unhandled synchronization primitive case");
797 } while (!monitor.ExclusiveWrite32(current_core, thread->mutex_wait_address, 802 } while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
798 mutex_val | Mutex::MutexHasWaitersFlag)); 803 mutex_val | Mutex::MutexHasWaitersFlag));
799 804
800 // The mutex is already owned by some other thread, make this thread wait on it. 805 // The mutex is already owned by some other thread, make this thread wait on it.
@@ -802,12 +807,12 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
802 Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask); 807 Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
803 auto owner = kernel.HandleTable().Get<Thread>(owner_handle); 808 auto owner = kernel.HandleTable().Get<Thread>(owner_handle);
804 ASSERT(owner); 809 ASSERT(owner);
805 ASSERT(thread->status == ThreadStatus::WaitMutex); 810 ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
806 thread->wakeup_callback = nullptr; 811 thread->InvalidateWakeupCallback();
807 812
808 owner->AddMutexWaiter(thread); 813 owner->AddMutexWaiter(thread);
809 814
810 Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); 815 Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
811 } 816 }
812 } 817 }
813 818
@@ -913,8 +918,8 @@ static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask)
913 return ERR_INVALID_HANDLE; 918 return ERR_INVALID_HANDLE;
914 } 919 }
915 920
916 *core = thread->ideal_core; 921 *core = thread->GetIdealCore();
917 *mask = thread->affinity_mask; 922 *mask = thread->GetAffinityMask();
918 923
919 return RESULT_SUCCESS; 924 return RESULT_SUCCESS;
920} 925}
@@ -930,11 +935,13 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
930 } 935 }
931 936
932 if (core == static_cast<u32>(THREADPROCESSORID_DEFAULT)) { 937 if (core == static_cast<u32>(THREADPROCESSORID_DEFAULT)) {
933 ASSERT(thread->owner_process->GetDefaultProcessorID() != 938 const u8 default_processor_id = thread->GetOwnerProcess()->GetDefaultProcessorID();
934 static_cast<u8>(THREADPROCESSORID_DEFAULT)); 939
940 ASSERT(default_processor_id != static_cast<u8>(THREADPROCESSORID_DEFAULT));
941
935 // Set the target CPU to the one specified in the process' exheader. 942 // Set the target CPU to the one specified in the process' exheader.
936 core = thread->owner_process->GetDefaultProcessorID(); 943 core = default_processor_id;
937 mask = 1ull << core; 944 mask = 1ULL << core;
938 } 945 }
939 946
940 if (mask == 0) { 947 if (mask == 0) {
@@ -945,7 +952,7 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
945 static constexpr u32 OnlyChangeMask = static_cast<u32>(-3); 952 static constexpr u32 OnlyChangeMask = static_cast<u32>(-3);
946 953
947 if (core == OnlyChangeMask) { 954 if (core == OnlyChangeMask) {
948 core = thread->ideal_core; 955 core = thread->GetIdealCore();
949 } else if (core >= Core::NUM_CPU_CORES && core != static_cast<u32>(-1)) { 956 } else if (core >= Core::NUM_CPU_CORES && core != static_cast<u32>(-1)) {
950 return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidProcessorId); 957 return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
951 } 958 }
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index b5c16cfbb..8e514cf9a 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -70,7 +70,7 @@ void Thread::Stop() {
70 70
71void WaitCurrentThread_Sleep() { 71void WaitCurrentThread_Sleep() {
72 Thread* thread = GetCurrentThread(); 72 Thread* thread = GetCurrentThread();
73 thread->status = ThreadStatus::WaitSleep; 73 thread->SetStatus(ThreadStatus::WaitSleep);
74} 74}
75 75
76void ExitCurrentThread() { 76void ExitCurrentThread() {
@@ -169,7 +169,7 @@ void Thread::ResumeFromWait() {
169 next_scheduler->ScheduleThread(this, current_priority); 169 next_scheduler->ScheduleThread(this, current_priority);
170 170
171 // Change thread's scheduler 171 // Change thread's scheduler
172 scheduler = next_scheduler; 172 scheduler = next_scheduler.get();
173 173
174 Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule(); 174 Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
175} 175}
@@ -233,7 +233,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
233 thread->name = std::move(name); 233 thread->name = std::move(name);
234 thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap(); 234 thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
235 thread->owner_process = owner_process; 235 thread->owner_process = owner_process;
236 thread->scheduler = Core::System::GetInstance().Scheduler(processor_id); 236 thread->scheduler = Core::System::GetInstance().Scheduler(processor_id).get();
237 thread->scheduler->AddThread(thread, priority); 237 thread->scheduler->AddThread(thread, priority);
238 thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread); 238 thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
239 239
@@ -269,9 +269,9 @@ SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 pri
269 SharedPtr<Thread> thread = std::move(thread_res).Unwrap(); 269 SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
270 270
271 // Register 1 must be a handle to the main thread 271 // Register 1 must be a handle to the main thread
272 thread->guest_handle = kernel.HandleTable().Create(thread).Unwrap(); 272 const Handle guest_handle = kernel.HandleTable().Create(thread).Unwrap();
273 273 thread->SetGuestHandle(guest_handle);
274 thread->context.cpu_registers[1] = thread->guest_handle; 274 thread->GetContext().cpu_registers[1] = guest_handle;
275 275
276 // Threads by default are dormant, wake up the main thread so it runs when the scheduler fires 276 // Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
277 thread->ResumeFromWait(); 277 thread->ResumeFromWait();
@@ -299,6 +299,18 @@ VAddr Thread::GetCommandBufferAddress() const {
299 return GetTLSAddress() + CommandHeaderOffset; 299 return GetTLSAddress() + CommandHeaderOffset;
300} 300}
301 301
302void Thread::SetStatus(ThreadStatus new_status) {
303 if (new_status == status) {
304 return;
305 }
306
307 if (status == ThreadStatus::Running) {
308 last_running_ticks = CoreTiming::GetTicks();
309 }
310
311 status = new_status;
312}
313
302void Thread::AddMutexWaiter(SharedPtr<Thread> thread) { 314void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
303 if (thread->lock_owner == this) { 315 if (thread->lock_owner == this) {
304 // If the thread is already waiting for this thread to release the mutex, ensure that the 316 // If the thread is already waiting for this thread to release the mutex, ensure that the
@@ -388,11 +400,23 @@ void Thread::ChangeCore(u32 core, u64 mask) {
388 next_scheduler->ScheduleThread(this, current_priority); 400 next_scheduler->ScheduleThread(this, current_priority);
389 401
390 // Change thread's scheduler 402 // Change thread's scheduler
391 scheduler = next_scheduler; 403 scheduler = next_scheduler.get();
392 404
393 Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule(); 405 Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
394} 406}
395 407
408bool Thread::AllWaitObjectsReady() {
409 return std::none_of(
410 wait_objects.begin(), wait_objects.end(),
411 [this](const SharedPtr<WaitObject>& object) { return object->ShouldWait(this); });
412}
413
414bool Thread::InvokeWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
415 SharedPtr<WaitObject> object, std::size_t index) {
416 ASSERT(wakeup_callback);
417 return wakeup_callback(reason, std::move(thread), std::move(object), index);
418}
419
396//////////////////////////////////////////////////////////////////////////////////////////////////// 420////////////////////////////////////////////////////////////////////////////////////////////////////
397 421
398/** 422/**
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 4250144c3..c6ffbd28c 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -65,6 +65,15 @@ public:
65 using TLSMemory = std::vector<u8>; 65 using TLSMemory = std::vector<u8>;
66 using TLSMemoryPtr = std::shared_ptr<TLSMemory>; 66 using TLSMemoryPtr = std::shared_ptr<TLSMemory>;
67 67
68 using MutexWaitingThreads = std::vector<SharedPtr<Thread>>;
69
70 using ThreadContext = Core::ARM_Interface::ThreadContext;
71
72 using ThreadWaitObjects = std::vector<SharedPtr<WaitObject>>;
73
74 using WakeupCallback = std::function<bool(ThreadWakeupReason reason, SharedPtr<Thread> thread,
75 SharedPtr<WaitObject> object, std::size_t index)>;
76
68 /** 77 /**
69 * Creates and returns a new thread. The new thread is immediately scheduled 78 * Creates and returns a new thread. The new thread is immediately scheduled
70 * @param kernel The kernel instance this thread will be created under. 79 * @param kernel The kernel instance this thread will be created under.
@@ -106,6 +115,14 @@ public:
106 } 115 }
107 116
108 /** 117 /**
118 * Gets the thread's nominal priority.
119 * @return The current thread's nominal priority.
120 */
121 u32 GetNominalPriority() const {
122 return nominal_priority;
123 }
124
125 /**
109 * Sets the thread's current priority 126 * Sets the thread's current priority
110 * @param priority The new priority 127 * @param priority The new priority
111 */ 128 */
@@ -133,7 +150,7 @@ public:
133 * Gets the thread's thread ID 150 * Gets the thread's thread ID
134 * @return The thread's ID 151 * @return The thread's ID
135 */ 152 */
136 u32 GetThreadId() const { 153 u32 GetThreadID() const {
137 return thread_id; 154 return thread_id;
138 } 155 }
139 156
@@ -203,6 +220,11 @@ public:
203 return tpidr_el0; 220 return tpidr_el0;
204 } 221 }
205 222
223 /// Sets the value of the TPIDR_EL0 Read/Write system register for this thread.
224 void SetTPIDR_EL0(u64 value) {
225 tpidr_el0 = value;
226 }
227
206 /* 228 /*
207 * Returns the address of the current thread's command buffer, located in the TLS. 229 * Returns the address of the current thread's command buffer, located in the TLS.
208 * @returns VAddr of the thread's command buffer. 230 * @returns VAddr of the thread's command buffer.
@@ -218,69 +240,193 @@ public:
218 return status == ThreadStatus::WaitSynchAll; 240 return status == ThreadStatus::WaitSynchAll;
219 } 241 }
220 242
221 Core::ARM_Interface::ThreadContext context; 243 ThreadContext& GetContext() {
244 return context;
245 }
246
247 const ThreadContext& GetContext() const {
248 return context;
249 }
250
251 ThreadStatus GetStatus() const {
252 return status;
253 }
254
255 void SetStatus(ThreadStatus new_status);
256
257 u64 GetLastRunningTicks() const {
258 return last_running_ticks;
259 }
260
261 s32 GetProcessorID() const {
262 return processor_id;
263 }
264
265 SharedPtr<Process>& GetOwnerProcess() {
266 return owner_process;
267 }
268
269 const SharedPtr<Process>& GetOwnerProcess() const {
270 return owner_process;
271 }
272
273 const ThreadWaitObjects& GetWaitObjects() const {
274 return wait_objects;
275 }
276
277 void SetWaitObjects(ThreadWaitObjects objects) {
278 wait_objects = std::move(objects);
279 }
280
281 void ClearWaitObjects() {
282 wait_objects.clear();
283 }
284
285 /// Determines whether all the objects this thread is waiting on are ready.
286 bool AllWaitObjectsReady();
287
288 const MutexWaitingThreads& GetMutexWaitingThreads() const {
289 return wait_mutex_threads;
290 }
291
292 Thread* GetLockOwner() const {
293 return lock_owner.get();
294 }
295
296 void SetLockOwner(SharedPtr<Thread> owner) {
297 lock_owner = std::move(owner);
298 }
299
300 VAddr GetCondVarWaitAddress() const {
301 return condvar_wait_address;
302 }
303
304 void SetCondVarWaitAddress(VAddr address) {
305 condvar_wait_address = address;
306 }
307
308 VAddr GetMutexWaitAddress() const {
309 return mutex_wait_address;
310 }
222 311
223 u32 thread_id; 312 void SetMutexWaitAddress(VAddr address) {
313 mutex_wait_address = address;
314 }
224 315
225 ThreadStatus status; 316 Handle GetWaitHandle() const {
226 VAddr entry_point; 317 return wait_handle;
227 VAddr stack_top; 318 }
228 319
229 u32 nominal_priority; ///< Nominal thread priority, as set by the emulated application 320 void SetWaitHandle(Handle handle) {
230 u32 current_priority; ///< Current thread priority, can be temporarily changed 321 wait_handle = handle;
322 }
231 323
232 u64 last_running_ticks; ///< CPU tick when thread was last running 324 VAddr GetArbiterWaitAddress() const {
325 return arb_wait_address;
326 }
233 327
234 s32 processor_id; 328 void SetArbiterWaitAddress(VAddr address) {
329 arb_wait_address = address;
330 }
235 331
236 VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread 332 void SetGuestHandle(Handle handle) {
237 u64 tpidr_el0; ///< TPIDR_EL0 read/write system register. 333 guest_handle = handle;
334 }
238 335
239 SharedPtr<Process> owner_process; ///< Process that owns this thread 336 bool HasWakeupCallback() const {
337 return wakeup_callback != nullptr;
338 }
339
340 void SetWakeupCallback(WakeupCallback callback) {
341 wakeup_callback = std::move(callback);
342 }
343
344 void InvalidateWakeupCallback() {
345 SetWakeupCallback(nullptr);
346 }
347
348 /**
349 * Invokes the thread's wakeup callback.
350 *
351 * @pre A valid wakeup callback has been set. Violating this precondition
352 * will cause an assertion to trigger.
353 */
354 bool InvokeWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
355 SharedPtr<WaitObject> object, std::size_t index);
356
357 u32 GetIdealCore() const {
358 return ideal_core;
359 }
360
361 u64 GetAffinityMask() const {
362 return affinity_mask;
363 }
364
365private:
366 explicit Thread(KernelCore& kernel);
367 ~Thread() override;
368
369 Core::ARM_Interface::ThreadContext context{};
370
371 u32 thread_id = 0;
372
373 ThreadStatus status = ThreadStatus::Dormant;
374
375 VAddr entry_point = 0;
376 VAddr stack_top = 0;
377
378 u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
379 u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
380
381 u64 last_running_ticks = 0; ///< CPU tick when thread was last running
382
383 s32 processor_id = 0;
384
385 VAddr tls_address = 0; ///< Virtual address of the Thread Local Storage of the thread
386 u64 tpidr_el0 = 0; ///< TPIDR_EL0 read/write system register.
387
388 /// Process that owns this thread
389 SharedPtr<Process> owner_process;
240 390
241 /// Objects that the thread is waiting on, in the same order as they were 391 /// Objects that the thread is waiting on, in the same order as they were
242 // passed to WaitSynchronization1/N. 392 /// passed to WaitSynchronization1/N.
243 std::vector<SharedPtr<WaitObject>> wait_objects; 393 ThreadWaitObjects wait_objects;
244 394
245 /// List of threads that are waiting for a mutex that is held by this thread. 395 /// List of threads that are waiting for a mutex that is held by this thread.
246 std::vector<SharedPtr<Thread>> wait_mutex_threads; 396 MutexWaitingThreads wait_mutex_threads;
247 397
248 /// Thread that owns the lock that this thread is waiting for. 398 /// Thread that owns the lock that this thread is waiting for.
249 SharedPtr<Thread> lock_owner; 399 SharedPtr<Thread> lock_owner;
250 400
251 // If waiting on a ConditionVariable, this is the ConditionVariable address 401 /// If waiting on a ConditionVariable, this is the ConditionVariable address
252 VAddr condvar_wait_address; 402 VAddr condvar_wait_address = 0;
253 VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address 403 /// If waiting on a Mutex, this is the mutex address
254 Handle wait_handle; ///< The handle used to wait for the mutex. 404 VAddr mutex_wait_address = 0;
405 /// The handle used to wait for the mutex.
406 Handle wait_handle = 0;
255 407
256 // If waiting for an AddressArbiter, this is the address being waited on. 408 /// If waiting for an AddressArbiter, this is the address being waited on.
257 VAddr arb_wait_address{0}; 409 VAddr arb_wait_address{0};
258 410
259 std::string name;
260
261 /// Handle used by guest emulated application to access this thread 411 /// Handle used by guest emulated application to access this thread
262 Handle guest_handle; 412 Handle guest_handle = 0;
263 413
264 /// Handle used as userdata to reference this object when inserting into the CoreTiming queue. 414 /// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
265 Handle callback_handle; 415 Handle callback_handle = 0;
266 416
267 using WakeupCallback = bool(ThreadWakeupReason reason, SharedPtr<Thread> thread, 417 /// Callback that will be invoked when the thread is resumed from a waiting state. If the thread
268 SharedPtr<WaitObject> object, std::size_t index); 418 /// was waiting via WaitSynchronizationN then the object will be the last object that became
269 // Callback that will be invoked when the thread is resumed from a waiting state. If the thread 419 /// available. In case of a timeout, the object will be nullptr.
270 // was waiting via WaitSynchronizationN then the object will be the last object that became 420 WakeupCallback wakeup_callback;
271 // available. In case of a timeout, the object will be nullptr.
272 std::function<WakeupCallback> wakeup_callback;
273 421
274 std::shared_ptr<Scheduler> scheduler; 422 Scheduler* scheduler = nullptr;
275 423
276 u32 ideal_core{0xFFFFFFFF}; 424 u32 ideal_core{0xFFFFFFFF};
277 u64 affinity_mask{0x1}; 425 u64 affinity_mask{0x1};
278 426
279private:
280 explicit Thread(KernelCore& kernel);
281 ~Thread() override;
282
283 TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>(); 427 TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>();
428
429 std::string name;
284}; 430};
285 431
286/** 432/**
diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp
index b190ceb98..530ee6af7 100644
--- a/src/core/hle/kernel/wait_object.cpp
+++ b/src/core/hle/kernel/wait_object.cpp
@@ -35,13 +35,15 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
35 u32 candidate_priority = THREADPRIO_LOWEST + 1; 35 u32 candidate_priority = THREADPRIO_LOWEST + 1;
36 36
37 for (const auto& thread : waiting_threads) { 37 for (const auto& thread : waiting_threads) {
38 const ThreadStatus thread_status = thread->GetStatus();
39
38 // The list of waiting threads must not contain threads that are not waiting to be awakened. 40 // The list of waiting threads must not contain threads that are not waiting to be awakened.
39 ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny || 41 ASSERT_MSG(thread_status == ThreadStatus::WaitSynchAny ||
40 thread->status == ThreadStatus::WaitSynchAll || 42 thread_status == ThreadStatus::WaitSynchAll ||
41 thread->status == ThreadStatus::WaitHLEEvent, 43 thread_status == ThreadStatus::WaitHLEEvent,
42 "Inconsistent thread statuses in waiting_threads"); 44 "Inconsistent thread statuses in waiting_threads");
43 45
44 if (thread->current_priority >= candidate_priority) 46 if (thread->GetPriority() >= candidate_priority)
45 continue; 47 continue;
46 48
47 if (ShouldWait(thread.get())) 49 if (ShouldWait(thread.get()))
@@ -50,16 +52,13 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
50 // A thread is ready to run if it's either in ThreadStatus::WaitSynchAny or 52 // A thread is ready to run if it's either in ThreadStatus::WaitSynchAny or
51 // in ThreadStatus::WaitSynchAll and the rest of the objects it is waiting on are ready. 53 // in ThreadStatus::WaitSynchAll and the rest of the objects it is waiting on are ready.
52 bool ready_to_run = true; 54 bool ready_to_run = true;
53 if (thread->status == ThreadStatus::WaitSynchAll) { 55 if (thread_status == ThreadStatus::WaitSynchAll) {
54 ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), 56 ready_to_run = thread->AllWaitObjectsReady();
55 [&thread](const SharedPtr<WaitObject>& object) {
56 return object->ShouldWait(thread.get());
57 });
58 } 57 }
59 58
60 if (ready_to_run) { 59 if (ready_to_run) {
61 candidate = thread.get(); 60 candidate = thread.get();
62 candidate_priority = thread->current_priority; 61 candidate_priority = thread->GetPriority();
63 } 62 }
64 } 63 }
65 64
@@ -75,24 +74,24 @@ void WaitObject::WakeupWaitingThread(SharedPtr<Thread> thread) {
75 if (!thread->IsSleepingOnWaitAll()) { 74 if (!thread->IsSleepingOnWaitAll()) {
76 Acquire(thread.get()); 75 Acquire(thread.get());
77 } else { 76 } else {
78 for (auto& object : thread->wait_objects) { 77 for (const auto& object : thread->GetWaitObjects()) {
79 ASSERT(!object->ShouldWait(thread.get())); 78 ASSERT(!object->ShouldWait(thread.get()));
80 object->Acquire(thread.get()); 79 object->Acquire(thread.get());
81 } 80 }
82 } 81 }
83 82
84 std::size_t index = thread->GetWaitObjectIndex(this); 83 const std::size_t index = thread->GetWaitObjectIndex(this);
85 84
86 for (auto& object : thread->wait_objects) 85 for (const auto& object : thread->GetWaitObjects())
87 object->RemoveWaitingThread(thread.get()); 86 object->RemoveWaitingThread(thread.get());
88 thread->wait_objects.clear(); 87 thread->ClearWaitObjects();
89 88
90 thread->CancelWakeupTimer(); 89 thread->CancelWakeupTimer();
91 90
92 bool resume = true; 91 bool resume = true;
93 92
94 if (thread->wakeup_callback) 93 if (thread->HasWakeupCallback())
95 resume = thread->wakeup_callback(ThreadWakeupReason::Signal, thread, this, index); 94 resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Signal, thread, this, index);
96 95
97 if (resume) 96 if (resume)
98 thread->ResumeFromWait(); 97 thread->ResumeFromWait();
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index 79580bcd9..0ecfb5af1 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -61,13 +61,13 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs
61AOC_U::~AOC_U() = default; 61AOC_U::~AOC_U() = default;
62 62
63void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) { 63void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
64 IPC::ResponseBuilder rb{ctx, 4}; 64 IPC::ResponseBuilder rb{ctx, 3};
65 rb.Push(RESULT_SUCCESS); 65 rb.Push(RESULT_SUCCESS);
66 66
67 const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID(); 67 const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
68 rb.Push<u32>(std::count_if(add_on_content.begin(), add_on_content.end(), [&current](u64 tid) { 68 rb.Push<u32>(static_cast<u32>(
69 return (tid & DLC_BASE_TITLE_ID_MASK) == current; 69 std::count_if(add_on_content.begin(), add_on_content.end(),
70 })); 70 [&current](u64 tid) { return (tid & DLC_BASE_TITLE_ID_MASK) == current; })));
71} 71}
72 72
73void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { 73void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
@@ -91,7 +91,7 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
91 return; 91 return;
92 } 92 }
93 93
94 count = std::min<size_t>(out.size() - offset, count); 94 count = static_cast<u32>(std::min<size_t>(out.size() - offset, count));
95 std::rotate(out.begin(), out.begin() + offset, out.end()); 95 std::rotate(out.begin(), out.begin() + offset, out.end());
96 out.resize(count); 96 out.resize(count);
97 97
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index aed2abb71..439e62d27 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -264,6 +264,15 @@ ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
264 return RESULT_SUCCESS; 264 return RESULT_SUCCESS;
265} 265}
266 266
267void SetPackedUpdate(FileSys::VirtualFile update_raw) {
268 LOG_TRACE(Service_FS, "Setting packed update for romfs");
269
270 if (romfs_factory == nullptr)
271 return;
272
273 romfs_factory->SetPackedUpdate(std::move(update_raw));
274}
275
267ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() { 276ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() {
268 LOG_TRACE(Service_FS, "Opening RomFS for current process"); 277 LOG_TRACE(Service_FS, "Opening RomFS for current process");
269 278
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 7039a2247..53b01bb01 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -39,6 +39,7 @@ ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory)
39ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory); 39ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
40ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory); 40ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
41 41
42void SetPackedUpdate(FileSys::VirtualFile update_raw);
42ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess(); 43ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess();
43ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id, 44ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
44 FileSys::ContentRecordType type); 45 FileSys::ContentRecordType type);
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
index d8b8037a8..7555bbe7d 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -157,7 +157,12 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
157 LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset); 157 LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
158 158
159 const auto itr = buffer_mappings.find(params.offset); 159 const auto itr = buffer_mappings.find(params.offset);
160 ASSERT_MSG(itr != buffer_mappings.end(), "Tried to unmap invalid mapping"); 160 if (itr == buffer_mappings.end()) {
161 LOG_WARNING(Service_NVDRV, "Tried to unmap an invalid offset 0x{:X}", params.offset);
162 // Hardware tests shows that unmapping an already unmapped buffer always returns successful
163 // and doesn't fail.
164 return 0;
165 }
161 166
162 auto& system_instance = Core::System::GetInstance(); 167 auto& system_instance = Core::System::GetInstance();
163 168
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index da2c51082..4f8145dda 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -6,9 +6,12 @@
6 6
7#include <memory> 7#include <memory>
8#include <string> 8#include <string>
9#include <type_traits>
9#include <unordered_map> 10#include <unordered_map>
10 11
12#include "core/hle/kernel/client_port.h"
11#include "core/hle/kernel/object.h" 13#include "core/hle/kernel/object.h"
14#include "core/hle/kernel/server_port.h"
12#include "core/hle/result.h" 15#include "core/hle/result.h"
13#include "core/hle/service/service.h" 16#include "core/hle/service/service.h"
14 17
@@ -48,6 +51,22 @@ public:
48 ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name); 51 ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name);
49 ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ConnectToService(const std::string& name); 52 ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ConnectToService(const std::string& name);
50 53
54 template <typename T>
55 std::shared_ptr<T> GetService(const std::string& service_name) const {
56 static_assert(std::is_base_of_v<Kernel::SessionRequestHandler, T>,
57 "Not a base of ServiceFrameworkBase");
58 auto service = registered_services.find(service_name);
59 if (service == registered_services.end()) {
60 LOG_DEBUG(Service, "Can't find service: {}", service_name);
61 return nullptr;
62 }
63 auto port = service->second->GetServerPort();
64 if (port == nullptr) {
65 return nullptr;
66 }
67 return std::static_pointer_cast<T>(port->hle_handler);
68 }
69
51 void InvokeControlRequest(Kernel::HLERequestContext& context); 70 void InvokeControlRequest(Kernel::HLERequestContext& context);
52 71
53private: 72private:
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 9a86e5824..951fd8257 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -3,6 +3,7 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <cinttypes> 5#include <cinttypes>
6#include <cstring>
6#include "common/common_funcs.h" 7#include "common/common_funcs.h"
7#include "common/file_util.h" 8#include "common/file_util.h"
8#include "common/logging/log.h" 9#include "common/logging/log.h"
@@ -140,7 +141,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
140 const FileSys::VirtualFile module_file = dir->GetFile(module); 141 const FileSys::VirtualFile module_file = dir->GetFile(module);
141 if (module_file != nullptr) { 142 if (module_file != nullptr) {
142 const VAddr load_addr = next_load_addr; 143 const VAddr load_addr = next_load_addr;
143 next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr, pm); 144 next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr,
145 std::strcmp(module, "rtld") == 0, pm);
144 LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); 146 LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
145 // Register module with GDBStub 147 // Register module with GDBStub
146 GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false); 148 GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index f2a183ba1..91659ec17 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -93,7 +93,7 @@ std::string GetFileTypeString(FileType type) {
93 return "unknown"; 93 return "unknown";
94} 94}
95 95
96constexpr std::array<const char*, 58> RESULT_MESSAGES{ 96constexpr std::array<const char*, 59> RESULT_MESSAGES{
97 "The operation completed successfully.", 97 "The operation completed successfully.",
98 "The loader requested to load is already loaded.", 98 "The loader requested to load is already loaded.",
99 "The operation is not implemented.", 99 "The operation is not implemented.",
@@ -152,6 +152,7 @@ constexpr std::array<const char*, 58> RESULT_MESSAGES{
152 "The BKTR-type NCA has a bad Relocation bucket.", 152 "The BKTR-type NCA has a bad Relocation bucket.",
153 "The BKTR-type NCA has a bad Subsection bucket.", 153 "The BKTR-type NCA has a bad Subsection bucket.",
154 "The BKTR-type NCA is missing the base RomFS.", 154 "The BKTR-type NCA is missing the base RomFS.",
155 "The NSP or XCI does not contain an update in addition to the base game.",
155}; 156};
156 157
157std::ostream& operator<<(std::ostream& os, ResultStatus status) { 158std::ostream& operator<<(std::ostream& os, ResultStatus status) {
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 20e66109b..0e0333db5 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -114,6 +114,7 @@ enum class ResultStatus : u16 {
114 ErrorBadRelocationBuckets, 114 ErrorBadRelocationBuckets,
115 ErrorBadSubsectionBuckets, 115 ErrorBadSubsectionBuckets,
116 ErrorMissingBKTRBaseRomFS, 116 ErrorMissingBKTRBaseRomFS,
117 ErrorNoPackedUpdate,
117}; 118};
118 119
119std::ostream& operator<<(std::ostream& os, ResultStatus status); 120std::ostream& operator<<(std::ostream& os, ResultStatus status);
@@ -196,10 +197,19 @@ public:
196 /** 197 /**
197 * Get the RomFS of the application 198 * Get the RomFS of the application
198 * Since the RomFS can be huge, we return a file reference instead of copying to a buffer 199 * Since the RomFS can be huge, we return a file reference instead of copying to a buffer
199 * @param dir The directory containing the RomFS 200 * @param file The directory containing the RomFS
200 * @return ResultStatus result of function 201 * @return ResultStatus result of function
201 */ 202 */
202 virtual ResultStatus ReadRomFS(FileSys::VirtualFile& dir) { 203 virtual ResultStatus ReadRomFS(FileSys::VirtualFile& file) {
204 return ResultStatus::ErrorNotImplemented;
205 }
206
207 /**
208 * Get the raw update of the application, should it come packed with one
209 * @param file The raw update NCA file (Program-type
210 * @return ResultStatus result of function
211 */
212 virtual ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) {
203 return ResultStatus::ErrorNotImplemented; 213 return ResultStatus::ErrorNotImplemented;
204 } 214 }
205 215
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp
index 073fb9d2f..42f4a777b 100644
--- a/src/core/loader/nax.cpp
+++ b/src/core/loader/nax.cpp
@@ -72,6 +72,10 @@ ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) {
72 return nca_loader->ReadRomFS(dir); 72 return nca_loader->ReadRomFS(dir);
73} 73}
74 74
75u64 AppLoader_NAX::ReadRomFSIVFCOffset() const {
76 return nca_loader->ReadRomFSIVFCOffset();
77}
78
75ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { 79ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) {
76 return nca_loader->ReadProgramId(out_program_id); 80 return nca_loader->ReadProgramId(out_program_id);
77} 81}
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h
index fc3c01876..b4d93bd01 100644
--- a/src/core/loader/nax.h
+++ b/src/core/loader/nax.h
@@ -36,6 +36,7 @@ public:
36 ResultStatus Load(Kernel::Process& process) override; 36 ResultStatus Load(Kernel::Process& process) override;
37 37
38 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; 38 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
39 u64 ReadRomFSIVFCOffset() const override;
39 ResultStatus ReadProgramId(u64& out_program_id) override; 40 ResultStatus ReadProgramId(u64& out_program_id) override;
40 41
41private: 42private:
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index c10f826a4..25dd3f04e 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -18,7 +18,9 @@
18#include "core/hle/kernel/process.h" 18#include "core/hle/kernel/process.h"
19#include "core/hle/kernel/vm_manager.h" 19#include "core/hle/kernel/vm_manager.h"
20#include "core/loader/nro.h" 20#include "core/loader/nro.h"
21#include "core/loader/nso.h"
21#include "core/memory.h" 22#include "core/memory.h"
23#include "core/settings.h"
22 24
23namespace Loader { 25namespace Loader {
24 26
@@ -150,6 +152,19 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
150 codeset->segments[i].size = PageAlignSize(nro_header.segments[i].size); 152 codeset->segments[i].size = PageAlignSize(nro_header.segments[i].size);
151 } 153 }
152 154
155 if (!Settings::values.program_args.empty()) {
156 const auto arg_data = Settings::values.program_args;
157 codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
158 NSOArgumentHeader args_header{
159 NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
160 const auto end_offset = program_image.size();
161 program_image.resize(static_cast<u32>(program_image.size()) +
162 NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
163 std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
164 std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(),
165 arg_data.size());
166 }
167
153 // Read MOD header 168 // Read MOD header
154 ModHeader mod_header{}; 169 ModHeader mod_header{};
155 // Default .bss to NRO header bss size if MOD0 section doesn't exist 170 // Default .bss to NRO header bss size if MOD0 section doesn't exist
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 2186b02af..28c6dd9b7 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -17,6 +17,7 @@
17#include "core/hle/kernel/vm_manager.h" 17#include "core/hle/kernel/vm_manager.h"
18#include "core/loader/nso.h" 18#include "core/loader/nso.h"
19#include "core/memory.h" 19#include "core/memory.h"
20#include "core/settings.h"
20 21
21namespace Loader { 22namespace Loader {
22 23
@@ -94,6 +95,7 @@ static constexpr u32 PageAlignSize(u32 size) {
94} 95}
95 96
96VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base, 97VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
98 bool should_pass_arguments,
97 boost::optional<FileSys::PatchManager> pm) { 99 boost::optional<FileSys::PatchManager> pm) {
98 if (file == nullptr) 100 if (file == nullptr)
99 return {}; 101 return {};
@@ -125,6 +127,19 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
125 codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size())); 127 codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
126 } 128 }
127 129
130 if (should_pass_arguments && !Settings::values.program_args.empty()) {
131 const auto arg_data = Settings::values.program_args;
132 codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
133 NSOArgumentHeader args_header{
134 NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
135 const auto end_offset = program_image.size();
136 program_image.resize(static_cast<u32>(program_image.size()) +
137 NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
138 std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
139 std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(),
140 arg_data.size());
141 }
142
128 // MOD header pointer is at .text offset + 4 143 // MOD header pointer is at .text offset + 4
129 u32 module_offset; 144 u32 module_offset;
130 std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32)); 145 std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32));
@@ -172,7 +187,7 @@ ResultStatus AppLoader_NSO::Load(Kernel::Process& process) {
172 187
173 // Load module 188 // Load module
174 const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); 189 const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
175 LoadModule(file, base_address); 190 LoadModule(file, base_address, true);
176 LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address); 191 LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address);
177 192
178 process.Run(base_address, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); 193 process.Run(base_address, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index 05353d4d9..70ab3b718 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -11,6 +11,15 @@
11 11
12namespace Loader { 12namespace Loader {
13 13
14constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
15
16struct NSOArgumentHeader {
17 u32_le allocated_size;
18 u32_le actual_size;
19 INSERT_PADDING_BYTES(0x18);
20};
21static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size.");
22
14/// Loads an NSO file 23/// Loads an NSO file
15class AppLoader_NSO final : public AppLoader, Linker { 24class AppLoader_NSO final : public AppLoader, Linker {
16public: 25public:
@@ -27,7 +36,7 @@ public:
27 return IdentifyType(file); 36 return IdentifyType(file);
28 } 37 }
29 38
30 static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base, 39 static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base, bool should_pass_arguments,
31 boost::optional<FileSys::PatchManager> pm = boost::none); 40 boost::optional<FileSys::PatchManager> pm = boost::none);
32 41
33 ResultStatus Load(Kernel::Process& process) override; 42 ResultStatus Load(Kernel::Process& process) override;
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index b7ba77ef4..5534ce01c 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -10,8 +10,10 @@
10#include "core/file_sys/control_metadata.h" 10#include "core/file_sys/control_metadata.h"
11#include "core/file_sys/nca_metadata.h" 11#include "core/file_sys/nca_metadata.h"
12#include "core/file_sys/patch_manager.h" 12#include "core/file_sys/patch_manager.h"
13#include "core/file_sys/registered_cache.h"
13#include "core/file_sys/submission_package.h" 14#include "core/file_sys/submission_package.h"
14#include "core/hle/kernel/process.h" 15#include "core/hle/kernel/process.h"
16#include "core/hle/service/filesystem/filesystem.h"
15#include "core/loader/deconstructed_rom_directory.h" 17#include "core/loader/deconstructed_rom_directory.h"
16#include "core/loader/nca.h" 18#include "core/loader/nca.h"
17#include "core/loader/nsp.h" 19#include "core/loader/nsp.h"
@@ -91,13 +93,39 @@ ResultStatus AppLoader_NSP::Load(Kernel::Process& process) {
91 if (result != ResultStatus::Success) 93 if (result != ResultStatus::Success)
92 return result; 94 return result;
93 95
96 FileSys::VirtualFile update_raw;
97 if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr)
98 Service::FileSystem::SetPackedUpdate(std::move(update_raw));
99
94 is_loaded = true; 100 is_loaded = true;
95 101
96 return ResultStatus::Success; 102 return ResultStatus::Success;
97} 103}
98 104
99ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& dir) { 105ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& file) {
100 return secondary_loader->ReadRomFS(dir); 106 return secondary_loader->ReadRomFS(file);
107}
108
109u64 AppLoader_NSP::ReadRomFSIVFCOffset() const {
110 return secondary_loader->ReadRomFSIVFCOffset();
111}
112
113ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& file) {
114 if (nsp->IsExtractedType())
115 return ResultStatus::ErrorNoPackedUpdate;
116
117 const auto read =
118 nsp->GetNCAFile(FileSys::GetUpdateTitleID(title_id), FileSys::ContentRecordType::Program);
119
120 if (read == nullptr)
121 return ResultStatus::ErrorNoPackedUpdate;
122 const auto nca_test = std::make_shared<FileSys::NCA>(read);
123
124 if (nca_test->GetStatus() != ResultStatus::ErrorMissingBKTRBaseRomFS)
125 return nca_test->GetStatus();
126
127 file = read;
128 return ResultStatus::Success;
101} 129}
102 130
103ResultStatus AppLoader_NSP::ReadProgramId(u64& out_program_id) { 131ResultStatus AppLoader_NSP::ReadProgramId(u64& out_program_id) {
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index eac9b819a..b006594a6 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -37,7 +37,9 @@ public:
37 37
38 ResultStatus Load(Kernel::Process& process) override; 38 ResultStatus Load(Kernel::Process& process) override;
39 39
40 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; 40 ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
41 u64 ReadRomFSIVFCOffset() const override;
42 ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) override;
41 ResultStatus ReadProgramId(u64& out_program_id) override; 43 ResultStatus ReadProgramId(u64& out_program_id) override;
42 ResultStatus ReadIcon(std::vector<u8>& buffer) override; 44 ResultStatus ReadIcon(std::vector<u8>& buffer) override;
43 ResultStatus ReadTitle(std::string& title) override; 45 ResultStatus ReadTitle(std::string& title) override;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index eda67a8c8..ee5452eb9 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -9,7 +9,11 @@
9#include "core/file_sys/content_archive.h" 9#include "core/file_sys/content_archive.h"
10#include "core/file_sys/control_metadata.h" 10#include "core/file_sys/control_metadata.h"
11#include "core/file_sys/patch_manager.h" 11#include "core/file_sys/patch_manager.h"
12#include "core/file_sys/registered_cache.h"
13#include "core/file_sys/romfs.h"
14#include "core/file_sys/submission_package.h"
12#include "core/hle/kernel/process.h" 15#include "core/hle/kernel/process.h"
16#include "core/hle/service/filesystem/filesystem.h"
13#include "core/loader/nca.h" 17#include "core/loader/nca.h"
14#include "core/loader/xci.h" 18#include "core/loader/xci.h"
15 19
@@ -63,13 +67,41 @@ ResultStatus AppLoader_XCI::Load(Kernel::Process& process) {
63 if (result != ResultStatus::Success) 67 if (result != ResultStatus::Success)
64 return result; 68 return result;
65 69
70 FileSys::VirtualFile update_raw;
71 if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr)
72 Service::FileSystem::SetPackedUpdate(std::move(update_raw));
73
66 is_loaded = true; 74 is_loaded = true;
67 75
68 return ResultStatus::Success; 76 return ResultStatus::Success;
69} 77}
70 78
71ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& dir) { 79ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& file) {
72 return nca_loader->ReadRomFS(dir); 80 return nca_loader->ReadRomFS(file);
81}
82
83u64 AppLoader_XCI::ReadRomFSIVFCOffset() const {
84 return nca_loader->ReadRomFSIVFCOffset();
85}
86
87ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& file) {
88 u64 program_id{};
89 nca_loader->ReadProgramId(program_id);
90 if (program_id == 0)
91 return ResultStatus::ErrorXCIMissingProgramNCA;
92
93 const auto read = xci->GetSecurePartitionNSP()->GetNCAFile(
94 FileSys::GetUpdateTitleID(program_id), FileSys::ContentRecordType::Program);
95
96 if (read == nullptr)
97 return ResultStatus::ErrorNoPackedUpdate;
98 const auto nca_test = std::make_shared<FileSys::NCA>(read);
99
100 if (nca_test->GetStatus() != ResultStatus::ErrorMissingBKTRBaseRomFS)
101 return nca_test->GetStatus();
102
103 file = read;
104 return ResultStatus::Success;
73} 105}
74 106
75ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) { 107ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) {
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index 17e47b658..770ed1437 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -37,7 +37,9 @@ public:
37 37
38 ResultStatus Load(Kernel::Process& process) override; 38 ResultStatus Load(Kernel::Process& process) override;
39 39
40 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; 40 ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
41 u64 ReadRomFSIVFCOffset() const override;
42 ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) override;
41 ResultStatus ReadProgramId(u64& out_program_id) override; 43 ResultStatus ReadProgramId(u64& out_program_id) override;
42 ResultStatus ReadIcon(std::vector<u8>& buffer) override; 44 ResultStatus ReadIcon(std::vector<u8>& buffer) override;
43 ResultStatus ReadTitle(std::string& title) override; 45 ResultStatus ReadTitle(std::string& title) override;
diff --git a/src/core/settings.h b/src/core/settings.h
index 0318d019c..83b9a04c8 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -155,6 +155,13 @@ struct Values {
155 // Debugging 155 // Debugging
156 bool use_gdbstub; 156 bool use_gdbstub;
157 u16 gdbstub_port; 157 u16 gdbstub_port;
158 std::string program_args;
159
160 // WebService
161 bool enable_telemetry;
162 std::string web_api_url;
163 std::string yuzu_username;
164 std::string yuzu_token;
158} extern values; 165} extern values;
159 166
160void Apply(); 167void Apply();
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index b0df154ca..f29fff1e7 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -6,6 +6,8 @@
6#include "common/common_types.h" 6#include "common/common_types.h"
7#include "common/file_util.h" 7#include "common/file_util.h"
8 8
9#include <mbedtls/ctr_drbg.h>
10#include <mbedtls/entropy.h>
9#include "core/core.h" 11#include "core/core.h"
10#include "core/file_sys/control_metadata.h" 12#include "core/file_sys/control_metadata.h"
11#include "core/file_sys/patch_manager.h" 13#include "core/file_sys/patch_manager.h"
@@ -13,10 +15,31 @@
13#include "core/settings.h" 15#include "core/settings.h"
14#include "core/telemetry_session.h" 16#include "core/telemetry_session.h"
15 17
18#ifdef ENABLE_WEB_SERVICE
19#include "web_service/telemetry_json.h"
20#include "web_service/verify_login.h"
21#endif
22
16namespace Core { 23namespace Core {
17 24
18static u64 GenerateTelemetryId() { 25static u64 GenerateTelemetryId() {
19 u64 telemetry_id{}; 26 u64 telemetry_id{};
27
28 mbedtls_entropy_context entropy;
29 mbedtls_entropy_init(&entropy);
30 mbedtls_ctr_drbg_context ctr_drbg;
31 std::string personalization = "yuzu Telemetry ID";
32
33 mbedtls_ctr_drbg_init(&ctr_drbg);
34 ASSERT(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
35 reinterpret_cast<const unsigned char*>(personalization.c_str()),
36 personalization.size()) == 0);
37 ASSERT(mbedtls_ctr_drbg_random(&ctr_drbg, reinterpret_cast<unsigned char*>(&telemetry_id),
38 sizeof(u64)) == 0);
39
40 mbedtls_ctr_drbg_free(&ctr_drbg);
41 mbedtls_entropy_free(&entropy);
42
20 return telemetry_id; 43 return telemetry_id;
21} 44}
22 45
@@ -25,14 +48,21 @@ u64 GetTelemetryId() {
25 const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + 48 const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
26 "telemetry_id"}; 49 "telemetry_id"};
27 50
28 if (FileUtil::Exists(filename)) { 51 bool generate_new_id = !FileUtil::Exists(filename);
52 if (!generate_new_id) {
29 FileUtil::IOFile file(filename, "rb"); 53 FileUtil::IOFile file(filename, "rb");
30 if (!file.IsOpen()) { 54 if (!file.IsOpen()) {
31 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); 55 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
32 return {}; 56 return {};
33 } 57 }
34 file.ReadBytes(&telemetry_id, sizeof(u64)); 58 file.ReadBytes(&telemetry_id, sizeof(u64));
35 } else { 59 if (telemetry_id == 0) {
60 LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id);
61 generate_new_id = true;
62 }
63 }
64
65 if (generate_new_id) {
36 FileUtil::IOFile file(filename, "wb"); 66 FileUtil::IOFile file(filename, "wb");
37 if (!file.IsOpen()) { 67 if (!file.IsOpen()) {
38 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); 68 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
@@ -59,23 +89,20 @@ u64 RegenerateTelemetryId() {
59 return new_telemetry_id; 89 return new_telemetry_id;
60} 90}
61 91
62std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func) { 92bool VerifyLogin(const std::string& username, const std::string& token) {
63#ifdef ENABLE_WEB_SERVICE 93#ifdef ENABLE_WEB_SERVICE
64 return WebService::VerifyLogin(username, token, Settings::values.verify_endpoint_url, func); 94 return WebService::VerifyLogin(Settings::values.web_api_url, username, token);
65#else 95#else
66 return std::async(std::launch::async, [func{std::move(func)}]() { 96 return false;
67 func();
68 return false;
69 });
70#endif 97#endif
71} 98}
72 99
73TelemetrySession::TelemetrySession() { 100TelemetrySession::TelemetrySession() {
74#ifdef ENABLE_WEB_SERVICE 101#ifdef ENABLE_WEB_SERVICE
75 if (Settings::values.enable_telemetry) { 102 if (Settings::values.enable_telemetry) {
76 backend = std::make_unique<WebService::TelemetryJson>( 103 backend = std::make_unique<WebService::TelemetryJson>(Settings::values.web_api_url,
77 Settings::values.telemetry_endpoint_url, Settings::values.yuzu_username, 104 Settings::values.yuzu_username,
78 Settings::values.yuzu_token); 105 Settings::values.yuzu_token);
79 } else { 106 } else {
80 backend = std::make_unique<Telemetry::NullVisitor>(); 107 backend = std::make_unique<Telemetry::NullVisitor>();
81 } 108 }
@@ -94,7 +121,8 @@ TelemetrySession::TelemetrySession() {
94 u64 program_id{}; 121 u64 program_id{};
95 const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadProgramId(program_id)}; 122 const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadProgramId(program_id)};
96 if (res == Loader::ResultStatus::Success) { 123 if (res == Loader::ResultStatus::Success) {
97 AddField(Telemetry::FieldType::Session, "ProgramId", program_id); 124 const std::string formatted_program_id{fmt::format("{:016X}", program_id)};
125 AddField(Telemetry::FieldType::Session, "ProgramId", formatted_program_id);
98 126
99 std::string name; 127 std::string name;
100 System::GetInstance().GetAppLoader().ReadTitle(name); 128 System::GetInstance().GetAppLoader().ReadTitle(name);
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index dbc4f8bd4..cec271df0 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -4,7 +4,6 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <future>
8#include <memory> 7#include <memory>
9#include "common/telemetry.h" 8#include "common/telemetry.h"
10 9
@@ -31,6 +30,8 @@ public:
31 field_collection.AddField(type, name, std::move(value)); 30 field_collection.AddField(type, name, std::move(value));
32 } 31 }
33 32
33 static void FinalizeAsyncJob();
34
34private: 35private:
35 Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session 36 Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session
36 std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields 37 std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
@@ -55,6 +56,6 @@ u64 RegenerateTelemetryId();
55 * @param func A function that gets exectued when the verification is finished 56 * @param func A function that gets exectued when the verification is finished
56 * @returns Future with bool indicating whether the verification succeeded 57 * @returns Future with bool indicating whether the verification succeeded
57 */ 58 */
58std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func); 59bool VerifyLogin(const std::string& username, const std::string& token);
59 60
60} // namespace Core 61} // namespace Core
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index f5ae57039..09ecc5bad 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -27,6 +27,8 @@ add_library(video_core STATIC
27 renderer_base.h 27 renderer_base.h
28 renderer_opengl/gl_buffer_cache.cpp 28 renderer_opengl/gl_buffer_cache.cpp
29 renderer_opengl/gl_buffer_cache.h 29 renderer_opengl/gl_buffer_cache.h
30 renderer_opengl/gl_primitive_assembler.cpp
31 renderer_opengl/gl_primitive_assembler.h
30 renderer_opengl/gl_rasterizer.cpp 32 renderer_opengl/gl_rasterizer.cpp
31 renderer_opengl/gl_rasterizer.h 33 renderer_opengl/gl_rasterizer.h
32 renderer_opengl/gl_rasterizer_cache.cpp 34 renderer_opengl/gl_rasterizer_cache.cpp
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index ea1555c5d..912e785b9 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -4,11 +4,13 @@
4 4
5#include "core/memory.h" 5#include "core/memory.h"
6#include "video_core/engines/fermi_2d.h" 6#include "video_core/engines/fermi_2d.h"
7#include "video_core/rasterizer_interface.h"
7#include "video_core/textures/decoders.h" 8#include "video_core/textures/decoders.h"
8 9
9namespace Tegra::Engines { 10namespace Tegra::Engines {
10 11
11Fermi2D::Fermi2D(MemoryManager& memory_manager) : memory_manager(memory_manager) {} 12Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager)
13 : memory_manager(memory_manager), rasterizer{rasterizer} {}
12 14
13void Fermi2D::WriteReg(u32 method, u32 value) { 15void Fermi2D::WriteReg(u32 method, u32 value) {
14 ASSERT_MSG(method < Regs::NUM_REGS, 16 ASSERT_MSG(method < Regs::NUM_REGS,
@@ -44,27 +46,31 @@ void Fermi2D::HandleSurfaceCopy() {
44 u32 src_bytes_per_pixel = RenderTargetBytesPerPixel(regs.src.format); 46 u32 src_bytes_per_pixel = RenderTargetBytesPerPixel(regs.src.format);
45 u32 dst_bytes_per_pixel = RenderTargetBytesPerPixel(regs.dst.format); 47 u32 dst_bytes_per_pixel = RenderTargetBytesPerPixel(regs.dst.format);
46 48
47 if (regs.src.linear == regs.dst.linear) { 49 if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst)) {
48 // If the input layout and the output layout are the same, just perform a raw copy. 50 // TODO(bunnei): The below implementation currently will not get hit, as
49 ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight()); 51 // AccelerateSurfaceCopy tries to always copy and will always return success. This should be
50 Memory::CopyBlock(dest_cpu, source_cpu, 52 // changed once we properly support flushing.
51 src_bytes_per_pixel * regs.dst.width * regs.dst.height); 53
52 return; 54 if (regs.src.linear == regs.dst.linear) {
53 } 55 // If the input layout and the output layout are the same, just perform a raw copy.
54 56 ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight());
55 u8* src_buffer = Memory::GetPointer(source_cpu); 57 Memory::CopyBlock(dest_cpu, source_cpu,
56 u8* dst_buffer = Memory::GetPointer(dest_cpu); 58 src_bytes_per_pixel * regs.dst.width * regs.dst.height);
57 59 return;
58 if (!regs.src.linear && regs.dst.linear) { 60 }
59 // If the input is tiled and the output is linear, deswizzle the input and copy it over. 61 u8* src_buffer = Memory::GetPointer(source_cpu);
60 Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel, 62 u8* dst_buffer = Memory::GetPointer(dest_cpu);
61 dst_bytes_per_pixel, src_buffer, dst_buffer, true, 63 if (!regs.src.linear && regs.dst.linear) {
62 regs.src.BlockHeight()); 64 // If the input is tiled and the output is linear, deswizzle the input and copy it over.
63 } else { 65 Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
64 // If the input is linear and the output is tiled, swizzle the input and copy it over. 66 dst_bytes_per_pixel, src_buffer, dst_buffer, true,
65 Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel, 67 regs.src.BlockHeight());
66 dst_bytes_per_pixel, dst_buffer, src_buffer, false, 68 } else {
67 regs.dst.BlockHeight()); 69 // If the input is linear and the output is tiled, swizzle the input and copy it over.
70 Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
71 dst_bytes_per_pixel, dst_buffer, src_buffer, false,
72 regs.dst.BlockHeight());
73 }
68 } 74 }
69} 75}
70 76
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
index 021b83eaa..81d15c62a 100644
--- a/src/video_core/engines/fermi_2d.h
+++ b/src/video_core/engines/fermi_2d.h
@@ -12,6 +12,10 @@
12#include "video_core/gpu.h" 12#include "video_core/gpu.h"
13#include "video_core/memory_manager.h" 13#include "video_core/memory_manager.h"
14 14
15namespace VideoCore {
16class RasterizerInterface;
17}
18
15namespace Tegra::Engines { 19namespace Tegra::Engines {
16 20
17#define FERMI2D_REG_INDEX(field_name) \ 21#define FERMI2D_REG_INDEX(field_name) \
@@ -19,7 +23,7 @@ namespace Tegra::Engines {
19 23
20class Fermi2D final { 24class Fermi2D final {
21public: 25public:
22 explicit Fermi2D(MemoryManager& memory_manager); 26 explicit Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager);
23 ~Fermi2D() = default; 27 ~Fermi2D() = default;
24 28
25 /// Write the value to the register identified by method. 29 /// Write the value to the register identified by method.
@@ -94,6 +98,8 @@ public:
94 MemoryManager& memory_manager; 98 MemoryManager& memory_manager;
95 99
96private: 100private:
101 VideoCore::RasterizerInterface& rasterizer;
102
97 /// Performs the copy from the source surface to the destination surface as configured in the 103 /// Performs the copy from the source surface to the destination surface as configured in the
98 /// registers. 104 /// registers.
99 void HandleSurfaceCopy(); 105 void HandleSurfaceCopy();
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 9f5581045..4290da33f 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -744,6 +744,12 @@ public:
744 return static_cast<GPUVAddr>((static_cast<GPUVAddr>(end_addr_high) << 32) | 744 return static_cast<GPUVAddr>((static_cast<GPUVAddr>(end_addr_high) << 32) |
745 end_addr_low); 745 end_addr_low);
746 } 746 }
747
748 /// Adjust the index buffer offset so it points to the first desired index.
749 GPUVAddr IndexStart() const {
750 return StartAddress() + static_cast<size_t>(first) *
751 static_cast<size_t>(FormatSizeInBytes());
752 }
747 } index_array; 753 } index_array;
748 754
749 INSERT_PADDING_WORDS(0x7); 755 INSERT_PADDING_WORDS(0x7);
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index baa8b63b7..9ba7e3533 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -25,7 +25,7 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) {
25GPU::GPU(VideoCore::RasterizerInterface& rasterizer) { 25GPU::GPU(VideoCore::RasterizerInterface& rasterizer) {
26 memory_manager = std::make_unique<Tegra::MemoryManager>(); 26 memory_manager = std::make_unique<Tegra::MemoryManager>();
27 maxwell_3d = std::make_unique<Engines::Maxwell3D>(rasterizer, *memory_manager); 27 maxwell_3d = std::make_unique<Engines::Maxwell3D>(rasterizer, *memory_manager);
28 fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager); 28 fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager);
29 maxwell_compute = std::make_unique<Engines::MaxwellCompute>(); 29 maxwell_compute = std::make_unique<Engines::MaxwellCompute>();
30 maxwell_dma = std::make_unique<Engines::MaxwellDMA>(*memory_manager); 30 maxwell_dma = std::make_unique<Engines::MaxwellDMA>(*memory_manager);
31 kepler_memory = std::make_unique<Engines::KeplerMemory>(*memory_manager); 31 kepler_memory = std::make_unique<Engines::KeplerMemory>(*memory_manager);
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index cd819d69f..06fc59dbe 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -5,6 +5,7 @@
5#pragma once 5#pragma once
6 6
7#include "common/common_types.h" 7#include "common/common_types.h"
8#include "video_core/engines/fermi_2d.h"
8#include "video_core/gpu.h" 9#include "video_core/gpu.h"
9#include "video_core/memory_manager.h" 10#include "video_core/memory_manager.h"
10 11
@@ -33,13 +34,9 @@ public:
33 /// and invalidated 34 /// and invalidated
34 virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0; 35 virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
35 36
36 /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 0 37 /// Attempt to use a faster method to perform a surface copy
37 virtual bool AccelerateDisplayTransfer(const void* config) { 38 virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
38 return false; 39 const Tegra::Engines::Fermi2D::Regs::Surface& dst) {
39 }
40
41 /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 1
42 virtual bool AccelerateTextureCopy(const void* config) {
43 return false; 40 return false;
44 } 41 }
45 42
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 578aca789..c142095c5 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -34,7 +34,7 @@ GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size
34 } 34 }
35 35
36 AlignBuffer(alignment); 36 AlignBuffer(alignment);
37 GLintptr uploaded_offset = buffer_offset; 37 const GLintptr uploaded_offset = buffer_offset;
38 38
39 Memory::ReadBlock(*cpu_addr, buffer_ptr, size); 39 Memory::ReadBlock(*cpu_addr, buffer_ptr, size);
40 40
@@ -57,13 +57,23 @@ GLintptr OGLBufferCache::UploadHostMemory(const void* raw_pointer, std::size_t s
57 std::size_t alignment) { 57 std::size_t alignment) {
58 AlignBuffer(alignment); 58 AlignBuffer(alignment);
59 std::memcpy(buffer_ptr, raw_pointer, size); 59 std::memcpy(buffer_ptr, raw_pointer, size);
60 GLintptr uploaded_offset = buffer_offset; 60 const GLintptr uploaded_offset = buffer_offset;
61 61
62 buffer_ptr += size; 62 buffer_ptr += size;
63 buffer_offset += size; 63 buffer_offset += size;
64 return uploaded_offset; 64 return uploaded_offset;
65} 65}
66 66
67std::tuple<u8*, GLintptr> OGLBufferCache::ReserveMemory(std::size_t size, std::size_t alignment) {
68 AlignBuffer(alignment);
69 u8* const uploaded_ptr = buffer_ptr;
70 const GLintptr uploaded_offset = buffer_offset;
71
72 buffer_ptr += size;
73 buffer_offset += size;
74 return std::make_tuple(uploaded_ptr, uploaded_offset);
75}
76
67void OGLBufferCache::Map(std::size_t max_size) { 77void OGLBufferCache::Map(std::size_t max_size) {
68 bool invalidate; 78 bool invalidate;
69 std::tie(buffer_ptr, buffer_offset_base, invalidate) = 79 std::tie(buffer_ptr, buffer_offset_base, invalidate) =
@@ -74,6 +84,7 @@ void OGLBufferCache::Map(std::size_t max_size) {
74 InvalidateAll(); 84 InvalidateAll();
75 } 85 }
76} 86}
87
77void OGLBufferCache::Unmap() { 88void OGLBufferCache::Unmap() {
78 stream_buffer.Unmap(buffer_offset - buffer_offset_base); 89 stream_buffer.Unmap(buffer_offset - buffer_offset_base);
79} 90}
@@ -84,7 +95,7 @@ GLuint OGLBufferCache::GetHandle() const {
84 95
85void OGLBufferCache::AlignBuffer(std::size_t alignment) { 96void OGLBufferCache::AlignBuffer(std::size_t alignment) {
86 // Align the offset, not the mapped pointer 97 // Align the offset, not the mapped pointer
87 GLintptr offset_aligned = 98 const GLintptr offset_aligned =
88 static_cast<GLintptr>(Common::AlignUp(static_cast<std::size_t>(buffer_offset), alignment)); 99 static_cast<GLintptr>(Common::AlignUp(static_cast<std::size_t>(buffer_offset), alignment));
89 buffer_ptr += offset_aligned - buffer_offset; 100 buffer_ptr += offset_aligned - buffer_offset;
90 buffer_offset = offset_aligned; 101 buffer_offset = offset_aligned;
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index 6c18461f4..965976334 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -6,6 +6,7 @@
6 6
7#include <cstddef> 7#include <cstddef>
8#include <memory> 8#include <memory>
9#include <tuple>
9 10
10#include "common/common_types.h" 11#include "common/common_types.h"
11#include "video_core/rasterizer_cache.h" 12#include "video_core/rasterizer_cache.h"
@@ -33,11 +34,17 @@ class OGLBufferCache final : public RasterizerCache<std::shared_ptr<CachedBuffer
33public: 34public:
34 explicit OGLBufferCache(std::size_t size); 35 explicit OGLBufferCache(std::size_t size);
35 36
37 /// Uploads data from a guest GPU address. Returns host's buffer offset where it's been
38 /// allocated.
36 GLintptr UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4, 39 GLintptr UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
37 bool cache = true); 40 bool cache = true);
38 41
42 /// Uploads from a host memory. Returns host's buffer offset where it's been allocated.
39 GLintptr UploadHostMemory(const void* raw_pointer, std::size_t size, std::size_t alignment = 4); 43 GLintptr UploadHostMemory(const void* raw_pointer, std::size_t size, std::size_t alignment = 4);
40 44
45 /// Reserves memory to be used by host's CPU. Returns mapped address and offset.
46 std::tuple<u8*, GLintptr> ReserveMemory(std::size_t size, std::size_t alignment = 4);
47
41 void Map(std::size_t max_size); 48 void Map(std::size_t max_size);
42 void Unmap(); 49 void Unmap();
43 50
diff --git a/src/video_core/renderer_opengl/gl_primitive_assembler.cpp b/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
new file mode 100644
index 000000000..ee1d9601b
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
@@ -0,0 +1,64 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <array>
7#include "common/assert.h"
8#include "common/common_types.h"
9#include "core/memory.h"
10#include "video_core/renderer_opengl/gl_buffer_cache.h"
11#include "video_core/renderer_opengl/gl_primitive_assembler.h"
12
13namespace OpenGL {
14
15constexpr u32 TRIANGLES_PER_QUAD = 6;
16constexpr std::array<u32, TRIANGLES_PER_QUAD> QUAD_MAP = {0, 1, 2, 0, 2, 3};
17
18PrimitiveAssembler::PrimitiveAssembler(OGLBufferCache& buffer_cache) : buffer_cache(buffer_cache) {}
19
20PrimitiveAssembler::~PrimitiveAssembler() = default;
21
22std::size_t PrimitiveAssembler::CalculateQuadSize(u32 count) const {
23 ASSERT_MSG(count % 4 == 0, "Quad count is expected to be a multiple of 4");
24 return (count / 4) * TRIANGLES_PER_QUAD * sizeof(GLuint);
25}
26
27GLintptr PrimitiveAssembler::MakeQuadArray(u32 first, u32 count) {
28 const std::size_t size{CalculateQuadSize(count)};
29 auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(size);
30
31 for (u32 primitive = 0; primitive < count / 4; ++primitive) {
32 for (u32 i = 0; i < TRIANGLES_PER_QUAD; ++i) {
33 const u32 index = first + primitive * 4 + QUAD_MAP[i];
34 std::memcpy(dst_pointer, &index, sizeof(index));
35 dst_pointer += sizeof(index);
36 }
37 }
38
39 return index_offset;
40}
41
42GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size,
43 u32 count) {
44 const std::size_t map_size{CalculateQuadSize(count)};
45 auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(map_size);
46
47 auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
48 const boost::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
49 const u8* source{Memory::GetPointer(*cpu_addr)};
50
51 for (u32 primitive = 0; primitive < count / 4; ++primitive) {
52 for (std::size_t i = 0; i < TRIANGLES_PER_QUAD; ++i) {
53 const u32 index = primitive * 4 + QUAD_MAP[i];
54 const u8* src_offset = source + (index * index_size);
55
56 std::memcpy(dst_pointer, src_offset, index_size);
57 dst_pointer += index_size;
58 }
59 }
60
61 return index_offset;
62}
63
64} // namespace OpenGL \ No newline at end of file
diff --git a/src/video_core/renderer_opengl/gl_primitive_assembler.h b/src/video_core/renderer_opengl/gl_primitive_assembler.h
new file mode 100644
index 000000000..a8cb88eb5
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_primitive_assembler.h
@@ -0,0 +1,33 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <vector>
8#include <glad/glad.h>
9
10#include "common/common_types.h"
11#include "video_core/memory_manager.h"
12
13namespace OpenGL {
14
15class OGLBufferCache;
16
17class PrimitiveAssembler {
18public:
19 explicit PrimitiveAssembler(OGLBufferCache& buffer_cache);
20 ~PrimitiveAssembler();
21
22 /// Calculates the size required by MakeQuadArray and MakeQuadIndexed.
23 std::size_t CalculateQuadSize(u32 count) const;
24
25 GLintptr MakeQuadArray(u32 first, u32 count);
26
27 GLintptr MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size, u32 count);
28
29private:
30 OGLBufferCache& buffer_cache;
31};
32
33} // namespace OpenGL \ No newline at end of file
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 587d9dffb..209bdf181 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -42,6 +42,41 @@ MICROPROFILE_DEFINE(OpenGL_Framebuffer, "OpenGL", "Framebuffer Setup", MP_RGB(12
42MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192)); 42MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192));
43MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192)); 43MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192));
44MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); 44MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
45MICROPROFILE_DEFINE(OpenGL_PrimitiveAssembly, "OpenGL", "Prim Asmbl", MP_RGB(255, 100, 100));
46
47struct DrawParameters {
48 GLenum primitive_mode;
49 GLsizei count;
50 GLint current_instance;
51 bool use_indexed;
52
53 GLint vertex_first;
54
55 GLenum index_format;
56 GLint base_vertex;
57 GLintptr index_buffer_offset;
58
59 void DispatchDraw() const {
60 if (use_indexed) {
61 const auto index_buffer_ptr = reinterpret_cast<const void*>(index_buffer_offset);
62 if (current_instance > 0) {
63 glDrawElementsInstancedBaseVertexBaseInstance(primitive_mode, count, index_format,
64 index_buffer_ptr, 1, base_vertex,
65 current_instance);
66 } else {
67 glDrawElementsBaseVertex(primitive_mode, count, index_format, index_buffer_ptr,
68 base_vertex);
69 }
70 } else {
71 if (current_instance > 0) {
72 glDrawArraysInstancedBaseInstance(primitive_mode, vertex_first, count, 1,
73 current_instance);
74 } else {
75 glDrawArrays(primitive_mode, vertex_first, count);
76 }
77 }
78 }
79};
45 80
46RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info) 81RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info)
47 : emu_window{window}, screen_info{info}, buffer_cache(STREAM_BUFFER_SIZE) { 82 : emu_window{window}, screen_info{info}, buffer_cache(STREAM_BUFFER_SIZE) {
@@ -172,6 +207,54 @@ void RasterizerOpenGL::SetupVertexArrays() {
172 } 207 }
173} 208}
174 209
210DrawParameters RasterizerOpenGL::SetupDraw() {
211 const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
212 const auto& regs = gpu.regs;
213 const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
214
215 DrawParameters params{};
216 params.current_instance = gpu.state.current_instance;
217
218 if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) {
219 MICROPROFILE_SCOPE(OpenGL_PrimitiveAssembly);
220
221 params.use_indexed = true;
222 params.primitive_mode = GL_TRIANGLES;
223
224 if (is_indexed) {
225 params.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
226 params.count = (regs.index_array.count / 4) * 6;
227 params.index_buffer_offset = primitive_assembler.MakeQuadIndexed(
228 regs.index_array.IndexStart(), regs.index_array.FormatSizeInBytes(),
229 regs.index_array.count);
230 params.base_vertex = static_cast<GLint>(regs.vb_element_base);
231 } else {
232 // MakeQuadArray always generates u32 indexes
233 params.index_format = GL_UNSIGNED_INT;
234 params.count = (regs.vertex_buffer.count / 4) * 6;
235 params.index_buffer_offset =
236 primitive_assembler.MakeQuadArray(regs.vertex_buffer.first, params.count);
237 }
238 return params;
239 }
240
241 params.use_indexed = is_indexed;
242 params.primitive_mode = MaxwellToGL::PrimitiveTopology(regs.draw.topology);
243
244 if (is_indexed) {
245 MICROPROFILE_SCOPE(OpenGL_Index);
246 params.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
247 params.count = regs.index_array.count;
248 params.index_buffer_offset =
249 buffer_cache.UploadMemory(regs.index_array.IndexStart(), CalculateIndexBufferSize());
250 params.base_vertex = static_cast<GLint>(regs.vb_element_base);
251 } else {
252 params.count = regs.vertex_buffer.count;
253 params.vertex_first = regs.vertex_buffer.first;
254 }
255 return params;
256}
257
175void RasterizerOpenGL::SetupShaders() { 258void RasterizerOpenGL::SetupShaders() {
176 MICROPROFILE_SCOPE(OpenGL_Shader); 259 MICROPROFILE_SCOPE(OpenGL_Shader);
177 const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); 260 const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
@@ -256,6 +339,13 @@ std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
256 return size; 339 return size;
257} 340}
258 341
342std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const {
343 const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
344
345 return static_cast<std::size_t>(regs.index_array.count) *
346 static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
347}
348
259bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) { 349bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) {
260 accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays; 350 accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays;
261 DrawArrays(); 351 DrawArrays();
@@ -459,16 +549,23 @@ void RasterizerOpenGL::DrawArrays() {
459 549
460 // Draw the vertex batch 550 // Draw the vertex batch
461 const bool is_indexed = accelerate_draw == AccelDraw::Indexed; 551 const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
462 const u64 index_buffer_size{static_cast<u64>(regs.index_array.count) *
463 static_cast<u64>(regs.index_array.FormatSizeInBytes())};
464 552
465 state.draw.vertex_buffer = buffer_cache.GetHandle(); 553 state.draw.vertex_buffer = buffer_cache.GetHandle();
466 state.Apply(); 554 state.Apply();
467 555
468 std::size_t buffer_size = CalculateVertexArraysSize(); 556 std::size_t buffer_size = CalculateVertexArraysSize();
469 557
470 if (is_indexed) { 558 // Add space for index buffer (keeping in mind non-core primitives)
471 buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) + index_buffer_size; 559 switch (regs.draw.topology) {
560 case Maxwell::PrimitiveTopology::Quads:
561 buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) +
562 primitive_assembler.CalculateQuadSize(regs.vertex_buffer.count);
563 break;
564 default:
565 if (is_indexed) {
566 buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) + CalculateIndexBufferSize();
567 }
568 break;
472 } 569 }
473 570
474 // Uniform space for the 5 shader stages 571 // Uniform space for the 5 shader stages
@@ -482,20 +579,7 @@ void RasterizerOpenGL::DrawArrays() {
482 buffer_cache.Map(buffer_size); 579 buffer_cache.Map(buffer_size);
483 580
484 SetupVertexArrays(); 581 SetupVertexArrays();
485 582 DrawParameters params = SetupDraw();
486 // If indexed mode, copy the index buffer
487 GLintptr index_buffer_offset = 0;
488 if (is_indexed) {
489 MICROPROFILE_SCOPE(OpenGL_Index);
490
491 // Adjust the index buffer offset so it points to the first desired index.
492 auto index_start = regs.index_array.StartAddress();
493 index_start += static_cast<size_t>(regs.index_array.first) *
494 static_cast<size_t>(regs.index_array.FormatSizeInBytes());
495
496 index_buffer_offset = buffer_cache.UploadMemory(index_start, index_buffer_size);
497 }
498
499 SetupShaders(); 583 SetupShaders();
500 584
501 buffer_cache.Unmap(); 585 buffer_cache.Unmap();
@@ -503,31 +587,8 @@ void RasterizerOpenGL::DrawArrays() {
503 shader_program_manager->ApplyTo(state); 587 shader_program_manager->ApplyTo(state);
504 state.Apply(); 588 state.Apply();
505 589
506 const GLenum primitive_mode{MaxwellToGL::PrimitiveTopology(regs.draw.topology)}; 590 // Execute draw call
507 if (is_indexed) { 591 params.DispatchDraw();
508 const GLint base_vertex{static_cast<GLint>(regs.vb_element_base)};
509
510 if (gpu.state.current_instance > 0) {
511 glDrawElementsInstancedBaseVertexBaseInstance(
512 primitive_mode, regs.index_array.count,
513 MaxwellToGL::IndexFormat(regs.index_array.format),
514 reinterpret_cast<const void*>(index_buffer_offset), 1, base_vertex,
515 gpu.state.current_instance);
516 } else {
517 glDrawElementsBaseVertex(primitive_mode, regs.index_array.count,
518 MaxwellToGL::IndexFormat(regs.index_array.format),
519 reinterpret_cast<const void*>(index_buffer_offset),
520 base_vertex);
521 }
522 } else {
523 if (gpu.state.current_instance > 0) {
524 glDrawArraysInstancedBaseInstance(primitive_mode, regs.vertex_buffer.first,
525 regs.vertex_buffer.count, 1,
526 gpu.state.current_instance);
527 } else {
528 glDrawArrays(primitive_mode, regs.vertex_buffer.first, regs.vertex_buffer.count);
529 }
530 }
531 592
532 // Disable scissor test 593 // Disable scissor test
533 state.scissor.enabled = false; 594 state.scissor.enabled = false;
@@ -556,14 +617,10 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
556 InvalidateRegion(addr, size); 617 InvalidateRegion(addr, size);
557} 618}
558 619
559bool RasterizerOpenGL::AccelerateDisplayTransfer(const void* config) { 620bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
621 const Tegra::Engines::Fermi2D::Regs::Surface& dst) {
560 MICROPROFILE_SCOPE(OpenGL_Blits); 622 MICROPROFILE_SCOPE(OpenGL_Blits);
561 UNREACHABLE(); 623 res_cache.FermiCopySurface(src, dst);
562 return true;
563}
564
565bool RasterizerOpenGL::AccelerateTextureCopy(const void* config) {
566 UNREACHABLE();
567 return true; 624 return true;
568} 625}
569 626
@@ -601,10 +658,13 @@ void RasterizerOpenGL::SamplerInfo::Create() {
601 sampler.Create(); 658 sampler.Create();
602 mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear; 659 mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear;
603 wrap_u = wrap_v = wrap_p = Tegra::Texture::WrapMode::Wrap; 660 wrap_u = wrap_v = wrap_p = Tegra::Texture::WrapMode::Wrap;
661 uses_depth_compare = false;
662 depth_compare_func = Tegra::Texture::DepthCompareFunc::Never;
604 663
605 // default is GL_LINEAR_MIPMAP_LINEAR 664 // default is GL_LINEAR_MIPMAP_LINEAR
606 glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 665 glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
607 // Other attributes have correct defaults 666 // Other attributes have correct defaults
667 glSamplerParameteri(sampler.handle, GL_TEXTURE_COMPARE_FUNC, GL_NEVER);
608} 668}
609 669
610void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) { 670void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) {
@@ -632,6 +692,21 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
632 glSamplerParameteri(s, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p)); 692 glSamplerParameteri(s, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p));
633 } 693 }
634 694
695 if (uses_depth_compare != (config.depth_compare_enabled == 1)) {
696 uses_depth_compare = (config.depth_compare_enabled == 1);
697 if (uses_depth_compare) {
698 glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
699 } else {
700 glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_NONE);
701 }
702 }
703
704 if (depth_compare_func != config.depth_compare_func) {
705 depth_compare_func = config.depth_compare_func;
706 glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC,
707 MaxwellToGL::DepthCompareFunc(depth_compare_func));
708 }
709
635 if (wrap_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border || 710 if (wrap_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border ||
636 wrap_p == Tegra::Texture::WrapMode::Border) { 711 wrap_p == Tegra::Texture::WrapMode::Border) {
637 const GLvec4 new_border_color = {{config.border_color_r, config.border_color_g, 712 const GLvec4 new_border_color = {{config.border_color_r, config.border_color_g,
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 4c8ecbd1c..0dab2018b 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -23,6 +23,7 @@
23#include "video_core/rasterizer_cache.h" 23#include "video_core/rasterizer_cache.h"
24#include "video_core/rasterizer_interface.h" 24#include "video_core/rasterizer_interface.h"
25#include "video_core/renderer_opengl/gl_buffer_cache.h" 25#include "video_core/renderer_opengl/gl_buffer_cache.h"
26#include "video_core/renderer_opengl/gl_primitive_assembler.h"
26#include "video_core/renderer_opengl/gl_rasterizer_cache.h" 27#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
27#include "video_core/renderer_opengl/gl_resource_manager.h" 28#include "video_core/renderer_opengl/gl_resource_manager.h"
28#include "video_core/renderer_opengl/gl_shader_cache.h" 29#include "video_core/renderer_opengl/gl_shader_cache.h"
@@ -38,6 +39,7 @@ class EmuWindow;
38namespace OpenGL { 39namespace OpenGL {
39 40
40struct ScreenInfo; 41struct ScreenInfo;
42struct DrawParameters;
41 43
42class RasterizerOpenGL : public VideoCore::RasterizerInterface { 44class RasterizerOpenGL : public VideoCore::RasterizerInterface {
43public: 45public:
@@ -50,8 +52,8 @@ public:
50 void FlushRegion(VAddr addr, u64 size) override; 52 void FlushRegion(VAddr addr, u64 size) override;
51 void InvalidateRegion(VAddr addr, u64 size) override; 53 void InvalidateRegion(VAddr addr, u64 size) override;
52 void FlushAndInvalidateRegion(VAddr addr, u64 size) override; 54 void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
53 bool AccelerateDisplayTransfer(const void* config) override; 55 bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
54 bool AccelerateTextureCopy(const void* config) override; 56 const Tegra::Engines::Fermi2D::Regs::Surface& dst) override;
55 bool AccelerateFill(const void* config) override; 57 bool AccelerateFill(const void* config) override;
56 bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr, 58 bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
57 u32 pixel_stride) override; 59 u32 pixel_stride) override;
@@ -94,6 +96,8 @@ private:
94 Tegra::Texture::WrapMode wrap_u; 96 Tegra::Texture::WrapMode wrap_u;
95 Tegra::Texture::WrapMode wrap_v; 97 Tegra::Texture::WrapMode wrap_v;
96 Tegra::Texture::WrapMode wrap_p; 98 Tegra::Texture::WrapMode wrap_p;
99 bool uses_depth_compare;
100 Tegra::Texture::DepthCompareFunc depth_compare_func;
97 GLvec4 border_color; 101 GLvec4 border_color;
98 }; 102 };
99 103
@@ -192,12 +196,17 @@ private:
192 static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024; 196 static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
193 OGLBufferCache buffer_cache; 197 OGLBufferCache buffer_cache;
194 OGLFramebuffer framebuffer; 198 OGLFramebuffer framebuffer;
199 PrimitiveAssembler primitive_assembler{buffer_cache};
195 GLint uniform_buffer_alignment; 200 GLint uniform_buffer_alignment;
196 201
197 std::size_t CalculateVertexArraysSize() const; 202 std::size_t CalculateVertexArraysSize() const;
198 203
204 std::size_t CalculateIndexBufferSize() const;
205
199 void SetupVertexArrays(); 206 void SetupVertexArrays();
200 207
208 DrawParameters SetupDraw();
209
201 void SetupShaders(); 210 void SetupShaders();
202 211
203 enum class AccelDraw { Disabled, Arrays, Indexed }; 212 enum class AccelDraw { Disabled, Arrays, Indexed };
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index ce967c4d6..56ff83eff 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -143,6 +143,28 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
143 return params; 143 return params;
144} 144}
145 145
146/*static*/ SurfaceParams SurfaceParams::CreateForFermiCopySurface(
147 const Tegra::Engines::Fermi2D::Regs::Surface& config) {
148 SurfaceParams params{};
149 params.addr = TryGetCpuAddr(config.Address());
150 params.is_tiled = !config.linear;
151 params.block_height = params.is_tiled ? config.BlockHeight() : 0,
152 params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
153 params.component_type = ComponentTypeFromRenderTarget(config.format);
154 params.type = GetFormatType(params.pixel_format);
155 params.width = config.width;
156 params.height = config.height;
157 params.unaligned_height = config.height;
158 params.target = SurfaceTarget::Texture2D;
159 params.depth = 1;
160 params.size_in_bytes_total = params.SizeInBytesTotal();
161 params.size_in_bytes_2d = params.SizeInBytes2D();
162 params.max_mip_level = 0;
163 params.rt = {};
164
165 return params;
166}
167
146static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{ 168static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{
147 {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U 169 {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U
148 {GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S 170 {GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S
@@ -559,6 +581,18 @@ static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface,
559 return true; 581 return true;
560} 582}
561 583
584static void FastCopySurface(const Surface& src_surface, const Surface& dst_surface) {
585 const auto& src_params{src_surface->GetSurfaceParams()};
586 const auto& dst_params{dst_surface->GetSurfaceParams()};
587
588 const u32 width{std::min(src_params.width, dst_params.width)};
589 const u32 height{std::min(src_params.height, dst_params.height)};
590
591 glCopyImageSubData(src_surface->Texture().handle, SurfaceTargetToGL(src_params.target), 0, 0, 0,
592 0, dst_surface->Texture().handle, SurfaceTargetToGL(dst_params.target), 0, 0,
593 0, 0, width, height, 1);
594}
595
562static void CopySurface(const Surface& src_surface, const Surface& dst_surface, 596static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
563 GLuint copy_pbo_handle, GLenum src_attachment = 0, 597 GLuint copy_pbo_handle, GLenum src_attachment = 0,
564 GLenum dst_attachment = 0, std::size_t cubemap_face = 0) { 598 GLenum dst_attachment = 0, std::size_t cubemap_face = 0) {
@@ -1033,6 +1067,26 @@ Surface RasterizerCacheOpenGL::GetUncachedSurface(const SurfaceParams& params) {
1033 return surface; 1067 return surface;
1034} 1068}
1035 1069
1070void RasterizerCacheOpenGL::FermiCopySurface(
1071 const Tegra::Engines::Fermi2D::Regs::Surface& src_config,
1072 const Tegra::Engines::Fermi2D::Regs::Surface& dst_config) {
1073
1074 const auto& src_params = SurfaceParams::CreateForFermiCopySurface(src_config);
1075 const auto& dst_params = SurfaceParams::CreateForFermiCopySurface(dst_config);
1076
1077 ASSERT(src_params.width == dst_params.width);
1078 ASSERT(src_params.height == dst_params.height);
1079 ASSERT(src_params.pixel_format == dst_params.pixel_format);
1080 ASSERT(src_params.block_height == dst_params.block_height);
1081 ASSERT(src_params.is_tiled == dst_params.is_tiled);
1082 ASSERT(src_params.depth == dst_params.depth);
1083 ASSERT(src_params.depth == 1); // Currently, FastCopySurface only works with 2D surfaces
1084 ASSERT(src_params.target == dst_params.target);
1085 ASSERT(src_params.rt.index == dst_params.rt.index);
1086
1087 FastCopySurface(GetSurface(src_params, true), GetSurface(dst_params, false));
1088}
1089
1036Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, 1090Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
1037 const SurfaceParams& new_params) { 1091 const SurfaceParams& new_params) {
1038 // Verify surface is compatible for blitting 1092 // Verify surface is compatible for blitting
@@ -1041,6 +1095,15 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
1041 // Get a new surface with the new parameters, and blit the previous surface to it 1095 // Get a new surface with the new parameters, and blit the previous surface to it
1042 Surface new_surface{GetUncachedSurface(new_params)}; 1096 Surface new_surface{GetUncachedSurface(new_params)};
1043 1097
1098 // For compatible surfaces, we can just do fast glCopyImageSubData based copy
1099 if (old_params.target == new_params.target && old_params.type == new_params.type &&
1100 old_params.depth == new_params.depth && old_params.depth == 1 &&
1101 SurfaceParams::GetFormatBpp(old_params.pixel_format) ==
1102 SurfaceParams::GetFormatBpp(new_params.pixel_format)) {
1103 FastCopySurface(old_surface, new_surface);
1104 return new_surface;
1105 }
1106
1044 // If the format is the same, just do a framebuffer blit. This is significantly faster than 1107 // If the format is the same, just do a framebuffer blit. This is significantly faster than
1045 // using PBOs. The is also likely less accurate, as textures will be converted rather than 1108 // using PBOs. The is also likely less accurate, as textures will be converted rather than
1046 // reinterpreted. When use_accurate_framebuffers setting is enabled, perform a more accurate 1109 // reinterpreted. When use_accurate_framebuffers setting is enabled, perform a more accurate
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index 49025a3fe..0b4940b3c 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -13,6 +13,7 @@
13#include "common/common_types.h" 13#include "common/common_types.h"
14#include "common/hash.h" 14#include "common/hash.h"
15#include "common/math_util.h" 15#include "common/math_util.h"
16#include "video_core/engines/fermi_2d.h"
16#include "video_core/engines/maxwell_3d.h" 17#include "video_core/engines/maxwell_3d.h"
17#include "video_core/rasterizer_cache.h" 18#include "video_core/rasterizer_cache.h"
18#include "video_core/renderer_opengl/gl_resource_manager.h" 19#include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -719,6 +720,10 @@ struct SurfaceParams {
719 Tegra::GPUVAddr zeta_address, 720 Tegra::GPUVAddr zeta_address,
720 Tegra::DepthFormat format); 721 Tegra::DepthFormat format);
721 722
723 /// Creates SurfaceParams for a Fermi2D surface copy
724 static SurfaceParams CreateForFermiCopySurface(
725 const Tegra::Engines::Fermi2D::Regs::Surface& config);
726
722 /// Checks if surfaces are compatible for caching 727 /// Checks if surfaces are compatible for caching
723 bool IsCompatibleSurface(const SurfaceParams& other) const { 728 bool IsCompatibleSurface(const SurfaceParams& other) const {
724 return std::tie(pixel_format, type, width, height, target, depth) == 729 return std::tie(pixel_format, type, width, height, target, depth) ==
@@ -837,6 +842,10 @@ public:
837 /// Tries to find a framebuffer using on the provided CPU address 842 /// Tries to find a framebuffer using on the provided CPU address
838 Surface TryFindFramebufferSurface(VAddr addr) const; 843 Surface TryFindFramebufferSurface(VAddr addr) const;
839 844
845 /// Copies the contents of one surface to another
846 void FermiCopySurface(const Tegra::Engines::Fermi2D::Regs::Surface& src_config,
847 const Tegra::Engines::Fermi2D::Regs::Surface& dst_config);
848
840private: 849private:
841 void LoadSurface(const Surface& surface); 850 void LoadSurface(const Surface& surface);
842 Surface GetSurface(const SurfaceParams& params, bool preserve_contents = true); 851 Surface GetSurface(const SurfaceParams& params, bool preserve_contents = true);
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 579a78702..7e57de78a 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -508,7 +508,7 @@ public:
508 /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if 508 /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if
509 /// necessary. 509 /// necessary.
510 std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type, 510 std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type,
511 bool is_array) { 511 bool is_array, bool is_shadow) {
512 const std::size_t offset = static_cast<std::size_t>(sampler.index.Value()); 512 const std::size_t offset = static_cast<std::size_t>(sampler.index.Value());
513 513
514 // If this sampler has already been used, return the existing mapping. 514 // If this sampler has already been used, return the existing mapping.
@@ -517,13 +517,14 @@ public:
517 [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; }); 517 [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; });
518 518
519 if (itr != used_samplers.end()) { 519 if (itr != used_samplers.end()) {
520 ASSERT(itr->GetType() == type && itr->IsArray() == is_array); 520 ASSERT(itr->GetType() == type && itr->IsArray() == is_array &&
521 itr->IsShadow() == is_shadow);
521 return itr->GetName(); 522 return itr->GetName();
522 } 523 }
523 524
524 // Otherwise create a new mapping for this sampler 525 // Otherwise create a new mapping for this sampler
525 const std::size_t next_index = used_samplers.size(); 526 const std::size_t next_index = used_samplers.size();
526 const SamplerEntry entry{stage, offset, next_index, type, is_array}; 527 const SamplerEntry entry{stage, offset, next_index, type, is_array, is_shadow};
527 used_samplers.emplace_back(entry); 528 used_samplers.emplace_back(entry);
528 return entry.GetName(); 529 return entry.GetName();
529 } 530 }
@@ -747,8 +748,9 @@ private:
747 } 748 }
748 749
749 /// Generates code representing a texture sampler. 750 /// Generates code representing a texture sampler.
750 std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array) { 751 std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array,
751 return regs.AccessSampler(sampler, type, is_array); 752 bool is_shadow) {
753 return regs.AccessSampler(sampler, type, is_array, is_shadow);
752 } 754 }
753 755
754 /** 756 /**
@@ -1002,6 +1004,24 @@ private:
1002 shader.AddLine('}'); 1004 shader.AddLine('}');
1003 } 1005 }
1004 1006
1007 static u32 TextureCoordinates(Tegra::Shader::TextureType texture_type) {
1008 switch (texture_type) {
1009 case Tegra::Shader::TextureType::Texture1D: {
1010 return 1;
1011 }
1012 case Tegra::Shader::TextureType::Texture2D: {
1013 return 2;
1014 }
1015 case Tegra::Shader::TextureType::TextureCube: {
1016 return 3;
1017 }
1018 default:
1019 LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", static_cast<u32>(texture_type));
1020 UNREACHABLE();
1021 return 0;
1022 }
1023 }
1024
1005 /* 1025 /*
1006 * Emits code to push the input target address to the SSY address stack, incrementing the stack 1026 * Emits code to push the input target address to the SSY address stack, incrementing the stack
1007 * top. 1027 * top.
@@ -1896,24 +1916,35 @@ private:
1896 "NODEP is not implemented"); 1916 "NODEP is not implemented");
1897 ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), 1917 ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
1898 "AOFFI is not implemented"); 1918 "AOFFI is not implemented");
1899 ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC),
1900 "DC is not implemented");
1901 1919
1902 switch (texture_type) { 1920 const bool depth_compare =
1903 case Tegra::Shader::TextureType::Texture1D: { 1921 instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
1922 u32 num_coordinates = TextureCoordinates(texture_type);
1923 if (depth_compare)
1924 num_coordinates += 1;
1925
1926 switch (num_coordinates) {
1927 case 1: {
1904 const std::string x = regs.GetRegisterAsFloat(instr.gpr8); 1928 const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
1905 coord = "float coords = " + x + ';'; 1929 coord = "float coords = " + x + ';';
1906 break; 1930 break;
1907 } 1931 }
1908 case Tegra::Shader::TextureType::Texture2D: { 1932 case 2: {
1909 const std::string x = regs.GetRegisterAsFloat(instr.gpr8); 1933 const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
1910 const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); 1934 const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
1911 coord = "vec2 coords = vec2(" + x + ", " + y + ");"; 1935 coord = "vec2 coords = vec2(" + x + ", " + y + ");";
1912 break; 1936 break;
1913 } 1937 }
1938 case 3: {
1939 const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
1940 const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
1941 const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
1942 coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
1943 break;
1944 }
1914 default: 1945 default:
1915 LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", 1946 LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}",
1916 static_cast<u32>(texture_type)); 1947 static_cast<u32>(num_coordinates));
1917 UNREACHABLE(); 1948 UNREACHABLE();
1918 1949
1919 // Fallback to interpreting as a 2D texture for now 1950 // Fallback to interpreting as a 2D texture for now
@@ -1924,9 +1955,10 @@ private:
1924 } 1955 }
1925 // TODO: make sure coordinates are always indexed to gpr8 and gpr20 is always bias 1956 // TODO: make sure coordinates are always indexed to gpr8 and gpr20 is always bias
1926 // or lod. 1957 // or lod.
1927 const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20); 1958 std::string op_c;
1928 1959
1929 const std::string sampler = GetSampler(instr.sampler, texture_type, false); 1960 const std::string sampler =
1961 GetSampler(instr.sampler, texture_type, false, depth_compare);
1930 // Add an extra scope and declare the texture coords inside to prevent 1962 // Add an extra scope and declare the texture coords inside to prevent
1931 // overwriting them in case they are used as outputs of the texs instruction. 1963 // overwriting them in case they are used as outputs of the texs instruction.
1932 1964
@@ -1935,7 +1967,7 @@ private:
1935 shader.AddLine(coord); 1967 shader.AddLine(coord);
1936 std::string texture; 1968 std::string texture;
1937 1969
1938 switch (instr.tex.process_mode) { 1970 switch (instr.tex.GetTextureProcessMode()) {
1939 case Tegra::Shader::TextureProcessMode::None: { 1971 case Tegra::Shader::TextureProcessMode::None: {
1940 texture = "texture(" + sampler + ", coords)"; 1972 texture = "texture(" + sampler + ", coords)";
1941 break; 1973 break;
@@ -1946,12 +1978,22 @@ private:
1946 } 1978 }
1947 case Tegra::Shader::TextureProcessMode::LB: 1979 case Tegra::Shader::TextureProcessMode::LB:
1948 case Tegra::Shader::TextureProcessMode::LBA: { 1980 case Tegra::Shader::TextureProcessMode::LBA: {
1981 if (num_coordinates <= 2) {
1982 op_c = regs.GetRegisterAsFloat(instr.gpr20);
1983 } else {
1984 op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
1985 }
1949 // TODO: Figure if A suffix changes the equation at all. 1986 // TODO: Figure if A suffix changes the equation at all.
1950 texture = "texture(" + sampler + ", coords, " + op_c + ')'; 1987 texture = "texture(" + sampler + ", coords, " + op_c + ')';
1951 break; 1988 break;
1952 } 1989 }
1953 case Tegra::Shader::TextureProcessMode::LL: 1990 case Tegra::Shader::TextureProcessMode::LL:
1954 case Tegra::Shader::TextureProcessMode::LLA: { 1991 case Tegra::Shader::TextureProcessMode::LLA: {
1992 if (num_coordinates <= 2) {
1993 op_c = regs.GetRegisterAsFloat(instr.gpr20);
1994 } else {
1995 op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
1996 }
1955 // TODO: Figure if A suffix changes the equation at all. 1997 // TODO: Figure if A suffix changes the equation at all.
1956 texture = "textureLod(" + sampler + ", coords, " + op_c + ')'; 1998 texture = "textureLod(" + sampler + ", coords, " + op_c + ')';
1957 break; 1999 break;
@@ -1959,18 +2001,22 @@ private:
1959 default: { 2001 default: {
1960 texture = "texture(" + sampler + ", coords)"; 2002 texture = "texture(" + sampler + ", coords)";
1961 LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}", 2003 LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}",
1962 static_cast<u32>(instr.tex.process_mode.Value())); 2004 static_cast<u32>(instr.tex.GetTextureProcessMode()));
1963 UNREACHABLE(); 2005 UNREACHABLE();
1964 } 2006 }
1965 } 2007 }
1966 std::size_t dest_elem{}; 2008 if (!depth_compare) {
1967 for (std::size_t elem = 0; elem < 4; ++elem) { 2009 std::size_t dest_elem{};
1968 if (!instr.tex.IsComponentEnabled(elem)) { 2010 for (std::size_t elem = 0; elem < 4; ++elem) {
1969 // Skip disabled components 2011 if (!instr.tex.IsComponentEnabled(elem)) {
1970 continue; 2012 // Skip disabled components
2013 continue;
2014 }
2015 regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
2016 ++dest_elem;
1971 } 2017 }
1972 regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem); 2018 } else {
1973 ++dest_elem; 2019 regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false);
1974 } 2020 }
1975 --shader.scope; 2021 --shader.scope;
1976 shader.AddLine("}"); 2022 shader.AddLine("}");
@@ -1983,11 +2029,15 @@ private:
1983 2029
1984 ASSERT_MSG(!instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), 2030 ASSERT_MSG(!instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
1985 "NODEP is not implemented"); 2031 "NODEP is not implemented");
1986 ASSERT_MSG(!instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC),
1987 "DC is not implemented");
1988 2032
1989 switch (texture_type) { 2033 const bool depth_compare =
1990 case Tegra::Shader::TextureType::Texture2D: { 2034 instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
2035 u32 num_coordinates = TextureCoordinates(texture_type);
2036 if (depth_compare)
2037 num_coordinates += 1;
2038
2039 switch (num_coordinates) {
2040 case 2: {
1991 if (is_array) { 2041 if (is_array) {
1992 const std::string index = regs.GetRegisterAsInteger(instr.gpr8); 2042 const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
1993 const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); 2043 const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
@@ -2000,17 +2050,25 @@ private:
2000 } 2050 }
2001 break; 2051 break;
2002 } 2052 }
2003 case Tegra::Shader::TextureType::TextureCube: { 2053 case 3: {
2004 ASSERT_MSG(!is_array, "Unimplemented"); 2054 if (is_array) {
2005 std::string x = regs.GetRegisterAsFloat(instr.gpr8); 2055 UNIMPLEMENTED_MSG("3-coordinate arrays not fully implemented");
2006 std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); 2056 const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
2007 std::string z = regs.GetRegisterAsFloat(instr.gpr20); 2057 const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
2008 coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; 2058 coord = "vec2 coords = vec2(" + x + ", " + y + ");";
2059 texture_type = Tegra::Shader::TextureType::Texture2D;
2060 is_array = false;
2061 } else {
2062 const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
2063 const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
2064 const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
2065 coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
2066 }
2009 break; 2067 break;
2010 } 2068 }
2011 default: 2069 default:
2012 LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", 2070 LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}",
2013 static_cast<u32>(texture_type)); 2071 static_cast<u32>(num_coordinates));
2014 UNREACHABLE(); 2072 UNREACHABLE();
2015 2073
2016 // Fallback to interpreting as a 2D texture for now 2074 // Fallback to interpreting as a 2D texture for now
@@ -2020,9 +2078,35 @@ private:
2020 texture_type = Tegra::Shader::TextureType::Texture2D; 2078 texture_type = Tegra::Shader::TextureType::Texture2D;
2021 is_array = false; 2079 is_array = false;
2022 } 2080 }
2023 const std::string sampler = GetSampler(instr.sampler, texture_type, is_array); 2081 const std::string sampler =
2024 const std::string texture = "texture(" + sampler + ", coords)"; 2082 GetSampler(instr.sampler, texture_type, is_array, depth_compare);
2025 WriteTexsInstruction(instr, coord, texture); 2083 std::string texture;
2084 switch (instr.texs.GetTextureProcessMode()) {
2085 case Tegra::Shader::TextureProcessMode::None: {
2086 texture = "texture(" + sampler + ", coords)";
2087 break;
2088 }
2089 case Tegra::Shader::TextureProcessMode::LZ: {
2090 texture = "textureLod(" + sampler + ", coords, 0.0)";
2091 break;
2092 }
2093 case Tegra::Shader::TextureProcessMode::LL: {
2094 const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
2095 texture = "textureLod(" + sampler + ", coords, " + op_c + ')';
2096 break;
2097 }
2098 default: {
2099 texture = "texture(" + sampler + ", coords)";
2100 LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}",
2101 static_cast<u32>(instr.texs.GetTextureProcessMode()));
2102 UNREACHABLE();
2103 }
2104 }
2105 if (!depth_compare) {
2106 WriteTexsInstruction(instr, coord, texture);
2107 } else {
2108 WriteTexsInstruction(instr, coord, "vec4(" + texture + ')');
2109 }
2026 break; 2110 break;
2027 } 2111 }
2028 case OpCode::Id::TLDS: { 2112 case OpCode::Id::TLDS: {
@@ -2062,9 +2146,26 @@ private:
2062 static_cast<u32>(texture_type)); 2146 static_cast<u32>(texture_type));
2063 UNREACHABLE(); 2147 UNREACHABLE();
2064 } 2148 }
2065 2149 const std::string sampler =
2066 const std::string sampler = GetSampler(instr.sampler, texture_type, is_array); 2150 GetSampler(instr.sampler, texture_type, is_array, false);
2067 const std::string texture = "texelFetch(" + sampler + ", coords, 0)"; 2151 std::string texture = "texelFetch(" + sampler + ", coords, 0)";
2152 const std::string op_c = regs.GetRegisterAsInteger(instr.gpr20.Value() + 1);
2153 switch (instr.tlds.GetTextureProcessMode()) {
2154 case Tegra::Shader::TextureProcessMode::LZ: {
2155 texture = "texelFetch(" + sampler + ", coords, 0)";
2156 break;
2157 }
2158 case Tegra::Shader::TextureProcessMode::LL: {
2159 texture = "texelFetch(" + sampler + ", coords, " + op_c + ')';
2160 break;
2161 }
2162 default: {
2163 texture = "texelFetch(" + sampler + ", coords, 0)";
2164 LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}",
2165 static_cast<u32>(instr.tlds.GetTextureProcessMode()));
2166 UNREACHABLE();
2167 }
2168 }
2068 WriteTexsInstruction(instr, coord, texture); 2169 WriteTexsInstruction(instr, coord, texture);
2069 break; 2170 break;
2070 } 2171 }
@@ -2077,28 +2178,43 @@ private:
2077 "NODEP is not implemented"); 2178 "NODEP is not implemented");
2078 ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), 2179 ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
2079 "AOFFI is not implemented"); 2180 "AOFFI is not implemented");
2080 ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC),
2081 "DC is not implemented");
2082 ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), 2181 ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV),
2083 "NDV is not implemented"); 2182 "NDV is not implemented");
2084 ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP), 2183 ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP),
2085 "PTP is not implemented"); 2184 "PTP is not implemented");
2086 2185 const bool depth_compare =
2087 switch (instr.tld4.texture_type) { 2186 instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
2088 case Tegra::Shader::TextureType::Texture2D: { 2187 auto texture_type = instr.tld4.texture_type.Value();
2188 u32 num_coordinates = TextureCoordinates(texture_type);
2189 if (depth_compare)
2190 num_coordinates += 1;
2191
2192 switch (num_coordinates) {
2193 case 2: {
2089 const std::string x = regs.GetRegisterAsFloat(instr.gpr8); 2194 const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
2090 const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); 2195 const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
2091 coord = "vec2 coords = vec2(" + x + ", " + y + ");"; 2196 coord = "vec2 coords = vec2(" + x + ", " + y + ");";
2092 break; 2197 break;
2093 } 2198 }
2199 case 3: {
2200 const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
2201 const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
2202 const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
2203 coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
2204 break;
2205 }
2094 default: 2206 default:
2095 LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", 2207 LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}",
2096 static_cast<u32>(instr.tld4.texture_type.Value())); 2208 static_cast<u32>(num_coordinates));
2097 UNREACHABLE(); 2209 UNREACHABLE();
2210 const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
2211 const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
2212 coord = "vec2 coords = vec2(" + x + ", " + y + ");";
2213 texture_type = Tegra::Shader::TextureType::Texture2D;
2098 } 2214 }
2099 2215
2100 const std::string sampler = 2216 const std::string sampler =
2101 GetSampler(instr.sampler, instr.tld4.texture_type, false); 2217 GetSampler(instr.sampler, texture_type, false, depth_compare);
2102 // Add an extra scope and declare the texture coords inside to prevent 2218 // Add an extra scope and declare the texture coords inside to prevent
2103 // overwriting them in case they are used as outputs of the texs instruction. 2219 // overwriting them in case they are used as outputs of the texs instruction.
2104 shader.AddLine("{"); 2220 shader.AddLine("{");
@@ -2106,15 +2222,18 @@ private:
2106 shader.AddLine(coord); 2222 shader.AddLine(coord);
2107 const std::string texture = "textureGather(" + sampler + ", coords, " + 2223 const std::string texture = "textureGather(" + sampler + ", coords, " +
2108 std::to_string(instr.tld4.component) + ')'; 2224 std::to_string(instr.tld4.component) + ')';
2109 2225 if (!depth_compare) {
2110 std::size_t dest_elem{}; 2226 std::size_t dest_elem{};
2111 for (std::size_t elem = 0; elem < 4; ++elem) { 2227 for (std::size_t elem = 0; elem < 4; ++elem) {
2112 if (!instr.tex.IsComponentEnabled(elem)) { 2228 if (!instr.tex.IsComponentEnabled(elem)) {
2113 // Skip disabled components 2229 // Skip disabled components
2114 continue; 2230 continue;
2231 }
2232 regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
2233 ++dest_elem;
2115 } 2234 }
2116 regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem); 2235 } else {
2117 ++dest_elem; 2236 regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false);
2118 } 2237 }
2119 --shader.scope; 2238 --shader.scope;
2120 shader.AddLine("}"); 2239 shader.AddLine("}");
@@ -2125,18 +2244,30 @@ private:
2125 "NODEP is not implemented"); 2244 "NODEP is not implemented");
2126 ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), 2245 ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
2127 "AOFFI is not implemented"); 2246 "AOFFI is not implemented");
2128 ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC),
2129 "DC is not implemented");
2130 2247
2248 const bool depth_compare =
2249 instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
2131 const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); 2250 const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
2132 const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); 2251 const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
2133 // TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction. 2252 // TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction.
2134 const std::string sampler = 2253 const std::string sampler = GetSampler(
2135 GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false); 2254 instr.sampler, Tegra::Shader::TextureType::Texture2D, false, depth_compare);
2136 const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; 2255 std::string coord;
2256 if (!depth_compare) {
2257 coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
2258 } else {
2259 // Note: TLD4S coordinate encoding works just like TEXS's
2260 const std::string op_c = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
2261 coord = "vec3 coords = vec3(" + op_a + ", " + op_c + ", " + op_b + ");";
2262 }
2137 const std::string texture = "textureGather(" + sampler + ", coords, " + 2263 const std::string texture = "textureGather(" + sampler + ", coords, " +
2138 std::to_string(instr.tld4s.component) + ')'; 2264 std::to_string(instr.tld4s.component) + ')';
2139 WriteTexsInstruction(instr, coord, texture); 2265
2266 if (!depth_compare) {
2267 WriteTexsInstruction(instr, coord, texture);
2268 } else {
2269 WriteTexsInstruction(instr, coord, "vec4(" + texture + ')');
2270 }
2140 break; 2271 break;
2141 } 2272 }
2142 case OpCode::Id::TXQ: { 2273 case OpCode::Id::TXQ: {
@@ -2147,7 +2278,7 @@ private:
2147 // Sadly, not all texture instructions specify the type of texture their sampler 2278 // Sadly, not all texture instructions specify the type of texture their sampler
2148 // uses. This must be fixed at a later instance. 2279 // uses. This must be fixed at a later instance.
2149 const std::string sampler = 2280 const std::string sampler =
2150 GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false); 2281 GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false);
2151 switch (instr.txq.query_type) { 2282 switch (instr.txq.query_type) {
2152 case Tegra::Shader::TextureQueryType::Dimension: { 2283 case Tegra::Shader::TextureQueryType::Dimension: {
2153 const std::string texture = "textureQueryLevels(" + sampler + ')'; 2284 const std::string texture = "textureQueryLevels(" + sampler + ')';
@@ -2172,7 +2303,8 @@ private:
2172 const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); 2303 const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
2173 const bool is_array = instr.tmml.array != 0; 2304 const bool is_array = instr.tmml.array != 0;
2174 auto texture_type = instr.tmml.texture_type.Value(); 2305 auto texture_type = instr.tmml.texture_type.Value();
2175 const std::string sampler = GetSampler(instr.sampler, texture_type, is_array); 2306 const std::string sampler =
2307 GetSampler(instr.sampler, texture_type, is_array, false);
2176 2308
2177 // TODO: add coordinates for different samplers once other texture types are 2309 // TODO: add coordinates for different samplers once other texture types are
2178 // implemented. 2310 // implemented.
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
index d53b93ad5..e56f39e78 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.h
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -75,8 +75,9 @@ class SamplerEntry {
75 75
76public: 76public:
77 SamplerEntry(Maxwell::ShaderStage stage, std::size_t offset, std::size_t index, 77 SamplerEntry(Maxwell::ShaderStage stage, std::size_t offset, std::size_t index,
78 Tegra::Shader::TextureType type, bool is_array) 78 Tegra::Shader::TextureType type, bool is_array, bool is_shadow)
79 : offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array) {} 79 : offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array),
80 is_shadow(is_shadow) {}
80 81
81 std::size_t GetOffset() const { 82 std::size_t GetOffset() const {
82 return offset; 83 return offset;
@@ -117,6 +118,8 @@ public:
117 } 118 }
118 if (is_array) 119 if (is_array)
119 glsl_type += "Array"; 120 glsl_type += "Array";
121 if (is_shadow)
122 glsl_type += "Shadow";
120 return glsl_type; 123 return glsl_type;
121 } 124 }
122 125
@@ -128,6 +131,10 @@ public:
128 return is_array; 131 return is_array;
129 } 132 }
130 133
134 bool IsShadow() const {
135 return is_shadow;
136 }
137
131 u32 GetHash() const { 138 u32 GetHash() const {
132 return (static_cast<u32>(stage) << 16) | static_cast<u32>(sampler_index); 139 return (static_cast<u32>(stage) << 16) | static_cast<u32>(sampler_index);
133 } 140 }
@@ -147,7 +154,8 @@ private:
147 Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used. 154 Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used.
148 std::size_t sampler_index; ///< Value used to index into the generated GLSL sampler array. 155 std::size_t sampler_index; ///< Value used to index into the generated GLSL sampler array.
149 Tegra::Shader::TextureType type; ///< The type used to sample this texture (Texture2D, etc) 156 Tegra::Shader::TextureType type; ///< The type used to sample this texture (Texture2D, etc)
150 bool is_array; ///< Whether the texture is being sampled as an array texture or not. 157 bool is_array; ///< Whether the texture is being sampled as an array texture or not.
158 bool is_shadow; ///< Whether the texture is being sampled as a depth texture or not.
151}; 159};
152 160
153struct ShaderEntries { 161struct ShaderEntries {
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index 67273e164..3c3bcaae4 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -159,6 +159,31 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) {
159 return {}; 159 return {};
160} 160}
161 161
162inline GLenum DepthCompareFunc(Tegra::Texture::DepthCompareFunc func) {
163 switch (func) {
164 case Tegra::Texture::DepthCompareFunc::Never:
165 return GL_NEVER;
166 case Tegra::Texture::DepthCompareFunc::Less:
167 return GL_LESS;
168 case Tegra::Texture::DepthCompareFunc::LessEqual:
169 return GL_LEQUAL;
170 case Tegra::Texture::DepthCompareFunc::Equal:
171 return GL_EQUAL;
172 case Tegra::Texture::DepthCompareFunc::NotEqual:
173 return GL_NOTEQUAL;
174 case Tegra::Texture::DepthCompareFunc::Greater:
175 return GL_GREATER;
176 case Tegra::Texture::DepthCompareFunc::GreaterEqual:
177 return GL_GEQUAL;
178 case Tegra::Texture::DepthCompareFunc::Always:
179 return GL_ALWAYS;
180 }
181 LOG_CRITICAL(Render_OpenGL, "Unimplemented texture depth compare function ={}",
182 static_cast<u32>(func));
183 UNREACHABLE();
184 return {};
185}
186
162inline GLenum BlendEquation(Maxwell::Blend::Equation equation) { 187inline GLenum BlendEquation(Maxwell::Blend::Equation equation) {
163 switch (equation) { 188 switch (equation) {
164 case Maxwell::Blend::Equation::Add: 189 case Maxwell::Blend::Equation::Add:
diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h
index 14aea4838..8f31d825a 100644
--- a/src/video_core/textures/texture.h
+++ b/src/video_core/textures/texture.h
@@ -227,6 +227,17 @@ enum class WrapMode : u32 {
227 MirrorOnceClampOGL = 7, 227 MirrorOnceClampOGL = 7,
228}; 228};
229 229
230enum class DepthCompareFunc : u32 {
231 Never = 0,
232 Less = 1,
233 Equal = 2,
234 LessEqual = 3,
235 Greater = 4,
236 NotEqual = 5,
237 GreaterEqual = 6,
238 Always = 7,
239};
240
230enum class TextureFilter : u32 { 241enum class TextureFilter : u32 {
231 Nearest = 1, 242 Nearest = 1,
232 Linear = 2, 243 Linear = 2,
@@ -244,7 +255,7 @@ struct TSCEntry {
244 BitField<3, 3, WrapMode> wrap_v; 255 BitField<3, 3, WrapMode> wrap_v;
245 BitField<6, 3, WrapMode> wrap_p; 256 BitField<6, 3, WrapMode> wrap_p;
246 BitField<9, 1, u32> depth_compare_enabled; 257 BitField<9, 1, u32> depth_compare_enabled;
247 BitField<10, 3, u32> depth_compare_func; 258 BitField<10, 3, DepthCompareFunc> depth_compare_func;
248 }; 259 };
249 union { 260 union {
250 BitField<0, 2, TextureFilter> mag_filter; 261 BitField<0, 2, TextureFilter> mag_filter;
diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt
new file mode 100644
index 000000000..1c83e9c34
--- /dev/null
+++ b/src/web_service/CMakeLists.txt
@@ -0,0 +1,16 @@
1add_library(web_service STATIC
2 telemetry_json.cpp
3 telemetry_json.h
4 verify_login.cpp
5 verify_login.h
6 web_backend.cpp
7 web_backend.h
8)
9
10create_target_directory_groups(web_service)
11
12get_directory_property(OPENSSL_LIBS
13 DIRECTORY ${CMAKE_SOURCE_DIR}/externals/libressl
14 DEFINITION OPENSSL_LIBS)
15target_compile_definitions(web_service PUBLIC -DCPPHTTPLIB_OPENSSL_SUPPORT)
16target_link_libraries(web_service PRIVATE common json-headers ${OPENSSL_LIBS} httplib lurlparser)
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
new file mode 100644
index 000000000..033ea1ea4
--- /dev/null
+++ b/src/web_service/telemetry_json.cpp
@@ -0,0 +1,99 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <thread>
6#include "common/assert.h"
7#include "common/detached_tasks.h"
8#include "web_service/telemetry_json.h"
9#include "web_service/web_backend.h"
10
11namespace WebService {
12
13TelemetryJson::TelemetryJson(const std::string& host, const std::string& username,
14 const std::string& token)
15 : host(std::move(host)), username(std::move(username)), token(std::move(token)) {}
16TelemetryJson::~TelemetryJson() = default;
17
18template <class T>
19void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) {
20 sections[static_cast<u8>(type)][name] = value;
21}
22
23void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) {
24 TopSection()[name] = sections[static_cast<unsigned>(type)];
25}
26
27void TelemetryJson::Visit(const Telemetry::Field<bool>& field) {
28 Serialize(field.GetType(), field.GetName(), field.GetValue());
29}
30
31void TelemetryJson::Visit(const Telemetry::Field<double>& field) {
32 Serialize(field.GetType(), field.GetName(), field.GetValue());
33}
34
35void TelemetryJson::Visit(const Telemetry::Field<float>& field) {
36 Serialize(field.GetType(), field.GetName(), field.GetValue());
37}
38
39void TelemetryJson::Visit(const Telemetry::Field<u8>& field) {
40 Serialize(field.GetType(), field.GetName(), field.GetValue());
41}
42
43void TelemetryJson::Visit(const Telemetry::Field<u16>& field) {
44 Serialize(field.GetType(), field.GetName(), field.GetValue());
45}
46
47void TelemetryJson::Visit(const Telemetry::Field<u32>& field) {
48 Serialize(field.GetType(), field.GetName(), field.GetValue());
49}
50
51void TelemetryJson::Visit(const Telemetry::Field<u64>& field) {
52 Serialize(field.GetType(), field.GetName(), field.GetValue());
53}
54
55void TelemetryJson::Visit(const Telemetry::Field<s8>& field) {
56 Serialize(field.GetType(), field.GetName(), field.GetValue());
57}
58
59void TelemetryJson::Visit(const Telemetry::Field<s16>& field) {
60 Serialize(field.GetType(), field.GetName(), field.GetValue());
61}
62
63void TelemetryJson::Visit(const Telemetry::Field<s32>& field) {
64 Serialize(field.GetType(), field.GetName(), field.GetValue());
65}
66
67void TelemetryJson::Visit(const Telemetry::Field<s64>& field) {
68 Serialize(field.GetType(), field.GetName(), field.GetValue());
69}
70
71void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) {
72 Serialize(field.GetType(), field.GetName(), field.GetValue());
73}
74
75void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) {
76 Serialize(field.GetType(), field.GetName(), std::string(field.GetValue()));
77}
78
79void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) {
80 Serialize(field.GetType(), field.GetName(), field.GetValue().count());
81}
82
83void TelemetryJson::Complete() {
84 SerializeSection(Telemetry::FieldType::App, "App");
85 SerializeSection(Telemetry::FieldType::Session, "Session");
86 SerializeSection(Telemetry::FieldType::Performance, "Performance");
87 SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
88 SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
89 SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
90
91 auto content = TopSection().dump();
92 // Send the telemetry async but don't handle the errors since they were written to the log
93 Common::DetachedTasks::AddTask(
94 [host{this->host}, username{this->username}, token{this->token}, content]() {
95 Client{host, username, token}.PostJson("/telemetry", content, true);
96 });
97}
98
99} // namespace WebService
diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h
new file mode 100644
index 000000000..0fe6f9a3e
--- /dev/null
+++ b/src/web_service/telemetry_json.h
@@ -0,0 +1,58 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <string>
9#include <json.hpp>
10#include "common/telemetry.h"
11#include "common/web_result.h"
12
13namespace WebService {
14
15/**
16 * Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the
17 * yuzu web service
18 */
19class TelemetryJson : public Telemetry::VisitorInterface {
20public:
21 TelemetryJson(const std::string& host, const std::string& username, const std::string& token);
22 ~TelemetryJson();
23
24 void Visit(const Telemetry::Field<bool>& field) override;
25 void Visit(const Telemetry::Field<double>& field) override;
26 void Visit(const Telemetry::Field<float>& field) override;
27 void Visit(const Telemetry::Field<u8>& field) override;
28 void Visit(const Telemetry::Field<u16>& field) override;
29 void Visit(const Telemetry::Field<u32>& field) override;
30 void Visit(const Telemetry::Field<u64>& field) override;
31 void Visit(const Telemetry::Field<s8>& field) override;
32 void Visit(const Telemetry::Field<s16>& field) override;
33 void Visit(const Telemetry::Field<s32>& field) override;
34 void Visit(const Telemetry::Field<s64>& field) override;
35 void Visit(const Telemetry::Field<std::string>& field) override;
36 void Visit(const Telemetry::Field<const char*>& field) override;
37 void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override;
38
39 void Complete() override;
40
41private:
42 nlohmann::json& TopSection() {
43 return sections[static_cast<u8>(Telemetry::FieldType::None)];
44 }
45
46 template <class T>
47 void Serialize(Telemetry::FieldType type, const std::string& name, T value);
48
49 void SerializeSection(Telemetry::FieldType type, const std::string& name);
50
51 nlohmann::json output;
52 std::array<nlohmann::json, 7> sections;
53 std::string host;
54 std::string username;
55 std::string token;
56};
57
58} // namespace WebService
diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp
new file mode 100644
index 000000000..124aa3863
--- /dev/null
+++ b/src/web_service/verify_login.cpp
@@ -0,0 +1,27 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <json.hpp>
6#include "web_service/verify_login.h"
7#include "web_service/web_backend.h"
8
9namespace WebService {
10
11bool VerifyLogin(const std::string& host, const std::string& username, const std::string& token) {
12 Client client(host, username, token);
13 auto reply = client.GetJson("/profile", false).returned_data;
14 if (reply.empty()) {
15 return false;
16 }
17 nlohmann::json json = nlohmann::json::parse(reply);
18 const auto iter = json.find("username");
19
20 if (iter == json.end()) {
21 return username.empty();
22 }
23
24 return username == *iter;
25}
26
27} // namespace WebService
diff --git a/src/web_service/verify_login.h b/src/web_service/verify_login.h
new file mode 100644
index 000000000..39db32dbb
--- /dev/null
+++ b/src/web_service/verify_login.h
@@ -0,0 +1,22 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <future>
9#include <string>
10
11namespace WebService {
12
13/**
14 * Checks if username and token is valid
15 * @param host the web API URL
16 * @param username yuzu username to use for authentication.
17 * @param token yuzu token to use for authentication.
18 * @returns a bool indicating whether the verification succeeded
19 */
20bool VerifyLogin(const std::string& host, const std::string& username, const std::string& token);
21
22} // namespace WebService
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
new file mode 100644
index 000000000..787b0fbcb
--- /dev/null
+++ b/src/web_service/web_backend.cpp
@@ -0,0 +1,149 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cstdlib>
6#include <string>
7#include <thread>
8#include <LUrlParser.h>
9#include "common/logging/log.h"
10#include "common/web_result.h"
11#include "core/settings.h"
12#include "web_service/web_backend.h"
13
14namespace WebService {
15
16constexpr std::array<const char, 1> API_VERSION{'1'};
17
18constexpr u32 HTTP_PORT = 80;
19constexpr u32 HTTPS_PORT = 443;
20
21constexpr u32 TIMEOUT_SECONDS = 30;
22
23Client::JWTCache Client::jwt_cache{};
24
25Client::Client(const std::string& host, const std::string& username, const std::string& token)
26 : host(host), username(username), token(token) {
27 std::lock_guard<std::mutex> lock(jwt_cache.mutex);
28 if (username == jwt_cache.username && token == jwt_cache.token) {
29 jwt = jwt_cache.jwt;
30 }
31}
32
33Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
34 const std::string& data, const std::string& jwt,
35 const std::string& username, const std::string& token) {
36 if (cli == nullptr) {
37 auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
38 int port;
39 if (parsedUrl.m_Scheme == "http") {
40 if (!parsedUrl.GetPort(&port)) {
41 port = HTTP_PORT;
42 }
43 cli =
44 std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS);
45 } else if (parsedUrl.m_Scheme == "https") {
46 if (!parsedUrl.GetPort(&port)) {
47 port = HTTPS_PORT;
48 }
49 cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port,
50 TIMEOUT_SECONDS);
51 } else {
52 LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
53 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
54 }
55 }
56 if (cli == nullptr) {
57 LOG_ERROR(WebService, "Invalid URL {}", host + path);
58 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
59 }
60
61 httplib::Headers params;
62 if (!jwt.empty()) {
63 params = {
64 {std::string("Authorization"), fmt::format("Bearer {}", jwt)},
65 };
66 } else if (!username.empty()) {
67 params = {
68 {std::string("x-username"), username},
69 {std::string("x-token"), token},
70 };
71 }
72
73 params.emplace(std::string("api-version"), std::string(API_VERSION.begin(), API_VERSION.end()));
74 if (method != "GET") {
75 params.emplace(std::string("Content-Type"), std::string("application/json"));
76 };
77
78 httplib::Request request;
79 request.method = method;
80 request.path = path;
81 request.headers = params;
82 request.body = data;
83
84 httplib::Response response;
85
86 if (!cli->send(request, response)) {
87 LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
88 return Common::WebResult{Common::WebResult::Code::LibError, "Null response"};
89 }
90
91 if (response.status >= 400) {
92 LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
93 response.status);
94 return Common::WebResult{Common::WebResult::Code::HttpError,
95 std::to_string(response.status)};
96 }
97
98 auto content_type = response.headers.find("content-type");
99
100 if (content_type == response.headers.end()) {
101 LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
102 return Common::WebResult{Common::WebResult::Code::WrongContent, ""};
103 }
104
105 if (content_type->second.find("application/json") == std::string::npos &&
106 content_type->second.find("text/html; charset=utf-8") == std::string::npos) {
107 LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
108 content_type->second);
109 return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
110 }
111 return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
112}
113
114void Client::UpdateJWT() {
115 if (!username.empty() && !token.empty()) {
116 auto result = GenericJson("POST", "/jwt/internal", "", "", username, token);
117 if (result.result_code != Common::WebResult::Code::Success) {
118 LOG_ERROR(WebService, "UpdateJWT failed");
119 } else {
120 std::lock_guard<std::mutex> lock(jwt_cache.mutex);
121 jwt_cache.username = username;
122 jwt_cache.token = token;
123 jwt_cache.jwt = jwt = result.returned_data;
124 }
125 }
126}
127
128Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
129 const std::string& data, bool allow_anonymous) {
130 if (jwt.empty()) {
131 UpdateJWT();
132 }
133
134 if (jwt.empty() && !allow_anonymous) {
135 LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
136 return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"};
137 }
138
139 auto result = GenericJson(method, path, data, jwt);
140 if (result.result_string == "401") {
141 // Try again with new JWT
142 UpdateJWT();
143 result = GenericJson(method, path, data, jwt);
144 }
145
146 return result;
147}
148
149} // namespace WebService
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
new file mode 100644
index 000000000..d75fbcc15
--- /dev/null
+++ b/src/web_service/web_backend.h
@@ -0,0 +1,92 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <mutex>
9#include <string>
10#include <tuple>
11#include <httplib.h>
12#include "common/common_types.h"
13#include "common/web_result.h"
14
15namespace httplib {
16class Client;
17}
18
19namespace WebService {
20
21class Client {
22public:
23 Client(const std::string& host, const std::string& username, const std::string& token);
24
25 /**
26 * Posts JSON to the specified path.
27 * @param path the URL segment after the host address.
28 * @param data String of JSON data to use for the body of the POST request.
29 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
30 * @return the result of the request.
31 */
32 Common::WebResult PostJson(const std::string& path, const std::string& data,
33 bool allow_anonymous) {
34 return GenericJson("POST", path, data, allow_anonymous);
35 }
36
37 /**
38 * Gets JSON from the specified path.
39 * @param path the URL segment after the host address.
40 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
41 * @return the result of the request.
42 */
43 Common::WebResult GetJson(const std::string& path, bool allow_anonymous) {
44 return GenericJson("GET", path, "", allow_anonymous);
45 }
46
47 /**
48 * Deletes JSON to the specified path.
49 * @param path the URL segment after the host address.
50 * @param data String of JSON data to use for the body of the DELETE request.
51 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
52 * @return the result of the request.
53 */
54 Common::WebResult DeleteJson(const std::string& path, const std::string& data,
55 bool allow_anonymous) {
56 return GenericJson("DELETE", path, data, allow_anonymous);
57 }
58
59private:
60 /// A generic function handles POST, GET and DELETE request together
61 Common::WebResult GenericJson(const std::string& method, const std::string& path,
62 const std::string& data, bool allow_anonymous);
63
64 /**
65 * A generic function with explicit authentication method specified
66 * JWT is used if the jwt parameter is not empty
67 * username + token is used if jwt is empty but username and token are not empty
68 * anonymous if all of jwt, username and token are empty
69 */
70 Common::WebResult GenericJson(const std::string& method, const std::string& path,
71 const std::string& data, const std::string& jwt = "",
72 const std::string& username = "", const std::string& token = "");
73
74 // Retrieve a new JWT from given username and token
75 void UpdateJWT();
76
77 std::string host;
78 std::string username;
79 std::string token;
80 std::string jwt;
81 std::unique_ptr<httplib::Client> cli;
82
83 struct JWTCache {
84 std::mutex mutex;
85 std::string username;
86 std::string token;
87 std::string jwt;
88 };
89 static JWTCache jwt_cache;
90};
91
92} // namespace WebService
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index f48b69809..04464ad5e 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -29,6 +29,8 @@ add_executable(yuzu
29 configuration/configure_input.h 29 configuration/configure_input.h
30 configuration/configure_system.cpp 30 configuration/configure_system.cpp
31 configuration/configure_system.h 31 configuration/configure_system.h
32 configuration/configure_web.cpp
33 configuration/configure_web.h
32 debugger/graphics/graphics_breakpoint_observer.cpp 34 debugger/graphics/graphics_breakpoint_observer.cpp
33 debugger/graphics/graphics_breakpoint_observer.h 35 debugger/graphics/graphics_breakpoint_observer.h
34 debugger/graphics/graphics_breakpoints.cpp 36 debugger/graphics/graphics_breakpoints.cpp
@@ -42,6 +44,7 @@ add_executable(yuzu
42 debugger/profiler.h 44 debugger/profiler.h
43 debugger/wait_tree.cpp 45 debugger/wait_tree.cpp
44 debugger/wait_tree.h 46 debugger/wait_tree.h
47 discord.h
45 game_list.cpp 48 game_list.cpp
46 game_list.h 49 game_list.h
47 game_list_p.h 50 game_list_p.h
@@ -57,6 +60,8 @@ add_executable(yuzu
57 util/spinbox.h 60 util/spinbox.h
58 util/util.cpp 61 util/util.cpp
59 util/util.h 62 util/util.h
63 compatdb.cpp
64 compatdb.h
60 yuzu.rc 65 yuzu.rc
61) 66)
62 67
@@ -70,8 +75,10 @@ set(UIS
70 configuration/configure_graphics.ui 75 configuration/configure_graphics.ui
71 configuration/configure_input.ui 76 configuration/configure_input.ui
72 configuration/configure_system.ui 77 configuration/configure_system.ui
78 configuration/configure_web.ui
73 hotkeys.ui 79 hotkeys.ui
74 main.ui 80 main.ui
81 compatdb.ui
75) 82)
76 83
77file(GLOB COMPAT_LIST 84file(GLOB COMPAT_LIST
@@ -113,6 +120,19 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core)
113target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets) 120target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)
114target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) 121target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
115 122
123if (YUZU_ENABLE_COMPATIBILITY_REPORTING)
124 add_definitions(-DYUZU_ENABLE_COMPATIBILITY_REPORTING)
125endif()
126
127if (USE_DISCORD_PRESENCE)
128 target_sources(yuzu PUBLIC
129 discord_impl.cpp
130 discord_impl.h
131 )
132 target_link_libraries(yuzu PRIVATE discord-rpc)
133 target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
134endif()
135
116if(UNIX AND NOT APPLE) 136if(UNIX AND NOT APPLE)
117 install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") 137 install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
118endif() 138endif()
diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp
new file mode 100644
index 000000000..91e754274
--- /dev/null
+++ b/src/yuzu/compatdb.cpp
@@ -0,0 +1,65 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QButtonGroup>
6#include <QMessageBox>
7#include <QPushButton>
8#include "common/logging/log.h"
9#include "common/telemetry.h"
10#include "core/core.h"
11#include "core/telemetry_session.h"
12#include "ui_compatdb.h"
13#include "yuzu/compatdb.h"
14
15CompatDB::CompatDB(QWidget* parent)
16 : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
17 ui{std::make_unique<Ui::CompatDB>()} {
18 ui->setupUi(this);
19 connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext);
20 connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext);
21 connect(ui->radioButton_Okay, &QRadioButton::clicked, this, &CompatDB::EnableNext);
22 connect(ui->radioButton_Bad, &QRadioButton::clicked, this, &CompatDB::EnableNext);
23 connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext);
24 connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext);
25 connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit);
26}
27
28CompatDB::~CompatDB() = default;
29
30enum class CompatDBPage {
31 Intro = 0,
32 Selection = 1,
33 Final = 2,
34};
35
36void CompatDB::Submit() {
37 QButtonGroup* compatibility = new QButtonGroup(this);
38 compatibility->addButton(ui->radioButton_Perfect, 0);
39 compatibility->addButton(ui->radioButton_Great, 1);
40 compatibility->addButton(ui->radioButton_Okay, 2);
41 compatibility->addButton(ui->radioButton_Bad, 3);
42 compatibility->addButton(ui->radioButton_IntroMenu, 4);
43 compatibility->addButton(ui->radioButton_WontBoot, 5);
44 switch ((static_cast<CompatDBPage>(currentId()))) {
45 case CompatDBPage::Selection:
46 if (compatibility->checkedId() == -1) {
47 button(NextButton)->setEnabled(false);
48 }
49 break;
50 case CompatDBPage::Final:
51 LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId());
52 Core::Telemetry().AddField(Telemetry::FieldType::UserFeedback, "Compatibility",
53 compatibility->checkedId());
54 // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a
55 // workaround
56 button(QWizard::CancelButton)->setVisible(false);
57 break;
58 default:
59 LOG_ERROR(Frontend, "Unexpected page: {}", currentId());
60 }
61}
62
63void CompatDB::EnableNext() {
64 button(NextButton)->setEnabled(true);
65}
diff --git a/src/yuzu/compatdb.h b/src/yuzu/compatdb.h
new file mode 100644
index 000000000..ca0dd11d6
--- /dev/null
+++ b/src/yuzu/compatdb.h
@@ -0,0 +1,26 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QWizard>
9
10namespace Ui {
11class CompatDB;
12}
13
14class CompatDB : public QWizard {
15 Q_OBJECT
16
17public:
18 explicit CompatDB(QWidget* parent = nullptr);
19 ~CompatDB();
20
21private:
22 std::unique_ptr<Ui::CompatDB> ui;
23
24 void Submit();
25 void EnableNext();
26};
diff --git a/src/yuzu/compatdb.ui b/src/yuzu/compatdb.ui
new file mode 100644
index 000000000..fed402176
--- /dev/null
+++ b/src/yuzu/compatdb.ui
@@ -0,0 +1,215 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>CompatDB</class>
4 <widget class="QWizard" name="CompatDB">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>600</width>
10 <height>482</height>
11 </rect>
12 </property>
13 <property name="minimumSize">
14 <size>
15 <width>500</width>
16 <height>410</height>
17 </size>
18 </property>
19 <property name="windowTitle">
20 <string>Report Compatibility</string>
21 </property>
22 <property name="options">
23 <set>QWizard::DisabledBackButtonOnLastPage|QWizard::HelpButtonOnRight|QWizard::NoBackButtonOnStartPage</set>
24 </property>
25 <widget class="QWizardPage" name="wizard_Info">
26 <property name="title">
27 <string>Report Game Compatibility</string>
28 </property>
29 <attribute name="pageId">
30 <string notr="true">0</string>
31 </attribute>
32 <layout class="QVBoxLayout" name="verticalLayout">
33 <item>
34 <widget class="QLabel" name="lbl_Spiel">
35 <property name="text">
36 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
37 </property>
38 <property name="wordWrap">
39 <bool>true</bool>
40 </property>
41 <property name="openExternalLinks">
42 <bool>true</bool>
43 </property>
44 </widget>
45 </item>
46 <item>
47 <spacer name="verticalSpacer_2">
48 <property name="orientation">
49 <enum>Qt::Vertical</enum>
50 </property>
51 <property name="sizeHint" stdset="0">
52 <size>
53 <width>20</width>
54 <height>0</height>
55 </size>
56 </property>
57 </spacer>
58 </item>
59 </layout>
60 </widget>
61 <widget class="QWizardPage" name="wizard_Report">
62 <property name="title">
63 <string>Report Game Compatibility</string>
64 </property>
65 <attribute name="pageId">
66 <string notr="true">1</string>
67 </attribute>
68 <layout class="QFormLayout" name="formLayout">
69 <item row="2" column="0">
70 <widget class="QRadioButton" name="radioButton_Perfect">
71 <property name="text">
72 <string>Perfect</string>
73 </property>
74 </widget>
75 </item>
76 <item row="2" column="1">
77 <widget class="QLabel" name="lbl_Perfect">
78 <property name="text">
79 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
80 </property>
81 <property name="wordWrap">
82 <bool>true</bool>
83 </property>
84 </widget>
85 </item>
86 <item row="4" column="0">
87 <widget class="QRadioButton" name="radioButton_Great">
88 <property name="text">
89 <string>Great </string>
90 </property>
91 </widget>
92 </item>
93 <item row="4" column="1">
94 <widget class="QLabel" name="lbl_Great">
95 <property name="text">
96 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
97 </property>
98 <property name="wordWrap">
99 <bool>true</bool>
100 </property>
101 </widget>
102 </item>
103 <item row="5" column="0">
104 <widget class="QRadioButton" name="radioButton_Okay">
105 <property name="text">
106 <string>Okay</string>
107 </property>
108 </widget>
109 </item>
110 <item row="5" column="1">
111 <widget class="QLabel" name="lbl_Okay">
112 <property name="text">
113 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
114 </property>
115 <property name="wordWrap">
116 <bool>true</bool>
117 </property>
118 </widget>
119 </item>
120 <item row="6" column="0">
121 <widget class="QRadioButton" name="radioButton_Bad">
122 <property name="text">
123 <string>Bad</string>
124 </property>
125 </widget>
126 </item>
127 <item row="6" column="1">
128 <widget class="QLabel" name="lbl_Bad">
129 <property name="text">
130 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
131 </property>
132 <property name="wordWrap">
133 <bool>true</bool>
134 </property>
135 </widget>
136 </item>
137 <item row="7" column="0">
138 <widget class="QRadioButton" name="radioButton_IntroMenu">
139 <property name="text">
140 <string>Intro/Menu</string>
141 </property>
142 </widget>
143 </item>
144 <item row="7" column="1">
145 <widget class="QLabel" name="lbl_IntroMenu">
146 <property name="text">
147 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
148 </property>
149 <property name="wordWrap">
150 <bool>true</bool>
151 </property>
152 </widget>
153 </item>
154 <item row="8" column="0">
155 <widget class="QRadioButton" name="radioButton_WontBoot">
156 <property name="text">
157 <string>Won't Boot</string>
158 </property>
159 <property name="checkable">
160 <bool>true</bool>
161 </property>
162 <property name="checked">
163 <bool>false</bool>
164 </property>
165 </widget>
166 </item>
167 <item row="8" column="1">
168 <widget class="QLabel" name="lbl_WontBoot">
169 <property name="text">
170 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
171 </property>
172 </widget>
173 </item>
174 <item row="0" column="0" colspan="2">
175 <widget class="QLabel" name="lbl_Independent">
176 <property name="font">
177 <font>
178 <pointsize>10</pointsize>
179 </font>
180 </property>
181 <property name="text">
182 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
183 </property>
184 <property name="wordWrap">
185 <bool>true</bool>
186 </property>
187 </widget>
188 </item>
189 <item row="1" column="0" colspan="2">
190 <spacer name="verticalSpacer">
191 <property name="orientation">
192 <enum>Qt::Vertical</enum>
193 </property>
194 <property name="sizeHint" stdset="0">
195 <size>
196 <width>20</width>
197 <height>0</height>
198 </size>
199 </property>
200 </spacer>
201 </item>
202 </layout>
203 </widget>
204 <widget class="QWizardPage" name="wizard_ThankYou">
205 <property name="title">
206 <string>Thank you for your submission!</string>
207 </property>
208 <attribute name="pageId">
209 <string notr="true">2</string>
210 </attribute>
211 </widget>
212 </widget>
213 <resources/>
214 <connections/>
215</ui>
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index d229225b4..7fec15991 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -134,10 +134,21 @@ void Config::ReadValues() {
134 qt_config->beginGroup("Debugging"); 134 qt_config->beginGroup("Debugging");
135 Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool(); 135 Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool();
136 Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt(); 136 Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();
137 Settings::values.program_args = qt_config->value("program_args", "").toString().toStdString();
138 qt_config->endGroup();
139
140 qt_config->beginGroup("WebService");
141 Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool();
142 Settings::values.web_api_url =
143 qt_config->value("web_api_url", "https://api.yuzu-emu.org").toString().toStdString();
144 Settings::values.yuzu_username = qt_config->value("yuzu_username").toString().toStdString();
145 Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();
137 qt_config->endGroup(); 146 qt_config->endGroup();
138 147
139 qt_config->beginGroup("UI"); 148 qt_config->beginGroup("UI");
140 UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); 149 UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
150 UISettings::values.enable_discord_presence =
151 qt_config->value("enable_discord_presence", true).toBool();
141 152
142 qt_config->beginGroup("UIGameList"); 153 qt_config->beginGroup("UIGameList");
143 UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); 154 UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
@@ -259,10 +270,19 @@ void Config::SaveValues() {
259 qt_config->beginGroup("Debugging"); 270 qt_config->beginGroup("Debugging");
260 qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub); 271 qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub);
261 qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port); 272 qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);
273 qt_config->setValue("program_args", QString::fromStdString(Settings::values.program_args));
274 qt_config->endGroup();
275
276 qt_config->beginGroup("WebService");
277 qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
278 qt_config->setValue("web_api_url", QString::fromStdString(Settings::values.web_api_url));
279 qt_config->setValue("yuzu_username", QString::fromStdString(Settings::values.yuzu_username));
280 qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
262 qt_config->endGroup(); 281 qt_config->endGroup();
263 282
264 qt_config->beginGroup("UI"); 283 qt_config->beginGroup("UI");
265 qt_config->setValue("theme", UISettings::values.theme); 284 qt_config->setValue("theme", UISettings::values.theme);
285 qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
266 286
267 qt_config->beginGroup("UIGameList"); 287 qt_config->beginGroup("UIGameList");
268 qt_config->setValue("show_unknown", UISettings::values.show_unknown); 288 qt_config->setValue("show_unknown", UISettings::values.show_unknown);
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 20f120134..9b297df28 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -54,6 +54,11 @@
54 <string>Debug</string> 54 <string>Debug</string>
55 </attribute> 55 </attribute>
56 </widget> 56 </widget>
57 <widget class="ConfigureWeb" name="webTab">
58 <attribute name="title">
59 <string>Web</string>
60 </attribute>
61 </widget>
57 </widget> 62 </widget>
58 </item> 63 </item>
59 <item> 64 <item>
@@ -108,6 +113,12 @@
108 <header>configuration/configure_graphics.h</header> 113 <header>configuration/configure_graphics.h</header>
109 <container>1</container> 114 <container>1</container>
110 </customwidget> 115 </customwidget>
116 <customwidget>
117 <class>ConfigureWeb</class>
118 <extends>QWidget</extends>
119 <header>configuration/configure_web.h</header>
120 <container>1</container>
121 </customwidget>
111 </customwidgets> 122 </customwidgets>
112 <resources/> 123 <resources/>
113 <connections> 124 <connections>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 45d84f19a..9e765fc93 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -33,6 +33,7 @@ void ConfigureDebug::setConfiguration() {
33 ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn()); 33 ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn());
34 ui->toggle_console->setChecked(UISettings::values.show_console); 34 ui->toggle_console->setChecked(UISettings::values.show_console);
35 ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter)); 35 ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
36 ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args));
36} 37}
37 38
38void ConfigureDebug::applyConfiguration() { 39void ConfigureDebug::applyConfiguration() {
@@ -40,6 +41,7 @@ void ConfigureDebug::applyConfiguration() {
40 Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); 41 Settings::values.gdbstub_port = ui->gdbport_spinbox->value();
41 UISettings::values.show_console = ui->toggle_console->isChecked(); 42 UISettings::values.show_console = ui->toggle_console->isChecked();
42 Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); 43 Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
44 Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
43 Debugger::ToggleConsole(); 45 Debugger::ToggleConsole();
44 Log::Filter filter; 46 Log::Filter filter;
45 filter.ParseFilterString(Settings::values.log_filter); 47 filter.ParseFilterString(Settings::values.log_filter);
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 5ae7276bd..ff4987604 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -107,6 +107,29 @@
107 </widget> 107 </widget>
108 </item> 108 </item>
109 <item> 109 <item>
110 <widget class="QGroupBox" name="groupBox_3">
111 <property name="title">
112 <string>Homebrew</string>
113 </property>
114 <layout class="QVBoxLayout" name="verticalLayout">
115 <item>
116 <layout class="QHBoxLayout" name="horizontalLayout">
117 <item>
118 <widget class="QLabel" name="label">
119 <property name="text">
120 <string>Arguments String</string>
121 </property>
122 </widget>
123 </item>
124 <item>
125 <widget class="QLineEdit" name="homebrew_args_edit"/>
126 </item>
127 </layout>
128 </item>
129 </layout>
130 </widget>
131 </item>
132 <item>
110 <spacer name="verticalSpacer"> 133 <spacer name="verticalSpacer">
111 <property name="orientation"> 134 <property name="orientation">
112 <enum>Qt::Vertical</enum> 135 <enum>Qt::Vertical</enum>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index daa4cc0d9..3905423e9 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -27,5 +27,6 @@ void ConfigureDialog::applyConfiguration() {
27 ui->graphicsTab->applyConfiguration(); 27 ui->graphicsTab->applyConfiguration();
28 ui->audioTab->applyConfiguration(); 28 ui->audioTab->applyConfiguration();
29 ui->debugTab->applyConfiguration(); 29 ui->debugTab->applyConfiguration();
30 ui->webTab->applyConfiguration();
30 Settings::Apply(); 31 Settings::Apply();
31} 32}
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 9292d9a42..f5db9e55b 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -13,7 +13,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
13 13
14 ui->setupUi(this); 14 ui->setupUi(this);
15 15
16 for (auto theme : UISettings::themes) { 16 for (const auto& theme : UISettings::themes) {
17 ui->theme_combobox->addItem(theme.first, theme.second); 17 ui->theme_combobox->addItem(theme.first, theme.second);
18 } 18 }
19 19
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 473937ea9..94789c064 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -5,6 +5,7 @@
5#include <algorithm> 5#include <algorithm>
6#include <memory> 6#include <memory>
7#include <utility> 7#include <utility>
8#include <QMenu>
8#include <QMessageBox> 9#include <QMessageBox>
9#include <QTimer> 10#include <QTimer>
10#include "common/param_package.h" 11#include "common/param_package.h"
@@ -128,28 +129,63 @@ ConfigureInput::ConfigureInput(QWidget* parent)
128 analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog}; 129 analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
129 130
130 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { 131 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
131 if (button_map[button_id]) 132 if (!button_map[button_id])
132 connect(button_map[button_id], &QPushButton::released, [=]() { 133 continue;
133 handleClick( 134 button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
134 button_map[button_id], 135 connect(button_map[button_id], &QPushButton::released, [=]() {
135 [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, 136 handleClick(
136 InputCommon::Polling::DeviceType::Button); 137 button_map[button_id],
137 }); 138 [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
139 InputCommon::Polling::DeviceType::Button);
140 });
141 connect(button_map[button_id], &QPushButton::customContextMenuRequested,
142 [=](const QPoint& menu_location) {
143 QMenu context_menu;
144 context_menu.addAction(tr("Clear"), [&] {
145 buttons_param[button_id].Clear();
146 button_map[button_id]->setText(tr("[not set]"));
147 });
148 context_menu.addAction(tr("Restore Default"), [&] {
149 buttons_param[button_id] = Common::ParamPackage{
150 InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
151 button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
152 });
153 context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
154 });
138 } 155 }
139 156
140 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { 157 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
141 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { 158 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
142 if (analog_map_buttons[analog_id][sub_button_id] != nullptr) { 159 if (!analog_map_buttons[analog_id][sub_button_id])
143 connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, 160 continue;
144 [=]() { 161 analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
145 handleClick(analog_map_buttons[analog_id][sub_button_id], 162 Qt::CustomContextMenu);
146 [=](const Common::ParamPackage& params) { 163 connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() {
147 SetAnalogButton(params, analogs_param[analog_id], 164 handleClick(analog_map_buttons[analog_id][sub_button_id],
148 analog_sub_buttons[sub_button_id]); 165 [=](const Common::ParamPackage& params) {
149 }, 166 SetAnalogButton(params, analogs_param[analog_id],
150 InputCommon::Polling::DeviceType::Button); 167 analog_sub_buttons[sub_button_id]);
168 },
169 InputCommon::Polling::DeviceType::Button);
170 });
171 connect(analog_map_buttons[analog_id][sub_button_id],
172 &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
173 QMenu context_menu;
174 context_menu.addAction(tr("Clear"), [&] {
175 analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
176 analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
151 }); 177 });
152 } 178 context_menu.addAction(tr("Restore Default"), [&] {
179 Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
180 Config::default_analogs[analog_id][sub_button_id])};
181 SetAnalogButton(params, analogs_param[analog_id],
182 analog_sub_buttons[sub_button_id]);
183 analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
184 analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
185 });
186 context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
187 menu_location));
188 });
153 } 189 }
154 connect(analog_map_stick[analog_id], &QPushButton::released, [=]() { 190 connect(analog_map_stick[analog_id], &QPushButton::released, [=]() {
155 QMessageBox::information(this, tr("Information"), 191 QMessageBox::information(this, tr("Information"),
@@ -162,6 +198,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
162 }); 198 });
163 } 199 }
164 200
201 connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
165 connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); 202 connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
166 203
167 timeout_timer->setSingleShot(true); 204 timeout_timer->setSingleShot(true);
@@ -215,7 +252,21 @@ void ConfigureInput::restoreDefaults() {
215 } 252 }
216 } 253 }
217 updateButtonLabels(); 254 updateButtonLabels();
218 applyConfiguration(); 255}
256
257void ConfigureInput::ClearAll() {
258 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
259 if (button_map[button_id] && button_map[button_id]->isEnabled())
260 buttons_param[button_id].Clear();
261 }
262 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
263 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
264 if (analog_map_buttons[analog_id][sub_button_id] &&
265 analog_map_buttons[analog_id][sub_button_id]->isEnabled())
266 analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
267 }
268 }
269 updateButtonLabels();
219} 270}
220 271
221void ConfigureInput::updateButtonLabels() { 272void ConfigureInput::updateButtonLabels() {
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index a0bef86d5..d1198db81 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -72,6 +72,9 @@ private:
72 void loadConfiguration(); 72 void loadConfiguration();
73 /// Restore all buttons to their default values. 73 /// Restore all buttons to their default values.
74 void restoreDefaults(); 74 void restoreDefaults();
75 /// Clear all input configuration
76 void ClearAll();
77
75 /// Update UI to reflect current configuration. 78 /// Update UI to reflect current configuration.
76 void updateButtonLabels(); 79 void updateButtonLabels();
77 80
diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui
index 8bfa5df62..8a019a693 100644
--- a/src/yuzu/configuration/configure_input.ui
+++ b/src/yuzu/configuration/configure_input.ui
@@ -695,6 +695,34 @@ Capture:</string>
695 </spacer> 695 </spacer>
696 </item> 696 </item>
697 <item> 697 <item>
698 <widget class="QPushButton" name="buttonClearAll">
699 <property name="sizePolicy">
700 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
701 <horstretch>0</horstretch>
702 <verstretch>0</verstretch>
703 </sizepolicy>
704 </property>
705 <property name="sizeIncrement">
706 <size>
707 <width>0</width>
708 <height>0</height>
709 </size>
710 </property>
711 <property name="baseSize">
712 <size>
713 <width>0</width>
714 <height>0</height>
715 </size>
716 </property>
717 <property name="layoutDirection">
718 <enum>Qt::LeftToRight</enum>
719 </property>
720 <property name="text">
721 <string>Clear All</string>
722 </property>
723 </widget>
724 </item>
725 <item>
698 <widget class="QPushButton" name="buttonRestoreDefaults"> 726 <widget class="QPushButton" name="buttonRestoreDefaults">
699 <property name="sizePolicy"> 727 <property name="sizePolicy">
700 <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> 728 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp
new file mode 100644
index 000000000..3c2ccb76f
--- /dev/null
+++ b/src/yuzu/configuration/configure_web.cpp
@@ -0,0 +1,119 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QIcon>
6#include <QMessageBox>
7#include <QtConcurrent/QtConcurrentRun>
8#include "core/settings.h"
9#include "core/telemetry_session.h"
10#include "ui_configure_web.h"
11#include "yuzu/configuration/configure_web.h"
12#include "yuzu/ui_settings.h"
13
14ConfigureWeb::ConfigureWeb(QWidget* parent)
15 : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
16 ui->setupUi(this);
17 connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
18 &ConfigureWeb::RefreshTelemetryID);
19 connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin);
20 connect(&verify_watcher, &QFutureWatcher<bool>::finished, this, &ConfigureWeb::OnLoginVerified);
21
22#ifndef USE_DISCORD_PRESENCE
23 ui->discord_group->setVisible(false);
24#endif
25 this->setConfiguration();
26}
27
28ConfigureWeb::~ConfigureWeb() = default;
29
30void ConfigureWeb::setConfiguration() {
31 ui->web_credentials_disclaimer->setWordWrap(true);
32 ui->telemetry_learn_more->setOpenExternalLinks(true);
33 ui->telemetry_learn_more->setText(
34 tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'><span style=\"text-decoration: "
35 "underline; color:#039be5;\">Learn more</span></a>"));
36
37 ui->web_signup_link->setOpenExternalLinks(true);
38 ui->web_signup_link->setText(
39 tr("<a href='https://profile.yuzu-emu.org/'><span style=\"text-decoration: underline; "
40 "color:#039be5;\">Sign up</span></a>"));
41 ui->web_token_info_link->setOpenExternalLinks(true);
42 ui->web_token_info_link->setText(
43 tr("<a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style=\"text-decoration: "
44 "underline; color:#039be5;\">What is my token?</span></a>"));
45
46 ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
47 ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username));
48 ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token));
49 // Connect after setting the values, to avoid calling OnLoginChanged now
50 connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
51 connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
52 ui->label_telemetry_id->setText(
53 tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
54 user_verified = true;
55
56 ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence);
57}
58
59void ConfigureWeb::applyConfiguration() {
60 Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
61 UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
62 if (user_verified) {
63 Settings::values.yuzu_username = ui->edit_username->text().toStdString();
64 Settings::values.yuzu_token = ui->edit_token->text().toStdString();
65 } else {
66 QMessageBox::warning(this, tr("Username and token not verified"),
67 tr("Username and token were not verified. The changes to your "
68 "username and/or token have not been saved."));
69 }
70}
71
72void ConfigureWeb::RefreshTelemetryID() {
73 const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
74 ui->label_telemetry_id->setText(
75 tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper()));
76}
77
78void ConfigureWeb::OnLoginChanged() {
79 if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) {
80 user_verified = true;
81 ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
82 ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
83 } else {
84 user_verified = false;
85 ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
86 ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
87 }
88}
89
90void ConfigureWeb::VerifyLogin() {
91 ui->button_verify_login->setDisabled(true);
92 ui->button_verify_login->setText(tr("Verifying"));
93 verify_watcher.setFuture(
94 QtConcurrent::run([this, username = ui->edit_username->text().toStdString(),
95 token = ui->edit_token->text().toStdString()]() {
96 return Core::VerifyLogin(username, token);
97 }));
98}
99
100void ConfigureWeb::OnLoginVerified() {
101 ui->button_verify_login->setEnabled(true);
102 ui->button_verify_login->setText(tr("Verify"));
103 if (verify_watcher.result()) {
104 user_verified = true;
105 ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
106 ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
107 } else {
108 ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
109 ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
110 QMessageBox::critical(
111 this, tr("Verification failed"),
112 tr("Verification failed. Check that you have entered your username and token "
113 "correctly, and that your internet connection is working."));
114 }
115}
116
117void ConfigureWeb::retranslateUi() {
118 ui->retranslateUi(this);
119}
diff --git a/src/yuzu/configuration/configure_web.h b/src/yuzu/configuration/configure_web.h
new file mode 100644
index 000000000..7741ab95d
--- /dev/null
+++ b/src/yuzu/configuration/configure_web.h
@@ -0,0 +1,38 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QFutureWatcher>
9#include <QWidget>
10
11namespace Ui {
12class ConfigureWeb;
13}
14
15class ConfigureWeb : public QWidget {
16 Q_OBJECT
17
18public:
19 explicit ConfigureWeb(QWidget* parent = nullptr);
20 ~ConfigureWeb();
21
22 void applyConfiguration();
23 void retranslateUi();
24
25public slots:
26 void RefreshTelemetryID();
27 void OnLoginChanged();
28 void VerifyLogin();
29 void OnLoginVerified();
30
31private:
32 void setConfiguration();
33
34 bool user_verified = true;
35 QFutureWatcher<bool> verify_watcher;
36
37 std::unique_ptr<Ui::ConfigureWeb> ui;
38};
diff --git a/src/yuzu/configuration/configure_web.ui b/src/yuzu/configuration/configure_web.ui
new file mode 100644
index 000000000..2f4b9dd73
--- /dev/null
+++ b/src/yuzu/configuration/configure_web.ui
@@ -0,0 +1,206 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureWeb</class>
4 <widget class="QWidget" name="ConfigureWeb">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>926</width>
10 <height>561</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Form</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout_3">
19 <item>
20 <widget class="QGroupBox" name="groupBoxWebConfig">
21 <property name="title">
22 <string>yuzu Web Service</string>
23 </property>
24 <layout class="QVBoxLayout" name="verticalLayoutYuzuWebService">
25 <item>
26 <widget class="QLabel" name="web_credentials_disclaimer">
27 <property name="text">
28 <string>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</string>
29 </property>
30 </widget>
31 </item>
32 <item>
33 <layout class="QGridLayout" name="gridLayoutYuzuUsername">
34 <item row="2" column="3">
35 <widget class="QPushButton" name="button_verify_login">
36 <property name="sizePolicy">
37 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
38 <horstretch>0</horstretch>
39 <verstretch>0</verstretch>
40 </sizepolicy>
41 </property>
42 <property name="layoutDirection">
43 <enum>Qt::RightToLeft</enum>
44 </property>
45 <property name="text">
46 <string>Verify</string>
47 </property>
48 </widget>
49 </item>
50 <item row="2" column="0">
51 <widget class="QLabel" name="web_signup_link">
52 <property name="text">
53 <string>Sign up</string>
54 </property>
55 </widget>
56 </item>
57 <item row="0" column="1" colspan="3">
58 <widget class="QLineEdit" name="edit_username">
59 <property name="maxLength">
60 <number>36</number>
61 </property>
62 </widget>
63 </item>
64 <item row="1" column="0">
65 <widget class="QLabel" name="label_token">
66 <property name="text">
67 <string>Token: </string>
68 </property>
69 </widget>
70 </item>
71 <item row="1" column="4">
72 <widget class="QLabel" name="label_token_verified">
73 </widget>
74 </item>
75 <item row="0" column="0">
76 <widget class="QLabel" name="label_username">
77 <property name="text">
78 <string>Username: </string>
79 </property>
80 </widget>
81 </item>
82 <item row="0" column="4">
83 <widget class="QLabel" name="label_username_verified">
84 </widget>
85 </item>
86 <item row="1" column="1" colspan="3">
87 <widget class="QLineEdit" name="edit_token">
88 <property name="maxLength">
89 <number>36</number>
90 </property>
91 <property name="echoMode">
92 <enum>QLineEdit::Password</enum>
93 </property>
94 </widget>
95 </item>
96 <item row="2" column="1">
97 <widget class="QLabel" name="web_token_info_link">
98 <property name="text">
99 <string>What is my token?</string>
100 </property>
101 </widget>
102 </item>
103 <item row="2" column="2">
104 <spacer name="horizontalSpacer">
105 <property name="orientation">
106 <enum>Qt::Horizontal</enum>
107 </property>
108 <property name="sizeHint" stdset="0">
109 <size>
110 <width>40</width>
111 <height>20</height>
112 </size>
113 </property>
114 </spacer>
115 </item>
116 </layout>
117 </item>
118 </layout>
119 </widget>
120 </item>
121 <item>
122 <widget class="QGroupBox" name="groupBox">
123 <property name="title">
124 <string>Telemetry</string>
125 </property>
126 <layout class="QVBoxLayout" name="verticalLayout_2">
127 <item>
128 <widget class="QCheckBox" name="toggle_telemetry">
129 <property name="text">
130 <string>Share anonymous usage data with the yuzu team</string>
131 </property>
132 </widget>
133 </item>
134 <item>
135 <widget class="QLabel" name="telemetry_learn_more">
136 <property name="text">
137 <string>Learn more</string>
138 </property>
139 </widget>
140 </item>
141 <item>
142 <layout class="QGridLayout" name="gridLayoutTelemetryId">
143 <item row="0" column="0">
144 <widget class="QLabel" name="label_telemetry_id">
145 <property name="text">
146 <string>Telemetry ID:</string>
147 </property>
148 </widget>
149 </item>
150 <item row="0" column="1">
151 <widget class="QPushButton" name="button_regenerate_telemetry_id">
152 <property name="sizePolicy">
153 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
154 <horstretch>0</horstretch>
155 <verstretch>0</verstretch>
156 </sizepolicy>
157 </property>
158 <property name="layoutDirection">
159 <enum>Qt::RightToLeft</enum>
160 </property>
161 <property name="text">
162 <string>Regenerate</string>
163 </property>
164 </widget>
165 </item>
166 </layout>
167 </item>
168 </layout>
169 </widget>
170 </item>
171 </layout>
172 </item>
173 <item>
174 <widget class="QGroupBox" name="discord_group">
175 <property name="title">
176 <string>Discord Presence</string>
177 </property>
178 <layout class="QVBoxLayout" name="verticalLayout_21">
179 <item>
180 <widget class="QCheckBox" name="toggle_discordrpc">
181 <property name="text">
182 <string>Show Current Game in your Discord Status</string>
183 </property>
184 </widget>
185 </item>
186 </layout>
187 </widget>
188 </item>
189 <item>
190 <spacer name="verticalSpacer">
191 <property name="orientation">
192 <enum>Qt::Vertical</enum>
193 </property>
194 <property name="sizeHint" stdset="0">
195 <size>
196 <width>20</width>
197 <height>40</height>
198 </size>
199 </property>
200 </spacer>
201 </item>
202 </layout>
203 </widget>
204 <resources/>
205 <connections/>
206</ui>
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index a3b1fd357..4a09da685 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -119,7 +119,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons
119 std::vector<std::unique_ptr<WaitTreeItem>> list; 119 std::vector<std::unique_ptr<WaitTreeItem>> list;
120 120
121 constexpr std::size_t BaseRegister = 29; 121 constexpr std::size_t BaseRegister = 29;
122 u64 base_pointer = thread.context.cpu_registers[BaseRegister]; 122 u64 base_pointer = thread.GetContext().cpu_registers[BaseRegister];
123 123
124 while (base_pointer != 0) { 124 while (base_pointer != 0) {
125 u64 lr = Memory::Read64(base_pointer + sizeof(u64)); 125 u64 lr = Memory::Read64(base_pointer + sizeof(u64));
@@ -213,7 +213,7 @@ WaitTreeThread::~WaitTreeThread() = default;
213QString WaitTreeThread::GetText() const { 213QString WaitTreeThread::GetText() const {
214 const auto& thread = static_cast<const Kernel::Thread&>(object); 214 const auto& thread = static_cast<const Kernel::Thread&>(object);
215 QString status; 215 QString status;
216 switch (thread.status) { 216 switch (thread.GetStatus()) {
217 case Kernel::ThreadStatus::Running: 217 case Kernel::ThreadStatus::Running:
218 status = tr("running"); 218 status = tr("running");
219 break; 219 break;
@@ -246,15 +246,17 @@ QString WaitTreeThread::GetText() const {
246 status = tr("dead"); 246 status = tr("dead");
247 break; 247 break;
248 } 248 }
249 QString pc_info = tr(" PC = 0x%1 LR = 0x%2") 249
250 .arg(thread.context.pc, 8, 16, QLatin1Char('0')) 250 const auto& context = thread.GetContext();
251 .arg(thread.context.cpu_registers[30], 8, 16, QLatin1Char('0')); 251 const QString pc_info = tr(" PC = 0x%1 LR = 0x%2")
252 .arg(context.pc, 8, 16, QLatin1Char('0'))
253 .arg(context.cpu_registers[30], 8, 16, QLatin1Char('0'));
252 return WaitTreeWaitObject::GetText() + pc_info + " (" + status + ") "; 254 return WaitTreeWaitObject::GetText() + pc_info + " (" + status + ") ";
253} 255}
254 256
255QColor WaitTreeThread::GetColor() const { 257QColor WaitTreeThread::GetColor() const {
256 const auto& thread = static_cast<const Kernel::Thread&>(object); 258 const auto& thread = static_cast<const Kernel::Thread&>(object);
257 switch (thread.status) { 259 switch (thread.GetStatus()) {
258 case Kernel::ThreadStatus::Running: 260 case Kernel::ThreadStatus::Running:
259 return QColor(Qt::GlobalColor::darkGreen); 261 return QColor(Qt::GlobalColor::darkGreen);
260 case Kernel::ThreadStatus::Ready: 262 case Kernel::ThreadStatus::Ready:
@@ -284,7 +286,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
284 const auto& thread = static_cast<const Kernel::Thread&>(object); 286 const auto& thread = static_cast<const Kernel::Thread&>(object);
285 287
286 QString processor; 288 QString processor;
287 switch (thread.processor_id) { 289 switch (thread.GetProcessorID()) {
288 case Kernel::ThreadProcessorId::THREADPROCESSORID_DEFAULT: 290 case Kernel::ThreadProcessorId::THREADPROCESSORID_DEFAULT:
289 processor = tr("default"); 291 processor = tr("default");
290 break; 292 break;
@@ -292,32 +294,35 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
292 case Kernel::ThreadProcessorId::THREADPROCESSORID_1: 294 case Kernel::ThreadProcessorId::THREADPROCESSORID_1:
293 case Kernel::ThreadProcessorId::THREADPROCESSORID_2: 295 case Kernel::ThreadProcessorId::THREADPROCESSORID_2:
294 case Kernel::ThreadProcessorId::THREADPROCESSORID_3: 296 case Kernel::ThreadProcessorId::THREADPROCESSORID_3:
295 processor = tr("core %1").arg(thread.processor_id); 297 processor = tr("core %1").arg(thread.GetProcessorID());
296 break; 298 break;
297 default: 299 default:
298 processor = tr("Unknown processor %1").arg(thread.processor_id); 300 processor = tr("Unknown processor %1").arg(thread.GetProcessorID());
299 break; 301 break;
300 } 302 }
301 303
302 list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor))); 304 list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor)));
303 list.push_back(std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.ideal_core)));
304 list.push_back( 305 list.push_back(
305 std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.affinity_mask))); 306 std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.GetIdealCore())));
306 list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId()))); 307 list.push_back(
308 std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.GetAffinityMask())));
309 list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadID())));
307 list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)") 310 list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)")
308 .arg(thread.current_priority) 311 .arg(thread.GetPriority())
309 .arg(thread.nominal_priority))); 312 .arg(thread.GetNominalPriority())));
310 list.push_back(std::make_unique<WaitTreeText>( 313 list.push_back(std::make_unique<WaitTreeText>(
311 tr("last running ticks = %1").arg(thread.last_running_ticks))); 314 tr("last running ticks = %1").arg(thread.GetLastRunningTicks())));
312 315
313 if (thread.mutex_wait_address != 0) 316 const VAddr mutex_wait_address = thread.GetMutexWaitAddress();
314 list.push_back(std::make_unique<WaitTreeMutexInfo>(thread.mutex_wait_address)); 317 if (mutex_wait_address != 0) {
315 else 318 list.push_back(std::make_unique<WaitTreeMutexInfo>(mutex_wait_address));
319 } else {
316 list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex"))); 320 list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex")));
321 }
317 322
318 if (thread.status == Kernel::ThreadStatus::WaitSynchAny || 323 if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynchAny ||
319 thread.status == Kernel::ThreadStatus::WaitSynchAll) { 324 thread.GetStatus() == Kernel::ThreadStatus::WaitSynchAll) {
320 list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects, 325 list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetWaitObjects(),
321 thread.IsSleepingOnWaitAll())); 326 thread.IsSleepingOnWaitAll()));
322 } 327 }
323 328
diff --git a/src/yuzu/discord.h b/src/yuzu/discord.h
new file mode 100644
index 000000000..a867cc4d6
--- /dev/null
+++ b/src/yuzu/discord.h
@@ -0,0 +1,25 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7namespace DiscordRPC {
8
9class DiscordInterface {
10public:
11 virtual ~DiscordInterface() = default;
12
13 virtual void Pause() = 0;
14 virtual void Update() = 0;
15};
16
17class NullImpl : public DiscordInterface {
18public:
19 ~NullImpl() = default;
20
21 void Pause() override {}
22 void Update() override {}
23};
24
25} // namespace DiscordRPC
diff --git a/src/yuzu/discord_impl.cpp b/src/yuzu/discord_impl.cpp
new file mode 100644
index 000000000..9d87a41eb
--- /dev/null
+++ b/src/yuzu/discord_impl.cpp
@@ -0,0 +1,52 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <chrono>
6#include <string>
7#include <discord_rpc.h>
8#include "common/common_types.h"
9#include "core/core.h"
10#include "core/loader/loader.h"
11#include "yuzu/discord_impl.h"
12#include "yuzu/ui_settings.h"
13
14namespace DiscordRPC {
15
16DiscordImpl::DiscordImpl() {
17 DiscordEventHandlers handlers{};
18
19 // The number is the client ID for yuzu, it's used for images and the
20 // application name
21 Discord_Initialize("471872241299226636", &handlers, 1, nullptr);
22}
23
24DiscordImpl::~DiscordImpl() {
25 Discord_ClearPresence();
26 Discord_Shutdown();
27}
28
29void DiscordImpl::Pause() {
30 Discord_ClearPresence();
31}
32
33void DiscordImpl::Update() {
34 s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
35 std::chrono::system_clock::now().time_since_epoch())
36 .count();
37 std::string title;
38 if (Core::System::GetInstance().IsPoweredOn())
39 Core::System::GetInstance().GetAppLoader().ReadTitle(title);
40 DiscordRichPresence presence{};
41 presence.largeImageKey = "yuzu_logo";
42 presence.largeImageText = "yuzu is an emulator for the Nintendo Switch";
43 if (Core::System::GetInstance().IsPoweredOn()) {
44 presence.state = title.c_str();
45 presence.details = "Currently in game";
46 } else {
47 presence.details = "Not in game";
48 }
49 presence.startTimestamp = start_time;
50 Discord_UpdatePresence(&presence);
51}
52} // namespace DiscordRPC
diff --git a/src/yuzu/discord_impl.h b/src/yuzu/discord_impl.h
new file mode 100644
index 000000000..4bfda8cdf
--- /dev/null
+++ b/src/yuzu/discord_impl.h
@@ -0,0 +1,20 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "yuzu/discord.h"
8
9namespace DiscordRPC {
10
11class DiscordImpl : public DiscordInterface {
12public:
13 DiscordImpl();
14 ~DiscordImpl() override;
15
16 void Pause() override;
17 void Update() override;
18};
19
20} // namespace DiscordRPC
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 1947bdb93..d2b3de683 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -57,16 +57,25 @@ QString FormatGameName(const std::string& physical_name) {
57 return physical_name_as_qstring; 57 return physical_name_as_qstring;
58} 58}
59 59
60QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) { 60QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
61 Loader::AppLoader& loader, bool updatable = true) {
61 QString out; 62 QString out;
62 for (const auto& kv : patch_manager.GetPatchVersionNames()) { 63 FileSys::VirtualFile update_raw;
64 loader.ReadUpdateRaw(update_raw);
65 for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) {
63 if (!updatable && kv.first == "Update") 66 if (!updatable && kv.first == "Update")
64 continue; 67 continue;
65 68
66 if (kv.second.empty()) { 69 if (kv.second.empty()) {
67 out.append(fmt::format("{}\n", kv.first).c_str()); 70 out.append(fmt::format("{}\n", kv.first).c_str());
68 } else { 71 } else {
69 out.append(fmt::format("{} ({})\n", kv.first, kv.second).c_str()); 72 auto ver = kv.second;
73
74 // Display container name for packed updates
75 if (ver == "PACKED" && kv.first == "Update")
76 ver = Loader::GetFileTypeString(loader.GetFileType());
77
78 out.append(fmt::format("{} ({})\n", kv.first, ver).c_str());
70 } 79 }
71 } 80 }
72 81
@@ -116,7 +125,7 @@ void GameListWorker::AddInstalledTitlesToGameList() {
116 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), 125 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
117 program_id), 126 program_id),
118 new GameListItemCompat(compatibility), 127 new GameListItemCompat(compatibility),
119 new GameListItem(FormatPatchNameVersions(patch)), 128 new GameListItem(FormatPatchNameVersions(patch, *loader)),
120 new GameListItem( 129 new GameListItem(
121 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), 130 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
122 new GameListItemSize(file->GetSize()), 131 new GameListItemSize(file->GetSize()),
@@ -206,7 +215,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
206 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), 215 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
207 program_id), 216 program_id),
208 new GameListItemCompat(compatibility), 217 new GameListItemCompat(compatibility),
209 new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())), 218 new GameListItem(
219 FormatPatchNameVersions(patch, *loader, loader->IsRomFSUpdatable())),
210 new GameListItem( 220 new GameListItem(
211 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), 221 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
212 new GameListItemSize(FileUtil::GetSize(physical_name)), 222 new GameListItemSize(FileUtil::GetSize(physical_name)),
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 27015d02c..e11833c5a 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -35,6 +35,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
35#include <QtWidgets> 35#include <QtWidgets>
36#include <fmt/format.h> 36#include <fmt/format.h>
37#include "common/common_paths.h" 37#include "common/common_paths.h"
38#include "common/detached_tasks.h"
38#include "common/file_util.h" 39#include "common/file_util.h"
39#include "common/logging/backend.h" 40#include "common/logging/backend.h"
40#include "common/logging/filter.h" 41#include "common/logging/filter.h"
@@ -65,6 +66,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
65#include "video_core/debug_utils/debug_utils.h" 66#include "video_core/debug_utils/debug_utils.h"
66#include "yuzu/about_dialog.h" 67#include "yuzu/about_dialog.h"
67#include "yuzu/bootmanager.h" 68#include "yuzu/bootmanager.h"
69#include "yuzu/compatdb.h"
68#include "yuzu/compatibility_list.h" 70#include "yuzu/compatibility_list.h"
69#include "yuzu/configuration/config.h" 71#include "yuzu/configuration/config.h"
70#include "yuzu/configuration/configure_dialog.h" 72#include "yuzu/configuration/configure_dialog.h"
@@ -73,12 +75,17 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
73#include "yuzu/debugger/graphics/graphics_surface.h" 75#include "yuzu/debugger/graphics/graphics_surface.h"
74#include "yuzu/debugger/profiler.h" 76#include "yuzu/debugger/profiler.h"
75#include "yuzu/debugger/wait_tree.h" 77#include "yuzu/debugger/wait_tree.h"
78#include "yuzu/discord.h"
76#include "yuzu/game_list.h" 79#include "yuzu/game_list.h"
77#include "yuzu/game_list_p.h" 80#include "yuzu/game_list_p.h"
78#include "yuzu/hotkeys.h" 81#include "yuzu/hotkeys.h"
79#include "yuzu/main.h" 82#include "yuzu/main.h"
80#include "yuzu/ui_settings.h" 83#include "yuzu/ui_settings.h"
81 84
85#ifdef USE_DISCORD_PRESENCE
86#include "yuzu/discord_impl.h"
87#endif
88
82#ifdef QT_STATICPLUGIN 89#ifdef QT_STATICPLUGIN
83Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); 90Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
84#endif 91#endif
@@ -102,23 +109,22 @@ enum class CalloutFlag : uint32_t {
102 DRDDeprecation = 0x2, 109 DRDDeprecation = 0x2,
103}; 110};
104 111
105static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { 112void GMainWindow::ShowTelemetryCallout() {
106 if (UISettings::values.callout_flags & static_cast<uint32_t>(flag)) { 113 if (UISettings::values.callout_flags & static_cast<uint32_t>(CalloutFlag::Telemetry)) {
107 return; 114 return;
108 } 115 }
109 116
110 UISettings::values.callout_flags |= static_cast<uint32_t>(flag); 117 UISettings::values.callout_flags |= static_cast<uint32_t>(CalloutFlag::Telemetry);
111 118 const QString telemetry_message =
112 QMessageBox msg; 119 tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous "
113 msg.setText(message); 120 "data is collected</a> to help improve yuzu. "
114 msg.setStandardButtons(QMessageBox::Ok); 121 "<br/><br/>Would you like to share your usage data with us?");
115 msg.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 122 if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) {
116 msg.setStyleSheet("QLabel{min-width: 900px;}"); 123 Settings::values.enable_telemetry = false;
117 msg.exec(); 124 Settings::Apply();
125 }
118} 126}
119 127
120void GMainWindow::ShowCallouts() {}
121
122const int GMainWindow::max_recent_files_item; 128const int GMainWindow::max_recent_files_item;
123 129
124static void InitializeLogging() { 130static void InitializeLogging() {
@@ -145,6 +151,9 @@ GMainWindow::GMainWindow()
145 default_theme_paths = QIcon::themeSearchPaths(); 151 default_theme_paths = QIcon::themeSearchPaths();
146 UpdateUITheme(); 152 UpdateUITheme();
147 153
154 SetDiscordEnabled(UISettings::values.enable_discord_presence);
155 discord_rpc->Update();
156
148 InitializeWidgets(); 157 InitializeWidgets();
149 InitializeDebugWidgets(); 158 InitializeDebugWidgets();
150 InitializeRecentFileMenuActions(); 159 InitializeRecentFileMenuActions();
@@ -168,7 +177,7 @@ GMainWindow::GMainWindow()
168 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 177 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
169 178
170 // Show one-time "callout" messages to the user 179 // Show one-time "callout" messages to the user
171 ShowCallouts(); 180 ShowTelemetryCallout();
172 181
173 QStringList args = QApplication::arguments(); 182 QStringList args = QApplication::arguments();
174 if (args.length() >= 2) { 183 if (args.length() >= 2) {
@@ -183,6 +192,9 @@ GMainWindow::~GMainWindow() {
183} 192}
184 193
185void GMainWindow::InitializeWidgets() { 194void GMainWindow::InitializeWidgets() {
195#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
196 ui.action_Report_Compatibility->setVisible(true);
197#endif
186 render_window = new GRenderWindow(this, emu_thread.get()); 198 render_window = new GRenderWindow(this, emu_thread.get());
187 render_window->hide(); 199 render_window->hide();
188 200
@@ -411,6 +423,8 @@ void GMainWindow::ConnectMenuEvents() {
411 connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame); 423 connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame);
412 connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame); 424 connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame);
413 connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame); 425 connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame);
426 connect(ui.action_Report_Compatibility, &QAction::triggered, this,
427 &GMainWindow::OnMenuReportCompatibility);
414 connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); 428 connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); });
415 connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); 429 connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
416 430
@@ -471,6 +485,8 @@ QStringList GMainWindow::GetUnsupportedGLExtensions() {
471 unsupported_ext.append("ARB_texture_storage"); 485 unsupported_ext.append("ARB_texture_storage");
472 if (!GLAD_GL_ARB_multi_bind) 486 if (!GLAD_GL_ARB_multi_bind)
473 unsupported_ext.append("ARB_multi_bind"); 487 unsupported_ext.append("ARB_multi_bind");
488 if (!GLAD_GL_ARB_copy_image)
489 unsupported_ext.append("ARB_copy_image");
474 490
475 // Extensions required to support some texture formats. 491 // Extensions required to support some texture formats.
476 if (!GLAD_GL_EXT_texture_compression_s3tc) 492 if (!GLAD_GL_EXT_texture_compression_s3tc)
@@ -647,6 +663,7 @@ void GMainWindow::BootGame(const QString& filename) {
647} 663}
648 664
649void GMainWindow::ShutdownGame() { 665void GMainWindow::ShutdownGame() {
666 discord_rpc->Pause();
650 emu_thread->RequestStop(); 667 emu_thread->RequestStop();
651 668
652 emit EmulationStopping(); 669 emit EmulationStopping();
@@ -655,6 +672,8 @@ void GMainWindow::ShutdownGame() {
655 emu_thread->wait(); 672 emu_thread->wait();
656 emu_thread = nullptr; 673 emu_thread = nullptr;
657 674
675 discord_rpc->Update();
676
658 // The emulation is stopped, so closing the window or not does not matter anymore 677 // The emulation is stopped, so closing the window or not does not matter anymore
659 disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); 678 disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
660 679
@@ -664,6 +683,7 @@ void GMainWindow::ShutdownGame() {
664 ui.action_Pause->setEnabled(false); 683 ui.action_Pause->setEnabled(false);
665 ui.action_Stop->setEnabled(false); 684 ui.action_Stop->setEnabled(false);
666 ui.action_Restart->setEnabled(false); 685 ui.action_Restart->setEnabled(false);
686 ui.action_Report_Compatibility->setEnabled(false);
667 render_window->hide(); 687 render_window->hide();
668 game_list->show(); 688 game_list->show();
669 game_list->setFilterFocus(); 689 game_list->setFilterFocus();
@@ -1147,6 +1167,9 @@ void GMainWindow::OnStartGame() {
1147 ui.action_Pause->setEnabled(true); 1167 ui.action_Pause->setEnabled(true);
1148 ui.action_Stop->setEnabled(true); 1168 ui.action_Stop->setEnabled(true);
1149 ui.action_Restart->setEnabled(true); 1169 ui.action_Restart->setEnabled(true);
1170 ui.action_Report_Compatibility->setEnabled(true);
1171
1172 discord_rpc->Update();
1150} 1173}
1151 1174
1152void GMainWindow::OnPauseGame() { 1175void GMainWindow::OnPauseGame() {
@@ -1161,6 +1184,20 @@ void GMainWindow::OnStopGame() {
1161 ShutdownGame(); 1184 ShutdownGame();
1162} 1185}
1163 1186
1187void GMainWindow::OnMenuReportCompatibility() {
1188 if (!Settings::values.yuzu_token.empty() && !Settings::values.yuzu_username.empty()) {
1189 CompatDB compatdb{this};
1190 compatdb.exec();
1191 } else {
1192 QMessageBox::critical(
1193 this, tr("Missing yuzu Account"),
1194 tr("In order to submit a game compatibility test case, you must link your yuzu "
1195 "account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration "
1196 "&gt; "
1197 "Web."));
1198 }
1199}
1200
1164void GMainWindow::ToggleFullscreen() { 1201void GMainWindow::ToggleFullscreen() {
1165 if (!emulation_running) { 1202 if (!emulation_running) {
1166 return; 1203 return;
@@ -1224,11 +1261,14 @@ void GMainWindow::ToggleWindowMode() {
1224void GMainWindow::OnConfigure() { 1261void GMainWindow::OnConfigure() {
1225 ConfigureDialog configureDialog(this, hotkey_registry); 1262 ConfigureDialog configureDialog(this, hotkey_registry);
1226 auto old_theme = UISettings::values.theme; 1263 auto old_theme = UISettings::values.theme;
1264 const bool old_discord_presence = UISettings::values.enable_discord_presence;
1227 auto result = configureDialog.exec(); 1265 auto result = configureDialog.exec();
1228 if (result == QDialog::Accepted) { 1266 if (result == QDialog::Accepted) {
1229 configureDialog.applyConfiguration(); 1267 configureDialog.applyConfiguration();
1230 if (UISettings::values.theme != old_theme) 1268 if (UISettings::values.theme != old_theme)
1231 UpdateUITheme(); 1269 UpdateUITheme();
1270 if (UISettings::values.enable_discord_presence != old_discord_presence)
1271 SetDiscordEnabled(UISettings::values.enable_discord_presence);
1232 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 1272 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
1233 config->Save(); 1273 config->Save();
1234 } 1274 }
@@ -1443,11 +1483,25 @@ void GMainWindow::UpdateUITheme() {
1443 emit UpdateThemedIcons(); 1483 emit UpdateThemedIcons();
1444} 1484}
1445 1485
1486void GMainWindow::SetDiscordEnabled(bool state) {
1487#ifdef USE_DISCORD_PRESENCE
1488 if (state) {
1489 discord_rpc = std::make_unique<DiscordRPC::DiscordImpl>();
1490 } else {
1491 discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
1492 }
1493#else
1494 discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
1495#endif
1496 discord_rpc->Update();
1497}
1498
1446#ifdef main 1499#ifdef main
1447#undef main 1500#undef main
1448#endif 1501#endif
1449 1502
1450int main(int argc, char* argv[]) { 1503int main(int argc, char* argv[]) {
1504 Common::DetachedTasks detached_tasks;
1451 MicroProfileOnThreadCreate("Frontend"); 1505 MicroProfileOnThreadCreate("Frontend");
1452 SCOPE_EXIT({ MicroProfileShutdown(); }); 1506 SCOPE_EXIT({ MicroProfileShutdown(); });
1453 1507
@@ -1465,5 +1519,7 @@ int main(int argc, char* argv[]) {
1465 GMainWindow main_window; 1519 GMainWindow main_window;
1466 // After settings have been loaded by GMainWindow, apply the filter 1520 // After settings have been loaded by GMainWindow, apply the filter
1467 main_window.show(); 1521 main_window.show();
1468 return app.exec(); 1522 int result = app.exec();
1523 detached_tasks.WaitForAllTasks();
1524 return result;
1469} 1525}
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 8ee9242b1..fe0e9a50a 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -41,6 +41,10 @@ enum class EmulatedDirectoryTarget {
41 SDMC, 41 SDMC,
42}; 42};
43 43
44namespace DiscordRPC {
45class DiscordInterface;
46}
47
44class GMainWindow : public QMainWindow { 48class GMainWindow : public QMainWindow {
45 Q_OBJECT 49 Q_OBJECT
46 50
@@ -61,6 +65,8 @@ public:
61 GMainWindow(); 65 GMainWindow();
62 ~GMainWindow() override; 66 ~GMainWindow() override;
63 67
68 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
69
64signals: 70signals:
65 71
66 /** 72 /**
@@ -99,7 +105,8 @@ private:
99 void BootGame(const QString& filename); 105 void BootGame(const QString& filename);
100 void ShutdownGame(); 106 void ShutdownGame();
101 107
102 void ShowCallouts(); 108 void ShowTelemetryCallout();
109 void SetDiscordEnabled(bool state);
103 110
104 /** 111 /**
105 * Stores the filename in the recently loaded files list. 112 * Stores the filename in the recently loaded files list.
@@ -135,6 +142,7 @@ private slots:
135 void OnStartGame(); 142 void OnStartGame();
136 void OnPauseGame(); 143 void OnPauseGame();
137 void OnStopGame(); 144 void OnStopGame();
145 void OnMenuReportCompatibility();
138 /// Called whenever a user selects a game in the game list widget. 146 /// Called whenever a user selects a game in the game list widget.
139 void OnGameListLoadFile(QString game_path); 147 void OnGameListLoadFile(QString game_path);
140 void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); 148 void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 3879d4813..cb1664b21 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -45,7 +45,7 @@
45 <x>0</x> 45 <x>0</x>
46 <y>0</y> 46 <y>0</y>
47 <width>1081</width> 47 <width>1081</width>
48 <height>19</height> 48 <height>21</height>
49 </rect> 49 </rect>
50 </property> 50 </property>
51 <widget class="QMenu" name="menu_File"> 51 <widget class="QMenu" name="menu_File">
@@ -101,6 +101,8 @@
101 <property name="title"> 101 <property name="title">
102 <string>&amp;Help</string> 102 <string>&amp;Help</string>
103 </property> 103 </property>
104 <addaction name="action_Report_Compatibility"/>
105 <addaction name="separator"/>
104 <addaction name="action_About"/> 106 <addaction name="action_About"/>
105 </widget> 107 </widget>
106 <addaction name="menu_File"/> 108 <addaction name="menu_File"/>
@@ -239,6 +241,18 @@
239 <string>Restart</string> 241 <string>Restart</string>
240 </property> 242 </property>
241 </action> 243 </action>
244 <action name="action_Report_Compatibility">
245 <property name="enabled">
246 <bool>false</bool>
247 </property>
248 <property name="text">
249 <string>Report Compatibility</string>
250 </property>
251 <property name="visible">
252 <bool>false</bool>
253 </property>
254 </action>
242 </widget> 255 </widget>
243 <resources/> 256 <resources/>
257 <connections/>
244</ui> 258</ui>
diff --git a/src/yuzu/ui_settings.cpp b/src/yuzu/ui_settings.cpp
index 120b34990..a314493fc 100644
--- a/src/yuzu/ui_settings.cpp
+++ b/src/yuzu/ui_settings.cpp
@@ -6,5 +6,11 @@
6 6
7namespace UISettings { 7namespace UISettings {
8 8
9const Themes themes{{
10 {"Default", "default"},
11 {"Dark", "qdarkstyle"},
12}};
13
9Values values = {}; 14Values values = {};
10} 15
16} // namespace UISettings
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index 051494bc5..2e617d52a 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -15,9 +15,8 @@ namespace UISettings {
15using ContextualShortcut = std::pair<QString, int>; 15using ContextualShortcut = std::pair<QString, int>;
16using Shortcut = std::pair<QString, ContextualShortcut>; 16using Shortcut = std::pair<QString, ContextualShortcut>;
17 17
18static const std::array<std::pair<QString, QString>, 2> themes = { 18using Themes = std::array<std::pair<const char*, const char*>, 2>;
19 {std::make_pair(QString("Default"), QString("default")), 19extern const Themes themes;
20 std::make_pair(QString("Dark"), QString("qdarkstyle"))}};
21 20
22struct Values { 21struct Values {
23 QByteArray geometry; 22 QByteArray geometry;
@@ -39,6 +38,9 @@ struct Values {
39 bool confirm_before_closing; 38 bool confirm_before_closing;
40 bool first_start; 39 bool first_start;
41 40
41 // Discord RPC
42 bool enable_discord_presence;
43
42 QString roms_path; 44 QString roms_path;
43 QString symbols_path; 45 QString symbols_path;
44 QString gamedir; 46 QString gamedir;
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index a478b0a56..2470f4640 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -138,6 +138,15 @@ void Config::ReadValues() {
138 Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); 138 Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
139 Settings::values.gdbstub_port = 139 Settings::values.gdbstub_port =
140 static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); 140 static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
141 Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", "");
142
143 // Web Service
144 Settings::values.enable_telemetry =
145 sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
146 Settings::values.web_api_url =
147 sdl2_config->Get("WebService", "web_api_url", "https://api.yuzu-emu.org");
148 Settings::values.yuzu_username = sdl2_config->Get("WebService", "yuzu_username", "");
149 Settings::values.yuzu_token = sdl2_config->Get("WebService", "yuzu_token", "");
141} 150}
142 151
143void Config::Reload() { 152void Config::Reload() {
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index d35c441e9..762396e3b 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -202,12 +202,10 @@ gdbstub_port=24689
202# Whether or not to enable telemetry 202# Whether or not to enable telemetry
203# 0: No, 1 (default): Yes 203# 0: No, 1 (default): Yes
204enable_telemetry = 204enable_telemetry =
205# Endpoint URL for submitting telemetry data 205# URL for Web API
206telemetry_endpoint_url = 206web_api_url = https://api.yuzu-emu.org
207# Endpoint URL to verify the username and token
208verify_endpoint_url =
209# Username and token for yuzu Web Service 207# Username and token for yuzu Web Service
210# See https://services.citra-emu.org/ for more info 208# See https://profile.yuzu-emu.org/ for more info
211yuzu_username = 209yuzu_username =
212yuzu_token = 210yuzu_token =
213)"; 211)";
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 0733301b2..155095095 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -98,6 +98,8 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() {
98 unsupported_ext.push_back("ARB_texture_storage"); 98 unsupported_ext.push_back("ARB_texture_storage");
99 if (!GLAD_GL_ARB_multi_bind) 99 if (!GLAD_GL_ARB_multi_bind)
100 unsupported_ext.push_back("ARB_multi_bind"); 100 unsupported_ext.push_back("ARB_multi_bind");
101 if (!GLAD_GL_ARB_copy_image)
102 unsupported_ext.push_back("ARB_copy_image");
101 103
102 // Extensions required to support some texture formats. 104 // Extensions required to support some texture formats.
103 if (!GLAD_GL_EXT_texture_compression_s3tc) 105 if (!GLAD_GL_EXT_texture_compression_s3tc)
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index b2559b717..27aba95f6 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -10,6 +10,7 @@
10#include <fmt/ostream.h> 10#include <fmt/ostream.h>
11 11
12#include "common/common_paths.h" 12#include "common/common_paths.h"
13#include "common/detached_tasks.h"
13#include "common/file_util.h" 14#include "common/file_util.h"
14#include "common/logging/backend.h" 15#include "common/logging/backend.h"
15#include "common/logging/filter.h" 16#include "common/logging/filter.h"
@@ -55,9 +56,10 @@ static void PrintHelp(const char* argv0) {
55 std::cout << "Usage: " << argv0 56 std::cout << "Usage: " << argv0
56 << " [options] <filename>\n" 57 << " [options] <filename>\n"
57 "-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n" 58 "-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n"
58 "-f, --fullscreen Start in fullscreen mode\n" 59 "-f, --fullscreen Start in fullscreen mode\n"
59 "-h, --help Display this help and exit\n" 60 "-h, --help Display this help and exit\n"
60 "-v, --version Output version information and exit\n"; 61 "-v, --version Output version information and exit\n"
62 "-p, --program Pass following string as arguments to executable\n";
61} 63}
62 64
63static void PrintVersion() { 65static void PrintVersion() {
@@ -78,6 +80,7 @@ static void InitializeLogging() {
78 80
79/// Application entry point 81/// Application entry point
80int main(int argc, char** argv) { 82int main(int argc, char** argv) {
83 Common::DetachedTasks detached_tasks;
81 Config config; 84 Config config;
82 85
83 int option_index = 0; 86 int option_index = 0;
@@ -101,15 +104,13 @@ int main(int argc, char** argv) {
101 bool fullscreen = false; 104 bool fullscreen = false;
102 105
103 static struct option long_options[] = { 106 static struct option long_options[] = {
104 {"gdbport", required_argument, 0, 'g'}, 107 {"gdbport", required_argument, 0, 'g'}, {"fullscreen", no_argument, 0, 'f'},
105 {"fullscreen", no_argument, 0, 'f'}, 108 {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'},
106 {"help", no_argument, 0, 'h'}, 109 {"program", optional_argument, 0, 'p'}, {0, 0, 0, 0},
107 {"version", no_argument, 0, 'v'},
108 {0, 0, 0, 0},
109 }; 110 };
110 111
111 while (optind < argc) { 112 while (optind < argc) {
112 char arg = getopt_long(argc, argv, "g:fhv", long_options, &option_index); 113 char arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index);
113 if (arg != -1) { 114 if (arg != -1) {
114 switch (arg) { 115 switch (arg) {
115 case 'g': 116 case 'g':
@@ -133,6 +134,10 @@ int main(int argc, char** argv) {
133 case 'v': 134 case 'v':
134 PrintVersion(); 135 PrintVersion();
135 return 0; 136 return 0;
137 case 'p':
138 Settings::values.program_args = argv[optind];
139 ++optind;
140 break;
136 } 141 }
137 } else { 142 } else {
138#ifdef _WIN32 143#ifdef _WIN32
@@ -213,5 +218,6 @@ int main(int argc, char** argv) {
213 system.RunLoop(); 218 system.RunLoop();
214 } 219 }
215 220
221 detached_tasks.WaitForAllTasks();
216 return 0; 222 return 0;
217} 223}