diff options
Diffstat (limited to 'src')
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() | |||
| 13 | if (ENABLE_QT) | 13 | if (ENABLE_QT) |
| 14 | add_subdirectory(yuzu) | 14 | add_subdirectory(yuzu) |
| 15 | endif() | 15 | endif() |
| 16 | if (ENABLE_WEB_SERVICE) | ||
| 17 | add_subdirectory(web_service) | ||
| 18 | endif() | ||
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 | |||
| 41 | add_library(common STATIC | 41 | add_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 | ||
| 92 | if(ARCHITECTURE_x86_64) | 95 | if(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 | |||
| 9 | namespace Common { | ||
| 10 | |||
| 11 | DetachedTasks* DetachedTasks::instance = nullptr; | ||
| 12 | |||
| 13 | DetachedTasks::DetachedTasks() { | ||
| 14 | ASSERT(instance == nullptr); | ||
| 15 | instance = this; | ||
| 16 | } | ||
| 17 | |||
| 18 | void DetachedTasks::WaitForAllTasks() { | ||
| 19 | std::unique_lock<std::mutex> lock(mutex); | ||
| 20 | cv.wait(lock, [this]() { return count == 0; }); | ||
| 21 | } | ||
| 22 | |||
| 23 | DetachedTasks::~DetachedTasks() { | ||
| 24 | std::unique_lock<std::mutex> lock(mutex); | ||
| 25 | ASSERT(count == 0); | ||
| 26 | instance = nullptr; | ||
| 27 | } | ||
| 28 | |||
| 29 | void 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 | |||
| 10 | namespace 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 | */ | ||
| 24 | class DetachedTasks { | ||
| 25 | public: | ||
| 26 | DetachedTasks(); | ||
| 27 | ~DetachedTasks(); | ||
| 28 | void WaitForAllTasks(); | ||
| 29 | |||
| 30 | static void AddTask(std::function<void()> task); | ||
| 31 | |||
| 32 | private: | ||
| 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 | ||
| 33 | void PrintMessage(const Entry& entry) { | 33 | void 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"; | |||
| 20 | constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1"; | 20 | constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1"; |
| 21 | constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2"; | 21 | constexpr 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) | ||
| 25 | constexpr char EMPTY_PLACEHOLDER[] = "[empty]"; | ||
| 26 | |||
| 23 | ParamPackage::ParamPackage(const std::string& serialized) { | 27 | ParamPackage::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 | ||
| 47 | std::string ParamPackage::Serialize() const { | 55 | std::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 | ||
| 131 | void ParamPackage::Erase(const std::string& key) { | ||
| 132 | data.erase(key); | ||
| 133 | } | ||
| 134 | |||
| 135 | void 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 | ||
| 36 | private: | 38 | private: |
| 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 | |||
| 9 | namespace Common { | ||
| 10 | struct 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 | ||
| 397 | target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) | 397 | target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) |
| 398 | target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives) | 398 | target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives) |
| 399 | if (ENABLE_WEB_SERVICE) | ||
| 400 | add_definitions(-DENABLE_WEB_SERVICE) | ||
| 401 | target_link_libraries(core PUBLIC json-headers web_service) | ||
| 402 | endif() | ||
| 399 | 403 | ||
| 400 | if (ARCHITECTURE_x86_64) | 404 | if (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 | ||
| 207 | VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, | 207 | VirtualFile 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 | ||
| 247 | std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames() const { | 254 | std::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 | ||
| 31 | RomFSFactory::~RomFSFactory() = default; | 31 | RomFSFactory::~RomFSFactory() = default; |
| 32 | 32 | ||
| 33 | void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) { | ||
| 34 | this->update_raw = std::move(update_raw); | ||
| 35 | } | ||
| 36 | |||
| 33 | ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() { | 37 | ResultVal<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 | ||
| 41 | ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) { | 46 | ResultVal<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 | ||
| 38 | private: | 39 | private: |
| 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. |
| 24 | static ResultCode WaitForAddress(VAddr address, s64 timeout) { | 24 | static 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 { | |||
| 17 | ClientPort::ClientPort(KernelCore& kernel) : Object{kernel} {} | 17 | ClientPort::ClientPort(KernelCore& kernel) : Object{kernel} {} |
| 18 | ClientPort::~ClientPort() = default; | 18 | ClientPort::~ClientPort() = default; |
| 19 | 19 | ||
| 20 | SharedPtr<ServerPort> ClientPort::GetServerPort() const { | ||
| 21 | return server_port; | ||
| 22 | } | ||
| 23 | |||
| 20 | ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { | 24 | ResultVal<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 | ||
| 210 | ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) { | 210 | ResultCode 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. |
| 48 | static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread, | 48 | static 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) { | |||
| 136 | void Scheduler::ScheduleThread(Thread* thread, u32 priority) { | 136 | void 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 | ||
| 143 | void Scheduler::UnscheduleThread(Thread* thread, u32 priority) { | 143 | void 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 | ||
| 15 | namespace Kernel { | 16 | namespace 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 |
| 178 | static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread, | 178 | static 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 |
| 487 | static u32 GetCurrentProcessorNumber() { | 487 | static 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 | ||
| 492 | static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size, | 492 | static 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 | ||
| 71 | void WaitCurrentThread_Sleep() { | 71 | void WaitCurrentThread_Sleep() { |
| 72 | Thread* thread = GetCurrentThread(); | 72 | Thread* thread = GetCurrentThread(); |
| 73 | thread->status = ThreadStatus::WaitSleep; | 73 | thread->SetStatus(ThreadStatus::WaitSleep); |
| 74 | } | 74 | } |
| 75 | 75 | ||
| 76 | void ExitCurrentThread() { | 76 | void 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 | ||
| 302 | void 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 | |||
| 302 | void Thread::AddMutexWaiter(SharedPtr<Thread> thread) { | 314 | void 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 | ||
| 408 | bool 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 | |||
| 414 | bool 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 | |||
| 365 | private: | ||
| 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 | ||
| 279 | private: | ||
| 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 | |||
| 61 | AOC_U::~AOC_U() = default; | 61 | AOC_U::~AOC_U() = default; |
| 62 | 62 | ||
| 63 | void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) { | 63 | void 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(), [¤t](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 | [¤t](u64 tid) { return (tid & DLC_BASE_TITLE_ID_MASK) == current; }))); |
| 71 | } | 71 | } |
| 72 | 72 | ||
| 73 | void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { | 73 | void 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 | ||
| 267 | void 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 | |||
| 267 | ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() { | 276 | ResultVal<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) | |||
| 39 | ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory); | 39 | ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory); |
| 40 | ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory); | 40 | ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory); |
| 41 | 41 | ||
| 42 | void SetPackedUpdate(FileSys::VirtualFile update_raw); | ||
| 42 | ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess(); | 43 | ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess(); |
| 43 | ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id, | 44 | ResultVal<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 | ||
| 53 | private: | 72 | private: |
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 | ||
| 96 | constexpr std::array<const char*, 58> RESULT_MESSAGES{ | 96 | constexpr 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 | ||
| 157 | std::ostream& operator<<(std::ostream& os, ResultStatus status) { | 158 | std::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 | ||
| 119 | std::ostream& operator<<(std::ostream& os, ResultStatus status); | 120 | std::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 | ||
| 75 | u64 AppLoader_NAX::ReadRomFSIVFCOffset() const { | ||
| 76 | return nca_loader->ReadRomFSIVFCOffset(); | ||
| 77 | } | ||
| 78 | |||
| 75 | ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { | 79 | ResultStatus 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 | ||
| 41 | private: | 42 | private: |
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 | ||
| 23 | namespace Loader { | 25 | namespace 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 | ||
| 21 | namespace Loader { | 22 | namespace Loader { |
| 22 | 23 | ||
| @@ -94,6 +95,7 @@ static constexpr u32 PageAlignSize(u32 size) { | |||
| 94 | } | 95 | } |
| 95 | 96 | ||
| 96 | VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base, | 97 | VAddr 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 | ||
| 12 | namespace Loader { | 12 | namespace Loader { |
| 13 | 13 | ||
| 14 | constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000; | ||
| 15 | |||
| 16 | struct NSOArgumentHeader { | ||
| 17 | u32_le allocated_size; | ||
| 18 | u32_le actual_size; | ||
| 19 | INSERT_PADDING_BYTES(0x18); | ||
| 20 | }; | ||
| 21 | static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size."); | ||
| 22 | |||
| 14 | /// Loads an NSO file | 23 | /// Loads an NSO file |
| 15 | class AppLoader_NSO final : public AppLoader, Linker { | 24 | class AppLoader_NSO final : public AppLoader, Linker { |
| 16 | public: | 25 | public: |
| @@ -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 | ||
| 99 | ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& dir) { | 105 | ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& file) { |
| 100 | return secondary_loader->ReadRomFS(dir); | 106 | return secondary_loader->ReadRomFS(file); |
| 107 | } | ||
| 108 | |||
| 109 | u64 AppLoader_NSP::ReadRomFSIVFCOffset() const { | ||
| 110 | return secondary_loader->ReadRomFSIVFCOffset(); | ||
| 111 | } | ||
| 112 | |||
| 113 | ResultStatus 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 | ||
| 103 | ResultStatus AppLoader_NSP::ReadProgramId(u64& out_program_id) { | 131 | ResultStatus 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 | ||
| 71 | ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& dir) { | 79 | ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& file) { |
| 72 | return nca_loader->ReadRomFS(dir); | 80 | return nca_loader->ReadRomFS(file); |
| 81 | } | ||
| 82 | |||
| 83 | u64 AppLoader_XCI::ReadRomFSIVFCOffset() const { | ||
| 84 | return nca_loader->ReadRomFSIVFCOffset(); | ||
| 85 | } | ||
| 86 | |||
| 87 | ResultStatus 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 | ||
| 75 | ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) { | 107 | ResultStatus 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 | ||
| 160 | void Apply(); | 167 | void 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 | |||
| 16 | namespace Core { | 23 | namespace Core { |
| 17 | 24 | ||
| 18 | static u64 GenerateTelemetryId() { | 25 | static 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 | ||
| 62 | std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func) { | 92 | bool 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 | ||
| 73 | TelemetrySession::TelemetrySession() { | 100 | TelemetrySession::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 | |||
| 34 | private: | 35 | private: |
| 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 | */ |
| 58 | std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func); | 59 | bool 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 | ||
| 9 | namespace Tegra::Engines { | 10 | namespace Tegra::Engines { |
| 10 | 11 | ||
| 11 | Fermi2D::Fermi2D(MemoryManager& memory_manager) : memory_manager(memory_manager) {} | 12 | Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager) |
| 13 | : memory_manager(memory_manager), rasterizer{rasterizer} {} | ||
| 12 | 14 | ||
| 13 | void Fermi2D::WriteReg(u32 method, u32 value) { | 15 | void 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 | ||
| 15 | namespace VideoCore { | ||
| 16 | class RasterizerInterface; | ||
| 17 | } | ||
| 18 | |||
| 15 | namespace Tegra::Engines { | 19 | namespace 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 | ||
| 20 | class Fermi2D final { | 24 | class Fermi2D final { |
| 21 | public: | 25 | public: |
| 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 | ||
| 96 | private: | 100 | private: |
| 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) { | |||
| 25 | GPU::GPU(VideoCore::RasterizerInterface& rasterizer) { | 25 | GPU::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 | ||
| 67 | std::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 | |||
| 67 | void OGLBufferCache::Map(std::size_t max_size) { | 77 | void 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 | |||
| 77 | void OGLBufferCache::Unmap() { | 88 | void 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 | ||
| 85 | void OGLBufferCache::AlignBuffer(std::size_t alignment) { | 96 | void 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 | |||
| 33 | public: | 34 | public: |
| 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 | |||
| 13 | namespace OpenGL { | ||
| 14 | |||
| 15 | constexpr u32 TRIANGLES_PER_QUAD = 6; | ||
| 16 | constexpr std::array<u32, TRIANGLES_PER_QUAD> QUAD_MAP = {0, 1, 2, 0, 2, 3}; | ||
| 17 | |||
| 18 | PrimitiveAssembler::PrimitiveAssembler(OGLBufferCache& buffer_cache) : buffer_cache(buffer_cache) {} | ||
| 19 | |||
| 20 | PrimitiveAssembler::~PrimitiveAssembler() = default; | ||
| 21 | |||
| 22 | std::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 | |||
| 27 | GLintptr 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 | |||
| 42 | GLintptr 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 | |||
| 13 | namespace OpenGL { | ||
| 14 | |||
| 15 | class OGLBufferCache; | ||
| 16 | |||
| 17 | class PrimitiveAssembler { | ||
| 18 | public: | ||
| 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 | |||
| 29 | private: | ||
| 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 | |||
| 42 | MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192)); | 42 | MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192)); |
| 43 | MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192)); | 43 | MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192)); |
| 44 | MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); | 44 | MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); |
| 45 | MICROPROFILE_DEFINE(OpenGL_PrimitiveAssembly, "OpenGL", "Prim Asmbl", MP_RGB(255, 100, 100)); | ||
| 46 | |||
| 47 | struct 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 | ||
| 46 | RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info) | 81 | RasterizerOpenGL::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 | ||
| 210 | DrawParameters 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 | |||
| 175 | void RasterizerOpenGL::SetupShaders() { | 258 | void 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 | ||
| 342 | std::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 | |||
| 259 | bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) { | 349 | bool 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 | ||
| 559 | bool RasterizerOpenGL::AccelerateDisplayTransfer(const void* config) { | 620 | bool 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 | |||
| 565 | bool 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 | ||
| 610 | void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) { | 670 | void 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; | |||
| 38 | namespace OpenGL { | 39 | namespace OpenGL { |
| 39 | 40 | ||
| 40 | struct ScreenInfo; | 41 | struct ScreenInfo; |
| 42 | struct DrawParameters; | ||
| 41 | 43 | ||
| 42 | class RasterizerOpenGL : public VideoCore::RasterizerInterface { | 44 | class RasterizerOpenGL : public VideoCore::RasterizerInterface { |
| 43 | public: | 45 | public: |
| @@ -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 | |||
| 146 | static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{ | 168 | static 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 | ||
| 584 | static 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 | |||
| 562 | static void CopySurface(const Surface& src_surface, const Surface& dst_surface, | 596 | static 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 | ||
| 1070 | void 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 | |||
| 1036 | Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, | 1090 | Surface 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 | |||
| 840 | private: | 849 | private: |
| 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 | ||
| 76 | public: | 76 | public: |
| 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 | ||
| 153 | struct ShaderEntries { | 161 | struct 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 | ||
| 162 | inline 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 | |||
| 162 | inline GLenum BlendEquation(Maxwell::Blend::Equation equation) { | 187 | inline 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 | ||
| 230 | enum 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 | |||
| 230 | enum class TextureFilter : u32 { | 241 | enum 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 @@ | |||
| 1 | add_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 | |||
| 10 | create_target_directory_groups(web_service) | ||
| 11 | |||
| 12 | get_directory_property(OPENSSL_LIBS | ||
| 13 | DIRECTORY ${CMAKE_SOURCE_DIR}/externals/libressl | ||
| 14 | DEFINITION OPENSSL_LIBS) | ||
| 15 | target_compile_definitions(web_service PUBLIC -DCPPHTTPLIB_OPENSSL_SUPPORT) | ||
| 16 | target_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 | |||
| 11 | namespace WebService { | ||
| 12 | |||
| 13 | TelemetryJson::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)) {} | ||
| 16 | TelemetryJson::~TelemetryJson() = default; | ||
| 17 | |||
| 18 | template <class T> | ||
| 19 | void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) { | ||
| 20 | sections[static_cast<u8>(type)][name] = value; | ||
| 21 | } | ||
| 22 | |||
| 23 | void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) { | ||
| 24 | TopSection()[name] = sections[static_cast<unsigned>(type)]; | ||
| 25 | } | ||
| 26 | |||
| 27 | void TelemetryJson::Visit(const Telemetry::Field<bool>& field) { | ||
| 28 | Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||
| 29 | } | ||
| 30 | |||
| 31 | void TelemetryJson::Visit(const Telemetry::Field<double>& field) { | ||
| 32 | Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||
| 33 | } | ||
| 34 | |||
| 35 | void TelemetryJson::Visit(const Telemetry::Field<float>& field) { | ||
| 36 | Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||
| 37 | } | ||
| 38 | |||
| 39 | void TelemetryJson::Visit(const Telemetry::Field<u8>& field) { | ||
| 40 | Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||
| 41 | } | ||
| 42 | |||
| 43 | void TelemetryJson::Visit(const Telemetry::Field<u16>& field) { | ||
| 44 | Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||
| 45 | } | ||
| 46 | |||
| 47 | void TelemetryJson::Visit(const Telemetry::Field<u32>& field) { | ||
| 48 | Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||
| 49 | } | ||
| 50 | |||
| 51 | void TelemetryJson::Visit(const Telemetry::Field<u64>& field) { | ||
| 52 | Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||
| 53 | } | ||
| 54 | |||
| 55 | void TelemetryJson::Visit(const Telemetry::Field<s8>& field) { | ||
| 56 | Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||
| 57 | } | ||
| 58 | |||
| 59 | void TelemetryJson::Visit(const Telemetry::Field<s16>& field) { | ||
| 60 | Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||
| 61 | } | ||
| 62 | |||
| 63 | void TelemetryJson::Visit(const Telemetry::Field<s32>& field) { | ||
| 64 | Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||
| 65 | } | ||
| 66 | |||
| 67 | void TelemetryJson::Visit(const Telemetry::Field<s64>& field) { | ||
| 68 | Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||
| 69 | } | ||
| 70 | |||
| 71 | void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) { | ||
| 72 | Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||
| 73 | } | ||
| 74 | |||
| 75 | void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) { | ||
| 76 | Serialize(field.GetType(), field.GetName(), std::string(field.GetValue())); | ||
| 77 | } | ||
| 78 | |||
| 79 | void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) { | ||
| 80 | Serialize(field.GetType(), field.GetName(), field.GetValue().count()); | ||
| 81 | } | ||
| 82 | |||
| 83 | void 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 | |||
| 13 | namespace WebService { | ||
| 14 | |||
| 15 | /** | ||
| 16 | * Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the | ||
| 17 | * yuzu web service | ||
| 18 | */ | ||
| 19 | class TelemetryJson : public Telemetry::VisitorInterface { | ||
| 20 | public: | ||
| 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 | |||
| 41 | private: | ||
| 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 | |||
| 9 | namespace WebService { | ||
| 10 | |||
| 11 | bool 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 | |||
| 11 | namespace 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 | */ | ||
| 20 | bool 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 | |||
| 14 | namespace WebService { | ||
| 15 | |||
| 16 | constexpr std::array<const char, 1> API_VERSION{'1'}; | ||
| 17 | |||
| 18 | constexpr u32 HTTP_PORT = 80; | ||
| 19 | constexpr u32 HTTPS_PORT = 443; | ||
| 20 | |||
| 21 | constexpr u32 TIMEOUT_SECONDS = 30; | ||
| 22 | |||
| 23 | Client::JWTCache Client::jwt_cache{}; | ||
| 24 | |||
| 25 | Client::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 | |||
| 33 | Common::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 | |||
| 114 | void 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 | |||
| 128 | Common::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 | |||
| 15 | namespace httplib { | ||
| 16 | class Client; | ||
| 17 | } | ||
| 18 | |||
| 19 | namespace WebService { | ||
| 20 | |||
| 21 | class Client { | ||
| 22 | public: | ||
| 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 | |||
| 59 | private: | ||
| 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 | ||
| 77 | file(GLOB COMPAT_LIST | 84 | file(GLOB COMPAT_LIST |
| @@ -113,6 +120,19 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core) | |||
| 113 | target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets) | 120 | target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets) |
| 114 | target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) | 121 | target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) |
| 115 | 122 | ||
| 123 | if (YUZU_ENABLE_COMPATIBILITY_REPORTING) | ||
| 124 | add_definitions(-DYUZU_ENABLE_COMPATIBILITY_REPORTING) | ||
| 125 | endif() | ||
| 126 | |||
| 127 | if (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) | ||
| 134 | endif() | ||
| 135 | |||
| 116 | if(UNIX AND NOT APPLE) | 136 | if(UNIX AND NOT APPLE) |
| 117 | install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") | 137 | install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") |
| 118 | endif() | 138 | endif() |
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 | |||
| 15 | CompatDB::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 | |||
| 28 | CompatDB::~CompatDB() = default; | ||
| 29 | |||
| 30 | enum class CompatDBPage { | ||
| 31 | Intro = 0, | ||
| 32 | Selection = 1, | ||
| 33 | Final = 2, | ||
| 34 | }; | ||
| 35 | |||
| 36 | void 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 | |||
| 63 | void 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 | |||
| 10 | namespace Ui { | ||
| 11 | class CompatDB; | ||
| 12 | } | ||
| 13 | |||
| 14 | class CompatDB : public QWizard { | ||
| 15 | Q_OBJECT | ||
| 16 | |||
| 17 | public: | ||
| 18 | explicit CompatDB(QWidget* parent = nullptr); | ||
| 19 | ~CompatDB(); | ||
| 20 | |||
| 21 | private: | ||
| 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><html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of yuzu you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected yuzu account</li></ul></body></html></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><html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html></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><html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html></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><html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html></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><html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html></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><html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html></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><html><head/><body><p>The game crashes when attempting to startup.</p></body></html></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><html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html></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 | ||
| 38 | void ConfigureDebug::applyConfiguration() { | 39 | void 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 | |||
| 257 | void 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 | ||
| 221 | void ConfigureInput::updateButtonLabels() { | 272 | void 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 | |||
| 14 | ConfigureWeb::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 | |||
| 28 | ConfigureWeb::~ConfigureWeb() = default; | ||
| 29 | |||
| 30 | void 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 | |||
| 59 | void 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 | |||
| 72 | void 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 | |||
| 78 | void 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 | |||
| 90 | void 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 | |||
| 100 | void 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 | |||
| 117 | void 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 | |||
| 11 | namespace Ui { | ||
| 12 | class ConfigureWeb; | ||
| 13 | } | ||
| 14 | |||
| 15 | class ConfigureWeb : public QWidget { | ||
| 16 | Q_OBJECT | ||
| 17 | |||
| 18 | public: | ||
| 19 | explicit ConfigureWeb(QWidget* parent = nullptr); | ||
| 20 | ~ConfigureWeb(); | ||
| 21 | |||
| 22 | void applyConfiguration(); | ||
| 23 | void retranslateUi(); | ||
| 24 | |||
| 25 | public slots: | ||
| 26 | void RefreshTelemetryID(); | ||
| 27 | void OnLoginChanged(); | ||
| 28 | void VerifyLogin(); | ||
| 29 | void OnLoginVerified(); | ||
| 30 | |||
| 31 | private: | ||
| 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; | |||
| 213 | QString WaitTreeThread::GetText() const { | 213 | QString 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 | ||
| 255 | QColor WaitTreeThread::GetColor() const { | 257 | QColor 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 | |||
| 7 | namespace DiscordRPC { | ||
| 8 | |||
| 9 | class DiscordInterface { | ||
| 10 | public: | ||
| 11 | virtual ~DiscordInterface() = default; | ||
| 12 | |||
| 13 | virtual void Pause() = 0; | ||
| 14 | virtual void Update() = 0; | ||
| 15 | }; | ||
| 16 | |||
| 17 | class NullImpl : public DiscordInterface { | ||
| 18 | public: | ||
| 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 | |||
| 14 | namespace DiscordRPC { | ||
| 15 | |||
| 16 | DiscordImpl::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 | |||
| 24 | DiscordImpl::~DiscordImpl() { | ||
| 25 | Discord_ClearPresence(); | ||
| 26 | Discord_Shutdown(); | ||
| 27 | } | ||
| 28 | |||
| 29 | void DiscordImpl::Pause() { | ||
| 30 | Discord_ClearPresence(); | ||
| 31 | } | ||
| 32 | |||
| 33 | void 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 | |||
| 9 | namespace DiscordRPC { | ||
| 10 | |||
| 11 | class DiscordImpl : public DiscordInterface { | ||
| 12 | public: | ||
| 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 | ||
| 60 | QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) { | 60 | QString 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 |
| 83 | Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); | 90 | Q_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 | ||
| 105 | static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { | 112 | void 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 | ||
| 120 | void GMainWindow::ShowCallouts() {} | ||
| 121 | |||
| 122 | const int GMainWindow::max_recent_files_item; | 128 | const int GMainWindow::max_recent_files_item; |
| 123 | 129 | ||
| 124 | static void InitializeLogging() { | 130 | static 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 | ||
| 185 | void GMainWindow::InitializeWidgets() { | 194 | void 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 | ||
| 649 | void GMainWindow::ShutdownGame() { | 665 | void 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 | ||
| 1152 | void GMainWindow::OnPauseGame() { | 1175 | void GMainWindow::OnPauseGame() { |
| @@ -1161,6 +1184,20 @@ void GMainWindow::OnStopGame() { | |||
| 1161 | ShutdownGame(); | 1184 | ShutdownGame(); |
| 1162 | } | 1185 | } |
| 1163 | 1186 | ||
| 1187 | void 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 > Configuration " | ||
| 1196 | "> " | ||
| 1197 | "Web.")); | ||
| 1198 | } | ||
| 1199 | } | ||
| 1200 | |||
| 1164 | void GMainWindow::ToggleFullscreen() { | 1201 | void GMainWindow::ToggleFullscreen() { |
| 1165 | if (!emulation_running) { | 1202 | if (!emulation_running) { |
| 1166 | return; | 1203 | return; |
| @@ -1224,11 +1261,14 @@ void GMainWindow::ToggleWindowMode() { | |||
| 1224 | void GMainWindow::OnConfigure() { | 1261 | void 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 | ||
| 1486 | void 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 | ||
| 1450 | int main(int argc, char* argv[]) { | 1503 | int 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 | ||
| 44 | namespace DiscordRPC { | ||
| 45 | class DiscordInterface; | ||
| 46 | } | ||
| 47 | |||
| 44 | class GMainWindow : public QMainWindow { | 48 | class 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 | |||
| 64 | signals: | 70 | signals: |
| 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>&Help</string> | 102 | <string>&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 | ||
| 7 | namespace UISettings { | 7 | namespace UISettings { |
| 8 | 8 | ||
| 9 | const Themes themes{{ | ||
| 10 | {"Default", "default"}, | ||
| 11 | {"Dark", "qdarkstyle"}, | ||
| 12 | }}; | ||
| 13 | |||
| 9 | Values values = {}; | 14 | Values 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 { | |||
| 15 | using ContextualShortcut = std::pair<QString, int>; | 15 | using ContextualShortcut = std::pair<QString, int>; |
| 16 | using Shortcut = std::pair<QString, ContextualShortcut>; | 16 | using Shortcut = std::pair<QString, ContextualShortcut>; |
| 17 | 17 | ||
| 18 | static const std::array<std::pair<QString, QString>, 2> themes = { | 18 | using Themes = std::array<std::pair<const char*, const char*>, 2>; |
| 19 | {std::make_pair(QString("Default"), QString("default")), | 19 | extern const Themes themes; |
| 20 | std::make_pair(QString("Dark"), QString("qdarkstyle"))}}; | ||
| 21 | 20 | ||
| 22 | struct Values { | 21 | struct 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 | ||
| 143 | void Config::Reload() { | 152 | void 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 |
| 204 | enable_telemetry = | 204 | enable_telemetry = |
| 205 | # Endpoint URL for submitting telemetry data | 205 | # URL for Web API |
| 206 | telemetry_endpoint_url = | 206 | web_api_url = https://api.yuzu-emu.org |
| 207 | # Endpoint URL to verify the username and token | ||
| 208 | verify_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 |
| 211 | yuzu_username = | 209 | yuzu_username = |
| 212 | yuzu_token = | 210 | yuzu_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 | ||
| 63 | static void PrintVersion() { | 65 | static void PrintVersion() { |
| @@ -78,6 +80,7 @@ static void InitializeLogging() { | |||
| 78 | 80 | ||
| 79 | /// Application entry point | 81 | /// Application entry point |
| 80 | int main(int argc, char** argv) { | 82 | int 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 | } |