From d3491bfced823e9d28b054ea286e2421957c1151 Mon Sep 17 00:00:00 2001 From: Lander Gallastegi Date: Sun, 13 Oct 2024 10:35:05 +0200 Subject: [PATCH 01/10] hotfix: correctly check for valid priority (#1359) --- src/core/libraries/dialogs/ime_dialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/dialogs/ime_dialog.cpp b/src/core/libraries/dialogs/ime_dialog.cpp index 3d168bb79..63b52706a 100644 --- a/src/core/libraries/dialogs/ime_dialog.cpp +++ b/src/core/libraries/dialogs/ime_dialog.cpp @@ -175,7 +175,7 @@ Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExt } if (extended) { - if (magic_enum::enum_contains(extended->priority)) { + if (!magic_enum::enum_contains(extended->priority)) { LOG_INFO(Lib_ImeDialog, "Invalid extended->priority"); return Error::INVALID_EXTENDED; } From 1cc4ab7e889a622fb97e6b68ed4c7d18e7d8f40d Mon Sep 17 00:00:00 2001 From: RDN000 <109141852+RDN000@users.noreply.github.com> Date: Sun, 13 Oct 2024 10:36:52 +0200 Subject: [PATCH 02/10] Update sq translation (#1351) * Update sq translation * Update sq translation --- src/qt_gui/translations/sq.ts | 112 +++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index 062226369..dae5525e8 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -434,6 +434,41 @@ Log Filter Filtri i Ditarit + + + Input + Hyrja + + + + Cursor + Kursori + + + + Hide Cursor + Fshih kursorin + + + + Hide Cursor Idle Timeout + Koha për fshehjen e kursorit joaktiv + + + + Input + Hyrja + + + + Controller + Dorezë + + + + Back Button Behavior + Sjellja e butonit mbrapa + Graphics @@ -1023,6 +1058,66 @@ GUIgroupBox Luaj muzikën e titullit:\nNëse një lojë e mbështet, aktivizohet luajtja e muzikës të veçantë kur të zgjidhësh lojën në GUI. + + + cursorGroupBox + Kursori:\nNdrysho cilësimet në lidhje me kursorin. + + + + hideCursorGroupBox + Fshih kursorin:\nCakto sjelljen e fshehjes së kursorit. + + + + idleTimeoutGroupBox + Koha për fshehjen e kursorit joaktiv:\Kohëzgjatja (në sekonda) pas së cilës kursori që ka nuk ka qënë në veprim fshihet. + + + + Never + Kurrë + + + + Idle + Pa vepruar + + + + Always + Gjithmonë + + + + backButtonBehaviorGroupBox + Back Button Behavior:\nAllows setting which part of the touchpad the back button will emulate a touch on. + + + + backButtonBehaviorGroupBox + Sjellja e butonit mbrapa:\nLejon të përcaktohet se në cilën pjesë të tastierës prekëse do të imitojë një prekje butoni prapa. + + + + Touchpad Left + Tastiera prekëse majtas + + + + Touchpad Right + Tastiera prekëse djathtas + + + + Touchpad Center + Tastiera prekëse në qendër + + + + None + Asnjë + graphicsAdapterGroupBox @@ -1048,6 +1143,21 @@ nullGpuCheckBox Aktivizo GPU-në Null:\nPër qëllime të korrigjimit teknik, çaktivizon pasqyrimin e lojës sikur nuk ka një kartë grafike. + + + gameFoldersBox + Dosjet e lojërave: Lista e dosjeve për t'u kontrolluar për lojëra të instaluara. + + + + addFolderButton + Shto: Shto një dosje në listë. + + + + removeFolderButton + Hiq: Hiq një dosje nga lista. + debugDump @@ -1114,7 +1224,7 @@ Play Time - Kohë Lojë + Koha e luajtjes From 8776eba8c89ae65aac3c0a7d0787a8843d216edd Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Sun, 13 Oct 2024 12:22:14 +0300 Subject: [PATCH 03/10] Added adaptive mutex initializer handling (#1353) --- .../libraries/kernel/thread_management.cpp | 19 ++++++++++++------- src/core/libraries/kernel/thread_management.h | 9 +++++++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index aa53d7570..39c0eaf80 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -36,6 +36,10 @@ void init_pthreads() { ScePthreadMutexattr default_mutexattr = nullptr; scePthreadMutexattrInit(&default_mutexattr); g_pthread_cxt->setDefaultMutexattr(default_mutexattr); + ScePthreadMutexattr adaptive_mutexattr = nullptr; + scePthreadMutexattrInit(&adaptive_mutexattr); + scePthreadMutexattrSettype(&adaptive_mutexattr, ORBIS_PTHREAD_MUTEX_ADAPTIVE); + g_pthread_cxt->setAdaptiveMutexattr(adaptive_mutexattr); // default cond init ScePthreadCondattr default_condattr = nullptr; scePthreadCondattrInit(&default_condattr); @@ -412,7 +416,8 @@ int PS4_SYSV_ABI scePthreadGetaffinity(ScePthread thread, /*SceKernelCpumask*/ u } ScePthreadMutex* createMutex(ScePthreadMutex* addr) { - if (addr == nullptr || *addr != nullptr) { + if (addr == nullptr || + (*addr != nullptr && *addr != ORBIS_PTHREAD_MUTEX_ADAPTIVE_INITIALIZER)) { return addr; } @@ -429,14 +434,14 @@ int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMut if (mutex == nullptr) { return SCE_KERNEL_ERROR_EINVAL; } - if (mutex_attr == nullptr) { - attr = g_pthread_cxt->getDefaultMutexattr(); - } else { - if (*mutex_attr == nullptr) { - attr = g_pthread_cxt->getDefaultMutexattr(); + if (mutex_attr == nullptr || *mutex_attr == nullptr) { + if (*mutex == ORBIS_PTHREAD_MUTEX_ADAPTIVE_INITIALIZER) { + attr = g_pthread_cxt->getAdaptiveMutexattr(); } else { - attr = mutex_attr; + attr = g_pthread_cxt->getDefaultMutexattr(); } + } else { + attr = mutex_attr; } *mutex = new PthreadMutexInternal{}; diff --git a/src/core/libraries/kernel/thread_management.h b/src/core/libraries/kernel/thread_management.h index 7385b55ce..3cdb300f7 100644 --- a/src/core/libraries/kernel/thread_management.h +++ b/src/core/libraries/kernel/thread_management.h @@ -13,6 +13,8 @@ #include "common/types.h" +#define ORBIS_PTHREAD_MUTEX_ADAPTIVE_INITIALIZER (reinterpret_cast(1)) + namespace Core::Loader { class SymbolsResolver; } @@ -134,6 +136,12 @@ public: void setDefaultMutexattr(ScePthreadMutexattr attr) { m_default_mutexattr = attr; } + ScePthreadMutexattr* getAdaptiveMutexattr() { + return &m_adaptive_mutexattr; + } + void setAdaptiveMutexattr(ScePthreadMutexattr attr) { + m_adaptive_mutexattr = attr; + } ScePthreadCondattr* getDefaultCondattr() { return &m_default_condattr; } @@ -161,6 +169,7 @@ public: private: ScePthreadMutexattr m_default_mutexattr = nullptr; + ScePthreadMutexattr m_adaptive_mutexattr = nullptr; ScePthreadCondattr m_default_condattr = nullptr; ScePthreadAttr m_default_attr = nullptr; PThreadPool* m_pthread_pool = nullptr; From cf2e617f08df118494dde8845e00b0fe4efc5134 Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Sun, 13 Oct 2024 09:02:22 -0300 Subject: [PATCH 04/10] Devtools - Inspect regs/User data/Shader disassembly (#1358) * devtools: pm4 - show markers * SaveDataDialogLib: fix compile with mingw * devtools: pm4 - show program state * devtools: pm4 - show program disassembly * devtools: pm4 - show frame regs * devtools: pm4 - show color buffer info as popup add ux improvements for open new windows with shift+click better window titles * imgui: skip all textures to avoid hanging with crash diagnostic enabled not sure why this happens :c * devtools: pm4 - show reg depth buffer --- CMakeLists.txt | 10 + src/core/debug_state.cpp | 65 +- src/core/debug_state.h | 46 +- src/core/devtools/layer.cpp | 111 +- src/core/devtools/options.cpp | 24 + src/core/devtools/options.h | 21 + src/core/devtools/widget/cmd_list.cpp | 558 ++-- src/core/devtools/widget/cmd_list.h | 69 +- src/core/devtools/widget/common.h | 100 + src/core/devtools/widget/frame_dump.cpp | 87 +- src/core/devtools/widget/frame_dump.h | 6 +- src/core/devtools/widget/reg_popup.cpp | 182 ++ src/core/devtools/widget/reg_popup.h | 38 + src/core/devtools/widget/reg_view.cpp | 305 +++ src/core/devtools/widget/reg_view.h | 50 + src/core/devtools/widget/text_editor.cpp | 2334 +++++++++++++++++ src/core/devtools/widget/text_editor.h | 408 +++ src/core/libraries/dialogs/error_dialog.cpp | 2 +- src/core/libraries/gnmdriver/gnmdriver.cpp | 31 +- src/core/libraries/gnmdriver/gnmdriver.h | 2 +- .../save_data/dialog/savedatadialog_ui.cpp | 4 +- src/core/libraries/system/msgdialog_ui.cpp | 2 +- src/imgui/imgui_std.h | 7 +- src/imgui/renderer/texture_manager.cpp | 6 + src/video_core/amdgpu/liverpool.cpp | 27 + src/video_core/amdgpu/liverpool.h | 6 +- src/video_core/amdgpu/pm4_cmds.h | 1 + 27 files changed, 4215 insertions(+), 287 deletions(-) create mode 100644 src/core/devtools/options.cpp create mode 100644 src/core/devtools/options.h create mode 100644 src/core/devtools/widget/common.h create mode 100644 src/core/devtools/widget/reg_popup.cpp create mode 100644 src/core/devtools/widget/reg_popup.h create mode 100644 src/core/devtools/widget/reg_view.cpp create mode 100644 src/core/devtools/widget/reg_view.h create mode 100644 src/core/devtools/widget/text_editor.cpp create mode 100644 src/core/devtools/widget/text_editor.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 37021746d..781e93e10 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -354,15 +354,25 @@ set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp set(DEV_TOOLS src/core/devtools/layer.cpp src/core/devtools/layer.h + src/core/devtools/options.cpp + src/core/devtools/options.h src/core/devtools/gcn/gcn_context_regs.cpp src/core/devtools/gcn/gcn_op_names.cpp src/core/devtools/gcn/gcn_shader_regs.cpp src/core/devtools/widget/cmd_list.cpp src/core/devtools/widget/cmd_list.h + src/core/devtools/widget/common.h src/core/devtools/widget/frame_dump.cpp src/core/devtools/widget/frame_dump.h src/core/devtools/widget/frame_graph.cpp src/core/devtools/widget/frame_graph.h + src/core/devtools/widget/imgui_memory_editor.h + src/core/devtools/widget/reg_popup.cpp + src/core/devtools/widget/reg_popup.h + src/core/devtools/widget/reg_view.cpp + src/core/devtools/widget/reg_view.h + src/core/devtools/widget/text_editor.cpp + src/core/devtools/widget/text_editor.h ) set(COMMON src/common/logging/backend.cpp diff --git a/src/core/debug_state.cpp b/src/core/debug_state.cpp index 050143e6e..93b00285d 100644 --- a/src/core/debug_state.cpp +++ b/src/core/debug_state.cpp @@ -1,13 +1,16 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include "common/assert.h" #include "common/native_clock.h" #include "common/singleton.h" #include "debug_state.h" -#include "libraries/kernel/event_queues.h" +#include "devtools/widget/common.h" #include "libraries/kernel/time_management.h" #include "libraries/system/msgdialog.h" +#include "video_core/amdgpu/pm4_cmds.h" using namespace DebugStateType; @@ -95,8 +98,68 @@ void DebugStateImpl::ResumeGuestThreads() { } void DebugStateImpl::RequestFrameDump(s32 count) { + ASSERT(!DumpingCurrentFrame()); gnm_frame_dump_request_count = count; frame_dump_list.clear(); frame_dump_list.resize(count); waiting_submit_pause = true; } + +void DebugStateImpl::PushQueueDump(QueueDump dump) { + ASSERT(DumpingCurrentFrame()); + std::unique_lock lock{frame_dump_list_mutex}; + auto& frame = GetFrameDump(); + { // Find draw calls + auto data = std::span{dump.data}; + auto initial_data = data.data(); + while (!data.empty()) { + const auto* header = reinterpret_cast(data.data()); + const auto type = header->type; + if (type == 2) { + data = data.subspan(1); + } else if (type != 3) { + UNREACHABLE(); + } + const AmdGpu::PM4ItOpcode opcode = header->opcode; + if (Core::Devtools::Widget::IsDrawCall(opcode)) { + const auto offset = + reinterpret_cast(header) - reinterpret_cast(initial_data); + const auto addr = dump.base_addr + offset; + waiting_reg_dumps.emplace(addr, &frame); + waiting_reg_dumps_dbg.emplace( + addr, + fmt::format("#{} h({}) queue {} {} {}", + frame_dump_list.size() - gnm_frame_dump_request_count, addr, + magic_enum::enum_name(dump.type), dump.submit_num, dump.num2)); + } + data = data.subspan(header->NumWords() + 1); + } + } + frame.queues.push_back(std::move(dump)); +} + +void DebugStateImpl::PushRegsDump(uintptr_t base_addr, uintptr_t header_addr, + const AmdGpu::Liverpool::Regs& regs) { + std::scoped_lock lock{frame_dump_list_mutex}; + const auto it = waiting_reg_dumps.find(header_addr); + if (it == waiting_reg_dumps.end()) { + return; + } + auto& frame = *it->second; + waiting_reg_dumps.erase(it); + waiting_reg_dumps_dbg.erase(waiting_reg_dumps_dbg.find(header_addr)); + auto& dump = frame.regs[header_addr - base_addr]; + dump.regs = regs; + for (int i = 0; i < RegDump::MaxShaderStages; i++) { + if (regs.stage_enable.IsStageEnabled(i)) { + auto stage = regs.ProgramForStage(i); + if (stage->address_lo != 0) { + auto code = stage->Code(); + dump.stages[i] = ShaderDump{ + .user_data = *stage, + .code = std::vector{code.begin(), code.end()}, + }; + } + } + } +} diff --git a/src/core/debug_state.h b/src/core/debug_state.h index 00c687fa5..26dfa202e 100644 --- a/src/core/debug_state.h +++ b/src/core/debug_state.h @@ -5,10 +5,14 @@ #include #include +#include +#include #include #include #include "common/types.h" +#include "video_core/amdgpu/liverpool.h" +#include "video_core/renderer_vulkan/vk_pipeline_cache.h" #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -42,10 +46,23 @@ struct QueueDump { u32 submit_num; u32 num2; // acb: queue_num; else: buffer_in_submit std::vector data; + uintptr_t base_addr; +}; + +struct ShaderDump { + Vulkan::Liverpool::ShaderProgram user_data{}; + std::vector code{}; +}; + +struct RegDump { + static constexpr size_t MaxShaderStages = 5; + Vulkan::Liverpool::Regs regs{}; + std::array stages{}; }; struct FrameDump { std::vector queues; + std::unordered_map regs; // address -> reg dump }; class DebugStateImpl { @@ -61,15 +78,24 @@ class DebugStateImpl { std::atomic_int32_t gnm_frame_count = 0; s32 gnm_frame_dump_request_count = -1; + std::unordered_map waiting_reg_dumps; + std::unordered_map waiting_reg_dumps_dbg; bool waiting_submit_pause = false; bool should_show_frame_dump = false; - std::mutex frame_dump_list_mutex; + std::shared_mutex frame_dump_list_mutex; std::vector frame_dump_list{}; std::queue debug_message_popup; public: + void ShowDebugMessage(std::string message) { + if (message.empty()) { + return; + } + debug_message_popup.push(std::move(message)); + } + void AddCurrentThreadToGuestList(); void RemoveCurrentThreadFromGuestList(); @@ -99,6 +125,11 @@ public: return gnm_frame_dump_request_count > 0; } + bool DumpingCurrentReg() { + std::shared_lock lock{frame_dump_list_mutex}; + return !waiting_reg_dumps.empty(); + } + bool ShouldPauseInSubmit() const { return waiting_submit_pause && gnm_frame_dump_request_count == 0; } @@ -109,17 +140,10 @@ public: return frame_dump_list[frame_dump_list.size() - gnm_frame_dump_request_count]; } - void PushQueueDump(QueueDump dump) { - std::unique_lock lock{frame_dump_list_mutex}; - GetFrameDump().queues.push_back(std::move(dump)); - } + void PushQueueDump(QueueDump dump); - void ShowDebugMessage(std::string message) { - if (message.empty()) { - return; - } - debug_message_popup.push(std::move(message)); - } + void PushRegsDump(uintptr_t base_addr, uintptr_t header_addr, + const AmdGpu::Liverpool::Regs& regs); }; } // namespace DebugStateType diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index 0c7e85e4c..17ef43bbc 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -10,6 +10,7 @@ #include "imgui/imgui_std.h" #include "imgui_internal.h" #include "layer.h" +#include "options.h" #include "widget/frame_dump.h" #include "widget/frame_graph.h" @@ -18,10 +19,9 @@ using namespace Core::Devtools; using L = Core::Devtools::Layer; static bool show_simple_fps = false; + static float fps_scale = 1.0f; - static bool show_advanced_debug = false; - static int dump_frame_count = 1; static Widget::FrameGraph frame_graph; @@ -29,12 +29,16 @@ static std::vector frame_viewers; static float debug_popup_timing = 3.0f; +static bool just_opened_options = false; + void L::DrawMenuBar() { const auto& ctx = *GImGui; const auto& io = ctx.IO; auto isSystemPaused = DebugState.IsGuestThreadsPaused(); + bool open_popup_options = false; + if (BeginMainMenuBar()) { if (BeginMenu("Options")) { if (MenuItemEx("Emulator Paused", nullptr, nullptr, isSystemPaused)) { @@ -55,6 +59,7 @@ void L::DrawMenuBar() { } ImGui::EndMenu(); } + open_popup_options = MenuItem("Options"); ImGui::EndMenu(); } EndMainMenuBar(); @@ -74,6 +79,11 @@ void L::DrawMenuBar() { } } } + + if (open_popup_options) { + OpenPopup("GPU Tools Options"); + just_opened_options = true; + } } void L::DrawAdvanced() { @@ -91,13 +101,19 @@ void L::DrawAdvanced() { ->AddText({10.0f, io.DisplaySize.y - 40.0f}, IM_COL32_WHITE, "Emulator paused"); } - if (DebugState.should_show_frame_dump) { + if (DebugState.should_show_frame_dump && DebugState.waiting_reg_dumps.empty()) { DebugState.should_show_frame_dump = false; std::unique_lock lock{DebugState.frame_dump_list_mutex}; while (!DebugState.frame_dump_list.empty()) { - auto frame_dump = std::move(DebugState.frame_dump_list.back()); - DebugState.frame_dump_list.pop_back(); + const auto& frame_dump = DebugState.frame_dump_list.back(); frame_viewers.emplace_back(frame_dump); + DebugState.frame_dump_list.pop_back(); + } + static bool first_time = true; + if (first_time) { + first_time = false; + DebugState.ShowDebugMessage("Tip: You can shift+click any\n" + "popup to open a new window"); } } @@ -133,6 +149,30 @@ void L::DrawAdvanced() { debug_popup_timing = 3.0f; } } + + bool close_popup_options = true; + if (BeginPopupModal("GPU Tools Options", &close_popup_options, + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings)) { + static char disassembly_cli[512]; + + if (just_opened_options) { + just_opened_options = false; + auto s = Options.disassembly_cli.copy(disassembly_cli, sizeof(disassembly_cli) - 1); + disassembly_cli[s] = '\0'; + } + + InputText("Shader disassembler: ", disassembly_cli, sizeof(disassembly_cli)); + if (IsItemHovered()) { + SetTooltip(R"(Command to disassemble shaders. Example "dis.exe" --raw "{src}")"); + } + + if (Button("Save")) { + Options.disassembly_cli = disassembly_cli; + SaveIniSettingsToDisk(io.IniFilename); + CloseCurrentPopup(); + } + EndPopup(); + } } void L::DrawSimple() { @@ -140,26 +180,54 @@ void L::DrawSimple() { Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); } +static void LoadSettings(const char* line) { + int i; + float f; + if (sscanf(line, "fps_scale=%f", &f) == 1) { + fps_scale = f; + return; + } + if (sscanf(line, "show_advanced_debug=%d", &i) == 1) { + show_advanced_debug = i != 0; + return; + } + if (sscanf(line, "show_frame_graph=%d", &i) == 1) { + frame_graph.is_open = i != 0; + return; + } + if (sscanf(line, "dump_frame_count=%d", &i) == 1) { + dump_frame_count = i; + return; + } +} + void L::SetupSettings() { frame_graph.is_open = true; + using SettingLoader = void (*)(const char*); + ImGuiSettingsHandler handler{}; handler.TypeName = "DevtoolsLayer"; handler.TypeHash = ImHashStr(handler.TypeName); handler.ReadOpenFn = [](ImGuiContext*, ImGuiSettingsHandler*, const char* name) { - return std::string_view("Data") == name ? (void*)1 : nullptr; + if (std::string_view("Data") == name) { + static_assert(std::is_same_v); + return (void*)&LoadSettings; + } + if (std::string_view("CmdList") == name) { + static_assert( + std::is_same_v); + return (void*)&Widget::CmdListViewer::LoadConfig; + } + if (std::string_view("Options") == name) { + static_assert(std::is_same_v); + return (void*)&LoadOptionsConfig; + } + return (void*)nullptr; }; - handler.ReadLineFn = [](ImGuiContext*, ImGuiSettingsHandler*, void*, const char* line) { - int v; - float f; - if (sscanf(line, "fps_scale=%f", &f) == 1) { - fps_scale = f; - } else if (sscanf(line, "show_advanced_debug=%d", &v) == 1) { - show_advanced_debug = v != 0; - } else if (sscanf(line, "show_frame_graph=%d", &v) == 1) { - frame_graph.is_open = v != 0; - } else if (sscanf(line, "dump_frame_count=%d", &v) == 1) { - dump_frame_count = v; + handler.ReadLineFn = [](ImGuiContext*, ImGuiSettingsHandler*, void* handle, const char* line) { + if (handle != nullptr) { + reinterpret_cast(handle)(line); } }; handler.WriteAllFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) { @@ -169,12 +237,19 @@ void L::SetupSettings() { buf->appendf("show_frame_graph=%d\n", frame_graph.is_open); buf->appendf("dump_frame_count=%d\n", dump_frame_count); buf->append("\n"); + buf->appendf("[%s][CmdList]\n", handler->TypeName); + Widget::CmdListViewer::SerializeConfig(buf); + buf->append("\n"); + buf->appendf("[%s][Options]\n", handler->TypeName); + SerializeOptionsConfig(buf); + buf->append("\n"); }; AddSettingsHandler(&handler); const ImGuiID dock_id = ImHashStr("FrameDumpDock"); DockBuilderAddNode(dock_id, 0); - DockBuilderSetNodePos(dock_id, ImVec2{50.0, 50.0}); + DockBuilderSetNodePos(dock_id, ImVec2{450.0, 150.0}); + DockBuilderSetNodeSize(dock_id, ImVec2{400.0, 500.0}); DockBuilderFinish(dock_id); } diff --git a/src/core/devtools/options.cpp b/src/core/devtools/options.cpp new file mode 100644 index 000000000..82fa6e87e --- /dev/null +++ b/src/core/devtools/options.cpp @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "options.h" + +namespace Core::Devtools { + +TOptions Options; + +void LoadOptionsConfig(const char* line) { + char str[512]; + if (sscanf(line, "disassembly_cli=%511[^\n]", str) == 1) { + Options.disassembly_cli = str; + return; + } +} + +void SerializeOptionsConfig(ImGuiTextBuffer* buf) { + buf->appendf("disassembly_cli=%s\n", Options.disassembly_cli.c_str()); +} + +} // namespace Core::Devtools diff --git a/src/core/devtools/options.h b/src/core/devtools/options.h new file mode 100644 index 000000000..9d291d768 --- /dev/null +++ b/src/core/devtools/options.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +struct ImGuiTextBuffer; + +namespace Core::Devtools { + +struct TOptions { + std::string disassembly_cli; +}; + +extern TOptions Options; + +void LoadOptionsConfig(const char* line); +void SerializeOptionsConfig(ImGuiTextBuffer* buf); + +} // namespace Core::Devtools diff --git a/src/core/devtools/widget/cmd_list.cpp b/src/core/devtools/widget/cmd_list.cpp index 012891c37..f5d31efef 100644 --- a/src/core/devtools/widget/cmd_list.cpp +++ b/src/core/devtools/widget/cmd_list.cpp @@ -32,6 +32,26 @@ const char* GetOpCodeName(u32 op); namespace Core::Devtools::Widget { +static bool group_batches = true; +static bool show_markers = false; + +void CmdListViewer::LoadConfig(const char* line) { + int i; + if (sscanf(line, "group_batches=%d", &i) == 1) { + group_batches = i != 0; + return; + } + if (sscanf(line, "show_markers=%d", &i) == 1) { + show_markers = i != 0; + return; + } +} + +void CmdListViewer::SerializeConfig(ImGuiTextBuffer* buf) { + buf->appendf("group_batches=%d\n", group_batches); + buf->appendf("show_markers=%d\n", show_markers); +} + template static HdrType GetNext(HdrType this_pm4, uint32_t n) { HdrType curr_pm4 = this_pm4; @@ -43,10 +63,11 @@ static HdrType GetNext(HdrType this_pm4, uint32_t n) { return curr_pm4; } -static void ParsePolygonControl(u32 value) { +void ParsePolygonControl(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("PA_SU_SC_MODE_CNTL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("PA_SU_SC_MODE_CNTL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("CULL_FRONT"); @@ -126,14 +147,17 @@ static void ParsePolygonControl(u32 value) { TableSetColumnIndex(1); Text("%X", reg.multi_prim_ib_ena.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseAaConfig(u32 value) { +void ParseAaConfig(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("PA_SC_AA_CONFIG", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("PA_SC_AA_CONFIG", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("MSAA_NUM_SAMPLES"); @@ -164,14 +188,17 @@ static void ParseAaConfig(u32 value) { TableSetColumnIndex(1); Text("%X", reg.detail_to_exposed_mode.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseViewportControl(u32 value) { +void ParseViewportControl(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("PA_CL_VTE_CNTL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("PA_CL_VTE_CNTL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("VPORT_X_SCALE_ENA"); @@ -232,14 +259,17 @@ static void ParseViewportControl(u32 value) { TableSetColumnIndex(1); Text("%X", reg.perfcounter_ref.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseColorControl(u32 value) { +void ParseColorControl(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("CB_COLOR_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("CB_COLOR_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("DISABLE_DUAL_QUAD__VI"); @@ -264,14 +294,17 @@ static void ParseColorControl(u32 value) { TableSetColumnIndex(1); Text("%X", reg.rop3.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseColor0Info(u32 value) { +void ParseColor0Info(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("CB_COLOR_INFO", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("CB_COLOR_INFO", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("ENDIAN"); @@ -380,14 +413,17 @@ static void ParseColor0Info(u32 value) { TableSetColumnIndex(1); Text("%X", reg.cmask_addr_type.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseColor0Attrib(u32 value) { +void ParseColor0Attrib(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("CB_COLOR_ATTRIB", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("CB_COLOR_ATTRIB", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("TILE_MODE_INDEX"); @@ -424,14 +460,17 @@ static void ParseColor0Attrib(u32 value) { TableSetColumnIndex(1); Text("%X", reg.force_dst_alpha_1.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseBlendControl(u32 value) { +void ParseBlendControl(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("CB_BLEND_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("CB_BLEND_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("COLOR_SRCBLEND"); @@ -490,14 +529,17 @@ static void ParseBlendControl(u32 value) { TableSetColumnIndex(1); Text("%X", reg.disable_rop3.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseDepthRenderControl(u32 value) { +void ParseDepthRenderControl(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("DB_RENDER_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("DB_RENDER_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("DEPTH_CLEAR_ENABLE"); @@ -558,14 +600,17 @@ static void ParseDepthRenderControl(u32 value) { TableSetColumnIndex(1); Text("%X", reg.decompress_enable.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseDepthControl(u32 value) { +void ParseDepthControl(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("DB_DEPTH_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("DB_DEPTH_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("STENCIL_ENABLE"); @@ -628,14 +673,17 @@ static void ParseDepthControl(u32 value) { TableSetColumnIndex(1); Text("%X", reg.disable_color_writes_on_depth_pass.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseEqaa(u32 value) { +void ParseEqaa(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("DB_DEPTH_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("DB_DEPTH_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("MAX_ANCHOR_SAMPLES"); @@ -708,14 +756,17 @@ static void ParseEqaa(u32 value) { TableSetColumnIndex(1); Text("%X", reg.enable_postz_overrasterization.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } -static void ParseZInfo(u32 value) { +void ParseZInfo(u32 value, bool begin_table) { auto const reg = reinterpret_cast(value); - if (BeginTable("DB_DEPTH_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (!begin_table || + BeginTable("DB_DEPTH_CONTROL", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { TableNextRow(); TableSetColumnIndex(0); Text("FORMAT"); @@ -776,40 +827,41 @@ static void ParseZInfo(u32 value) { TableSetColumnIndex(1); Text("%X", reg.zrange_precision.Value()); - EndTable(); + if (begin_table) { + EndTable(); + } } } void CmdListViewer::OnNop(AmdGpu::PM4Type3Header const* header, u32 const* body) { using namespace std::string_view_literals; - enum class NOP_PAYLOAD : u32 { - ACB_SUBMIT_MRK = 0x68750013, - ALLOC_ALIGN8 = 0x68753000, - PUSH_MARKER = 0x68750001, - SET_VSHARP = 0x68750004, - SET_TSHARP = 0x68750005, - SET_SSHARP = 0x68750006, - SET_USER_DATA = 0x6875000d, - }; - auto get_noppayload_text = [](NOP_PAYLOAD const nop_payload) { +#define NOP_PAYLOAD \ + P(PUSH_MARKER, 0x68750001) \ + P(POP_MARKER, 0x68750002) \ + P(SET_MARKER, 0x68750003) \ + P(SET_VSHARP, 0x68750004) \ + P(SET_TSHARP, 0x68750005) \ + P(SET_SSHARP, 0x68750006) \ + P(ACB_SUBMIT_MRK, 0x68750013) \ + P(SET_USER_DATA, 0x6875000D) \ + P(PATCHED_FLIP, 0x68750776) \ + P(PREPARE_FLIP, 0x68750777) \ + P(PREPARE_FLIP_LABEL, 0x68750778) \ + P(PREPARE_FLIP_INTERRUPT, 0x68750780) \ + P(PREPARE_FLIP_INTERRUPT_LABEL, 0x68750781) \ + P(ALLOC_ALIGN8, 0x68753000) + + auto get_nop_payload_text = [](u32 const nop_payload) { switch (nop_payload) { - case NOP_PAYLOAD::ACB_SUBMIT_MRK: - return "ACB_SUBMIT_MRK"sv; - case NOP_PAYLOAD::ALLOC_ALIGN8: - return "ALLOC_ALIGN8"sv; - case NOP_PAYLOAD::PUSH_MARKER: - return "PUSH_MARKER"sv; - case NOP_PAYLOAD::SET_VSHARP: - return "SET_VSHARP"sv; - case NOP_PAYLOAD::SET_TSHARP: - return "SET_TSHARP"sv; - case NOP_PAYLOAD::SET_SSHARP: - return "SET_SSHARP"sv; - case NOP_PAYLOAD::SET_USER_DATA: - return "SET_USER_DATA"sv; +#define P(name, value) \ + case value: \ + return #name##sv; + NOP_PAYLOAD +#undef P + default: + return ""sv; } - return ""sv; }; Separator(); @@ -822,7 +874,7 @@ void CmdListViewer::OnNop(AmdGpu::PM4Type3Header const* header, u32 const* body) for (unsigned i = 0; i < pkt->header.count + 1; ++i) { Text("%02X: %08X", i, payload[i]); if ((payload[i] & 0xffff0000) == 0x68750000) { - const auto& e = get_noppayload_text((NOP_PAYLOAD)payload[i]); + const auto& e = get_nop_payload_text(payload[i]); if (!e.empty()) { SameLine(); Text("(%s)", e.data()); @@ -836,7 +888,7 @@ void CmdListViewer::OnSetBase(AmdGpu::PM4Type3Header const* header, u32 const* b Separator(); BeginGroup(); - auto const* pkt = reinterpret_cast(header); + // auto const* pkt = reinterpret_cast(header); Text("BASE_INDEX: %08X", body[0]); Text("ADDRESS0 : %08X", body[1]); Text("ADDRESS1 : %08X", body[2]); @@ -1025,20 +1077,31 @@ void CmdListViewer::OnDispatch(AmdGpu::PM4Type3Header const* header, u32 const* EndGroup(); } -CmdListViewer::CmdListViewer(FrameDumpViewer* parent, const std::vector& cmd_list) - : parent(parent) { +CmdListViewer::CmdListViewer(DebugStateType::FrameDump* _frame_dump, + const std::vector& cmd_list, uintptr_t _base_addr, + std::string _name) + : frame_dump(_frame_dump), base_addr(_base_addr), name(std::move(_name)) { using namespace AmdGpu; cmdb_addr = (uintptr_t)cmd_list.data(); cmdb_size = cmd_list.size() * sizeof(u32); + cmdb_view_name = fmt::format("[GFX] Command buffer {}###cmdview_hex_{}", this->name, cmdb_addr); + cmdb_view.Open = false; + cmdb_view.ReadOnly = true; + auto const* pm4_hdr = reinterpret_cast(cmdb_addr); size_t processed_size = 0; size_t prev_offset = 0; + u32 batch_id = 0; std::string marker{}; + if (cmdb_size > 0) { + events.emplace_back(BatchBegin{.id = 0}); + } + while (processed_size < cmdb_size) { auto* next_pm4_hdr = GetNext(pm4_hdr, 1); auto processed_len = @@ -1048,22 +1111,28 @@ CmdListViewer::CmdListViewer(FrameDumpViewer* parent, const std::vector& cm if (pm4_hdr->type == PM4Type3Header::TYPE) { auto const* pm4_t3 = reinterpret_cast(pm4_hdr); + auto opcode = pm4_t3->opcode; - if (pm4_t3->opcode == PM4ItOpcode::Nop) { + if (opcode == PM4ItOpcode::Nop) { auto const* it_body = reinterpret_cast(pm4_hdr + 1); - if (it_body[0] == 0x68750001) { + switch (it_body[0]) { + case PM4CmdNop::PayloadType::DebugSetMarker: marker = std::string{(char*)&it_body[1]}; + break; + case PM4CmdNop::PayloadType::DebugMarkerPush: + events.emplace_back(PushMarker{ + .name = std::string{(char*)&it_body[1]}, + }); + break; + case PM4CmdNop::PayloadType::DebugMarkerPop: + events.emplace_back(PopMarker{}); + break; + default: + break; } } - if (pm4_t3->opcode == PM4ItOpcode::DispatchDirect || - pm4_t3->opcode == PM4ItOpcode::DispatchIndirect || - pm4_t3->opcode == PM4ItOpcode::DrawIndex2 || - pm4_t3->opcode == PM4ItOpcode::DrawIndexAuto || - pm4_t3->opcode == PM4ItOpcode::DrawIndexOffset2 || - pm4_t3->opcode == PM4ItOpcode::DrawIndexIndirect - // ... - ) { + if (IsDrawCall(opcode)) { // All these commands are terminated by NOP at the end, so // it is safe to skip it to be even with CP // next_pm4_hdr = get_next(next_pm4_hdr, 1); @@ -1071,15 +1140,17 @@ CmdListViewer::CmdListViewer(FrameDumpViewer* parent, const std::vector& cm // processed_len += nop_len; // processed_size += nop_len; - batches.emplace_back(BatchInfo{ - marker, - prev_offset, - processed_size, - processed_size - processed_len, - pm4_t3->opcode, + events.emplace_back(BatchInfo{ + .id = batch_id++, + .marker = marker, + .start_addr = prev_offset, + .end_addr = processed_size, + .command_addr = processed_size - processed_len, + .type = opcode, }); prev_offset = processed_size; marker.clear(); + events.emplace_back(BatchBegin{.id = batch_id}); } } @@ -1088,18 +1159,58 @@ CmdListViewer::CmdListViewer(FrameDumpViewer* parent, const std::vector& cm // state batch (last) if (processed_size - prev_offset > 0) { - batches.emplace_back(BatchInfo{ - marker, - prev_offset, - processed_size, - 0, - static_cast(0xFF), + events.emplace_back(BatchInfo{ + .id = batch_id++, + .marker = marker, + .start_addr = prev_offset, + .end_addr = processed_size, + .command_addr = 0, + .type = static_cast(0xFF), }); } + if (!events.empty() && std::holds_alternative(events.back())) { + events.pop_back(); + } } void CmdListViewer::Draw() { + const auto& ctx = *GetCurrentContext(); + + if (batch_view.open) { + batch_view.Draw(); + } + for (auto it = extra_batch_view.begin(); it != extra_batch_view.end();) { + if (!it->open) { + it = extra_batch_view.erase(it); + continue; + } + it->Draw(); + ++it; + } + + if (cmdb_view.Open) { + MemoryEditor::Sizes s; + cmdb_view.CalcSizes(s, cmdb_size, cmdb_addr); + SetNextWindowSize({s.WindowWidth, s.WindowWidth * 0.6f}, ImGuiCond_FirstUseEver); + SetNextWindowSizeConstraints({0.0f}, {s.WindowWidth, FLT_MAX}); + if (Begin(cmdb_view_name.c_str(), &cmdb_view.Open, + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings)) { + cmdb_view.DrawContents((void*)cmdb_addr, cmdb_size, base_addr); + if (cmdb_view.ContentsWidthChanged) { + cmdb_view.CalcSizes(s, cmdb_size, cmdb_addr); + SetWindowSize({s.WindowWidth, s.WindowWidth * 0.6f}); + } + } + End(); + } + + PushID(name.c_str()); if (BeginChild("cmd_queue", {})) { + + Checkbox("Group batches", &group_batches); + SameLine(); + Checkbox("Show markers", &show_markers); + char queue_name[32]{}; if (vqid < 254) { std::snprintf(queue_name, sizeof(queue_name), "%s %d", vqid > 254 ? "GFX" : "ASC", @@ -1111,113 +1222,240 @@ void CmdListViewer::Draw() { Text("queue : %s", queue_name); Text("base addr: %08llX", cmdb_addr); SameLine(); - if (SmallButton(">")) { - parent->cmdb_view.Open ^= true; + if (SmallButton("Memory >")) { + cmdb_view.Open ^= true; } Text("size : %04llX", cmdb_size); Separator(); - char batch_hdr[128]; - for (int batch_id = 0; batch_id < batches.size(); ++batch_id) { - auto processed_size = 0ull; - auto const* pm4_hdr = - reinterpret_cast(cmdb_addr + batches[batch_id].start_addr); + if (TreeNode("Batches")) { + int tree_depth = 0; + int tree_depth_show = 0; - sprintf(batch_hdr, "%08llX: batch-%03d | %s", cmdb_addr + batches[batch_id].start_addr, - batch_id, batches[batch_id].marker.c_str()); - - if (batch_id == batch_bp) { // highlight batch at breakpoint - PushStyleColor(ImGuiCol_Header, ImVec4{1.0f, 0.5f, 0.5f, 0.5f}); + u32 last_batch_id = ~0u; + if (!events.empty() && std::holds_alternative(events.back())) { + last_batch_id = std::get(events.back()).id; } - if (batches[batch_id].type == static_cast(0xFF) || - CollapsingHeader(batch_hdr)) { - auto const batch_sz = batches[batch_id].end_addr - batches[batch_id].start_addr; - while (processed_size < batch_sz) { - AmdGpu::PM4ItOpcode op{0xFFu}; + u32 batch_id = ~0u; + u32 current_highlight_batch = ~0u; - if (pm4_hdr->type == AmdGpu::PM4Type3Header::TYPE) { - auto const* pm4_t3 = - reinterpret_cast(pm4_hdr); - op = pm4_t3->opcode; + int id = 0; + PushID(0); + for (const auto& event : events) { + PopID(); + PushID(id++); - static char header_name[128]; - sprintf(header_name, "%08llX: %s", - cmdb_addr + batches[batch_id].start_addr + processed_size, - Gcn::GetOpCodeName((u32)op)); + if (std::holds_alternative(event)) { + batch_id = std::get(event).id; + } - if (TreeNode(header_name)) { - bool just_opened = IsItemToggledOpen(); - if (BeginTable("split", 1)) { - TableNextColumn(); - Text("size: %d", pm4_hdr->count + 1); + if (show_markers) { + if (std::holds_alternative(event)) { + if (tree_depth_show >= tree_depth) { + auto& marker = std::get(event); + bool show = TreeNode(&event, "%s", marker.name.c_str()); + if (show) { + tree_depth_show++; + } + } + tree_depth++; + continue; + } + if (std::holds_alternative(event)) { + if (tree_depth_show >= tree_depth) { + tree_depth_show--; + TreePop(); + } + tree_depth--; + continue; + } + if (tree_depth_show < tree_depth) { + continue; + } + } - if (just_opened) { + if (!std::holds_alternative(event)) { + continue; + } + + auto& batch = std::get(event); + auto const* pm4_hdr = + reinterpret_cast(cmdb_addr + batch.start_addr); + + char batch_hdr[128]; + if (batch.type == static_cast(0xFF)) { + snprintf(batch_hdr, sizeof(batch_hdr), "State batch"); + } else if (!batch.marker.empty()) { + snprintf(batch_hdr, sizeof(batch_hdr), "%08llX: batch-%03d %s | %s", + cmdb_addr + batch.start_addr, batch.id, + Gcn::GetOpCodeName(static_cast(batch.type)), + batch.marker.c_str()); + } else { + snprintf(batch_hdr, sizeof(batch_hdr), "%08llX: batch-%03d %s", + cmdb_addr + batch.start_addr, batch.id, + Gcn::GetOpCodeName(static_cast(batch.type))); + } + + if (batch.id == batch_bp) { // highlight batch at breakpoint + PushStyleColor(ImGuiCol_Header, ImVec4{1.0f, 0.5f, 0.5f, 0.5f}); + } + if (batch.id == highlight_batch) { + PushStyleColor(ImGuiCol_Text, ImVec4{1.0f, 0.7f, 0.7f, 1.0f}); + } + + const auto open_batch_view = [&, this] { + if (frame_dump->regs.contains(batch.command_addr)) { + auto data = frame_dump->regs.at(batch.command_addr); + if (GetIO().KeyShift) { + auto& pop = extra_batch_view.emplace_back(); + pop.SetData(data, batch_id); + pop.open = true; + } else { + batch_view.SetData(data, batch_id); + batch_view.open = true; + } + } + }; + + bool show_batch_content = true; + + if (group_batches) { + show_batch_content = + CollapsingHeader(batch_hdr, ImGuiTreeNodeFlags_AllowOverlap); + SameLine(GetContentRegionAvail().x - 40.0f); + if (Button("->", {40.0f, 0.0f})) { + open_batch_view(); + } + } + + if (show_batch_content) { + auto processed_size = 0ull; + auto bb = ctx.LastItemData.Rect; + if (group_batches) { + Indent(); + } + auto const batch_sz = batch.end_addr - batch.start_addr; + + while (processed_size < batch_sz) { + AmdGpu::PM4ItOpcode op{0xFFu}; + + if (pm4_hdr->type == AmdGpu::PM4Type3Header::TYPE) { + auto const* pm4_t3 = + reinterpret_cast(pm4_hdr); + op = pm4_t3->opcode; + + char header_name[128]; + sprintf(header_name, "%08llX: %s", + cmdb_addr + batch.start_addr + processed_size, + Gcn::GetOpCodeName((u32)op)); + + bool open_pm4 = TreeNode(header_name); + if (!group_batches) { + if (IsDrawCall(op)) { + SameLine(GetContentRegionAvail().x - 40.0f); + if (Button("->", {40.0f, 0.0f})) { + open_batch_view(); + } + } + if (IsItemHovered() && ctx.IO.KeyShift) { + if (BeginTooltip()) { + Text("Batch %d", batch_id); + EndTooltip(); + } + } + } + if (open_pm4) { + if (IsItemToggledOpen()) { // Editor - parent->cmdb_view.GotoAddrAndHighlight( + cmdb_view.GotoAddrAndHighlight( reinterpret_cast(pm4_hdr) - cmdb_addr, reinterpret_cast(pm4_hdr) - cmdb_addr + (pm4_hdr->count + 2) * 4); } - auto const* it_body = - reinterpret_cast(pm4_hdr + 1); + if (BeginTable("split", 1)) { + TableNextColumn(); + Text("size: %d", pm4_hdr->count + 1); - switch (op) { - case AmdGpu::PM4ItOpcode::Nop: { - OnNop(pm4_t3, it_body); - break; - } - case AmdGpu::PM4ItOpcode::SetBase: { - OnSetBase(pm4_t3, it_body); - break; - } - case AmdGpu::PM4ItOpcode::SetContextReg: { - OnSetContextReg(pm4_t3, it_body); - break; - } - case AmdGpu::PM4ItOpcode::SetShReg: { - OnSetShReg(pm4_t3, it_body); - break; - } - case AmdGpu::PM4ItOpcode::DispatchDirect: { - OnDispatch(pm4_t3, it_body); - break; - } - default: { - auto const* payload = &it_body[0]; - for (unsigned i = 0; i < pm4_hdr->count + 1; ++i) { - Text("%02X: %08X", i, payload[i]); + auto const* it_body = + reinterpret_cast(pm4_hdr + 1); + + switch (op) { + case AmdGpu::PM4ItOpcode::Nop: { + OnNop(pm4_t3, it_body); + break; + } + case AmdGpu::PM4ItOpcode::SetBase: { + OnSetBase(pm4_t3, it_body); + break; + } + case AmdGpu::PM4ItOpcode::SetContextReg: { + OnSetContextReg(pm4_t3, it_body); + break; + } + case AmdGpu::PM4ItOpcode::SetShReg: { + OnSetShReg(pm4_t3, it_body); + break; + } + case AmdGpu::PM4ItOpcode::DispatchDirect: { + OnDispatch(pm4_t3, it_body); + break; + } + default: { + auto const* payload = &it_body[0]; + for (unsigned i = 0; i < pm4_hdr->count + 1; ++i) { + Text("%02X: %08X", i, payload[i]); + } + } } - } - } - EndTable(); + EndTable(); + } + TreePop(); } - TreePop(); + + } else { + Text(""); } - } else { - Text(""); + + auto const* next_pm4_hdr = GetNext(pm4_hdr, 1); + auto const processed_len = reinterpret_cast(next_pm4_hdr) - + reinterpret_cast(pm4_hdr); + pm4_hdr = next_pm4_hdr; + processed_size += processed_len; } - auto const* next_pm4_hdr = GetNext(pm4_hdr, 1); - auto const processed_len = reinterpret_cast(next_pm4_hdr) - - reinterpret_cast(pm4_hdr); - pm4_hdr = next_pm4_hdr; - processed_size += processed_len; + if (group_batches) { + Unindent(); + }; + bb = {{0.0f, bb.Max.y}, ctx.LastItemData.Rect.Max}; + if (bb.Contains(ctx.IO.MousePos)) { + current_highlight_batch = batch.id; + } + } + + if (batch.id == highlight_batch) { + PopStyleColor(); + } + + if (batch.id == batch_bp) { + PopStyleColor(); + } + + if (batch.id == last_batch_id) { + Separator(); } } + PopID(); - if (batch_id == batch_bp) { - PopStyleColor(); - } + highlight_batch = current_highlight_batch; - if (batch_id == batches.size() - 2) { - Separator(); - } + TreePop(); } } EndChild(); + PopID(); } } // namespace Core::Devtools::Widget \ No newline at end of file diff --git a/src/core/devtools/widget/cmd_list.h b/src/core/devtools/widget/cmd_list.h index a6ecd9323..971c8fffe 100644 --- a/src/core/devtools/widget/cmd_list.h +++ b/src/core/devtools/widget/cmd_list.h @@ -5,10 +5,14 @@ #pragma once +#include #include +#include +#include "common.h" #include "common/types.h" -#include "video_core/buffer_cache/buffer_cache.h" +#include "imgui_memory_editor.h" +#include "reg_view.h" namespace AmdGpu { union PM4Type3Header; @@ -19,43 +23,50 @@ namespace Core::Devtools::Widget { class FrameDumpViewer; -class CmdListViewer { - /* - * Generic PM4 header - */ - union PM4Header { - struct { - u32 reserved : 16; - u32 count : 14; - u32 type : 2; // PM4_TYPE - }; - u32 u32All; - }; - struct BatchInfo { - std::string marker{}; - size_t start_addr; - size_t end_addr; - size_t command_addr; - AmdGpu::PM4ItOpcode type; - bool bypass{false}; - }; +void ParsePolygonControl(u32 value, bool begin_table = true); +void ParseAaConfig(u32 value, bool begin_table = true); +void ParseViewportControl(u32 value, bool begin_table = true); +void ParseColorControl(u32 value, bool begin_table = true); +void ParseColor0Info(u32 value, bool begin_table = true); +void ParseColor0Attrib(u32 value, bool begin_table = true); +void ParseBlendControl(u32 value, bool begin_table = true); +void ParseDepthRenderControl(u32 value, bool begin_table = true); +void ParseDepthControl(u32 value, bool begin_table = true); +void ParseEqaa(u32 value, bool begin_table = true); +void ParseZInfo(u32 value, bool begin_table = true); - FrameDumpViewer* parent; - std::vector batches{}; +class CmdListViewer { + + DebugStateType::FrameDump* frame_dump; + + uintptr_t base_addr; + std::string name; + std::vector events{}; uintptr_t cmdb_addr; size_t cmdb_size; + std::string cmdb_view_name; + MemoryEditor cmdb_view; + int batch_bp{-1}; int vqid{255}; + u32 highlight_batch{~0u}; - void OnNop(AmdGpu::PM4Type3Header const* header, u32 const* body); - void OnSetBase(AmdGpu::PM4Type3Header const* header, u32 const* body); - void OnSetContextReg(AmdGpu::PM4Type3Header const* header, u32 const* body); - void OnSetShReg(AmdGpu::PM4Type3Header const* header, u32 const* body); - void OnDispatch(AmdGpu::PM4Type3Header const* header, u32 const* body); + RegView batch_view; + std::vector extra_batch_view; + + static void OnNop(AmdGpu::PM4Type3Header const* header, u32 const* body); + static void OnSetBase(AmdGpu::PM4Type3Header const* header, u32 const* body); + static void OnSetContextReg(AmdGpu::PM4Type3Header const* header, u32 const* body); + static void OnSetShReg(AmdGpu::PM4Type3Header const* header, u32 const* body); + static void OnDispatch(AmdGpu::PM4Type3Header const* header, u32 const* body); public: - explicit CmdListViewer(FrameDumpViewer* parent, const std::vector& cmd_list); + static void LoadConfig(const char* line); + static void SerializeConfig(ImGuiTextBuffer* buf); + + explicit CmdListViewer(DebugStateType::FrameDump* frame_dump, const std::vector& cmd_list, + uintptr_t base_addr = 0, std::string name = ""); void Draw(); }; diff --git a/src/core/devtools/widget/common.h b/src/core/devtools/widget/common.h new file mode 100644 index 000000000..701d16399 --- /dev/null +++ b/src/core/devtools/widget/common.h @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include + +#include "common/types.h" +#include "video_core/amdgpu/pm4_opcodes.h" + +namespace Core::Devtools::Widget { + +/* + * Generic PM4 header + */ +union PM4Header { + struct { + u32 reserved : 16; + u32 count : 14; + u32 type : 2; // PM4_TYPE + }; + u32 u32All; +}; + +struct PushMarker { + std::string name{}; +}; + +struct PopMarker {}; + +struct BatchBegin { + u32 id; +}; + +struct BatchInfo { + u32 id; + std::string marker{}; + size_t start_addr; + size_t end_addr; + size_t command_addr; + AmdGpu::PM4ItOpcode type; + bool bypass{false}; +}; + +using GPUEvent = std::variant; + +template +void DrawRow(const char* text, const char* fmt, Args... args) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted(text); + ImGui::TableNextColumn(); + char buf[128]; + snprintf(buf, sizeof(buf), fmt, args...); + ImGui::TextUnformatted(buf); +} + +template +void DrawEnumRow(const char* text, T value) { + DrawRow(text, "%X (%s)", V(value), magic_enum::enum_name(value).data()); +} + +template +void DrawMultipleRow(const char* text, const char* fmt, V arg, Extra&&... extra_args) { + DrawRow(text, fmt, arg); + if constexpr (sizeof...(extra_args) > 0) { + DrawMultipleRow(std::forward(extra_args)...); + } +} + +template +static void DoTooltip(const char* str_id, Args&&... args) { + if (ImGui::BeginTooltip()) { + if (ImGui::BeginTable(str_id, 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + DrawMultipleRow(std::forward(args)...); + ImGui::EndTable(); + } + ImGui::EndTooltip(); + } +} + +static bool IsDrawCall(AmdGpu::PM4ItOpcode opcode) { + using AmdGpu::PM4ItOpcode; + switch (opcode) { + case PM4ItOpcode::DrawIndex2: + case PM4ItOpcode::DrawIndexOffset2: + case PM4ItOpcode::DrawIndexAuto: + case PM4ItOpcode::DrawIndirect: + case PM4ItOpcode::DrawIndexIndirect: + case PM4ItOpcode::DispatchDirect: + case PM4ItOpcode::DispatchIndirect: + return true; + default: + return false; + } +} + +} // namespace Core::Devtools::Widget \ No newline at end of file diff --git a/src/core/devtools/widget/frame_dump.cpp b/src/core/devtools/widget/frame_dump.cpp index d27bab90a..29b5cb8ee 100644 --- a/src/core/devtools/widget/frame_dump.cpp +++ b/src/core/devtools/widget/frame_dump.cpp @@ -36,7 +36,8 @@ static std::array small_int_to_str(const s32 i) { namespace Core::Devtools::Widget { -FrameDumpViewer::FrameDumpViewer(FrameDump _frame_dump) : frame_dump(std::move(_frame_dump)) { +FrameDumpViewer::FrameDumpViewer(const FrameDump& _frame_dump) + : frame_dump(std::make_shared(_frame_dump)) { static int unique_id = 0; id = unique_id++; @@ -44,20 +45,19 @@ FrameDumpViewer::FrameDumpViewer(FrameDump _frame_dump) : frame_dump(std::move(_ selected_submit_num = 0; selected_queue_num2 = 0; - cmd_list_viewer.reserve(frame_dump.queues.size()); - for (const auto& cmd : frame_dump.queues) { - cmd_list_viewer.emplace_back(this, cmd.data); - if (cmd.type == QueueType::dcb && cmd.submit_num == selected_submit_num && - cmd.num2 == selected_queue_num2) { - selected_cmd = cmd_list_viewer.size() - 1; + cmd_list_viewer.reserve(frame_dump->queues.size()); + for (const auto& cmd : frame_dump->queues) { + const auto fname = + fmt::format("{}_{}_{:02}_{:02}", id, magic_enum::enum_name(selected_queue_type), + selected_submit_num, selected_queue_num2); + cmd_list_viewer.emplace_back(frame_dump.get(), cmd.data, cmd.base_addr, fname); + if (cmd.type == QueueType::dcb && cmd.submit_num == 0 && cmd.num2 == 0) { + selected_cmd = static_cast(cmd_list_viewer.size() - 1); } } - - cmdb_view.Open = false; - cmdb_view.ReadOnly = true; } -FrameDumpViewer::~FrameDumpViewer() {} +FrameDumpViewer::~FrameDumpViewer() = default; void FrameDumpViewer::Draw() { if (!is_open) { @@ -66,11 +66,11 @@ void FrameDumpViewer::Draw() { char name[32]; snprintf(name, sizeof(name), "Frame #%d dump", id); - static ImGuiID dock_id = ImHashStr("FrameDumpDock"); - SetNextWindowDockID(dock_id, ImGuiCond_Appearing); if (Begin(name, &is_open, ImGuiWindowFlags_NoSavedSettings)) { if (IsWindowAppearing()) { auto window = GetCurrentWindow(); + static ImGuiID dock_id = ImHashStr("FrameDumpDock"); + SetWindowDock(window, dock_id, ImGuiCond_Once | ImGuiCond_FirstUseEver); SetWindowSize(window, ImVec2{470.0f, 600.0f}); } BeginGroup(); @@ -89,12 +89,30 @@ void FrameDumpViewer::Draw() { EndCombo(); } SameLine(); + BeginDisabled(selected_cmd == -1); + if (SmallButton("Dump cmd")) { + auto now_time = fmt::localtime(std::time(nullptr)); + const auto fname = fmt::format("{:%F %H-%M-%S} {}_{}_{}.bin", now_time, + magic_enum::enum_name(selected_queue_type), + selected_submit_num, selected_queue_num2); + Common::FS::IOFile file(fname, Common::FS::FileAccessMode::Write); + const auto& data = frame_dump->queues[selected_cmd].data; + if (file.IsOpen()) { + DebugState.ShowDebugMessage(fmt::format("Dumping cmd as {}", fname)); + file.Write(data); + } else { + DebugState.ShowDebugMessage(fmt::format("Failed to save {}", fname)); + LOG_ERROR(Core, "Failed to open file {}", fname); + } + } + EndDisabled(); + TextEx("Submit num"); SameLine(); if (BeginCombo("##select_submit_num", small_int_to_str(selected_submit_num).data(), ImGuiComboFlags_WidthFitPreview)) { std::array available_submits{}; - for (const auto& cmd : frame_dump.queues) { + for (const auto& cmd : frame_dump->queues) { if (cmd.type == selected_queue_type) { available_submits[cmd.submit_num] = true; } @@ -119,7 +137,7 @@ void FrameDumpViewer::Draw() { if (BeginCombo("##select_queue_num2", small_int_to_str(selected_queue_num2).data(), ImGuiComboFlags_WidthFitPreview)) { std::array available_queues{}; - for (const auto& cmd : frame_dump.queues) { + for (const auto& cmd : frame_dump->queues) { if (cmd.type == selected_queue_type && cmd.submit_num == selected_submit_num) { available_queues[cmd.num2] = true; } @@ -134,34 +152,16 @@ void FrameDumpViewer::Draw() { } } if (selected) { - const auto it = std::ranges::find_if(frame_dump.queues, [&](const auto& cmd) { + const auto it = std::ranges::find_if(frame_dump->queues, [&](const auto& cmd) { return cmd.type == selected_queue_type && cmd.submit_num == selected_submit_num && cmd.num2 == selected_queue_num2; }); - if (it != frame_dump.queues.end()) { - selected_cmd = std::distance(frame_dump.queues.begin(), it); + if (it != frame_dump->queues.end()) { + selected_cmd = static_cast(std::distance(frame_dump->queues.begin(), it)); } } EndCombo(); } - SameLine(); - BeginDisabled(selected_cmd == -1); - if (SmallButton("Dump cmd")) { - auto now_time = fmt::localtime(std::time(nullptr)); - const auto fname = fmt::format("{:%F %H-%M-%S} {}_{}_{}.bin", now_time, - magic_enum::enum_name(selected_queue_type), - selected_submit_num, selected_queue_num2); - Common::FS::IOFile file(fname, Common::FS::FileAccessMode::Write); - auto& data = frame_dump.queues[selected_cmd].data; - if (file.IsOpen()) { - DebugState.ShowDebugMessage(fmt::format("Dumping cmd as {}", fname)); - file.Write(data); - } else { - DebugState.ShowDebugMessage(fmt::format("Failed to save {}", fname)); - LOG_ERROR(Core, "Failed to open file {}", fname); - } - } - EndDisabled(); EndGroup(); if (selected_cmd != -1) { @@ -169,21 +169,6 @@ void FrameDumpViewer::Draw() { } } End(); - - if (cmdb_view.Open && selected_cmd != -1) { - auto& cmd = frame_dump.queues[selected_cmd].data; - auto cmd_size = cmd.size() * sizeof(u32); - MemoryEditor::Sizes s; - cmdb_view.CalcSizes(s, cmd_size, (size_t)cmd.data()); - SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(s.WindowWidth, FLT_MAX)); - - char name[64]; - snprintf(name, sizeof(name), "[GFX] Command buffer %d###cmdbuf_hex_%d", id, id); - if (Begin(name, &cmdb_view.Open, ImGuiWindowFlags_NoScrollbar)) { - cmdb_view.DrawContents(cmd.data(), cmd_size, (size_t)cmd.data()); - } - End(); - } } } // namespace Core::Devtools::Widget diff --git a/src/core/devtools/widget/frame_dump.h b/src/core/devtools/widget/frame_dump.h index d9d11f825..2b3ff2411 100644 --- a/src/core/devtools/widget/frame_dump.h +++ b/src/core/devtools/widget/frame_dump.h @@ -8,7 +8,6 @@ #include "cmd_list.h" #include "core/debug_state.h" -#include "imgui_memory_editor.h" namespace Core::Devtools::Widget { @@ -17,11 +16,10 @@ class CmdListViewer; class FrameDumpViewer { friend class CmdListViewer; - DebugStateType::FrameDump frame_dump; + std::shared_ptr frame_dump; int id; std::vector cmd_list_viewer; - MemoryEditor cmdb_view; DebugStateType::QueueType selected_queue_type; s32 selected_submit_num; @@ -31,7 +29,7 @@ class FrameDumpViewer { public: bool is_open = true; - explicit FrameDumpViewer(DebugStateType::FrameDump frame_dump); + explicit FrameDumpViewer(const DebugStateType::FrameDump& frame_dump); ~FrameDumpViewer(); diff --git a/src/core/devtools/widget/reg_popup.cpp b/src/core/devtools/widget/reg_popup.cpp new file mode 100644 index 000000000..d012437c3 --- /dev/null +++ b/src/core/devtools/widget/reg_popup.cpp @@ -0,0 +1,182 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "reg_popup.h" + +#include +#include +#include + +#include "cmd_list.h" +#include "common.h" + +using namespace ImGui; +using magic_enum::enum_name; + +namespace Core::Devtools::Widget { + +void RegPopup::DrawColorBuffer(const AmdGpu::Liverpool::ColorBuffer& buffer) { + if (BeginTable("COLOR_BUFFER", 2, ImGuiTableFlags_Borders)) { + TableNextRow(); + + // clang-format off + + DrawMultipleRow( + "BASE_ADDR", "%X", buffer.base_address, + "PITCH.TILE_MAX", "%X", buffer.pitch.tile_max, + "PITCH.FMASK_TILE_MAX", "%X", buffer.pitch.fmask_tile_max, + "SLICE.TILE_MAX", "%X", buffer.slice.tile_max, + "VIEW.SLICE_START", "%X", buffer.view.slice_start, + "VIEW.SLICE_MAX", "%X", buffer.view.slice_max + ); + + TableNextRow(); + TableNextColumn(); + if (TreeNode("Color0Info")) { + TableNextRow(); + TableNextColumn(); + ParseColor0Info(buffer.info.u32all, false); + TreePop(); + } + + TableNextRow(); + TableNextColumn(); + if (TreeNode("Color0Attrib")) { + TableNextRow(); + TableNextColumn(); + ParseColor0Attrib(buffer.attrib.u32all, false); + TreePop(); + } + + TableNextRow(); + DrawMultipleRow( + "CMASK_BASE_EXT", "%X", buffer.cmask_base_address, + "FMASK_BASE_EXT", "%X", buffer.fmask_base_address, + "FMASK_SLICE.TILE_MAX", "%X", buffer.fmask_slice.tile_max, + "CLEAR_WORD0", "%X", buffer.clear_word0, + "CLEAR_WORD1", "%X", buffer.clear_word1 + ); + + DrawMultipleRow( + "Pitch()", "%X", buffer.Pitch(), + "Height()", "%X", buffer.Height(), + "Address()", "%X", buffer.Address(), + "CmaskAddress", "%X", buffer.CmaskAddress(), + "FmaskAddress", "%X", buffer.FmaskAddress(), + "NumSamples()", "%X", buffer.NumSamples(), + "NumSlices()", "%X", buffer.NumSlices(), + "GetColorSliceSize()", "%X", buffer.GetColorSliceSize() + ); + + auto tiling_mode = buffer.GetTilingMode(); + auto num_format = buffer.NumFormat(); + DrawEnumRow("GetTilingMode()", tiling_mode); + DrawRow("IsTiled()", "%X", buffer.IsTiled()); + DrawEnumRow("NumFormat()", num_format); + + // clang-format on + + EndTable(); + } +} + +void RegPopup::DrawDepthBuffer(const DepthBuffer& depth_data) { + const auto& [depth_buffer, depth_control] = depth_data; + + SeparatorText("Depth buffer"); + + if (BeginTable("DEPTH_BUFFER", 2, ImGuiTableFlags_Borders)) { + TableNextRow(); + + // clang-format off + DrawEnumRow("Z_INFO.FORMAT", depth_buffer.z_info.format.Value()); + DrawMultipleRow( + "Z_INFO.NUM_SAMPLES", "%X", depth_buffer.z_info.num_samples, + "Z_INFO.TILE_SPLIT", "%X", depth_buffer.z_info.tile_split, + "Z_INFO.TILE_MODE_INDEX", "%X", depth_buffer.z_info.tile_mode_index, + "Z_INFO.DECOMPRESS_ON_N_ZPLANES", "%X", depth_buffer.z_info.decompress_on_n_zplanes, + "Z_INFO.ALLOW_EXPCLEAR", "%X", depth_buffer.z_info.allow_expclear, + "Z_INFO.READ_SIZE", "%X", depth_buffer.z_info.read_size, + "Z_INFO.TILE_SURFACE_EN", "%X", depth_buffer.z_info.tile_surface_en, + "Z_INFO.CLEAR_DISALLOWED", "%X", depth_buffer.z_info.clear_disallowed, + "Z_INFO.ZRANGE_PRECISION", "%X", depth_buffer.z_info.zrange_precision + ); + + DrawEnumRow("STENCIL_INFO.FORMAT", depth_buffer.stencil_info.format.Value()); + + DrawMultipleRow( + "Z_READ_BASE", "%X", depth_buffer.z_read_base, + "STENCIL_READ_BASE", "%X", depth_buffer.stencil_read_base, + "Z_WRITE_BASE", "%X", depth_buffer.z_write_base, + "STENCIL_WRITE_BASE", "%X", depth_buffer.stencil_write_base, + "DEPTH_SIZE.PITCH_TILE_MAX", "%X", depth_buffer.depth_size.pitch_tile_max, + "DEPTH_SIZE.HEIGHT_TILE_MAX", "%X", depth_buffer.depth_size.height_tile_max, + "DEPTH_SLICE.TILE_MAX", "%X", depth_buffer.depth_slice.tile_max, + "Pitch()", "%X", depth_buffer.Pitch(), + "Height()", "%X", depth_buffer.Height(), + "Address()", "%X", depth_buffer.Address(), + "NumSamples()", "%X", depth_buffer.NumSamples(), + "NumBits()", "%X", depth_buffer.NumBits(), + "GetDepthSliceSize()", "%X", depth_buffer.GetDepthSliceSize() + ); + // clang-format on + EndTable(); + } + SeparatorText("Depth control"); + if (BeginTable("DEPTH_CONTROL", 2, ImGuiTableFlags_Borders)) { + TableNextRow(); + + // clang-format off + DrawMultipleRow( + "STENCIL_ENABLE", "%X", depth_control.stencil_enable, + "DEPTH_ENABLE", "%X", depth_control.depth_enable, + "DEPTH_WRITE_ENABLE", "%X", depth_control.depth_write_enable, + "DEPTH_BOUNDS_ENABLE", "%X", depth_control.depth_bounds_enable + ); + DrawEnumRow("DEPTH_FUNC", depth_control.depth_func.Value()); + DrawRow("BACKFACE_ENABLE", "%X", depth_control.backface_enable); + DrawEnumRow("STENCIL_FUNC", depth_control.stencil_ref_func.Value()); + DrawEnumRow("STENCIL_FUNC_BF", depth_control.stencil_bf_func.Value()); + DrawMultipleRow( + "ENABLE_COLOR_WRITES_ON_DEPTH_FAIL", "%X", depth_control.enable_color_writes_on_depth_fail, + "DISABLE_COLOR_WRITES_ON_DEPTH_PASS", "%X", depth_control.disable_color_writes_on_depth_pass + ); + // clang-format on + + EndTable(); + } +} + +RegPopup::RegPopup() { + static int unique_id = 0; + id = unique_id++; +} + +void RegPopup::SetData(AmdGpu::Liverpool::ColorBuffer color_buffer, u32 batch_id, u32 cb_id) { + this->data = color_buffer; + this->title = fmt::format("Batch #{} CB #{}", batch_id, cb_id); +} + +void RegPopup::SetData(AmdGpu::Liverpool::DepthBuffer depth_buffer, + AmdGpu::Liverpool::DepthControl depth_control, u32 batch_id) { + this->data = std::make_tuple(depth_buffer, depth_control); + this->title = fmt::format("Batch #{} Depth", batch_id); +} + +void RegPopup::Draw() { + + char name[128]; + snprintf(name, sizeof(name), "%s###reg_popup_%d", title.c_str(), id); + + SetNextWindowSize({250.0f, 300.0f}, ImGuiCond_FirstUseEver); + if (Begin(name, &open, ImGuiWindowFlags_NoSavedSettings)) { + if (const auto* buffer = std::get_if(&data)) { + DrawColorBuffer(*buffer); + } else if (const auto* depth_data = std::get_if(&data)) { + DrawDepthBuffer(*depth_data); + } + } + End(); +} + +} // namespace Core::Devtools::Widget diff --git a/src/core/devtools/widget/reg_popup.h b/src/core/devtools/widget/reg_popup.h new file mode 100644 index 000000000..ba4224d73 --- /dev/null +++ b/src/core/devtools/widget/reg_popup.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/types.h" +#include "video_core/renderer_vulkan/liverpool_to_vk.h" + +namespace Core::Devtools::Widget { + +class RegPopup { + int id; + + using DepthBuffer = std::tuple; + + std::variant data; + std::string title{}; + + void DrawColorBuffer(const AmdGpu::Liverpool::ColorBuffer& buffer); + + void DrawDepthBuffer(const DepthBuffer& depth_data); + +public: + bool open = false; + + RegPopup(); + + void SetData(AmdGpu::Liverpool::ColorBuffer color_buffer, u32 batch_id, u32 cb_id); + + void SetData(AmdGpu::Liverpool::DepthBuffer depth_buffer, + AmdGpu::Liverpool::DepthControl depth_control, u32 batch_id); + + void Draw(); +}; + +} // namespace Core::Devtools::Widget diff --git a/src/core/devtools/widget/reg_view.cpp b/src/core/devtools/widget/reg_view.cpp new file mode 100644 index 000000000..2e8bb8f54 --- /dev/null +++ b/src/core/devtools/widget/reg_view.cpp @@ -0,0 +1,305 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "common/io_file.h" +#include "core/devtools/options.h" +#include "imgui_internal.h" +#include "reg_view.h" + +#if defined(_WIN32) +#define popen _popen +#define pclose _pclose +#endif + +using namespace ImGui; +using magic_enum::enum_name; + +static std::optional exec_cli(const char* cli) { + std::array buffer{}; + std::string output; + const auto f = popen(cli, "r"); + if (!f) { + pclose(f); + return {}; + } + while (fgets(buffer.data(), buffer.size(), f)) { + output += buffer.data(); + } + pclose(f); + return output; +} + +namespace Core::Devtools::Widget { + +void RegView::ProcessShader(int shader_id) { + auto shader = data.stages[shader_id]; + + std::string shader_dis; + + if (Options.disassembly_cli.empty()) { + shader_dis = "No disassembler set"; + } else { + auto bin_path = std::filesystem::temp_directory_path() / "shadps4_tmp_shader.bin"; + + constexpr std::string_view src_arg = "{src}"; + std::string cli = Options.disassembly_cli; + const auto pos = cli.find(src_arg); + if (pos == std::string::npos) { + DebugState.ShowDebugMessage("Disassembler CLI does not contain {src} argument"); + } else { + cli.replace(pos, src_arg.size(), "\"" + bin_path.string() + "\""); + Common::FS::IOFile file(bin_path, Common::FS::FileAccessMode::Write); + file.Write(shader.code); + file.Close(); + + auto result = exec_cli(cli.c_str()); + shader_dis = result.value_or("Could not disassemble shader"); + if (shader_dis.empty()) { + shader_dis = "Disassembly empty or failed"; + } + + std::filesystem::remove(bin_path); + } + } + + MemoryEditor hex_view; + hex_view.Open = true; + hex_view.ReadOnly = true; + hex_view.Cols = 16; + hex_view.OptShowAscii = false; + + TextEditor dis_view; + dis_view.SetPalette(TextEditor::GetDarkPalette()); + dis_view.SetReadOnly(true); + dis_view.SetText(shader_dis); + + ShaderCache cache{ + .hex_view = hex_view, + .dis_view = dis_view, + .user_data = shader.user_data.user_data, + }; + shader_decomp.emplace(shader_id, std::move(cache)); +} +void RegView::SelectShader(int id) { + selected_shader = id; + if (!shader_decomp.contains(id)) { + ProcessShader(id); + } +} + +void RegView::DrawRegs() { + const auto& regs = data.regs; + + if (BeginTable("REGS", 2, ImGuiTableFlags_Borders)) { + + auto& s = regs.screen_scissor; + DrawRow("Scissor", "(%d, %d, %d, %d)", s.top_left_x, s.top_left_y, s.bottom_right_x, + s.bottom_right_y); + + auto cc_mode = regs.color_control.mode.Value(); + DrawRow("Color control", "%X (%s)", cc_mode, enum_name(cc_mode).data()); + + const auto open_new_popup = [&](int cb, auto... args) { + if (GetIO().KeyShift) { + auto& pop = extra_reg_popup.emplace_back(); + pop.SetData(args...); + pop.open = true; + } else if (last_selected_cb == cb && default_reg_popup.open) { + default_reg_popup.open = false; + } else { + last_selected_cb = cb; + default_reg_popup.SetData(args...); + if (!default_reg_popup.open) { + default_reg_popup.open = true; + auto popup_pos = + GetCurrentContext()->LastItemData.Rect.Max + ImVec2(5.0f, 0.0f); + SetNextWindowPos(popup_pos, ImGuiCond_Always); + default_reg_popup.Draw(); + } + } + }; + + for (int cb = 0; cb < AmdGpu::Liverpool::NumColorBuffers; ++cb) { + PushID(cb); + + TableNextRow(); + TableNextColumn(); + + const auto& buffer = regs.color_buffers[cb]; + + Text("Color buffer %d", cb); + TableNextColumn(); + if (!buffer || !regs.color_target_mask.GetMask(cb)) { + TextUnformatted("N/A"); + } else { + const char* text = last_selected_cb == cb && default_reg_popup.open ? "x" : "->"; + if (SmallButton(text)) { + open_new_popup(cb, buffer, batch_id, cb); + } + } + + PopID(); + } + + TableNextRow(); + TableNextColumn(); + TextUnformatted("Depth buffer"); + TableNextColumn(); + if (regs.depth_buffer.Address() == 0 || !regs.depth_control.depth_enable) { + TextUnformatted("N/A"); + } else { + constexpr auto depth_id = 0xF3; + const char* text = last_selected_cb == depth_id && default_reg_popup.open ? "x" : "->"; + if (SmallButton(text)) { + open_new_popup(depth_id, regs.depth_buffer, regs.depth_control, batch_id); + } + } + + EndTable(); + } +} + +RegView::RegView() { + static int unique_id = 0; + id = unique_id++; + + char name[128]; + snprintf(name, sizeof(name), "BatchView###reg_dump_%d", id); + SetNextWindowPos({400.0f, 200.0f}); + SetNextWindowSize({450.0f, 500.0f}); + ImGuiID root_dock_id; + Begin(name); + { + char dock_name[64]; + snprintf(dock_name, sizeof(dock_name), "BatchView###reg_dump_%d/dock_space", id); + root_dock_id = ImHashStr(dock_name); + DockSpace(root_dock_id); + } + End(); + + ImGuiID up1, down1; + + DockBuilderRemoveNodeChildNodes(root_dock_id); + DockBuilderSplitNode(root_dock_id, ImGuiDir_Up, 0.2f, &up1, &down1); + + snprintf(name, sizeof(name), "User data###reg_dump_%d/user_data", id); + DockBuilderDockWindow(name, up1); + + snprintf(name, sizeof(name), "Regs###reg_dump_%d/regs", id); + DockBuilderDockWindow(name, down1); + + snprintf(name, sizeof(name), "Disassembly###reg_dump_%d/disassembly", id); + DockBuilderDockWindow(name, down1); + + DockBuilderFinish(root_dock_id); +} + +void RegView::SetData(DebugStateType::RegDump data, u32 batch_id) { + this->data = std::move(data); + this->batch_id = batch_id; + // clear cache + selected_shader = -1; + shader_decomp.clear(); + default_reg_popup.open = false; + extra_reg_popup.clear(); +} + +void RegView::Draw() { + + char name[128]; + snprintf(name, sizeof(name), "BatchView %u###reg_dump_%d", batch_id, id); + if (Begin(name, &open, ImGuiWindowFlags_MenuBar)) { + const char* names[] = {"vs", "ps", "gs", "es", "hs", "ls"}; + + if (BeginMenuBar()) { + if (BeginMenu("Stage")) { + for (int i = 0; i < DebugStateType::RegDump::MaxShaderStages; i++) { + if (data.regs.stage_enable.IsStageEnabled(i)) { + bool selected = selected_shader == i; + if (Selectable(names[i], &selected)) { + SelectShader(i); + } + } + } + ImGui::EndMenu(); + } + if (BeginMenu("Windows")) { + Checkbox("Registers", &show_registers); + Checkbox("User data", &show_user_data); + Checkbox("Disassembly", &show_disassembly); + ImGui::EndMenu(); + } + EndMenuBar(); + } + + char dock_name[64]; + snprintf(dock_name, sizeof(dock_name), "BatchView###reg_dump_%d/dock_space", id); + auto root_dock_id = ImHashStr(dock_name); + DockSpace(root_dock_id); + } + End(); + + auto get_shader = [&]() -> ShaderCache* { + auto shader_cache = shader_decomp.find(selected_shader); + if (shader_cache == shader_decomp.end()) { + return nullptr; + } + return &shader_cache->second; + }; + + if (show_user_data) { + snprintf(name, sizeof(name), "User data###reg_dump_%d/user_data", id); + if (Begin(name, &show_user_data)) { + auto shader = get_shader(); + if (!shader) { + Text("Select a stage"); + } else { + shader->hex_view.DrawContents(shader->user_data.data(), shader->user_data.size()); + } + } + End(); + } + + if (show_disassembly) { + snprintf(name, sizeof(name), "Disassembly###reg_dump_%d/disassembly", id); + if (Begin(name, &show_disassembly)) { + auto shader = get_shader(); + if (!shader) { + Text("Select a stage"); + } else { + shader->dis_view.Render("Disassembly", GetContentRegionAvail()); + } + } + End(); + } + + if (show_registers) { + snprintf(name, sizeof(name), "Regs###reg_dump_%d/regs", id); + if (Begin(name, &show_registers)) { + DrawRegs(); + } + End(); + } + + if (default_reg_popup.open) { + default_reg_popup.Draw(); + } + for (auto it = extra_reg_popup.begin(); it != extra_reg_popup.end();) { + if (!it->open) { + it = extra_reg_popup.erase(it); + continue; + } + it->Draw(); + ++it; + } +} + +} // namespace Core::Devtools::Widget \ No newline at end of file diff --git a/src/core/devtools/widget/reg_view.h b/src/core/devtools/widget/reg_view.h new file mode 100644 index 000000000..67ab1e04f --- /dev/null +++ b/src/core/devtools/widget/reg_view.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include "core/debug_state.h" +#include "imgui_memory_editor.h" +#include "reg_popup.h" +#include "text_editor.h" + +namespace Core::Devtools::Widget { + +struct ShaderCache { + MemoryEditor hex_view; + TextEditor dis_view; + Vulkan::Liverpool::UserData user_data; +}; + +class RegView { + int id; + + DebugStateType::RegDump data; + u32 batch_id{~0u}; + + std::unordered_map shader_decomp; + int selected_shader{-1}; + RegPopup default_reg_popup; + int last_selected_cb{-1}; + std::vector extra_reg_popup; + + bool show_registers{true}; + bool show_user_data{true}; + bool show_disassembly{true}; + + void ProcessShader(int shader_id); + + void SelectShader(int shader_id); + + void DrawRegs(); + +public: + bool open = false; + + RegView(); + + void SetData(DebugStateType::RegDump data, u32 batch_id); + + void Draw(); +}; + +} // namespace Core::Devtools::Widget \ No newline at end of file diff --git a/src/core/devtools/widget/text_editor.cpp b/src/core/devtools/widget/text_editor.cpp new file mode 100644 index 000000000..f447e45a2 --- /dev/null +++ b/src/core/devtools/widget/text_editor.cpp @@ -0,0 +1,2334 @@ +// SPDX-FileCopyrightText: Copyright (c) 2017 BalazsJako +// SPDX-License-Identifier: MIT + +// source: https://github.com/BalazsJako/ImGuiColorTextEdit + +#include +#include +#include +#include +#include + +#include "text_editor.h" + +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui.h" // for imGui::GetCurrentWindow() + +// TODO +// - multiline comments vs single-line: latter is blocking start of a ML + +namespace Core::Devtools::Widget { + +template +bool equals(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, BinaryPredicate p) { + for (; first1 != last1 && first2 != last2; ++first1, ++first2) { + if (!p(*first1, *first2)) + return false; + } + return first1 == last1 && first2 == last2; +} + +TextEditor::TextEditor() + : mLineSpacing(1.0f), mUndoIndex(0), mTabSize(4), mOverwrite(false), mReadOnly(false), + mWithinRender(false), mScrollToCursor(false), mScrollToTop(false), mTextChanged(false), + mColorizerEnabled(true), mTextStart(20.0f), mLeftMargin(10), mCursorPositionChanged(false), + mColorRangeMin(0), mColorRangeMax(0), mSelectionMode(SelectionMode::Normal), + mCheckComments(true), mLastClick(-1.0f), mHandleKeyboardInputs(true), + mHandleMouseInputs(true), mIgnoreImGuiChild(false), mShowWhitespaces(true), + mStartTime(std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count()) { + SetPalette(GetDarkPalette()); + mLines.push_back(Line()); +} + +TextEditor::~TextEditor() {} + +void TextEditor::SetLanguageDefinition(const LanguageDefinition& aLanguageDef) { + mLanguageDefinition = aLanguageDef; + mRegexList.clear(); + + for (auto& r : mLanguageDefinition.mTokenRegexStrings) + mRegexList.push_back( + std::make_pair(std::regex(r.first, std::regex_constants::optimize), r.second)); + + Colorize(); +} + +void TextEditor::SetPalette(const Palette& aValue) { + mPaletteBase = aValue; +} + +std::string TextEditor::GetText(const Coordinates& aStart, const Coordinates& aEnd) const { + std::string result; + + auto lstart = aStart.mLine; + auto lend = aEnd.mLine; + auto istart = GetCharacterIndex(aStart); + auto iend = GetCharacterIndex(aEnd); + size_t s = 0; + + for (size_t i = lstart; i < lend; i++) + s += mLines[i].size(); + + result.reserve(s + s / 8); + + while (istart < iend || lstart < lend) { + if (lstart >= (int)mLines.size()) + break; + + auto& line = mLines[lstart]; + if (istart < (int)line.size()) { + result += line[istart].mChar; + istart++; + } else { + istart = 0; + ++lstart; + result += '\n'; + } + } + + return result; +} + +TextEditor::Coordinates TextEditor::GetActualCursorCoordinates() const { + return SanitizeCoordinates(mState.mCursorPosition); +} + +TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates& aValue) const { + auto line = aValue.mLine; + auto column = aValue.mColumn; + if (line >= (int)mLines.size()) { + if (mLines.empty()) { + line = 0; + column = 0; + } else { + line = (int)mLines.size() - 1; + column = GetLineMaxColumn(line); + } + return Coordinates(line, column); + } else { + column = mLines.empty() ? 0 : std::min(column, GetLineMaxColumn(line)); + return Coordinates(line, column); + } +} + +// https://en.wikipedia.org/wiki/UTF-8 +// We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code +// sequence (non-10xxxxxx code) +static int UTF8CharLength(TextEditor::Char c) { + if ((c & 0xFE) == 0xFC) + return 6; + if ((c & 0xFC) == 0xF8) + return 5; + if ((c & 0xF8) == 0xF0) + return 4; + else if ((c & 0xF0) == 0xE0) + return 3; + else if ((c & 0xE0) == 0xC0) + return 2; + return 1; +} + +// "Borrowed" from ImGui source +static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) { + if (c < 0x80) { + buf[0] = (char)c; + return 1; + } + if (c < 0x800) { + if (buf_size < 2) + return 0; + buf[0] = (char)(0xc0 + (c >> 6)); + buf[1] = (char)(0x80 + (c & 0x3f)); + return 2; + } + if (c >= 0xdc00 && c < 0xe000) { + return 0; + } + if (c >= 0xd800 && c < 0xdc00) { + if (buf_size < 4) + return 0; + buf[0] = (char)(0xf0 + (c >> 18)); + buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); + buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[3] = (char)(0x80 + ((c) & 0x3f)); + return 4; + } + // else if (c < 0x10000) + { + if (buf_size < 3) + return 0; + buf[0] = (char)(0xe0 + (c >> 12)); + buf[1] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[2] = (char)(0x80 + ((c) & 0x3f)); + return 3; + } +} + +void TextEditor::Advance(Coordinates& aCoordinates) const { + if (aCoordinates.mLine < (int)mLines.size()) { + auto& line = mLines[aCoordinates.mLine]; + auto cindex = GetCharacterIndex(aCoordinates); + + if (cindex + 1 < (int)line.size()) { + auto delta = UTF8CharLength(line[cindex].mChar); + cindex = std::min(cindex + delta, (int)line.size() - 1); + } else { + ++aCoordinates.mLine; + cindex = 0; + } + aCoordinates.mColumn = GetCharacterColumn(aCoordinates.mLine, cindex); + } +} + +void TextEditor::DeleteRange(const Coordinates& aStart, const Coordinates& aEnd) { + ASSERT(aEnd >= aStart); + ASSERT(!mReadOnly); + + // printf("D(%d.%d)-(%d.%d)\n", aStart.mLine, aStart.mColumn, aEnd.mLine, aEnd.mColumn); + + if (aEnd == aStart) + return; + + auto start = GetCharacterIndex(aStart); + auto end = GetCharacterIndex(aEnd); + + if (aStart.mLine == aEnd.mLine) { + auto& line = mLines[aStart.mLine]; + auto n = GetLineMaxColumn(aStart.mLine); + if (aEnd.mColumn >= n) + line.erase(line.begin() + start, line.end()); + else + line.erase(line.begin() + start, line.begin() + end); + } else { + auto& firstLine = mLines[aStart.mLine]; + auto& lastLine = mLines[aEnd.mLine]; + + firstLine.erase(firstLine.begin() + start, firstLine.end()); + lastLine.erase(lastLine.begin(), lastLine.begin() + end); + + if (aStart.mLine < aEnd.mLine) + firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end()); + + if (aStart.mLine < aEnd.mLine) + RemoveLine(aStart.mLine + 1, aEnd.mLine + 1); + } + + mTextChanged = true; +} + +int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char* aValue) { + ASSERT(!mReadOnly); + + int cindex = GetCharacterIndex(aWhere); + int totalLines = 0; + while (*aValue != '\0') { + ASSERT(!mLines.empty()); + + if (*aValue == '\r') { + // skip + ++aValue; + } else if (*aValue == '\n') { + if (cindex < (int)mLines[aWhere.mLine].size()) { + auto& newLine = InsertLine(aWhere.mLine + 1); + auto& line = mLines[aWhere.mLine]; + newLine.insert(newLine.begin(), line.begin() + cindex, line.end()); + line.erase(line.begin() + cindex, line.end()); + } else { + InsertLine(aWhere.mLine + 1); + } + ++aWhere.mLine; + aWhere.mColumn = 0; + cindex = 0; + ++totalLines; + ++aValue; + } else { + auto& line = mLines[aWhere.mLine]; + auto d = UTF8CharLength(*aValue); + while (d-- > 0 && *aValue != '\0') + line.insert(line.begin() + cindex++, Glyph(*aValue++, PaletteIndex::Default)); + ++aWhere.mColumn; + } + + mTextChanged = true; + } + + return totalLines; +} + +void TextEditor::AddUndo(UndoRecord& aValue) { + ASSERT(!mReadOnly); + // printf("AddUndo: (@%d.%d) +\'%s' [%d.%d .. %d.%d], -\'%s', [%d.%d .. %d.%d] (@%d.%d)\n", + // aValue.mBefore.mCursorPosition.mLine, aValue.mBefore.mCursorPosition.mColumn, + // aValue.mAdded.c_str(), aValue.mAddedStart.mLine, aValue.mAddedStart.mColumn, + // aValue.mAddedEnd.mLine, aValue.mAddedEnd.mColumn, aValue.mRemoved.c_str(), + // aValue.mRemovedStart.mLine, aValue.mRemovedStart.mColumn, aValue.mRemovedEnd.mLine, + // aValue.mRemovedEnd.mColumn, aValue.mAfter.mCursorPosition.mLine, + // aValue.mAfter.mCursorPosition.mColumn + // ); + + mUndoBuffer.resize((size_t)(mUndoIndex + 1)); + mUndoBuffer.back() = aValue; + ++mUndoIndex; +} + +TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition) const { + ImVec2 origin = ImGui::GetCursorScreenPos(); + ImVec2 local(aPosition.x - origin.x, aPosition.y - origin.y); + + int lineNo = std::max(0, (int)floor(local.y / mCharAdvance.y)); + + int columnCoord = 0; + + if (lineNo >= 0 && lineNo < (int)mLines.size()) { + auto& line = mLines.at(lineNo); + + int columnIndex = 0; + float columnX = 0.0f; + + while ((size_t)columnIndex < line.size()) { + float columnWidth = 0.0f; + + if (line[columnIndex].mChar == '\t') { + float spaceSize = + ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ").x; + float oldX = columnX; + float newColumnX = + (1.0f + std::floor((1.0f + columnX) / (float(mTabSize) * spaceSize))) * + (float(mTabSize) * spaceSize); + columnWidth = newColumnX - oldX; + if (mTextStart + columnX + columnWidth * 0.5f > local.x) + break; + columnX = newColumnX; + columnCoord = (columnCoord / mTabSize) * mTabSize + mTabSize; + columnIndex++; + } else { + char buf[7]; + auto d = UTF8CharLength(line[columnIndex].mChar); + int i = 0; + while (i < 6 && d-- > 0) + buf[i++] = line[columnIndex++].mChar; + buf[i] = '\0'; + columnWidth = + ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf).x; + if (mTextStart + columnX + columnWidth * 0.5f > local.x) + break; + columnX += columnWidth; + columnCoord++; + } + } + } + + return SanitizeCoordinates(Coordinates(lineNo, columnCoord)); +} + +TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates& aFrom) const { + Coordinates at = aFrom; + if (at.mLine >= (int)mLines.size()) + return at; + + auto& line = mLines[at.mLine]; + auto cindex = GetCharacterIndex(at); + + if (cindex >= (int)line.size()) + return at; + + while (cindex > 0 && isspace(line[cindex].mChar)) + --cindex; + + auto cstart = (PaletteIndex)line[cindex].mColorIndex; + while (cindex > 0) { + auto c = line[cindex].mChar; + if ((c & 0xC0) != 0x80) // not UTF code sequence 10xxxxxx + { + if (c <= 32 && isspace(c)) { + cindex++; + break; + } + if (cstart != (PaletteIndex)line[size_t(cindex - 1)].mColorIndex) + break; + } + --cindex; + } + return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex)); +} + +TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates& aFrom) const { + Coordinates at = aFrom; + if (at.mLine >= (int)mLines.size()) + return at; + + auto& line = mLines[at.mLine]; + auto cindex = GetCharacterIndex(at); + + if (cindex >= (int)line.size()) + return at; + + bool prevspace = (bool)isspace(line[cindex].mChar); + auto cstart = (PaletteIndex)line[cindex].mColorIndex; + while (cindex < (int)line.size()) { + auto c = line[cindex].mChar; + auto d = UTF8CharLength(c); + if (cstart != (PaletteIndex)line[cindex].mColorIndex) + break; + + if (prevspace != !!isspace(c)) { + if (isspace(c)) + while (cindex < (int)line.size() && isspace(line[cindex].mChar)) + ++cindex; + break; + } + cindex += d; + } + return Coordinates(aFrom.mLine, GetCharacterColumn(aFrom.mLine, cindex)); +} + +TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates& aFrom) const { + Coordinates at = aFrom; + if (at.mLine >= (int)mLines.size()) + return at; + + // skip to the next non-word character + auto cindex = GetCharacterIndex(aFrom); + bool isword = false; + bool skip = false; + if (cindex < (int)mLines[at.mLine].size()) { + auto& line = mLines[at.mLine]; + isword = isalnum(line[cindex].mChar); + skip = isword; + } + + while (!isword || skip) { + if (at.mLine >= mLines.size()) { + auto l = std::max(0, (int)mLines.size() - 1); + return Coordinates(l, GetLineMaxColumn(l)); + } + + auto& line = mLines[at.mLine]; + if (cindex < (int)line.size()) { + isword = isalnum(line[cindex].mChar); + + if (isword && !skip) + return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex)); + + if (!isword) + skip = false; + + cindex++; + } else { + cindex = 0; + ++at.mLine; + skip = false; + isword = false; + } + } + + return at; +} + +int TextEditor::GetCharacterIndex(const Coordinates& aCoordinates) const { + if (aCoordinates.mLine >= mLines.size()) + return -1; + auto& line = mLines[aCoordinates.mLine]; + int c = 0; + int i = 0; + for (; i < line.size() && c < aCoordinates.mColumn;) { + if (line[i].mChar == '\t') + c = (c / mTabSize) * mTabSize + mTabSize; + else + ++c; + i += UTF8CharLength(line[i].mChar); + } + return i; +} + +int TextEditor::GetCharacterColumn(int aLine, int aIndex) const { + if (aLine >= mLines.size()) + return 0; + auto& line = mLines[aLine]; + int col = 0; + int i = 0; + while (i < aIndex && i < (int)line.size()) { + auto c = line[i].mChar; + i += UTF8CharLength(c); + if (c == '\t') + col = (col / mTabSize) * mTabSize + mTabSize; + else + col++; + } + return col; +} + +int TextEditor::GetLineCharacterCount(int aLine) const { + if (aLine >= mLines.size()) + return 0; + auto& line = mLines[aLine]; + int c = 0; + for (unsigned i = 0; i < line.size(); c++) + i += UTF8CharLength(line[i].mChar); + return c; +} + +int TextEditor::GetLineMaxColumn(int aLine) const { + if (aLine >= mLines.size()) + return 0; + auto& line = mLines[aLine]; + int col = 0; + for (unsigned i = 0; i < line.size();) { + auto c = line[i].mChar; + if (c == '\t') + col = (col / mTabSize) * mTabSize + mTabSize; + else + col++; + i += UTF8CharLength(c); + } + return col; +} + +bool TextEditor::IsOnWordBoundary(const Coordinates& aAt) const { + if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0) + return true; + + auto& line = mLines[aAt.mLine]; + auto cindex = GetCharacterIndex(aAt); + if (cindex >= (int)line.size()) + return true; + + if (mColorizerEnabled) + return line[cindex].mColorIndex != line[size_t(cindex - 1)].mColorIndex; + + return isspace(line[cindex].mChar) != isspace(line[cindex - 1].mChar); +} + +void TextEditor::RemoveLine(int aStart, int aEnd) { + ASSERT(!mReadOnly); + ASSERT(aEnd >= aStart); + ASSERT(mLines.size() > (size_t)(aEnd - aStart)); + + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) { + ErrorMarkers::value_type e(i.first >= aStart ? i.first - 1 : i.first, i.second); + if (e.first >= aStart && e.first <= aEnd) + continue; + etmp.insert(e); + } + mErrorMarkers = std::move(etmp); + + Breakpoints btmp; + for (auto i : mBreakpoints) { + if (i >= aStart && i <= aEnd) + continue; + btmp.insert(i >= aStart ? i - 1 : i); + } + mBreakpoints = std::move(btmp); + + mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd); + ASSERT(!mLines.empty()); + + mTextChanged = true; +} + +void TextEditor::RemoveLine(int aIndex) { + ASSERT(!mReadOnly); + ASSERT(mLines.size() > 1); + + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) { + ErrorMarkers::value_type e(i.first > aIndex ? i.first - 1 : i.first, i.second); + if (e.first - 1 == aIndex) + continue; + etmp.insert(e); + } + mErrorMarkers = std::move(etmp); + + Breakpoints btmp; + for (auto i : mBreakpoints) { + if (i == aIndex) + continue; + btmp.insert(i >= aIndex ? i - 1 : i); + } + mBreakpoints = std::move(btmp); + + mLines.erase(mLines.begin() + aIndex); + ASSERT(!mLines.empty()); + + mTextChanged = true; +} + +TextEditor::Line& TextEditor::InsertLine(int aIndex) { + ASSERT(!mReadOnly); + + auto& result = *mLines.insert(mLines.begin() + aIndex, Line()); + + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) + etmp.insert(ErrorMarkers::value_type(i.first >= aIndex ? i.first + 1 : i.first, i.second)); + mErrorMarkers = std::move(etmp); + + Breakpoints btmp; + for (auto i : mBreakpoints) + btmp.insert(i >= aIndex ? i + 1 : i); + mBreakpoints = std::move(btmp); + + return result; +} + +std::string TextEditor::GetWordUnderCursor() const { + auto c = GetCursorPosition(); + return GetWordAt(c); +} + +std::string TextEditor::GetWordAt(const Coordinates& aCoords) const { + auto start = FindWordStart(aCoords); + auto end = FindWordEnd(aCoords); + + std::string r; + + auto istart = GetCharacterIndex(start); + auto iend = GetCharacterIndex(end); + + for (auto it = istart; it < iend; ++it) + r.push_back(mLines[aCoords.mLine][it].mChar); + + return r; +} + +ImU32 TextEditor::GetGlyphColor(const Glyph& aGlyph) const { + if (!mColorizerEnabled) + return mPalette[(int)PaletteIndex::Default]; + if (aGlyph.mComment) + return mPalette[(int)PaletteIndex::Comment]; + if (aGlyph.mMultiLineComment) + return mPalette[(int)PaletteIndex::MultiLineComment]; + auto const color = mPalette[(int)aGlyph.mColorIndex]; + if (aGlyph.mPreprocessor) { + const auto ppcolor = mPalette[(int)PaletteIndex::Preprocessor]; + const int c0 = ((ppcolor & 0xff) + (color & 0xff)) / 2; + const int c1 = (((ppcolor >> 8) & 0xff) + ((color >> 8) & 0xff)) / 2; + const int c2 = (((ppcolor >> 16) & 0xff) + ((color >> 16) & 0xff)) / 2; + const int c3 = (((ppcolor >> 24) & 0xff) + ((color >> 24) & 0xff)) / 2; + return ImU32(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24)); + } + return color; +} + +void TextEditor::HandleKeyboardInputs() { + ImGuiIO& io = ImGui::GetIO(); + auto shift = io.KeyShift; + auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; + auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; + + if (ImGui::IsWindowFocused()) { + if (ImGui::IsWindowHovered()) + ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput); + // ImGui::CaptureKeyboardFromApp(true); + + io.WantCaptureKeyboard = true; + io.WantTextInput = true; + + if (!IsReadOnly() && ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Z))) + Undo(); + else if (!IsReadOnly() && !ctrl && !shift && alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) + Undo(); + else if (!IsReadOnly() && ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Y))) + Redo(); + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow))) + MoveUp(1, shift); + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow))) + MoveDown(1, shift); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow))) + MoveLeft(1, shift, ctrl); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow))) + MoveRight(1, shift, ctrl); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp))) + MoveUp(GetPageSize() - 4, shift); + else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown))) + MoveDown(GetPageSize() - 4, shift); + else if (!alt && ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) + MoveTop(shift); + else if (ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) + MoveBottom(shift); + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home))) + MoveHome(shift); + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End))) + MoveEnd(shift); + else if (!IsReadOnly() && !ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) + Delete(); + else if (!IsReadOnly() && !ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) + Backspace(); + else if (!ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + mOverwrite ^= true; + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + Copy(); + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C))) + Copy(); + else if (!IsReadOnly() && !ctrl && shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert))) + Paste(); + else if (!IsReadOnly() && ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_V))) + Paste(); + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_X))) + Cut(); + else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) + Cut(); + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_A))) + SelectAll(); + else if (!IsReadOnly() && !ctrl && !shift && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter))) + EnterCharacter('\n', false); + else if (!IsReadOnly() && !ctrl && !alt && + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Tab))) + EnterCharacter('\t', shift); + + if (!IsReadOnly() && !io.InputQueueCharacters.empty()) { + for (int i = 0; i < io.InputQueueCharacters.Size; i++) { + auto c = io.InputQueueCharacters[i]; + if (c != 0 && (c == '\n' || c >= 32)) + EnterCharacter(c, shift); + } + io.InputQueueCharacters.resize(0); + } + } +} + +void TextEditor::HandleMouseInputs() { + ImGuiIO& io = ImGui::GetIO(); + auto shift = io.KeyShift; + auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; + auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; + + if (ImGui::IsWindowHovered()) { + if (!shift && !alt) { + auto click = ImGui::IsMouseClicked(0); + auto doubleClick = ImGui::IsMouseDoubleClicked(0); + auto t = ImGui::GetTime(); + auto tripleClick = click && !doubleClick && + (mLastClick != -1.0f && (t - mLastClick) < io.MouseDoubleClickTime); + + /* + Left mouse button triple click + */ + + if (tripleClick) { + if (!ctrl) { + mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = + ScreenPosToCoordinates(ImGui::GetMousePos()); + mSelectionMode = SelectionMode::Line; + SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + } + + mLastClick = -1.0f; + } + + /* + Left mouse button double click + */ + + else if (doubleClick) { + if (!ctrl) { + mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = + ScreenPosToCoordinates(ImGui::GetMousePos()); + if (mSelectionMode == SelectionMode::Line) + mSelectionMode = SelectionMode::Normal; + else + mSelectionMode = SelectionMode::Word; + SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + } + + mLastClick = (float)ImGui::GetTime(); + } + + /* + Left mouse button click + */ + else if (click) { + mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = + ScreenPosToCoordinates(ImGui::GetMousePos()); + if (ctrl) + mSelectionMode = SelectionMode::Word; + else + mSelectionMode = SelectionMode::Normal; + SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + + mLastClick = (float)ImGui::GetTime(); + } + // Mouse left button dragging (=> update selection) + else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0)) { + io.WantCaptureMouse = true; + mState.mCursorPosition = mInteractiveEnd = + ScreenPosToCoordinates(ImGui::GetMousePos()); + SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + } + } + } +} + +void TextEditor::Render() { + /* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/ + const float fontSize = + ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr) + .x; + mCharAdvance = ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * mLineSpacing); + + /* Update palette with the current alpha from style */ + for (int i = 0; i < (int)PaletteIndex::Max; ++i) { + auto color = ImGui::ColorConvertU32ToFloat4(mPaletteBase[i]); + color.w *= ImGui::GetStyle().Alpha; + mPalette[i] = ImGui::ColorConvertFloat4ToU32(color); + } + + ASSERT(mLineBuffer.empty()); + + auto contentSize = ImGui::GetWindowContentRegionMax(); + auto drawList = ImGui::GetWindowDrawList(); + float longest(mTextStart); + + if (mScrollToTop) { + mScrollToTop = false; + ImGui::SetScrollY(0.f); + } + + ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos(); + auto scrollX = ImGui::GetScrollX(); + auto scrollY = ImGui::GetScrollY(); + + auto lineNo = (int)floor(scrollY / mCharAdvance.y); + auto globalLineMax = (int)mLines.size(); + auto lineMax = + std::max(0, std::min((int)mLines.size() - 1, + lineNo + (int)floor((scrollY + contentSize.y) / mCharAdvance.y))); + + // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width + char buf[16]; + snprintf(buf, 16, " %d ", globalLineMax); + mTextStart = ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr) + .x + + mLeftMargin; + + if (!mLines.empty()) { + float spaceSize = + ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr) + .x; + + while (lineNo <= lineMax) { + ImVec2 lineStartScreenPos = + ImVec2(cursorScreenPos.x, cursorScreenPos.y + lineNo * mCharAdvance.y); + ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y); + + auto& line = mLines[lineNo]; + longest = std::max( + mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), + longest); + auto columnNo = 0; + Coordinates lineStartCoord(lineNo, 0); + Coordinates lineEndCoord(lineNo, GetLineMaxColumn(lineNo)); + + // Draw selection for the current line + float sstart = -1.0f; + float ssend = -1.0f; + + ASSERT(mState.mSelectionStart <= mState.mSelectionEnd); + if (mState.mSelectionStart <= lineEndCoord) + sstart = mState.mSelectionStart > lineStartCoord + ? TextDistanceToLineStart(mState.mSelectionStart) + : 0.0f; + if (mState.mSelectionEnd > lineStartCoord) + ssend = TextDistanceToLineStart( + mState.mSelectionEnd < lineEndCoord ? mState.mSelectionEnd : lineEndCoord); + + if (mState.mSelectionEnd.mLine > lineNo) + ssend += mCharAdvance.x; + + if (sstart != -1 && ssend != -1 && sstart < ssend) { + ImVec2 vstart(lineStartScreenPos.x + mTextStart + sstart, lineStartScreenPos.y); + ImVec2 vend(lineStartScreenPos.x + mTextStart + ssend, + lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(vstart, vend, mPalette[(int)PaletteIndex::Selection]); + } + + // Draw breakpoints + auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y); + + if (mBreakpoints.count(lineNo + 1) != 0) { + auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, + lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::Breakpoint]); + } + + // Draw error markers + auto errorIt = mErrorMarkers.find(lineNo + 1); + if (errorIt != mErrorMarkers.end()) { + auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, + lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::ErrorMarker]); + + if (ImGui::IsMouseHoveringRect(lineStartScreenPos, end)) { + ImGui::BeginTooltip(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f)); + ImGui::Text("Error at line %d:", errorIt->first); + ImGui::PopStyleColor(); + ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.2f, 1.0f)); + ImGui::Text("%s", errorIt->second.c_str()); + ImGui::PopStyleColor(); + ImGui::EndTooltip(); + } + } + + // Draw line number (right aligned) + snprintf(buf, 16, "%d ", lineNo + 1); + + auto lineNoWidth = + ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr) + .x; + drawList->AddText( + ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), + mPalette[(int)PaletteIndex::LineNumber], buf); + + if (mState.mCursorPosition.mLine == lineNo) { + auto focused = ImGui::IsWindowFocused(); + + // Highlight the current line (where the cursor is) + if (!HasSelection()) { + auto end = ImVec2(start.x + contentSize.x + scrollX, start.y + mCharAdvance.y); + drawList->AddRectFilled( + start, end, + mPalette[(int)(focused ? PaletteIndex::CurrentLineFill + : PaletteIndex::CurrentLineFillInactive)]); + drawList->AddRect(start, end, mPalette[(int)PaletteIndex::CurrentLineEdge], + 1.0f); + } + + // Render the cursor + if (focused) { + auto timeEnd = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + auto elapsed = timeEnd - mStartTime; + if (elapsed > 400) { + float width = 1.0f; + auto cindex = GetCharacterIndex(mState.mCursorPosition); + float cx = TextDistanceToLineStart(mState.mCursorPosition); + + if (mOverwrite && cindex < (int)line.size()) { + auto c = line[cindex].mChar; + if (c == '\t') { + auto x = (1.0f + + std::floor((1.0f + cx) / (float(mTabSize) * spaceSize))) * + (float(mTabSize) * spaceSize); + width = x - cx; + } else { + char buf2[2]; + buf2[0] = line[cindex].mChar; + buf2[1] = '\0'; + width = + ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf2) + .x; + } + } + ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y); + ImVec2 cend(textScreenPos.x + cx + width, + lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]); + if (elapsed > 800) + mStartTime = timeEnd; + } + } + } + + // Render colorized text + auto prevColor = + line.empty() ? mPalette[(int)PaletteIndex::Default] : GetGlyphColor(line[0]); + ImVec2 bufferOffset; + + for (int i = 0; i < line.size();) { + auto& glyph = line[i]; + auto color = GetGlyphColor(glyph); + + if ((color != prevColor || glyph.mChar == '\t' || glyph.mChar == ' ') && + !mLineBuffer.empty()) { + const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, + textScreenPos.y + bufferOffset.y); + drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); + auto textSize = + ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, + mLineBuffer.c_str(), nullptr, nullptr); + bufferOffset.x += textSize.x; + mLineBuffer.clear(); + } + prevColor = color; + + if (glyph.mChar == '\t') { + auto oldX = bufferOffset.x; + bufferOffset.x = (1.0f + std::floor((1.0f + bufferOffset.x) / + (float(mTabSize) * spaceSize))) * + (float(mTabSize) * spaceSize); + ++i; + + if (mShowWhitespaces) { + const auto s = ImGui::GetFontSize(); + const auto x1 = textScreenPos.x + oldX + 1.0f; + const auto x2 = textScreenPos.x + bufferOffset.x - 1.0f; + const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; + const ImVec2 p1(x1, y); + const ImVec2 p2(x2, y); + const ImVec2 p3(x2 - s * 0.2f, y - s * 0.2f); + const ImVec2 p4(x2 - s * 0.2f, y + s * 0.2f); + drawList->AddLine(p1, p2, 0x90909090); + drawList->AddLine(p2, p3, 0x90909090); + drawList->AddLine(p2, p4, 0x90909090); + } + } else if (glyph.mChar == ' ') { + if (mShowWhitespaces) { + const auto s = ImGui::GetFontSize(); + const auto x = textScreenPos.x + bufferOffset.x + spaceSize * 0.5f; + const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; + drawList->AddCircleFilled(ImVec2(x, y), 1.5f, 0x80808080, 4); + } + bufferOffset.x += spaceSize; + i++; + } else { + auto l = UTF8CharLength(glyph.mChar); + while (l-- > 0) + mLineBuffer.push_back(line[i++].mChar); + } + ++columnNo; + } + + if (!mLineBuffer.empty()) { + const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, + textScreenPos.y + bufferOffset.y); + drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); + mLineBuffer.clear(); + } + + ++lineNo; + } + + // Draw a tooltip on known identifiers/preprocessor symbols + if (ImGui::IsMousePosValid()) { + auto id = GetWordAt(ScreenPosToCoordinates(ImGui::GetMousePos())); + if (!id.empty()) { + auto it = mLanguageDefinition.mIdentifiers.find(id); + if (it != mLanguageDefinition.mIdentifiers.end()) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(it->second.mDeclaration.c_str()); + ImGui::EndTooltip(); + } else { + auto pi = mLanguageDefinition.mPreprocIdentifiers.find(id); + if (pi != mLanguageDefinition.mPreprocIdentifiers.end()) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(pi->second.mDeclaration.c_str()); + ImGui::EndTooltip(); + } + } + } + } + } + + ImGui::Dummy(ImVec2((longest + 2), mLines.size() * mCharAdvance.y)); + + if (mScrollToCursor) { + EnsureCursorVisible(); + ImGui::SetWindowFocus(); + mScrollToCursor = false; + } +} + +void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) { + mWithinRender = true; + mTextChanged = false; + mCursorPositionChanged = false; + + ImGui::PushStyleColor(ImGuiCol_ChildBg, + ImGui::ColorConvertU32ToFloat4(mPalette[(int)PaletteIndex::Background])); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + if (!mIgnoreImGuiChild) + ImGui::BeginChild(aTitle, aSize, aBorder, + ImGuiWindowFlags_HorizontalScrollbar | + ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoMove); + + if (mHandleKeyboardInputs) { + HandleKeyboardInputs(); + ImGui::PushAllowKeyboardFocus(true); + } + + if (mHandleMouseInputs) + HandleMouseInputs(); + + ColorizeInternal(); + Render(); + + if (mHandleKeyboardInputs) + ImGui::PopAllowKeyboardFocus(); + + if (!mIgnoreImGuiChild) + ImGui::EndChild(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + mWithinRender = false; +} + +void TextEditor::SetText(const std::string& aText) { + mLines.clear(); + mLines.emplace_back(Line()); + for (auto chr : aText) { + if (chr == '\r') { + // ignore the carriage return character + } else if (chr == '\n') + mLines.emplace_back(Line()); + else { + mLines.back().emplace_back(Glyph(chr, PaletteIndex::Default)); + } + } + + mTextChanged = true; + mScrollToTop = true; + + mUndoBuffer.clear(); + mUndoIndex = 0; + + Colorize(); +} + +void TextEditor::SetTextLines(const std::vector& aLines) { + mLines.clear(); + + if (aLines.empty()) { + mLines.emplace_back(Line()); + } else { + mLines.resize(aLines.size()); + + for (size_t i = 0; i < aLines.size(); ++i) { + const std::string& aLine = aLines[i]; + + mLines[i].reserve(aLine.size()); + for (size_t j = 0; j < aLine.size(); ++j) + mLines[i].emplace_back(Glyph(aLine[j], PaletteIndex::Default)); + } + } + + mTextChanged = true; + mScrollToTop = true; + + mUndoBuffer.clear(); + mUndoIndex = 0; + + Colorize(); +} + +void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) { + ASSERT(!mReadOnly); + + UndoRecord u; + + u.mBefore = mState; + + if (HasSelection()) { + if (aChar == '\t' && mState.mSelectionStart.mLine != mState.mSelectionEnd.mLine) { + + auto start = mState.mSelectionStart; + auto end = mState.mSelectionEnd; + auto originalEnd = end; + + if (start > end) + std::swap(start, end); + start.mColumn = 0; + // end.mColumn = end.mLine < mLines.size() ? mLines[end.mLine].size() : 0; + if (end.mColumn == 0 && end.mLine > 0) + --end.mLine; + if (end.mLine >= (int)mLines.size()) + end.mLine = mLines.empty() ? 0 : (int)mLines.size() - 1; + end.mColumn = GetLineMaxColumn(end.mLine); + + // if (end.mColumn >= GetLineMaxColumn(end.mLine)) + // end.mColumn = GetLineMaxColumn(end.mLine) - 1; + + u.mRemovedStart = start; + u.mRemovedEnd = end; + u.mRemoved = GetText(start, end); + + bool modified = false; + + for (int i = start.mLine; i <= end.mLine; i++) { + auto& line = mLines[i]; + if (aShift) { + if (!line.empty()) { + if (line.front().mChar == '\t') { + line.erase(line.begin()); + modified = true; + } else { + for (int j = 0; + j < mTabSize && !line.empty() && line.front().mChar == ' '; j++) { + line.erase(line.begin()); + modified = true; + } + } + } + } else { + line.insert(line.begin(), Glyph('\t', TextEditor::PaletteIndex::Background)); + modified = true; + } + } + + if (modified) { + start = Coordinates(start.mLine, GetCharacterColumn(start.mLine, 0)); + Coordinates rangeEnd; + if (originalEnd.mColumn != 0) { + end = Coordinates(end.mLine, GetLineMaxColumn(end.mLine)); + rangeEnd = end; + u.mAdded = GetText(start, end); + } else { + end = Coordinates(originalEnd.mLine, 0); + rangeEnd = Coordinates(end.mLine - 1, GetLineMaxColumn(end.mLine - 1)); + u.mAdded = GetText(start, rangeEnd); + } + + u.mAddedStart = start; + u.mAddedEnd = rangeEnd; + u.mAfter = mState; + + mState.mSelectionStart = start; + mState.mSelectionEnd = end; + AddUndo(u); + + mTextChanged = true; + + EnsureCursorVisible(); + } + + return; + } // c == '\t' + else { + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + DeleteSelection(); + } + } // HasSelection + + auto coord = GetActualCursorCoordinates(); + u.mAddedStart = coord; + + ASSERT(!mLines.empty()); + + if (aChar == '\n') { + InsertLine(coord.mLine + 1); + auto& line = mLines[coord.mLine]; + auto& newLine = mLines[coord.mLine + 1]; + + if (mLanguageDefinition.mAutoIndentation) + for (size_t it = 0; + it < line.size() && isascii(line[it].mChar) && isblank(line[it].mChar); ++it) + newLine.push_back(line[it]); + + const size_t whitespaceSize = newLine.size(); + auto cindex = GetCharacterIndex(coord); + newLine.insert(newLine.end(), line.begin() + cindex, line.end()); + line.erase(line.begin() + cindex, line.begin() + line.size()); + SetCursorPosition( + Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, (int)whitespaceSize))); + u.mAdded = (char)aChar; + } else { + char buf[7]; + int e = ImTextCharToUtf8(buf, 7, aChar); + if (e > 0) { + buf[e] = '\0'; + auto& line = mLines[coord.mLine]; + auto cindex = GetCharacterIndex(coord); + + if (mOverwrite && cindex < (int)line.size()) { + auto d = UTF8CharLength(line[cindex].mChar); + + u.mRemovedStart = mState.mCursorPosition; + u.mRemovedEnd = + Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + d)); + + while (d-- > 0 && cindex < (int)line.size()) { + u.mRemoved += line[cindex].mChar; + line.erase(line.begin() + cindex); + } + } + + for (auto p = buf; *p != '\0'; p++, ++cindex) + line.insert(line.begin() + cindex, Glyph(*p, PaletteIndex::Default)); + u.mAdded = buf; + + SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex))); + } else + return; + } + + mTextChanged = true; + + u.mAddedEnd = GetActualCursorCoordinates(); + u.mAfter = mState; + + AddUndo(u); + + Colorize(coord.mLine - 1, 3); + EnsureCursorVisible(); +} + +void TextEditor::SetReadOnly(bool aValue) { + mReadOnly = aValue; +} + +void TextEditor::SetColorizerEnable(bool aValue) { + mColorizerEnabled = aValue; +} + +void TextEditor::SetCursorPosition(const Coordinates& aPosition) { + if (mState.mCursorPosition != aPosition) { + mState.mCursorPosition = aPosition; + mCursorPositionChanged = true; + EnsureCursorVisible(); + } +} + +void TextEditor::SetSelectionStart(const Coordinates& aPosition) { + mState.mSelectionStart = SanitizeCoordinates(aPosition); + if (mState.mSelectionStart > mState.mSelectionEnd) + std::swap(mState.mSelectionStart, mState.mSelectionEnd); +} + +void TextEditor::SetSelectionEnd(const Coordinates& aPosition) { + mState.mSelectionEnd = SanitizeCoordinates(aPosition); + if (mState.mSelectionStart > mState.mSelectionEnd) + std::swap(mState.mSelectionStart, mState.mSelectionEnd); +} + +void TextEditor::SetSelection(const Coordinates& aStart, const Coordinates& aEnd, + SelectionMode aMode) { + auto oldSelStart = mState.mSelectionStart; + auto oldSelEnd = mState.mSelectionEnd; + + mState.mSelectionStart = SanitizeCoordinates(aStart); + mState.mSelectionEnd = SanitizeCoordinates(aEnd); + if (mState.mSelectionStart > mState.mSelectionEnd) + std::swap(mState.mSelectionStart, mState.mSelectionEnd); + + switch (aMode) { + case TextEditor::SelectionMode::Normal: + break; + case TextEditor::SelectionMode::Word: { + mState.mSelectionStart = FindWordStart(mState.mSelectionStart); + if (!IsOnWordBoundary(mState.mSelectionEnd)) + mState.mSelectionEnd = FindWordEnd(FindWordStart(mState.mSelectionEnd)); + break; + } + case TextEditor::SelectionMode::Line: { + const auto lineNo = mState.mSelectionEnd.mLine; + const auto lineSize = (size_t)lineNo < mLines.size() ? mLines[lineNo].size() : 0; + mState.mSelectionStart = Coordinates(mState.mSelectionStart.mLine, 0); + mState.mSelectionEnd = Coordinates(lineNo, GetLineMaxColumn(lineNo)); + break; + } + default: + break; + } + + if (mState.mSelectionStart != oldSelStart || mState.mSelectionEnd != oldSelEnd) + mCursorPositionChanged = true; +} + +void TextEditor::SetTabSize(int aValue) { + mTabSize = std::max(0, std::min(32, aValue)); +} + +void TextEditor::InsertText(const std::string& aValue) { + InsertText(aValue.c_str()); +} + +void TextEditor::InsertText(const char* aValue) { + if (aValue == nullptr) + return; + + auto pos = GetActualCursorCoordinates(); + auto start = std::min(pos, mState.mSelectionStart); + int totalLines = pos.mLine - start.mLine; + + totalLines += InsertTextAt(pos, aValue); + + SetSelection(pos, pos); + SetCursorPosition(pos); + Colorize(start.mLine - 1, totalLines + 2); +} + +void TextEditor::DeleteSelection() { + ASSERT(mState.mSelectionEnd >= mState.mSelectionStart); + + if (mState.mSelectionEnd == mState.mSelectionStart) + return; + + DeleteRange(mState.mSelectionStart, mState.mSelectionEnd); + + SetSelection(mState.mSelectionStart, mState.mSelectionStart); + SetCursorPosition(mState.mSelectionStart); + Colorize(mState.mSelectionStart.mLine, 1); +} + +void TextEditor::MoveUp(int aAmount, bool aSelect) { + auto oldPos = mState.mCursorPosition; + mState.mCursorPosition.mLine = std::max(0, mState.mCursorPosition.mLine - aAmount); + if (oldPos != mState.mCursorPosition) { + if (aSelect) { + if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else { + mInteractiveStart = mState.mCursorPosition; + mInteractiveEnd = oldPos; + } + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + + EnsureCursorVisible(); + } +} + +void TextEditor::MoveDown(int aAmount, bool aSelect) { + ASSERT(mState.mCursorPosition.mColumn >= 0); + auto oldPos = mState.mCursorPosition; + mState.mCursorPosition.mLine = + std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + aAmount)); + + if (mState.mCursorPosition != oldPos) { + if (aSelect) { + if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else { + mInteractiveStart = oldPos; + mInteractiveEnd = mState.mCursorPosition; + } + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + + EnsureCursorVisible(); + } +} + +static bool IsUTFSequence(char c) { + return (c & 0xC0) == 0x80; +} + +void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode) { + if (mLines.empty()) + return; + + auto oldPos = mState.mCursorPosition; + mState.mCursorPosition = GetActualCursorCoordinates(); + auto line = mState.mCursorPosition.mLine; + auto cindex = GetCharacterIndex(mState.mCursorPosition); + + while (aAmount-- > 0) { + if (cindex == 0) { + if (line > 0) { + --line; + if ((int)mLines.size() > line) + cindex = (int)mLines[line].size(); + else + cindex = 0; + } + } else { + --cindex; + if (cindex > 0) { + if ((int)mLines.size() > line) { + while (cindex > 0 && IsUTFSequence(mLines[line][cindex].mChar)) + --cindex; + } + } + } + + mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); + if (aWordMode) { + mState.mCursorPosition = FindWordStart(mState.mCursorPosition); + cindex = GetCharacterIndex(mState.mCursorPosition); + } + } + + mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); + + ASSERT(mState.mCursorPosition.mColumn >= 0); + if (aSelect) { + if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else { + mInteractiveStart = mState.mCursorPosition; + mInteractiveEnd = oldPos; + } + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd, + aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal); + + EnsureCursorVisible(); +} + +void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode) { + auto oldPos = mState.mCursorPosition; + + if (mLines.empty() || oldPos.mLine >= mLines.size()) + return; + + auto cindex = GetCharacterIndex(mState.mCursorPosition); + while (aAmount-- > 0) { + auto lindex = mState.mCursorPosition.mLine; + auto& line = mLines[lindex]; + + if (cindex >= line.size()) { + if (mState.mCursorPosition.mLine < mLines.size() - 1) { + mState.mCursorPosition.mLine = + std::max(0, std::min((int)mLines.size() - 1, mState.mCursorPosition.mLine + 1)); + mState.mCursorPosition.mColumn = 0; + } else + return; + } else { + cindex += UTF8CharLength(line[cindex].mChar); + mState.mCursorPosition = Coordinates(lindex, GetCharacterColumn(lindex, cindex)); + if (aWordMode) + mState.mCursorPosition = FindNextWord(mState.mCursorPosition); + } + } + + if (aSelect) { + if (oldPos == mInteractiveEnd) + mInteractiveEnd = SanitizeCoordinates(mState.mCursorPosition); + else if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else { + mInteractiveStart = oldPos; + mInteractiveEnd = mState.mCursorPosition; + } + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd, + aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal); + + EnsureCursorVisible(); +} + +void TextEditor::MoveTop(bool aSelect) { + auto oldPos = mState.mCursorPosition; + SetCursorPosition(Coordinates(0, 0)); + + if (mState.mCursorPosition != oldPos) { + if (aSelect) { + mInteractiveEnd = oldPos; + mInteractiveStart = mState.mCursorPosition; + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + } +} + +void TextEditor::TextEditor::MoveBottom(bool aSelect) { + auto oldPos = GetCursorPosition(); + auto newPos = Coordinates((int)mLines.size() - 1, 0); + SetCursorPosition(newPos); + if (aSelect) { + mInteractiveStart = oldPos; + mInteractiveEnd = newPos; + } else + mInteractiveStart = mInteractiveEnd = newPos; + SetSelection(mInteractiveStart, mInteractiveEnd); +} + +void TextEditor::MoveHome(bool aSelect) { + auto oldPos = mState.mCursorPosition; + SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, 0)); + + if (mState.mCursorPosition != oldPos) { + if (aSelect) { + if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else { + mInteractiveStart = mState.mCursorPosition; + mInteractiveEnd = oldPos; + } + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + } +} + +void TextEditor::MoveEnd(bool aSelect) { + auto oldPos = mState.mCursorPosition; + SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, GetLineMaxColumn(oldPos.mLine))); + + if (mState.mCursorPosition != oldPos) { + if (aSelect) { + if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else { + mInteractiveStart = oldPos; + mInteractiveEnd = mState.mCursorPosition; + } + } else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + } +} + +void TextEditor::Delete() { + ASSERT(!mReadOnly); + + if (mLines.empty()) + return; + + UndoRecord u; + u.mBefore = mState; + + if (HasSelection()) { + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + + DeleteSelection(); + } else { + auto pos = GetActualCursorCoordinates(); + SetCursorPosition(pos); + auto& line = mLines[pos.mLine]; + + if (pos.mColumn == GetLineMaxColumn(pos.mLine)) { + if (pos.mLine == (int)mLines.size() - 1) + return; + + u.mRemoved = '\n'; + u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); + Advance(u.mRemovedEnd); + + auto& nextLine = mLines[pos.mLine + 1]; + line.insert(line.end(), nextLine.begin(), nextLine.end()); + RemoveLine(pos.mLine + 1); + } else { + auto cindex = GetCharacterIndex(pos); + u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); + u.mRemovedEnd.mColumn++; + u.mRemoved = GetText(u.mRemovedStart, u.mRemovedEnd); + + auto d = UTF8CharLength(line[cindex].mChar); + while (d-- > 0 && cindex < (int)line.size()) + line.erase(line.begin() + cindex); + } + + mTextChanged = true; + + Colorize(pos.mLine, 1); + } + + u.mAfter = mState; + AddUndo(u); +} + +void TextEditor::Backspace() { + ASSERT(!mReadOnly); + + if (mLines.empty()) + return; + + UndoRecord u; + u.mBefore = mState; + + if (HasSelection()) { + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + + DeleteSelection(); + } else { + auto pos = GetActualCursorCoordinates(); + SetCursorPosition(pos); + + if (mState.mCursorPosition.mColumn == 0) { + if (mState.mCursorPosition.mLine == 0) + return; + + u.mRemoved = '\n'; + u.mRemovedStart = u.mRemovedEnd = + Coordinates(pos.mLine - 1, GetLineMaxColumn(pos.mLine - 1)); + Advance(u.mRemovedEnd); + + auto& line = mLines[mState.mCursorPosition.mLine]; + auto& prevLine = mLines[mState.mCursorPosition.mLine - 1]; + auto prevSize = GetLineMaxColumn(mState.mCursorPosition.mLine - 1); + prevLine.insert(prevLine.end(), line.begin(), line.end()); + + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) + etmp.insert(ErrorMarkers::value_type( + i.first - 1 == mState.mCursorPosition.mLine ? i.first - 1 : i.first, i.second)); + mErrorMarkers = std::move(etmp); + + RemoveLine(mState.mCursorPosition.mLine); + --mState.mCursorPosition.mLine; + mState.mCursorPosition.mColumn = prevSize; + } else { + auto& line = mLines[mState.mCursorPosition.mLine]; + auto cindex = GetCharacterIndex(pos) - 1; + auto cend = cindex + 1; + while (cindex > 0 && IsUTFSequence(line[cindex].mChar)) + --cindex; + + // if (cindex > 0 && UTF8CharLength(line[cindex].mChar) > 1) + // --cindex; + + u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); + --u.mRemovedStart.mColumn; + --mState.mCursorPosition.mColumn; + + while (cindex < line.size() && cend-- > cindex) { + u.mRemoved += line[cindex].mChar; + line.erase(line.begin() + cindex); + } + } + + mTextChanged = true; + + EnsureCursorVisible(); + Colorize(mState.mCursorPosition.mLine, 1); + } + + u.mAfter = mState; + AddUndo(u); +} + +void TextEditor::SelectWordUnderCursor() { + auto c = GetCursorPosition(); + SetSelection(FindWordStart(c), FindWordEnd(c)); +} + +void TextEditor::SelectAll() { + SetSelection(Coordinates(0, 0), Coordinates((int)mLines.size(), 0)); +} + +bool TextEditor::HasSelection() const { + return mState.mSelectionEnd > mState.mSelectionStart; +} + +void TextEditor::Copy() { + if (HasSelection()) { + ImGui::SetClipboardText(GetSelectedText().c_str()); + } else { + if (!mLines.empty()) { + std::string str; + auto& line = mLines[GetActualCursorCoordinates().mLine]; + for (auto& g : line) + str.push_back(g.mChar); + ImGui::SetClipboardText(str.c_str()); + } + } +} + +void TextEditor::Cut() { + if (IsReadOnly()) { + Copy(); + } else { + if (HasSelection()) { + UndoRecord u; + u.mBefore = mState; + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + + Copy(); + DeleteSelection(); + + u.mAfter = mState; + AddUndo(u); + } + } +} + +void TextEditor::Paste() { + if (IsReadOnly()) + return; + + auto clipText = ImGui::GetClipboardText(); + if (clipText != nullptr && strlen(clipText) > 0) { + UndoRecord u; + u.mBefore = mState; + + if (HasSelection()) { + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + DeleteSelection(); + } + + u.mAdded = clipText; + u.mAddedStart = GetActualCursorCoordinates(); + + InsertText(clipText); + + u.mAddedEnd = GetActualCursorCoordinates(); + u.mAfter = mState; + AddUndo(u); + } +} + +bool TextEditor::CanUndo() const { + return !mReadOnly && mUndoIndex > 0; +} + +bool TextEditor::CanRedo() const { + return !mReadOnly && mUndoIndex < (int)mUndoBuffer.size(); +} + +void TextEditor::Undo(int aSteps) { + while (CanUndo() && aSteps-- > 0) + mUndoBuffer[--mUndoIndex].Undo(this); +} + +void TextEditor::Redo(int aSteps) { + while (CanRedo() && aSteps-- > 0) + mUndoBuffer[mUndoIndex++].Redo(this); +} + +const TextEditor::Palette& TextEditor::GetDarkPalette() { + const static Palette p = {{ + 0xff7f7f7f, // Default + 0xffd69c56, // Keyword + 0xff00ff00, // Number + 0xff7070e0, // String + 0xff70a0e0, // Char literal + 0xffffffff, // Punctuation + 0xff408080, // Preprocessor + 0xffaaaaaa, // Identifier + 0xff9bc64d, // Known identifier + 0xffc040a0, // Preproc identifier + 0xff206020, // Comment (single line) + 0xff406020, // Comment (multi line) + 0xff101010, // Background + 0xffe0e0e0, // Cursor + 0x80a06020, // Selection + 0x800020ff, // ErrorMarker + 0x40f08000, // Breakpoint + 0xff707000, // Line number + 0x40000000, // Current line fill + 0x40808080, // Current line fill (inactive) + 0x40a0a0a0, // Current line edge + }}; + return p; +} + +const TextEditor::Palette& TextEditor::GetLightPalette() { + const static Palette p = {{ + 0xff7f7f7f, // None + 0xffff0c06, // Keyword + 0xff008000, // Number + 0xff2020a0, // String + 0xff304070, // Char literal + 0xff000000, // Punctuation + 0xff406060, // Preprocessor + 0xff404040, // Identifier + 0xff606010, // Known identifier + 0xffc040a0, // Preproc identifier + 0xff205020, // Comment (single line) + 0xff405020, // Comment (multi line) + 0xffffffff, // Background + 0xff000000, // Cursor + 0x80600000, // Selection + 0xa00010ff, // ErrorMarker + 0x80f08000, // Breakpoint + 0xff505000, // Line number + 0x40000000, // Current line fill + 0x40808080, // Current line fill (inactive) + 0x40000000, // Current line edge + }}; + return p; +} + +const TextEditor::Palette& TextEditor::GetRetroBluePalette() { + const static Palette p = {{ + 0xff00ffff, // None + 0xffffff00, // Keyword + 0xff00ff00, // Number + 0xff808000, // String + 0xff808000, // Char literal + 0xffffffff, // Punctuation + 0xff008000, // Preprocessor + 0xff00ffff, // Identifier + 0xffffffff, // Known identifier + 0xffff00ff, // Preproc identifier + 0xff808080, // Comment (single line) + 0xff404040, // Comment (multi line) + 0xff800000, // Background + 0xff0080ff, // Cursor + 0x80ffff00, // Selection + 0xa00000ff, // ErrorMarker + 0x80ff8000, // Breakpoint + 0xff808000, // Line number + 0x40000000, // Current line fill + 0x40808080, // Current line fill (inactive) + 0x40000000, // Current line edge + }}; + return p; +} + +std::string TextEditor::GetText() const { + return GetText(Coordinates(), Coordinates((int)mLines.size(), 0)); +} + +std::vector TextEditor::GetTextLines() const { + std::vector result; + + result.reserve(mLines.size()); + + for (auto& line : mLines) { + std::string text; + + text.resize(line.size()); + + for (size_t i = 0; i < line.size(); ++i) + text[i] = line[i].mChar; + + result.emplace_back(std::move(text)); + } + + return result; +} + +std::string TextEditor::GetSelectedText() const { + return GetText(mState.mSelectionStart, mState.mSelectionEnd); +} + +std::string TextEditor::GetCurrentLineText() const { + auto lineLength = GetLineMaxColumn(mState.mCursorPosition.mLine); + return GetText(Coordinates(mState.mCursorPosition.mLine, 0), + Coordinates(mState.mCursorPosition.mLine, lineLength)); +} + +void TextEditor::ProcessInputs() {} + +void TextEditor::Colorize(int aFromLine, int aLines) { + int toLine = + aLines == -1 ? (int)mLines.size() : std::min((int)mLines.size(), aFromLine + aLines); + mColorRangeMin = std::min(mColorRangeMin, aFromLine); + mColorRangeMax = std::max(mColorRangeMax, toLine); + mColorRangeMin = std::max(0, mColorRangeMin); + mColorRangeMax = std::max(mColorRangeMin, mColorRangeMax); + mCheckComments = true; +} + +void TextEditor::ColorizeRange(int aFromLine, int aToLine) { + if (mLines.empty() || aFromLine >= aToLine) + return; + + std::string buffer; + std::cmatch results; + std::string id; + + int endLine = std::max(0, std::min((int)mLines.size(), aToLine)); + for (int i = aFromLine; i < endLine; ++i) { + auto& line = mLines[i]; + + if (line.empty()) + continue; + + buffer.resize(line.size()); + for (size_t j = 0; j < line.size(); ++j) { + auto& col = line[j]; + buffer[j] = col.mChar; + col.mColorIndex = PaletteIndex::Default; + } + + const char* bufferBegin = &buffer.front(); + const char* bufferEnd = bufferBegin + buffer.size(); + + auto last = bufferEnd; + + for (auto first = bufferBegin; first != last;) { + const char* token_begin = nullptr; + const char* token_end = nullptr; + PaletteIndex token_color = PaletteIndex::Default; + + bool hasTokenizeResult = false; + + if (mLanguageDefinition.mTokenize != nullptr) { + if (mLanguageDefinition.mTokenize(first, last, token_begin, token_end, token_color)) + hasTokenizeResult = true; + } + + if (hasTokenizeResult == false) { + // todo : remove + // printf("using regex for %.*s\n", first + 10 < last ? 10 : int(last - first), + // first); + + for (auto& p : mRegexList) { + if (std::regex_search(first, last, results, p.first, + std::regex_constants::match_continuous)) { + hasTokenizeResult = true; + + auto& v = *results.begin(); + token_begin = v.first; + token_end = v.second; + token_color = p.second; + break; + } + } + } + + if (hasTokenizeResult == false) { + first++; + } else { + const size_t token_length = token_end - token_begin; + + if (token_color == PaletteIndex::Identifier) { + id.assign(token_begin, token_end); + + // todo : allmost all language definitions use lower case to specify keywords, + // so shouldn't this use ::tolower ? + if (!mLanguageDefinition.mCaseSensitive) + std::transform(id.begin(), id.end(), id.begin(), ::toupper); + + if (!line[first - bufferBegin].mPreprocessor) { + if (mLanguageDefinition.mKeywords.count(id) != 0) + token_color = PaletteIndex::Keyword; + else if (mLanguageDefinition.mIdentifiers.count(id) != 0) + token_color = PaletteIndex::KnownIdentifier; + else if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0) + token_color = PaletteIndex::PreprocIdentifier; + } else { + if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0) + token_color = PaletteIndex::PreprocIdentifier; + } + } + + for (size_t j = 0; j < token_length; ++j) + line[(token_begin - bufferBegin) + j].mColorIndex = token_color; + + first = token_end; + } + } + } +} + +void TextEditor::ColorizeInternal() { + if (mLines.empty() || !mColorizerEnabled) + return; + + if (mCheckComments) { + auto endLine = mLines.size(); + auto endIndex = 0; + auto commentStartLine = endLine; + auto commentStartIndex = endIndex; + auto withinString = false; + auto withinSingleLineComment = false; + auto withinPreproc = false; + auto firstChar = true; // there is no other non-whitespace characters in the line before + auto concatenate = false; // '\' on the very end of the line + auto currentLine = 0; + auto currentIndex = 0; + while (currentLine < endLine || currentIndex < endIndex) { + auto& line = mLines[currentLine]; + + if (currentIndex == 0 && !concatenate) { + withinSingleLineComment = false; + withinPreproc = false; + firstChar = true; + } + + concatenate = false; + + if (!line.empty()) { + auto& g = line[currentIndex]; + auto c = g.mChar; + + if (c != mLanguageDefinition.mPreprocChar && !isspace(c)) + firstChar = false; + + if (currentIndex == (int)line.size() - 1 && line[line.size() - 1].mChar == '\\') + concatenate = true; + + bool inComment = + (commentStartLine < currentLine || + (commentStartLine == currentLine && commentStartIndex <= currentIndex)); + + if (withinString) { + line[currentIndex].mMultiLineComment = inComment; + + if (c == '\"') { + if (currentIndex + 1 < (int)line.size() && + line[currentIndex + 1].mChar == '\"') { + currentIndex += 1; + if (currentIndex < (int)line.size()) + line[currentIndex].mMultiLineComment = inComment; + } else + withinString = false; + } else if (c == '\\') { + currentIndex += 1; + if (currentIndex < (int)line.size()) + line[currentIndex].mMultiLineComment = inComment; + } + } else { + if (firstChar && c == mLanguageDefinition.mPreprocChar) + withinPreproc = true; + + if (c == '\"') { + withinString = true; + line[currentIndex].mMultiLineComment = inComment; + } else { + auto pred = [](const char& a, const Glyph& b) { return a == b.mChar; }; + auto from = line.begin() + currentIndex; + auto& startStr = mLanguageDefinition.mCommentStart; + auto& singleStartStr = mLanguageDefinition.mSingleLineComment; + + if (singleStartStr.size() > 0 && + currentIndex + singleStartStr.size() <= line.size() && + equals(singleStartStr.begin(), singleStartStr.end(), from, + from + singleStartStr.size(), pred)) { + withinSingleLineComment = true; + } else if (!withinSingleLineComment && + currentIndex + startStr.size() <= line.size() && + equals(startStr.begin(), startStr.end(), from, + from + startStr.size(), pred)) { + commentStartLine = currentLine; + commentStartIndex = currentIndex; + } + + inComment = inComment = + (commentStartLine < currentLine || (commentStartLine == currentLine && + commentStartIndex <= currentIndex)); + + line[currentIndex].mMultiLineComment = inComment; + line[currentIndex].mComment = withinSingleLineComment; + + auto& endStr = mLanguageDefinition.mCommentEnd; + if (currentIndex + 1 >= (int)endStr.size() && + equals(endStr.begin(), endStr.end(), from + 1 - endStr.size(), from + 1, + pred)) { + commentStartIndex = endIndex; + commentStartLine = endLine; + } + } + } + line[currentIndex].mPreprocessor = withinPreproc; + currentIndex += UTF8CharLength(c); + if (currentIndex >= (int)line.size()) { + currentIndex = 0; + ++currentLine; + } + } else { + currentIndex = 0; + ++currentLine; + } + } + mCheckComments = false; + } + + if (mColorRangeMin < mColorRangeMax) { + const int increment = (mLanguageDefinition.mTokenize == nullptr) ? 10 : 10000; + const int to = std::min(mColorRangeMin + increment, mColorRangeMax); + ColorizeRange(mColorRangeMin, to); + mColorRangeMin = to; + + if (mColorRangeMax == mColorRangeMin) { + mColorRangeMin = std::numeric_limits::max(); + mColorRangeMax = 0; + } + return; + } +} + +float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const { + auto& line = mLines[aFrom.mLine]; + float distance = 0.0f; + float spaceSize = + ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr) + .x; + int colIndex = GetCharacterIndex(aFrom); + for (size_t it = 0u; it < line.size() && it < colIndex;) { + if (line[it].mChar == '\t') { + distance = (1.0f + std::floor((1.0f + distance) / (float(mTabSize) * spaceSize))) * + (float(mTabSize) * spaceSize); + ++it; + } else { + auto d = UTF8CharLength(line[it].mChar); + char tempCString[7]; + int i = 0; + for (; i < 6 && d-- > 0 && it < (int)line.size(); i++, it++) + tempCString[i] = line[it].mChar; + + tempCString[i] = '\0'; + distance += ImGui::GetFont() + ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, tempCString, + nullptr, nullptr) + .x; + } + } + + return distance; +} + +void TextEditor::EnsureCursorVisible() { + if (!mWithinRender) { + mScrollToCursor = true; + return; + } + + float scrollX = ImGui::GetScrollX(); + float scrollY = ImGui::GetScrollY(); + + auto height = ImGui::GetWindowHeight(); + auto width = ImGui::GetWindowWidth(); + + auto top = 1 + (int)ceil(scrollY / mCharAdvance.y); + auto bottom = (int)ceil((scrollY + height) / mCharAdvance.y); + + auto left = (int)ceil(scrollX / mCharAdvance.x); + auto right = (int)ceil((scrollX + width) / mCharAdvance.x); + + auto pos = GetActualCursorCoordinates(); + auto len = TextDistanceToLineStart(pos); + + if (pos.mLine < top) + ImGui::SetScrollY(std::max(0.0f, (pos.mLine - 1) * mCharAdvance.y)); + if (pos.mLine > bottom - 4) + ImGui::SetScrollY(std::max(0.0f, (pos.mLine + 4) * mCharAdvance.y - height)); + if (len + mTextStart < left + 4) + ImGui::SetScrollX(std::max(0.0f, len + mTextStart - 4)); + if (len + mTextStart > right - 4) + ImGui::SetScrollX(std::max(0.0f, len + mTextStart + 4 - width)); +} + +int TextEditor::GetPageSize() const { + auto height = ImGui::GetWindowHeight() - 20.0f; + return (int)floor(height / mCharAdvance.y); +} + +TextEditor::UndoRecord::UndoRecord( + const std::string& aAdded, const TextEditor::Coordinates aAddedStart, + const TextEditor::Coordinates aAddedEnd, const std::string& aRemoved, + const TextEditor::Coordinates aRemovedStart, const TextEditor::Coordinates aRemovedEnd, + TextEditor::EditorState& aBefore, TextEditor::EditorState& aAfter) + : mAdded(aAdded), mAddedStart(aAddedStart), mAddedEnd(aAddedEnd), mRemoved(aRemoved), + mRemovedStart(aRemovedStart), mRemovedEnd(aRemovedEnd), mBefore(aBefore), mAfter(aAfter) { + ASSERT(mAddedStart <= mAddedEnd); + ASSERT(mRemovedStart <= mRemovedEnd); +} + +void TextEditor::UndoRecord::Undo(TextEditor* aEditor) { + if (!mAdded.empty()) { + aEditor->DeleteRange(mAddedStart, mAddedEnd); + aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 2); + } + + if (!mRemoved.empty()) { + auto start = mRemovedStart; + aEditor->InsertTextAt(start, mRemoved.c_str()); + aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 2); + } + + aEditor->mState = mBefore; + aEditor->EnsureCursorVisible(); +} + +void TextEditor::UndoRecord::Redo(TextEditor* aEditor) { + if (!mRemoved.empty()) { + aEditor->DeleteRange(mRemovedStart, mRemovedEnd); + aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 1); + } + + if (!mAdded.empty()) { + auto start = mAddedStart; + aEditor->InsertTextAt(start, mAdded.c_str()); + aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 1); + } + + aEditor->mState = mAfter; + aEditor->EnsureCursorVisible(); +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::GLSL() { + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) { + static const char* const keywords[] = { + "auto", "break", "case", "char", "const", "continue", + "default", "do", "double", "else", "enum", "extern", + "float", "for", "goto", "if", "inline", "int", + "long", "register", "restrict", "return", "short", "signed", + "sizeof", "static", "struct", "switch", "typedef", "union", + "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", + "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", "_Noreturn", + "_Static_assert", "_Thread_local"}; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "asin", "atan", "atexit", "atof", + "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", + "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", + "isalpha", "isdigit", "isgraph", "ispunct", "isspace", "isupper", "kbhit", + "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", + "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", + "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper"}; + for (auto& k : identifiers) { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenRegexStrings.push_back(std::make_pair( + "[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", + PaletteIndex::Punctuation)); + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; + + langDef.mName = "GLSL"; + + inited = true; + } + return langDef; +} + +} // namespace Core::Devtools::Widget diff --git a/src/core/devtools/widget/text_editor.h b/src/core/devtools/widget/text_editor.h new file mode 100644 index 000000000..5c3f29f11 --- /dev/null +++ b/src/core/devtools/widget/text_editor.h @@ -0,0 +1,408 @@ +// SPDX-FileCopyrightText: Copyright (c) 2017 BalazsJako +// SPDX-License-Identifier: MIT + +// source: https://github.com/BalazsJako/ImGuiColorTextEdit + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/assert.h" +#include "imgui.h" + +namespace Core::Devtools::Widget { + +class TextEditor { +public: + enum class PaletteIndex { + Default, + Keyword, + Number, + String, + CharLiteral, + Punctuation, + Preprocessor, + Identifier, + KnownIdentifier, + PreprocIdentifier, + Comment, + MultiLineComment, + Background, + Cursor, + Selection, + ErrorMarker, + Breakpoint, + LineNumber, + CurrentLineFill, + CurrentLineFillInactive, + CurrentLineEdge, + Max + }; + + enum class SelectionMode { Normal, Word, Line }; + + struct Breakpoint { + int mLine; + bool mEnabled; + std::string mCondition; + + Breakpoint() : mLine(-1), mEnabled(false) {} + }; + + // Represents a character coordinate from the user's point of view, + // i. e. consider an uniform grid (assuming fixed-width font) on the + // screen as it is rendered, and each cell has its own coordinate, starting from 0. + // Tabs are counted as [1..mTabSize] count empty spaces, depending on + // how many space is necessary to reach the next tab stop. + // For example, coordinate (1, 5) represents the character 'B' in a line "\tABC", when mTabSize + // = 4, because it is rendered as " ABC" on the screen. + struct Coordinates { + int mLine, mColumn; + Coordinates() : mLine(0), mColumn(0) {} + Coordinates(int aLine, int aColumn) : mLine(aLine), mColumn(aColumn) { + ASSERT(aLine >= 0); + ASSERT(aColumn >= 0); + } + static Coordinates Invalid() { + static Coordinates invalid(-1, -1); + return invalid; + } + + bool operator==(const Coordinates& o) const { + return mLine == o.mLine && mColumn == o.mColumn; + } + + bool operator!=(const Coordinates& o) const { + return mLine != o.mLine || mColumn != o.mColumn; + } + + bool operator<(const Coordinates& o) const { + if (mLine != o.mLine) + return mLine < o.mLine; + return mColumn < o.mColumn; + } + + bool operator>(const Coordinates& o) const { + if (mLine != o.mLine) + return mLine > o.mLine; + return mColumn > o.mColumn; + } + + bool operator<=(const Coordinates& o) const { + if (mLine != o.mLine) + return mLine < o.mLine; + return mColumn <= o.mColumn; + } + + bool operator>=(const Coordinates& o) const { + if (mLine != o.mLine) + return mLine > o.mLine; + return mColumn >= o.mColumn; + } + }; + + struct Identifier { + Coordinates mLocation; + std::string mDeclaration; + }; + + typedef std::string String; + typedef std::unordered_map Identifiers; + typedef std::unordered_set Keywords; + typedef std::map ErrorMarkers; + typedef std::unordered_set Breakpoints; + typedef std::array Palette; + typedef uint8_t Char; + + struct Glyph { + Char mChar; + PaletteIndex mColorIndex = PaletteIndex::Default; + bool mComment : 1; + bool mMultiLineComment : 1; + bool mPreprocessor : 1; + + Glyph(Char aChar, PaletteIndex aColorIndex) + : mChar(aChar), mColorIndex(aColorIndex), mComment(false), mMultiLineComment(false), + mPreprocessor(false) {} + }; + + typedef std::vector Line; + typedef std::vector Lines; + + struct LanguageDefinition { + typedef std::pair TokenRegexString; + typedef std::vector TokenRegexStrings; + typedef bool (*TokenizeCallback)(const char* in_begin, const char* in_end, + const char*& out_begin, const char*& out_end, + PaletteIndex& paletteIndex); + + std::string mName; + Keywords mKeywords; + Identifiers mIdentifiers; + Identifiers mPreprocIdentifiers; + std::string mCommentStart, mCommentEnd, mSingleLineComment; + char mPreprocChar; + bool mAutoIndentation; + + TokenizeCallback mTokenize; + + TokenRegexStrings mTokenRegexStrings; + + bool mCaseSensitive; + + LanguageDefinition() + : mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true) {} + + static const LanguageDefinition& GLSL(); + }; + + TextEditor(); + ~TextEditor(); + + void SetLanguageDefinition(const LanguageDefinition& aLanguageDef); + const LanguageDefinition& GetLanguageDefinition() const { + return mLanguageDefinition; + } + + const Palette& GetPalette() const { + return mPaletteBase; + } + void SetPalette(const Palette& aValue); + + void SetErrorMarkers(const ErrorMarkers& aMarkers) { + mErrorMarkers = aMarkers; + } + void SetBreakpoints(const Breakpoints& aMarkers) { + mBreakpoints = aMarkers; + } + + void Render(const char* aTitle, const ImVec2& aSize = ImVec2(), bool aBorder = false); + void SetText(const std::string& aText); + std::string GetText() const; + + void SetTextLines(const std::vector& aLines); + std::vector GetTextLines() const; + + std::string GetSelectedText() const; + std::string GetCurrentLineText() const; + + int GetTotalLines() const { + return (int)mLines.size(); + } + bool IsOverwrite() const { + return mOverwrite; + } + + void SetReadOnly(bool aValue); + bool IsReadOnly() const { + return mReadOnly; + } + bool IsTextChanged() const { + return mTextChanged; + } + bool IsCursorPositionChanged() const { + return mCursorPositionChanged; + } + + bool IsColorizerEnabled() const { + return mColorizerEnabled; + } + void SetColorizerEnable(bool aValue); + + Coordinates GetCursorPosition() const { + return GetActualCursorCoordinates(); + } + void SetCursorPosition(const Coordinates& aPosition); + + inline void SetHandleMouseInputs(bool aValue) { + mHandleMouseInputs = aValue; + } + inline bool IsHandleMouseInputsEnabled() const { + return mHandleKeyboardInputs; + } + + inline void SetHandleKeyboardInputs(bool aValue) { + mHandleKeyboardInputs = aValue; + } + inline bool IsHandleKeyboardInputsEnabled() const { + return mHandleKeyboardInputs; + } + + inline void SetImGuiChildIgnored(bool aValue) { + mIgnoreImGuiChild = aValue; + } + inline bool IsImGuiChildIgnored() const { + return mIgnoreImGuiChild; + } + + inline void SetShowWhitespaces(bool aValue) { + mShowWhitespaces = aValue; + } + inline bool IsShowingWhitespaces() const { + return mShowWhitespaces; + } + + void SetTabSize(int aValue); + inline int GetTabSize() const { + return mTabSize; + } + + void InsertText(const std::string& aValue); + void InsertText(const char* aValue); + + void MoveUp(int aAmount = 1, bool aSelect = false); + void MoveDown(int aAmount = 1, bool aSelect = false); + void MoveLeft(int aAmount = 1, bool aSelect = false, bool aWordMode = false); + void MoveRight(int aAmount = 1, bool aSelect = false, bool aWordMode = false); + void MoveTop(bool aSelect = false); + void MoveBottom(bool aSelect = false); + void MoveHome(bool aSelect = false); + void MoveEnd(bool aSelect = false); + + void SetSelectionStart(const Coordinates& aPosition); + void SetSelectionEnd(const Coordinates& aPosition); + void SetSelection(const Coordinates& aStart, const Coordinates& aEnd, + SelectionMode aMode = SelectionMode::Normal); + void SelectWordUnderCursor(); + void SelectAll(); + bool HasSelection() const; + + void Copy(); + void Cut(); + void Paste(); + void Delete(); + + bool CanUndo() const; + bool CanRedo() const; + void Undo(int aSteps = 1); + void Redo(int aSteps = 1); + + static const Palette& GetDarkPalette(); + static const Palette& GetLightPalette(); + static const Palette& GetRetroBluePalette(); + +private: + typedef std::vector> RegexList; + + struct EditorState { + Coordinates mSelectionStart; + Coordinates mSelectionEnd; + Coordinates mCursorPosition; + }; + + class UndoRecord { + public: + UndoRecord() {} + ~UndoRecord() {} + + UndoRecord(const std::string& aAdded, const TextEditor::Coordinates aAddedStart, + const TextEditor::Coordinates aAddedEnd, + + const std::string& aRemoved, const TextEditor::Coordinates aRemovedStart, + const TextEditor::Coordinates aRemovedEnd, + + TextEditor::EditorState& aBefore, TextEditor::EditorState& aAfter); + + void Undo(TextEditor* aEditor); + void Redo(TextEditor* aEditor); + + std::string mAdded; + Coordinates mAddedStart; + Coordinates mAddedEnd; + + std::string mRemoved; + Coordinates mRemovedStart; + Coordinates mRemovedEnd; + + EditorState mBefore; + EditorState mAfter; + }; + + typedef std::vector UndoBuffer; + + void ProcessInputs(); + void Colorize(int aFromLine = 0, int aCount = -1); + void ColorizeRange(int aFromLine = 0, int aToLine = 0); + void ColorizeInternal(); + float TextDistanceToLineStart(const Coordinates& aFrom) const; + void EnsureCursorVisible(); + int GetPageSize() const; + std::string GetText(const Coordinates& aStart, const Coordinates& aEnd) const; + Coordinates GetActualCursorCoordinates() const; + Coordinates SanitizeCoordinates(const Coordinates& aValue) const; + void Advance(Coordinates& aCoordinates) const; + void DeleteRange(const Coordinates& aStart, const Coordinates& aEnd); + int InsertTextAt(Coordinates& aWhere, const char* aValue); + void AddUndo(UndoRecord& aValue); + Coordinates ScreenPosToCoordinates(const ImVec2& aPosition) const; + Coordinates FindWordStart(const Coordinates& aFrom) const; + Coordinates FindWordEnd(const Coordinates& aFrom) const; + Coordinates FindNextWord(const Coordinates& aFrom) const; + int GetCharacterIndex(const Coordinates& aCoordinates) const; + int GetCharacterColumn(int aLine, int aIndex) const; + int GetLineCharacterCount(int aLine) const; + int GetLineMaxColumn(int aLine) const; + bool IsOnWordBoundary(const Coordinates& aAt) const; + void RemoveLine(int aStart, int aEnd); + void RemoveLine(int aIndex); + Line& InsertLine(int aIndex); + void EnterCharacter(ImWchar aChar, bool aShift); + void Backspace(); + void DeleteSelection(); + std::string GetWordUnderCursor() const; + std::string GetWordAt(const Coordinates& aCoords) const; + ImU32 GetGlyphColor(const Glyph& aGlyph) const; + + void HandleKeyboardInputs(); + void HandleMouseInputs(); + void Render(); + + float mLineSpacing; + Lines mLines; + EditorState mState; + UndoBuffer mUndoBuffer; + int mUndoIndex; + + int mTabSize; + bool mOverwrite; + bool mReadOnly; + bool mWithinRender; + bool mScrollToCursor; + bool mScrollToTop; + bool mTextChanged; + bool mColorizerEnabled; + float mTextStart; // position (in pixels) where a code line starts relative to the left of the + // TextEditor. + int mLeftMargin; + bool mCursorPositionChanged; + int mColorRangeMin, mColorRangeMax; + SelectionMode mSelectionMode; + bool mHandleKeyboardInputs; + bool mHandleMouseInputs; + bool mIgnoreImGuiChild; + bool mShowWhitespaces; + + Palette mPaletteBase; + Palette mPalette; + LanguageDefinition mLanguageDefinition; + RegexList mRegexList; + + bool mCheckComments; + Breakpoints mBreakpoints; + ErrorMarkers mErrorMarkers; + ImVec2 mCharAdvance; + Coordinates mInteractiveStart, mInteractiveEnd; + std::string mLineBuffer; + uint64_t mStartTime; + + float mLastClick; +}; + +} // namespace Core::Devtools::Widget \ No newline at end of file diff --git a/src/core/libraries/dialogs/error_dialog.cpp b/src/core/libraries/dialogs/error_dialog.cpp index b122e2d0a..811f2cb99 100644 --- a/src/core/libraries/dialogs/error_dialog.cpp +++ b/src/core/libraries/dialogs/error_dialog.cpp @@ -75,7 +75,7 @@ public: std::min(io.DisplaySize.y, 300.0f), }; - CentralizeWindow(); + CentralizeNextWindow(); SetNextWindowSize(window_size); SetNextWindowCollapsed(false); if (first_render || !io.NavActive) { diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index ce30895ca..4d8aa8817 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -519,10 +519,12 @@ void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw) { // Dumping them using the current ring pointer would result in files containing only the // `IndirectBuffer` command. To access the actual command stream, we need to unwrap the IB. auto acb = acb_span; + auto base_addr = reinterpret_cast(acb_ptr); const auto* indirect_buffer = reinterpret_cast(acb_span.data()); if (indirect_buffer->header.opcode == PM4ItOpcode::IndirectBuffer) { - acb = {indirect_buffer->Address(), indirect_buffer->ib_size}; + base_addr = reinterpret_cast(indirect_buffer->Address()); + acb = {reinterpret_cast(base_addr), indirect_buffer->ib_size}; } using namespace DebugStateType; @@ -532,9 +534,9 @@ void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw) { .submit_num = seq_num, .num2 = gnm_vqid, .data = {acb.begin(), acb.end()}, + .base_addr = base_addr, }); } - liverpool->SubmitAsc(vqid, acb_span); *asc_queue.read_addr += acb_size; @@ -1125,9 +1127,25 @@ int PS4_SYSV_ABI sceGnmInsertSetColorMarker() { return ORBIS_OK; } -int PS4_SYSV_ABI sceGnmInsertSetMarker() { - LOG_ERROR(Lib_GnmDriver, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceGnmInsertSetMarker(u32* cmdbuf, u32 size, const char* marker) { + LOG_TRACE(Lib_GnmDriver, "called"); + + if (cmdbuf && marker) { + const auto len = std::strlen(marker); + const u32 packet_size = ((len + 8) >> 2) + ((len + 0xc) >> 3) * 2; + if (packet_size + 2 == size) { + auto* nop = reinterpret_cast(cmdbuf); + nop->header = + PM4Type3Header{PM4ItOpcode::Nop, packet_size, PM4ShaderType::ShaderGraphics}; + nop->data_block[0] = PM4CmdNop::PayloadType::DebugSetMarker; + const auto marker_len = len + 1; + std::memcpy(&nop->data_block[1], marker, marker_len); + std::memset(reinterpret_cast(&nop->data_block[1]) + marker_len, 0, + packet_size * 4 - marker_len); + return ORBIS_OK; + } + } + return -1; } int PS4_SYSV_ABI sceGnmInsertThreadTraceMarker() { @@ -2163,15 +2181,16 @@ s32 PS4_SYSV_ABI sceGnmSubmitCommandBuffers(u32 count, const u32* dcb_gpu_addrs[ .submit_num = seq_num, .num2 = cbpair, .data = {dcb_span.begin(), dcb_span.end()}, + .base_addr = reinterpret_cast(dcb_gpu_addrs[cbpair]), }); DebugState.PushQueueDump({ .type = QueueType::ccb, .submit_num = seq_num, .num2 = cbpair, .data = {ccb_span.begin(), ccb_span.end()}, + .base_addr = reinterpret_cast(ccb), }); } - liverpool->SubmitGfx(dcb_span, ccb_span); } diff --git a/src/core/libraries/gnmdriver/gnmdriver.h b/src/core/libraries/gnmdriver/gnmdriver.h index 33bccf427..a95daa90d 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.h +++ b/src/core/libraries/gnmdriver/gnmdriver.h @@ -108,7 +108,7 @@ s32 PS4_SYSV_ABI sceGnmInsertPopMarker(u32* cmdbuf, u32 size); s32 PS4_SYSV_ABI sceGnmInsertPushColorMarker(u32* cmdbuf, u32 size, const char* marker, u32 color); s32 PS4_SYSV_ABI sceGnmInsertPushMarker(u32* cmdbuf, u32 size, const char* marker); int PS4_SYSV_ABI sceGnmInsertSetColorMarker(); -int PS4_SYSV_ABI sceGnmInsertSetMarker(); +s32 PS4_SYSV_ABI sceGnmInsertSetMarker(u32* cmdbuf, u32 size, const char* marker); int PS4_SYSV_ABI sceGnmInsertThreadTraceMarker(); s32 PS4_SYSV_ABI sceGnmInsertWaitFlipDone(u32* cmdbuf, u32 size, s32 vo_handle, u32 buf_idx); int PS4_SYSV_ABI sceGnmIsCoredumpValid(); diff --git a/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp index c4bf84258..01e56f8b8 100644 --- a/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp +++ b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp @@ -98,7 +98,7 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) { param_sfo.Open(param_sfo_path); auto last_write = param_sfo.GetLastWrite(); -#ifdef _WIN32 +#if defined(_WIN32) && !defined(__GNUC__) && !defined(__MINGW32__) && !defined(__MINGW64__) auto utc_time = std::chrono::file_clock::to_utc(last_write); #else auto utc_time = std::chrono::file_clock::to_sys(last_write); @@ -402,7 +402,7 @@ void SaveDialogUi::Draw() { }; } - CentralizeWindow(); + CentralizeNextWindow(); SetNextWindowSize(window_size); SetNextWindowCollapsed(false); if (first_render || !io.NavActive) { diff --git a/src/core/libraries/system/msgdialog_ui.cpp b/src/core/libraries/system/msgdialog_ui.cpp index ae1dced12..862f5a569 100644 --- a/src/core/libraries/system/msgdialog_ui.cpp +++ b/src/core/libraries/system/msgdialog_ui.cpp @@ -256,7 +256,7 @@ void MsgDialogUi::Draw() { std::min(io.DisplaySize.y, 300.0f), }; - CentralizeWindow(); + CentralizeNextWindow(); SetNextWindowSize(window_size); SetNextWindowCollapsed(false); if (first_render || !io.NavActive) { diff --git a/src/imgui/imgui_std.h b/src/imgui/imgui_std.h index 168204ea8..ce79da705 100644 --- a/src/imgui/imgui_std.h +++ b/src/imgui/imgui_std.h @@ -25,11 +25,16 @@ inline float FastInFastOutCubic(float x) { } // namespace Easing -inline void CentralizeWindow() { +inline void CentralizeNextWindow() { const auto display_size = GetIO().DisplaySize; SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f}); } +inline void CentralizeWindow() { + const auto display_size = GetIO().DisplaySize; + SetWindowPos(display_size / 2.0f); +} + inline void KeepWindowInside(ImVec2 display_size = GetIO().DisplaySize) { const auto cur_pos = GetWindowPos(); if (cur_pos.x < 0.0f || cur_pos.y < 0.0f) { diff --git a/src/imgui/renderer/texture_manager.cpp b/src/imgui/renderer/texture_manager.cpp index ba4a05d01..7f9c69d49 100644 --- a/src/imgui/renderer/texture_manager.cpp +++ b/src/imgui/renderer/texture_manager.cpp @@ -7,6 +7,7 @@ #include #include "common/assert.h" +#include "common/config.h" #include "common/io_file.h" #include "common/polyfill_thread.h" #include "imgui_impl_vulkan.h" @@ -147,6 +148,11 @@ void WorkerLoop() { g_job_list.pop_front(); g_job_list_mtx.unlock(); + if (Config::vkCrashDiagnosticEnabled()) { + // FIXME: Crash diagnostic hangs when building the command buffer here + continue; + } + if (!path.empty()) { // Decode PNG from file Common::FS::IOFile file(path, Common::FS::FileAccessMode::Read); if (!file.IsOpen()) { diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index b3b718836..d4ebeb883 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -6,6 +6,7 @@ #include "common/debug.h" #include "common/polyfill_thread.h" #include "common/thread.h" +#include "core/debug_state.h" #include "core/libraries/videoout/driver.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/amdgpu/pm4_cmds.h" @@ -187,6 +188,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(dcb.data()); while (!dcb.empty()) { const auto* header = reinterpret_cast(dcb.data()); const u32 type = header->type; @@ -359,6 +361,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanindex_base_hi); regs.num_indices = draw_index->index_count; regs.draw_initiator = draw_index->draw_initiator; + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } if (rasterizer) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndex2", cmd_address)); @@ -373,6 +378,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanmax_size; regs.num_indices = draw_index_off->index_count; regs.draw_initiator = draw_index_off->draw_initiator; + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } if (rasterizer) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin( @@ -386,6 +394,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); regs.num_indices = draw_index->index_count; regs.draw_initiator = draw_index->draw_initiator; + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } if (rasterizer) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndexAuto", cmd_address)); @@ -399,6 +410,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spandata_offset; const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr; const auto size = sizeof(PM4CmdDrawIndirect::DrawInstancedArgs); + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } if (rasterizer) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndirect", cmd_address)); @@ -413,6 +427,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spandata_offset; const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr; const auto size = sizeof(PM4CmdDrawIndexIndirect::DrawIndexInstancedArgs); + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } if (rasterizer) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin( @@ -428,6 +445,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spandim_y; regs.cs_program.dim_z = dispatch_direct->dim_z; regs.cs_program.dispatch_initiator = dispatch_direct->dispatch_initiator; + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:Dispatch", cmd_address)); @@ -442,6 +462,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spandata_offset; const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr; const auto size = sizeof(PM4CmdDispatchIndirect::GroupDimensions); + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin( @@ -576,6 +599,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span acb, int vqid) { TracyFiberEnter(acb_task_name); + auto base_addr = reinterpret_cast(acb.data()); while (!acb.empty()) { const auto* header = reinterpret_cast(acb.data()); const u32 type = header->type; @@ -620,6 +644,9 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, int vqid) { regs.cs_program.dim_y = dispatch_direct->dim_y; regs.cs_program.dim_z = dispatch_direct->dim_z; regs.cs_program.dispatch_initiator = dispatch_direct->dispatch_initiator; + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin(fmt::format("acb[{}]:{}:Dispatch", vqid, cmd_address)); diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 1c994d0a0..a4cf79334 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -772,6 +772,8 @@ struct Liverpool { BitField<27, 1, u32> fmask_compress_1frag_only; BitField<28, 1, u32> dcc_enable; BitField<29, 1, u32> cmask_addr_type; + + u32 u32all; } info; union Color0Attrib { BitField<0, 5, TilingMode> tile_mode_index; @@ -780,6 +782,8 @@ struct Liverpool { BitField<12, 3, u32> num_samples_log2; BitField<15, 2, u32> num_fragments_log2; BitField<17, 1, u32> force_dst_alpha_1; + + u32 u32all; } attrib; INSERT_PADDING_WORDS(1); u32 cmask_base_address; @@ -935,7 +939,7 @@ struct Liverpool { BitField<5, 1, u32> gs_en; BitField<6, 1, u32> vs_en; - bool IsStageEnabled(u32 stage) { + bool IsStageEnabled(u32 stage) const { switch (stage) { case 0: case 1: diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index b9fbfcb89..a7a862ea3 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -213,6 +213,7 @@ struct PM4CmdNop { enum PayloadType : u32 { DebugMarkerPush = 0x68750001u, ///< Begin of GPU event scope DebugMarkerPop = 0x68750002u, ///< End of GPU event scope + DebugSetMarker = 0x68750003u, ///< Set GPU event marker SetVsharpInUdata = 0x68750004u, ///< Indicates that V# will be set in the next packet SetTsharpInUdata = 0x68750005u, ///< Indicates that T# will be set in the next packet SetSsharpInUdata = 0x68750006u, ///< Indicates that S# will be set in the next packet From f0ee3919e038deefa6f3b57ec5b68730ee3902f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A5IGA?= <164882787+Xphalnos@users.noreply.github.com> Date: Sun, 13 Oct 2024 14:03:19 +0200 Subject: [PATCH 05/10] improved documentation + better toolbar icons (#1364) --- .gitmodules | 3 +- README.md | 17 ++++------ documents/Quickstart/Quickstart.md | 50 ++++++++++------------------- documents/changelog.txt | 2 +- externals/glslang | 2 +- externals/toml11 | 2 +- externals/vulkan-headers | 2 +- externals/xbyak | 2 +- externals/xxhash | 2 +- externals/zydis | 2 +- src/images/pause_icon.png | Bin 1113 -> 965 bytes src/images/settings_icon.png | Bin 4471 -> 2219 bytes src/images/stop_icon.png | Bin 1084 -> 658 bytes src/qt_gui/main_window_ui.h | 2 +- 14 files changed, 33 insertions(+), 53 deletions(-) diff --git a/.gitmodules b/.gitmodules index 6e4eac2b4..88635e645 100644 --- a/.gitmodules +++ b/.gitmodules @@ -97,4 +97,5 @@ shallow = true [submodule "externals/discord-rpc"] path = externals/discord-rpc - url = https://github.com/shadps4-emu/ext-discord-rpc + url = https://github.com/shadps4-emu/ext-discord-rpc.git + shallow = true \ No newline at end of file diff --git a/README.md b/README.md index 95428dfd0..18e69546c 100644 --- a/README.md +++ b/README.md @@ -36,14 +36,10 @@ SPDX-License-Identifier: GPL-2.0-or-later **shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++. -If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md). - -To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-emu/shadps4-game-compatibility). - -To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6). - -To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/). - +If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md).\ +To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-emu/shadps4-game-compatibility).\ +To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6).\ +To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).\ For those who'd like to donate to the project, we now have a [**Kofi page**](https://ko-fi.com/shadps4)! # Status @@ -51,7 +47,7 @@ For those who'd like to donate to the project, we now have a [**Kofi page**](htt > [!IMPORTANT] > shadPS4 is early in development, don't expect a flawless experience. -Currently, the emulator successfully runs small games like [**Sonic Mania**](https://www.youtube.com/watch?v=AAHoNzhHyCU), [**Undertale**](https://youtu.be/5zIvdy65Ro4) and it can even run [**Bloodborne**](https://www.youtube.com/watch?v=wC6s0avpQRE). +Currently, the emulator can successfully run games like [**Bloodborne**](https://www.youtube.com/watch?v=wC6s0avpQRE), [**Dark Souls Remastered**](https://www.youtube.com/watch?v=-3PA-Xwszts), [**Red Dead Redemption**](https://www.youtube.com/watch?v=Al7yz_5nLag) and many other games. # Why @@ -123,8 +119,7 @@ Logo is done by [**Xphalnos**](https://github.com/Xphalnos) # Contributing -If you want to contribute, please look the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file. - +If you want to contribute, please look the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file.\ Open a PR and we'll check it :) # Contributors diff --git a/documents/Quickstart/Quickstart.md b/documents/Quickstart/Quickstart.md index 58549e067..b2931e51e 100644 --- a/documents/Quickstart/Quickstart.md +++ b/documents/Quickstart/Quickstart.md @@ -7,16 +7,16 @@ SPDX-License-Identifier: GPL-2.0-or-later ## Summary -- [PC Requirements](#pc-requirements) - - [CPU](#cpu) - - [GPU](#gpu) - - [RAM](#ram) - - [OS](#os) -- [Have the latest WIP version](#have-the-latest-wip-version) -- [Install PKG files (Games and Updates)](#install-pkg-files) -- [Configure the emulator](#configure-the-emulator) +- [**PC Requirements**](#minimum-pc-requirements) + - [**CPU**](#cpu) + - [**GPU**](#gpu) + - [**RAM**](#ram) + - [**OS**](#os) +- [**Have the latest WIP version**](#how-to-run-the-latest-work-in-progress-builds-of-shadps4) +- [**Install PKG files (Games and Updates)**](#install-pkg-files) +- [**Configure the emulator**](#configure-the-emulator) -## PC Requirements +## Minimum PC requirements ### CPU @@ -38,41 +38,25 @@ SPDX-License-Identifier: GPL-2.0-or-later - Windows 10 or Ubuntu 22.04 -## How to run the latest Work-in-Progress builds of ShadPS4 +## How to run the latest Work-in-Progress builds of shadPS4 1. Go to In the release identified as 'pre-release' click on the down arrow(Assets), select your operating system of choice (the "**qt**" versions have a user interface, which is probably the one you want. The others are SDL versions, which can only be run via command line). ![image](https://github.com/user-attachments/assets/af520c77-797c-41a0-8f67-d87f5de3e3df) -2. Once downloaded, extract to its own folder, and run ShadPS4's executable from the extracted folder. +2. Once downloaded, extract to its own folder, and run shadPS4's executable from the extracted folder. -3. Upon first launch, ShadPS4 will prompt you to select a folder to store your installed games in. Select "Browse" and then select a folder that ShadPS4 can use to install your PKG files to. +3. Upon first launch, shadPS4 will prompt you to select a folder to store your installed games in. Select "Browse" and then select a folder that shadPS4 can use to install your PKG files to. ## Install PKG files To install PKG files (game and updates), you will need the Qt application (with UI). You will have to go to "File" then to "Install Packages (PKG)", a window will open then you will have to select the files. You can install multiple PKG files at once. Once finished, the game should appear in the application. - + ## Configure the emulator -You can configure the emulator by editing the `config.toml` file found in the `user` folder created after starting the application.\ -Some settings may be related to more technical development and debugging. For more information on those, see [Debugging](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md#configuration). +To configure the emulator, you can go through the interface and go to "settings". -Here's a list of configuration entries that are worth changing: - -- `[General]` - - - `Fullscreen`: Display the game in a full screen borderless window. - - - `logType`: Configures logging synchronization (`sync`/`async`) - - It can be beneficial to set this to `sync` in order for the log to accurately maintain message order, at the cost of performance. - - Use when sending logs to developers. See more about [reporting issues](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md#reporting-and-communicating-about-issues). - - `logFilter`: Sets the logging category for various logging classes. - - Format: `: ...`, `: <*:level> ...` - - Valid log levels: `Trace, Debug, Info, Warning, Error, Critical` - in this order, setting a level silences all levels preceding it and logs every level after it. - - Examples: - - If the log is being spammed with messages coming from Lib.Pad, you can use `Lib.Pad:Critical` to only log critical-level messages. - - If you'd like to mute everything, but still want to receive messages from Vulkan rendering: `*:Error Render.Vulkan:Info` - -- `[GPU]` - - `screenWidth` and `screenHeight`: Configures the game window width and height. +You can also configure the emulator by editing the `config.toml` file located in the `user` folder created after the application is started (Mostly useful if you are using the SDL version). +Some settings may be related to more technical development and debugging.\ +For more information on this, see [**Debugging**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md#configuration). \ No newline at end of file diff --git a/documents/changelog.txt b/documents/changelog.txt index 33c3f77be..6df09472d 100644 --- a/documents/changelog.txt +++ b/documents/changelog.txt @@ -6,7 +6,7 @@ v0.3.0 23/09/2024 - codename broamic - New translations support (26 languages) - Support for unlocking trophies - Support for more controllers (Dualshock and Xbox) -- Many GUI imporovements +- Many GUI improvements - AVplayer v0.2.0 15/08/2024 - codename validptr diff --git a/externals/glslang b/externals/glslang index 46ef757e0..e61d7bb30 160000 --- a/externals/glslang +++ b/externals/glslang @@ -1 +1 @@ -Subproject commit 46ef757e048e760b46601e6e77ae0cb72c97bd2f +Subproject commit e61d7bb3006f451968714e2f653412081871e1ee diff --git a/externals/toml11 b/externals/toml11 index d050c6b13..f925e7f28 160000 --- a/externals/toml11 +++ b/externals/toml11 @@ -1 +1 @@ -Subproject commit d050c6b137199666cae75c2628a75d63b49b1c22 +Subproject commit f925e7f287c0008813c2294798cf9ca167fd9ffd diff --git a/externals/vulkan-headers b/externals/vulkan-headers index 29f979ee5..d91597a82 160000 --- a/externals/vulkan-headers +++ b/externals/vulkan-headers @@ -1 +1 @@ -Subproject commit 29f979ee5aa58b7b005f805ea8df7a855c39ff37 +Subproject commit d91597a82f881d473887b560a03a7edf2720b72c diff --git a/externals/xbyak b/externals/xbyak index ccdf68421..d067f0d3f 160000 --- a/externals/xbyak +++ b/externals/xbyak @@ -1 +1 @@ -Subproject commit ccdf68421bc8eb85693f573080fc0a5faad862db +Subproject commit d067f0d3f55696ae8bc9a25ad7012ee80f221d54 diff --git a/externals/xxhash b/externals/xxhash index 3e321b440..d4ad85e4a 160000 --- a/externals/xxhash +++ b/externals/xxhash @@ -1 +1 @@ -Subproject commit 3e321b4407318ac1348c0b80fb6fbae8c81ad5fa +Subproject commit d4ad85e4afaad5c780f54db1dc967fff5a869ffd diff --git a/externals/zydis b/externals/zydis index bd73bc03b..9d298eb80 160000 --- a/externals/zydis +++ b/externals/zydis @@ -1 +1 @@ -Subproject commit bd73bc03b0aacaa89c9c203b9b43cd08f1b1843b +Subproject commit 9d298eb8067ff62a237203d1e1470785033e185c diff --git a/src/images/pause_icon.png b/src/images/pause_icon.png index e4356949ae52a000982a1b055d8ce3ea5e530f3e..5375689b7880bb46d843eca0f8494e66bee69f19 100644 GIT binary patch literal 965 zcmeAS@N?(olHy`uVBq!ia0vp^DImtU-d1msLTBv`w>9f#KDkjb=i$8av~#YX{;OV|SJ{5L_TAEltb!0QbM7;y zxd$W{u}{pdTEZO}P* z=XB%hfAdnG|Kpj%CUzky_kag0|CjGy1vAzczRNw3!CSna!R(UK>%aW#0xy4Eyi7cG zy8Z5TJMDkOUs>|(>X&mFe;&`>wCK>zaK`yUXKd&EDt_wH)SmU@by{ojeJ_jehC)@r z1}9hyICxAdZ5fVnpPS7zEzF?!o9n~ygSE&0>YP1iYjIY*DO_dK)`Skl`K=M%jn%Br zZkAFq2JMXPm#^KX2PV@1u2pis@n4=i3V-r0m>{- zIU2ml$!l8Uq|^sf)>L?fd7Lx~*u*nulBoYlF3m_bpiJW z`Re&gZ|-bg7FqdaQcIsWPte!W#fNrQ3!kf;YA{&@cD64BQY zxBmzo-6i+Rdc)?nowu{&bS$6Qzlcs^{;;L3y^22vnA~Llv4?-Xqg=?&+KN6{`R67{1=U l`|swmAL-)I{E#;Lew559d%-E~fxvvp;OXk;vd$@?2>?c3y|VxS literal 1113 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q{^b&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IX1_tJ5o-U3d6?5LsJew!tF5>1d z$geHn$RwiL#mg_?JYhq?9S#>q-JiS8`IP(mcX9OSu^;10u+tUO>0|9qKKLkNg9R3*POt0&Kd(&A z2xdP<^;Mtr3dDEF3a&qXitXw1-~57sM;P|8uVeh}VA4~Yz-{riMz^?MF5#Q*4n`mQ z4Fz8fS=TWi7q^YS*wpxbM(nLydNH*&4CnN+_L(V%svB8oE)9L4Ry41H!Y_52at9eZ6*T<@Hych>ww{3SWY z=?$J~=U8^|*yPRFy;b9<)!fq@Mdv5n-|&Xv{de2a?4)* zJ-7PV>~x;LI=@nuZIc!JUzQtk_GaEsx!jXybv5JD+r<8qE-TybzW>gh{Hib7wjhxj zjo|95i|?QIS$n>@^U~+iWA)-)I71hEm=7jV<1G)wXWm*U_m?j{>wGoTkAKRSZM(Gi z{z;#=iT!m;pT9nHq3L~kmbB(l&+BfQOPde4o&KKTotNkN(+a5le#73Vw;BbBeR1NA z%3E*QefTZ2_g+xdzIQH17BJsnT4W~2d|I|@4a4r4cKcU6e_dmz%wfmuW30aaa+=`k zqq`^1v}X;w`dL(eM^ww}nXDbJg6mmlq&$;hS!Y&b%yc7WN=>ZWszbb9UN0>Qy8q}2 zKal*O{UQBp73&YqADbTjs6G(oa^?N}Nk4ZPbR{%QxujJv`A_~)Jsb9G%8&g1hrHLY zjgNM#o+K(*&+z(;-QCZh7=C;V)`@%hGs^f_?ZUG6R>yLl&$@lXq*rqTr%d#h8u_#K zWewlOK9y|MsFsh|QNJp=?S8M=y!DwqW&C+cOTVxd@L90dgie(=+#PbSuj6IJ%>{pI pO1p$}{>A?(Ux+P-fn)oev`BP|6r<#y|G;8_!PC{xWt~$(696Gs`vL#} diff --git a/src/images/settings_icon.png b/src/images/settings_icon.png index 6fd024e361d62eb2a5ea66cac10d97fcfbc814e8..c88cd7a6f16cbc54bbb44730d3dac206f3affe2b 100644 GIT binary patch literal 2219 zcmV;c2vqlpP)8h7-~%dxtX<5B=S#lS&Y8J$ zXYP05n_n_X({^Ug%-lQ6IcLrV0)apv5C{YUfj}T2aUFnC0P_J<0C)?)QUL2YE#==; z@c)!@o`53;0eBO@X8?W!&}Nw^?+yTO02mlX$Bq#I{)jOMszhZD4}%h&Z5{?Ao3@4l ziOva+143m7B5)0WR*wVH%5?+`8Dh|>#{hf{pc=qxPSyOoV+Q%Dj({N(g^r}Zl^B#! zVo*-NkjE6Ac+#Q=RXep=VKmHHr0B%`7M=K`qAWT*1`JuH=)^jU9;{Q8wK9x`8ASjN zD?0IAi%wjkDC_4!84yxUECetYKwpde(}ab1wnce!h4LP>D6bEfxd6Zbq?{Q5&gL}u zJa0#DG0cBGfCfe9H3R5pArqC^B5a-e8 z0KW4q>pJ9_>7E<7pLaE`=qnr)T_sv`@&B90#CLb zFLV{=OWJ#P@_Yb2Y`8^L**-|E?~6{tne3D!O@pzWo6>7OD#U+|3Y%jLMEkUf4~9N? zU8vWK{eFj{GSZ?B_E6toR*kngP6t^#?V2HiM%l*ntmTZ*NK)Djbvzi29tI9xmrBHf-oJ-Vxcd zSyO&9>x#xGc260$;8hBy+iW8Jlr2|rTTS}pC3v8S?^oy=*~Xn=v(DebESrtXQDn8#hR_{F3TyVL}L>_Xz}p8V$l93?yE%T!-pL3g!q;4eic!OcnBv zaVY0Fp`QC=t{smfWvC8Dbc5E`+r7!LVlX54kC7T($1X))vNooU7ll)?jui=7-F7MJ zT5gb6wY#JTDs`w_g2=yD#|A~-wI1YDts``<&~aXo_aR(H140hTwgXa`ptnq%0im|1 zjxc4VjD3oyht&2uDinE-r)*K*smMzknpE*bOclx$A^%v1a*mI=?S0wtWg;(37HlOg zh6ls$f@kAehqA60PT1WH?H;Ow;Ti6Wyy04PQY#Fq=?yH2QG)6uikzRMQFUUXT%;%d z<_Kk$3uV+i99|>sf0XWtx%(nbHFma%O}BW?}{^X@`-Umc%sSZZc?OB`XS5CfxD7 zfa@L}@^-YPn7!unX2Laba&WQr>&aF;lZKvP-Bi}efu^Op2-qRK4#2w`%S5k7L^g&N{)p0c#Ri5rGf<1B) zbtUb}j>8>Q*yP#Sx-7!FqIxTAc2lNYEjZ}?<;&R`9P2~XXo`a1lMH)@)EZtOy}&2% z83|lRSeTFN2)gTR4GCV@>CVc)3byoZ;Iy2zw)Yw8rd-fP&X}NCP&-=SDNxw6ywEn}s5MTpwQ(6M z<9si`xlmn`xbA&9SN2+1#u}EnU&Mtx@xTGlDA#gBYmDZz#Kj0()ba%6FORU5ZXa6X zHce;^x*VH0*BS($*=yL~j?$KxHV_phF$4YUg3mPOEBFl9AF0h_I$#-RQAPg<0cUKJ zO<+x@m|R?mllHNeATHfF>YGnleK_`69nyR^^aGE#2}ahF1IkTyLo<9ZEHtrI)D~pTWUOqiFJ8p z!fPPqbF7dYk;xMv2led6P`w!d+_sw@@!P{eb`fT9=G+1 zr$`7K5Q6dNiB*aL37@OT6BUX9p_sc-IiM)35KRTv%u;kBeZD9v*+`@9)1w~bzG9gu zEX3iXU@84pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H15d=v@ zK~#90?VWqDUsZL-KkJ4YAPK?I1Vr97m_VYW0Wm;%gaFbpL6hJN5TK&1qw)}yM?38x zFQpNtkb|sH0T$3$lUF0LN9J=V;(>D$r93`b9S`BCCP-J5D_V+yT6lQRGPA z3}C$D+&Wb~IHSBqpwE*nB4dHyI8H4BKB1~_<&@iry&9%CPLEU7XY$Cc6aAqZAMH7Q zXB${xQc-4+WNr$om4D zR4YVekcj*fa5q**A0`2hiO6S4F{Fei`f~htZV$F>cqgzv;kuQ;ehE4{fD?e*fK86y zH|EL4USU5>sH?AhB{(+~68#D=x#iknz}$rER|EUU9F6fja1d}Ba340ok*1MeESc=yvK{y-aAHoPpT|ByD^4OI5jZl1j1J&m zbC4P@>wv3)&VXcJlF^2Tv8Ua4eg*J8hn^3YO?c6UxU?wIF~Yx>v0=9Z4+0kgqeBtw zux$pgg!HeGOhjiO{PJ4J#uo9Di2iU6nH|7)QZ(9f;ETXMWfAPLZ$E7QU`%0#*woZZ*Y(1_G>6wVmK=?(alZ}1rx|F{P92K}B2LnUvCN!n3r(<&i zt!lvW$-cm&d`8_3UUva@!v>3Y0@nb;1ISDw{AplNT3MNW&_rYw zJG<-rPF4HWe0Gx{BIAJvfcH2~pQft!r;(FqA;h&T`eqI}14s!~T?U+qJbu9)K26fd#U4?0C)AbrO5`u6?&99XfY4jPhg)Daf|{M zi^x7XWDcMWqM*fN9j8*qo@FLmZAACCp3|clAUrstShnwDGk9_QGOe6ko+_%k0P`(# zVGKt2d-KR1z@H-W%>4njG8RWS7PXQJMck4u!#prt7y~RBAiNkNqQ8%g#9g>eRoAB$ zSnz|t-CA_wD$hVkY}<~AXkXhtPR7~A!hwA)ci&9UCC>s!tLpz2Qj`-SG75{$8V&4? zja~<13prb{0nSQbrIzi=n-U_@yWD%e=aOf!snTq2*MaWBb}l$qSNKHi0i1_TS+;Ze ziI>gM@6H2;)$~LQ@Gl8At{~@pdq^V(VS@#ifp=BVNaL|F#MX=i`$`w~iW*-r(cen2 z={X9#7DF5RkjKaiN^GEUz`dB8atM;+r6-a{&se&ocap42~%zw?)C;z@r68R_yb^8(W>R z(l>Z?yjC^QF~Yk%cHIz2#{sxs8-)TEMO6`zcVS(nFM(VZEK${yiVBHdBTrzxO)C_b zrWJJEK15yme0ENA-3|P?2FQLWB98!n5j(sQxHjQuFJTKhZvD}nG>_s>bZ-gbKMpML z$PX5dThPAQ9{UyqlK-Uw9IOF83+xh;$uWUQUJraRP({b%?#9+q(x^L!jYF^*xVViy z1Xw)5a<3HSA1gT zzoEUiXG0L#Gs|&m2sWEn6xU+eA+zR7kK-H>nb3i)w!~5AJ{W)LIJO0we{Uk@11^`V z)L}V3sOmOVy;xQEQdLPRSXkCLz!mo;z^59Ol{iZEc%tLfy{h^~#pa}fs=kRiH^gyb zRG+Hs*rE2x^>J(mg*YZRifrJ|u*sb`mQ-y>Ml5z5+Z(G-rBF+#e~07L{!Jr$jOX~H zm0FPnVP{aw`(hG_t!K$M&q;ihLOEIM%2=Pnt%yD^P8}rf+r{+2@*eZq$A!O(T1i<5(vd zlEl=6_v6kYwX$kMlF)&>>vYXxRRP~Fdq-DlMRvGyoj?Q0c6}e7B_eeUjGo6m`%~D> zsboxY9NUc5r!nZjGUvrH7PwnP#zw*Rl^|L&T%wkO5wda81OFb)$tYD?_rjDplOMxmq6w83@((zK&gp(Y_xt#K&IMbH3*jCej z;`?7H!(kLlyq=vG0iFX6)gr>*){zvSy%jiDRZDj>j=Jt2;E&J#h^jsnKu-s@Mi6&a z+=T5$n1mOBRhksoSMV;p5%=A7O28*-Cdru(W>atGg|h?do9fZ?wZgIk7nlt6g9Y)MZ>J;Cldb@XOS za>a1P>ld2n<2}0O2azAh$SEEp=Y(vGNY_l#GPodh;(_-UMFDJ-C~RD zsa72ObJt1sULFG8%KL=1(RVo6GaPnfNobZ~84j04|95AUvmE$P33WQ%qwnEhIszCu z68B<&Vx!bt$>&n&9ZEmRU5b5a_+~AGWH&5KcJxF)ozOtHltFaNhOyYxYMEJy7f@nW zV&I~(pXx$nryzPR6$bQUizm0^pWVt@wqo!6(({c2b38UI1P0eicGHNC*)|Nzh|IfWLIqP_&Rn&ti)!X zYS|-1M1}*80f%`mS%_t!daID0EM!OiRuAI$_M8Wt)fA%p5~7Fb0o=|IU>OyrX@R5! zS^dUm;U{_yaOMEf`JlFw&t&Y&#)Xdp_e2j|E~I|4MdUzig5DLKF$e3qT`|f7$N&); zfu*5#xfTK3sj6q?k)7qqJ_9$=^B5pH7*zFT>gJzhi#I~NYX4^@2*TT_oC^B(0{$aaOZ2rySfj?Aif0GZ(W z+}f5^NEWgmz+%7Rco(oJ@)aut5g9Hb*NVs-5xKCb!F*LB>N#Dn%NH^W%Q%NS3HP>6 zig{h;UgyOIfX~Eat_Du8V~#zC{ks!*?H-%r^+kkaUWLnJz{MWrig`DV%OP_l$qOrs zf#d5Y)MNVuY&~EbwtjyL0TzL%A$+~K9|${Ng(KFBUZ8S1D$-Bt&##1G~p_5FNkEmjGv!NbqRjLTt6qH(v3T zIm9+)X`^c#)-(F#-9!Ob=@5-^U4n5hU_1B4f3qj>tAyYE40t$W!(I}Q;K9JxFo(n< zH=_bSNkcgBA#BX!lXGL?dIf5n>l2LJ2uzMWDgTvl{oO>}ANF4K?aLi6>jRSg%^ajA zxi{aHL^!s><%=OxU{;WN1#F;iv}ue@SoWYK$H04WxZq%+tMf_jM!N&nC8^vfIg*RLcPIhFtFYYT%xewU2jr;?#U4306)sSSj^CjPaxfPF! z{jS|7O+pq|bR}!wjE+{`Cg+&#(DF@N_lfl0n_*@NSzkqe`$CL2sM|)??^_Wv* zy8++T!MQ1cY{5Ru(gu_Nn2a+|HIe=K48Lzjc)ODO^D3zX&t0m9YcOw%UnN|+7C28; z&(?cR$V{|?-*s+rfPvtoekilC|+fPaokEofSD`LuAo>pMbZ zP>y7{+NIBrFZwFke{dY@Aj}SVhvIIRqC4Hzj=oCvbDra~ilo7g+@Nmsoc8T<&}UF6 z4_ciNk+r}VgP^!X5{zWUBaEi`yK^za>cWLdxmJqeaYUez~9^NjLra`DY8Sp;&0&QkN;qmtC9m^~iQ58_A_Neb2c6`26F&neh9wX8V@~tzW;(G}W2c zV$IJF8*WT}W%zvk+R3x{_4QmgG&`1`Hd{(ZYX zo~-abb2xIc_?6j@PP7%qypfAClUr`HE-8-JdPYH@YJY9&fr;+FOpnE$JACjEW5gWi zrSB!eE%{l08wvAA&-oIU`?=!$*IBc+T$q1+es!qgW6giFvT7LrzZTvs^#5YJ`tvQR mi{;-c?( zPRE%&dgn6y_upDl`&{B!`}65@j+WorSN!Sk<(b8|PWbp--}t=cNAbZK&S+mFez0`AE|b>}RX)=AS-%@{HkK->mrE&)b7|Yqmw+*F3ke zd297Aw^R?)4@sNlzMaoz+i-JD{A!++_fp>eTzDpDeseB^^PA28XRF`}SFC|OI0RQU zmjCMRurNHZHNxLzPL7}Lx-%=;{_TyNuLbe8JILG3X@BDi`()SLy&SLQys9zZ{I#~= z!}FgTU5+pn?6kc5?&rdg#W@U*@BOm*5Lh_v?RK^~uh-7M`1a?-sZ8n5#6Hv(|61Kq zvD@f;nd=ej1L9|2zBl?Dm>HKArk}#Oga6C>1@oD%H|84`2TA>Dnzc`N{l%lTJHIc@ zeQ@?nPJRx*`F5#_szB`nzTfr>-(kMiXni0p?cEmp?Ef}#^Vse!|9!^xdd+E;h_h!d z-8N>JX?dA3X7O(uKlx_4O%!hw{P0{R z=feN563q4t-Oi;mUmE|o?~}i8T4VQt*9U~E^N-|Q=i9*g$Lsr?-GO_f?M^&s%-#26 gy+3M(L65_F#&fmY$7YmX1eFgAp00i_>zopr0PC9oR{#J2 diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 45b2ab4e0..373b2924e 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -190,7 +190,7 @@ public: settingsButton = new QPushButton(centralWidget); settingsButton->setFlat(true); settingsButton->setIcon(QIcon(":images/settings_icon.png")); - settingsButton->setIconSize(QSize(40, 40)); + settingsButton->setIconSize(QSize(44, 44)); controllerButton = new QPushButton(centralWidget); controllerButton->setFlat(true); controllerButton->setIcon(QIcon(":images/controller_icon.png")); From bd9f82df94847b4a5f3d2676ae938f064505c992 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Sun, 13 Oct 2024 09:49:39 -0300 Subject: [PATCH 06/10] fix descriptionText size | missing translations (#1319) * fix descriptionText size * + avoid 'blinking' * Update ru_RU.ts * TR * Update pt_BR.ts * emulatorLanguage alphabetical order --------- Co-authored-by: georgemoralis --- src/qt_gui/settings_dialog.cpp | 41 ++++-- src/qt_gui/settings_dialog.h | 2 + src/qt_gui/settings_dialog.ui | 8 +- src/qt_gui/translations/ar.ts | 127 +++++++++++++++++- src/qt_gui/translations/da_DK.ts | 127 +++++++++++++++++- src/qt_gui/translations/de.ts | 127 +++++++++++++++++- src/qt_gui/translations/el.ts | 127 +++++++++++++++++- src/qt_gui/translations/en.ts | 69 ++++++---- src/qt_gui/translations/es_ES.ts | 127 +++++++++++++++++- src/qt_gui/translations/fa_IR.ts | 127 +++++++++++++++++- src/qt_gui/translations/fi.ts | 127 +++++++++++++++++- src/qt_gui/translations/fr.ts | 213 ++++++++++++++++++++++++------- src/qt_gui/translations/hu_HU.ts | 127 +++++++++++++++++- src/qt_gui/translations/id.ts | 127 +++++++++++++++++- src/qt_gui/translations/it.ts | 127 +++++++++++++++++- src/qt_gui/translations/ja_JP.ts | 127 +++++++++++++++++- src/qt_gui/translations/ko_KR.ts | 127 +++++++++++++++++- src/qt_gui/translations/lt_LT.ts | 127 +++++++++++++++++- src/qt_gui/translations/nb.ts | 127 +++++++++++++++++- src/qt_gui/translations/nl.ts | 127 +++++++++++++++++- src/qt_gui/translations/pl_PL.ts | 127 +++++++++++++++++- src/qt_gui/translations/pt_BR.ts | 137 +++++++++++++++++++- src/qt_gui/translations/ro_RO.ts | 127 +++++++++++++++++- src/qt_gui/translations/ru_RU.ts | 127 +++++++++++++++++- src/qt_gui/translations/sq.ts | 120 ++++++++++++++++- src/qt_gui/translations/tr_TR.ts | 127 +++++++++++++++++- src/qt_gui/translations/vi_VN.ts | 127 +++++++++++++++++- src/qt_gui/translations/zh_CN.ts | 127 +++++++++++++++++- src/qt_gui/translations/zh_TW.ts | 127 +++++++++++++++++- 29 files changed, 3267 insertions(+), 117 deletions(-) diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index efc2cd3eb..b63f14c05 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -51,6 +51,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge : QDialog(parent), ui(new Ui::SettingsDialog) { ui->setupUi(this); ui->tabWidgetSettings->setUsesScrollButtons(false); + initialHeight = this->height(); const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus(); @@ -268,6 +269,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge ui->fullscreenCheckBox->installEventFilter(this); ui->showSplashCheckBox->installEventFilter(this); ui->ps4proCheckBox->installEventFilter(this); + ui->discordRPCCheckbox->installEventFilter(this); ui->userName->installEventFilter(this); ui->logTypeGroupBox->installEventFilter(this); ui->logFilter->installEventFilter(this); @@ -275,7 +277,6 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge ui->GUIgroupBox->installEventFilter(this); // Input - ui->cursorGroupBox->installEventFilter(this); ui->hideCursorGroupBox->installEventFilter(this); ui->idleTimeoutGroupBox->installEventFilter(this); ui->backButtonBehaviorGroupBox->installEventFilter(this); @@ -361,15 +362,30 @@ void SettingsDialog::LoadValuesFromConfig() { void SettingsDialog::InitializeEmulatorLanguages() { QDirIterator it(QStringLiteral(":/translations"), QDirIterator::NoIteratorFlags); - int idx = 0; + QVector> languagesList; + while (it.hasNext()) { QString locale = it.next(); locale.truncate(locale.lastIndexOf(QLatin1Char{'.'})); locale.remove(0, locale.lastIndexOf(QLatin1Char{'/'}) + 1); const QString lang = QLocale::languageToString(QLocale(locale).language()); const QString country = QLocale::territoryToString(QLocale(locale).territory()); - ui->emulatorLanguageComboBox->addItem(QStringLiteral("%1 (%2)").arg(lang, country), locale); + QString displayName = QStringLiteral("%1 (%2)").arg(lang, country); + languagesList.append(qMakePair(locale, displayName)); + } + + std::sort(languagesList.begin(), languagesList.end(), + [](const QPair& a, const QPair& b) { + return a.second < b.second; + }); + + int idx = 0; + for (const auto& pair : languagesList) { + const QString& locale = pair.first; + const QString& displayName = pair.second; + + ui->emulatorLanguageComboBox->addItem(displayName, locale); languages[locale.toStdString()] = idx; idx++; } @@ -419,6 +435,8 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("showSplashCheckBox"); } else if (elementName == "ps4proCheckBox") { text = tr("ps4proCheckBox"); + } else if (elementName == "discordRPCCheckbox") { + text = tr("discordRPCCheckbox"); } else if (elementName == "userName") { text = tr("userName"); } else if (elementName == "logTypeGroupBox") { @@ -432,9 +450,7 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { } // Input - if (elementName == "cursorGroupBox") { - text = tr("cursorGroupBox"); - } else if (elementName == "hideCursorGroupBox") { + if (elementName == "hideCursorGroupBox") { text = tr("hideCursorGroupBox"); } else if (elementName == "idleTimeoutGroupBox") { text = tr("idleTimeoutGroupBox"); @@ -493,15 +509,22 @@ bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) { } // if the text exceeds the size of the box, it will increase the size + QRect currentGeometry = this->geometry(); + int newWidth = currentGeometry.width(); + int documentHeight = ui->descriptionText->document()->size().height(); int visibleHeight = ui->descriptionText->viewport()->height(); if (documentHeight > visibleHeight) { - ui->descriptionText->setMinimumHeight(90); + ui->descriptionText->setMaximumSize(16777215, 110); + this->setGeometry(currentGeometry.x(), currentGeometry.y(), newWidth, + currentGeometry.height() + 40); } else { - ui->descriptionText->setMinimumHeight(70); + ui->descriptionText->setMaximumSize(16777215, 70); + this->setGeometry(currentGeometry.x(), currentGeometry.y(), newWidth, + initialHeight); } return true; } } return QDialog::eventFilter(obj, event); -} +} \ No newline at end of file diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h index d09617ec3..8cdded980 100644 --- a/src/qt_gui/settings_dialog.h +++ b/src/qt_gui/settings_dialog.h @@ -40,4 +40,6 @@ private: std::map languages; QString defaultTextEdit; + + int initialHeight; }; diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 9743e51bd..b98fe228d 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -12,7 +12,7 @@ 0 0 854 - 605 + 630 @@ -1133,7 +1133,7 @@ 100 360 - 80 + 91 24 @@ -1144,9 +1144,9 @@ - 210 + 199 360 - 80 + 91 24 diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts index ec03c04b5..8efacc063 100644 --- a/src/qt_gui/translations/ar.ts +++ b/src/qt_gui/translations/ar.ts @@ -414,6 +414,11 @@ Is PS4 Pro PS4 Pro هل هو + + + Enable Discord Rich Presence + تفعيل حالة الثراء في ديسكورد + Username @@ -434,6 +439,36 @@ Log Filter مرشح السجل + + + Input + إدخال + + + + Cursor + مؤشر + + + + Hide Cursor + إخفاء المؤشر + + + + Hide Cursor Idle Timeout + مهلة إخفاء المؤشر عند الخمول + + + + Controller + التحكم + + + + Back Button Behavior + سلوك زر العودة + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU تمكين وحدة معالجة الرسومات الفارغة + + + Paths + المسارات + + + + Game Folders + مجلدات اللعبة + + + + Add... + إضافة... + + + + Remove + إزالة + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox هل هو PS4 Pro:\nيجعل المحاكي يعمل كـ PS4 PRO، مما قد يتيح ميزات خاصة في الألعاب التي تدعمه. + + + discordRPCCheckbox + تفعيل حالة الثراء في ديسكورد:\nيعرض أيقونة المحاكي ومعلومات ذات صلة على ملفك الشخصي في ديسكورد. + userName @@ -1011,7 +1071,7 @@ logFilter - فلتر السجل: يقوم بتصفية السجل لطباعة معلومات محددة فقط. أمثلة: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" المستويات: Trace, Debug, Info, Warning, Error, Critical - بالترتيب، مستوى محدد يخفي جميع المستويات التي تسبقه ويعرض جميع المستويات بعده. + فلتر السجل:\nيقوم بتصفية السجل لطباعة معلومات محددة فقط.\nأمثلة: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" المستويات: Trace, Debug, Info, Warning, Error, Critical - بالترتيب، مستوى محدد يخفي جميع المستويات التي تسبقه ويعرض جميع المستويات بعده. @@ -1023,6 +1083,56 @@ GUIgroupBox تشغيل موسيقى العنوان:\nإذا كانت اللعبة تدعم ذلك، قم بتمكين تشغيل موسيقى خاصة عند اختيار اللعبة في واجهة المستخدم. + + + hideCursorGroupBox + إخفاء المؤشر:\nاختر متى سيختفي المؤشر:\nأبداً: سترى الفأرة دائماً.\nعاطل: حدد وقتاً لاختفائه بعد أن يكون غير مستخدم.\nدائماً: لن ترى الفأرة أبداً. + + + + idleTimeoutGroupBox + حدد وقتاً لاختفاء الفأرة بعد أن تكون غير مستخدم. + + + + backButtonBehaviorGroupBox + سلوك زر العودة:\nيضبط زر العودة في وحدة التحكم ليحاكي الضغط على الموضع المحدد على لوحة اللمس في PS4. + + + + Never + أبداً + + + + Idle + خامل + + + + Always + دائماً + + + + Touchpad Left + لوحة اللمس اليسرى + + + + Touchpad Right + لوحة اللمس اليمنى + + + + Touchpad Center + وسط لوحة اللمس + + + + None + لا شيء + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox تمكين GPU الافتراضية:\nلأغراض تصحيح الأخطاء التقنية، يقوم بتعطيل عرض اللعبة كما لو لم يكن هناك بطاقة رسومات. + + + gameFoldersBox + مجلدات اللعبة:\nقائمة بالمجلدات للتحقق من الألعاب المثبتة. + + + + addFolderButton + إضافة:\nأضف مجلداً إلى القائمة. + + + + removeFolderButton + إزالة:\nأزل مجلداً من القائمة. + debugDump diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index c7edfb102..9abe22c3d 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -414,6 +414,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Aktiver Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Log Filter + + + Input + Indtastning + + + + Cursor + Markør + + + + Hide Cursor + Skjul markør + + + + Hide Cursor Idle Timeout + Timeout for skjul markør ved inaktivitet + + + + Controller + Controller + + + + Back Button Behavior + Tilbageknap adfærd + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Enable NULL GPU + + + Paths + Stier + + + + Game Folders + Spilmapper + + + + Add... + Tilføj... + + + + Remove + Fjern + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Er det en PS4 Pro:\nGør det muligt for emulatoren at fungere som en PS4 PRO, hvilket kan aktivere visse funktioner i spil, der understøtter det. + + + discordRPCCheckbox + Aktiver Discord Rich Presence:\nViser emulatorikonet og relevante oplysninger på din Discord-profil. + userName @@ -1011,7 +1071,7 @@ logFilter - Logfilter: Filtrerer loggen for kun at udskrive bestemte oplysninger. Eksempler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveaus: Trace, Debug, Info, Warning, Error, Critical - i rækkefølge, et valgt niveau skjuler alle forudgående niveauer og viser alle efterfølgende niveauer. + Logfilter:\nFiltrerer loggen for kun at udskrive bestemte oplysninger.\nEksempler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveaus: Trace, Debug, Info, Warning, Error, Critical - i rækkefølge, et valgt niveau skjuler alle forudgående niveauer og viser alle efterfølgende niveauer. @@ -1023,6 +1083,56 @@ GUIgroupBox Titelsmusikafspilning:\nHvis spillet understøtter det, aktiver speciel musik, når spillet vælges i brugergrænsefladen. + + + hideCursorGroupBox + Skjul Cursor:\nVælg hvornår cursoren skal forsvinde:\nAldrig: Du vil altid se musen.\nInaktiv: Indstil en tid for, hvornår den skal forsvinde efter at være inaktiv.\nAltid: du vil aldrig se musen. + + + + idleTimeoutGroupBox + Indstil en tid for, at musen skal forsvinde efter at være inaktiv. + + + + backButtonBehaviorGroupBox + Tilbageknap Adfærd:\nIndstiller controllerens tilbageknap til at efterligne tryk på den angivne position på PS4 berøringsflade. + + + + Never + Aldrig + + + + Idle + Inaktiv + + + + Always + Altid + + + + Touchpad Left + Berøringsplade Venstre + + + + Touchpad Right + Berøringsplade Højre + + + + Touchpad Center + Berøringsplade Center + + + + None + Ingen + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Aktiver virtuel GPU:\nTil teknisk fejlfinding deaktiverer det spilvisning, som om der ikke var et grafikkort. + + + gameFoldersBox + Spilmappen:\nListen over mapper til at tjekke for installerede spil. + + + + addFolderButton + Tilføj:\nTilføj en mappe til listen. + + + + removeFolderButton + Fjern:\nFjern en mappe fra listen. + debugDump diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index 43e19a37f..a6904d9a1 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -414,6 +414,11 @@ Is PS4 Pro Ist PS4 Pro + + + Enable Discord Rich Presence + Discord Rich Presence aktivieren + Username @@ -434,6 +439,36 @@ Log Filter Log-Filter + + + Input + Eingabe + + + + Cursor + Cursor + + + + Hide Cursor + Cursor ausblenden + + + + Hide Cursor Idle Timeout + Inaktivitätszeitüberschreitung zum Ausblenden des Cursors + + + + Controller + Controller + + + + Back Button Behavior + Verhalten der Zurück-Taste + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU NULL GPU aktivieren + + + Paths + Pfad + + + + Game Folders + Spieleordner + + + + Add... + Hinzufügen... + + + + Remove + Entfernen + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Ist es eine PS4 Pro:\nErmöglicht es dem Emulator, als PS4 PRO zu arbeiten, was in Spielen, die dies unterstützen, spezielle Funktionen aktivieren kann. + + + discordRPCCheckbox + Discord Rich Presence aktivieren:\nZeigt das Emulator-Icon und relevante Informationen in deinem Discord-Profil an. + userName @@ -1011,7 +1071,7 @@ logFilter - Protokollfilter: Filtert das Protokoll so, dass nur bestimmte Informationen ausgegeben werden. Beispiele: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Ebenen: Trace, Debug, Info, Warning, Error, Critical - in dieser Reihenfolge, ein ausgewähltes Level blendet alle vorherigen Ebenen aus und zeigt alle nachfolgenden an. + Protokollfilter:\nFiltert das Protokoll so, dass nur bestimmte Informationen ausgegeben werden.\nBeispiele: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Ebenen: Trace, Debug, Info, Warning, Error, Critical - in dieser Reihenfolge, ein ausgewähltes Level blendet alle vorherigen Ebenen aus und zeigt alle nachfolgenden an. @@ -1023,6 +1083,56 @@ GUIgroupBox Wiedergabe der Titelmusik:\nWenn das Spiel dies unterstützt, wird beim Auswählen des Spiels in der Benutzeroberfläche spezielle Musik abgespielt. + + + hideCursorGroupBox + Maus ausblenden:\nWählen Sie, wann der Cursor verschwinden soll:\nNie: Sie sehen die Maus immer.\nInaktiv: Legen Sie eine Zeit fest, nach der sie nach Inaktivität verschwindet.\nImmer: Sie sehen die Maus niemals. + + + + idleTimeoutGroupBox + Stellen Sie eine Zeit ein, nach der die Maus nach Inaktivität verschwinden soll. + + + + backButtonBehaviorGroupBox + Zurück-Button Verhalten:\nStellt die Zurück-Taste des Controllers so ein, dass sie das Antippen der angegebenen Position auf dem PS4-Touchpad emuliert. + + + + Never + Niemals + + + + Idle + Im Leerlauf + + + + Always + Immer + + + + Touchpad Left + Touchpad Links + + + + Touchpad Right + Touchpad Rechts + + + + Touchpad Center + Touchpad Mitte + + + + None + Keine + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Virtuelle GPU aktivieren:\nFür das technische Debugging deaktiviert es die Spielanzeige, als ob keine Grafikkarte vorhanden wäre. + + + gameFoldersBox + Spieleordner:\nDie Liste der Ordner, in denen nach installierten Spielen gesucht wird. + + + + addFolderButton + Hinzufügen:\nFügen Sie einen Ordner zur Liste hinzu. + + + + removeFolderButton + Entfernen:\nEntfernen Sie einen Ordner aus der Liste. + debugDump diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts index 8009dfd88..963ca0ab1 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -414,6 +414,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Ενεργοποίηση Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Log Filter + + + Input + Είσοδος + + + + Cursor + Δείκτης + + + + Hide Cursor + Απόκρυψη δείκτη + + + + Hide Cursor Idle Timeout + Χρόνος αδράνειας απόκρυψης δείκτη + + + + Controller + Controller + + + + Back Button Behavior + Συμπεριφορά κουμπιού επιστροφής + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Enable NULL GPU + + + Paths + Διαδρομές + + + + Game Folders + Φάκελοι παιχνιδιών + + + + Add... + Προσθήκη... + + + + Remove + Αφαίρεση + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Είναι PS4 Pro:\nΕπιτρέπει στον εξομοιωτή να λειτουργεί σαν PS4 PRO, κάτι που μπορεί να ενεργοποιήσει συγκεκριμένες λειτουργίες σε παιχνίδια που το υποστηρίζουν. + + + discordRPCCheckbox + Ενεργοποίηση Discord Rich Presence:\nΕμφανίζει το εικονίδιο του emulator και σχετικές πληροφορίες στο προφίλ σας στο Discord. + userName @@ -1011,7 +1071,7 @@ logFilter - Φίλτρο Καταγραφής: Φιλτράρει τις καταγραφές ώστε να εκτυπώνονται μόνο συγκεκριμένες πληροφορίες. Παραδείγματα: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Επίπεδα: Trace, Debug, Info, Warning, Error, Critical - με τη σειρά αυτή, κάθε επίπεδο που επιλέγεται αποκλείει τα προηγούμενα και εμφανίζει τα επόμενα επίπεδα. + Φίλτρο Καταγραφής:\nΦιλτράρει τις καταγραφές ώστε να εκτυπώνονται μόνο συγκεκριμένες πληροφορίες.\nΠαραδείγματα: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Επίπεδα: Trace, Debug, Info, Warning, Error, Critical - με τη σειρά αυτή, κάθε επίπεδο που επιλέγεται αποκλείει τα προηγούμενα και εμφανίζει τα επόμενα επίπεδα. @@ -1023,6 +1083,56 @@ GUIgroupBox Αναπαραγωγή Μουσικής Τίτλων:\nΕάν το παιχνίδι το υποστηρίζει, ενεργοποιεί ειδική μουσική κατά την επιλογή του παιχνιδιού από τη διεπαφή χρήστη. + + + hideCursorGroupBox + Απόκρυψη Κέρσορα:\nΕπιλέξτε πότε θα εξαφανιστεί ο κέρσορας:\nΠοτέ: θα βλέπετε πάντα το ποντίκι.\nΑδρανές: ορίστε έναν χρόνο για να εξαφανιστεί μετά από αδράνεια.\nΠάντα: δεν θα δείτε ποτέ το ποντίκι. + + + + idleTimeoutGroupBox + Ορίστε έναν χρόνο για να εξαφανιστεί το ποντίκι μετά από αδράνεια. + + + + backButtonBehaviorGroupBox + Συμπεριφορά Κουμπιού Επιστροφής:\nΟρίζει το κουμπί επιστροφής του ελεγκτή να προσομοιώνει το πάτημα της καθορισμένης θέσης στην οθόνη αφής PS4. + + + + Never + Ποτέ + + + + Idle + Αδρανής + + + + Always + Πάντα + + + + Touchpad Left + Touchpad Αριστερά + + + + Touchpad Right + Touchpad Δεξιά + + + + Touchpad Center + Κέντρο Touchpad + + + + None + Κανένα + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Ενεργοποίηση Εικονικής GPU:\nΓια τεχνικό εντοπισμό σφαλμάτων, απενεργοποιεί την εμφάνιση του παιχνιδιού σαν να μην υπάρχει κάρτα γραφικών. + + + gameFoldersBox + Φάκελοι Παιχνιδιών:\nΗ λίστα των φακέλων για έλεγχο των εγκατεστημένων παιχνιδιών. + + + + addFolderButton + Προσθήκη:\nΠροσθέστε έναν φάκελο στη λίστα. + + + + removeFolderButton + Αφαίρεση:\nΑφαιρέστε έναν φάκελο από τη λίστα. + debugDump diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index 974045de1..d781bc2fe 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -414,6 +414,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Enable Discord Rich Presence + Username @@ -454,11 +459,6 @@ Hide Cursor Idle Timeout Hide Cursor Idle Timeout - - - Input - Input - Controller @@ -509,6 +509,26 @@ Enable NULL GPU Enable NULL GPU + + + Paths + Paths + + + + Game Folders + Game Folders + + + + Add... + Add... + + + + Remove + Remove + Debug @@ -1033,6 +1053,11 @@ ps4proCheckBox Is PS4 Pro:\nMakes the emulator act as a PS4 PRO, which may enable special features in games that support it. + + + discordRPCCheckbox + Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. + userName @@ -1046,7 +1071,7 @@ logFilter - Log Filter: Filters the log to only print specific information. Examples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Levels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. @@ -1059,20 +1084,20 @@ Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. - - cursorGroupBox - Cursor:\nChange settings related to the cursor. - - - + hideCursorGroupBox - Hide Cursor:\nSet cursor hiding behavior. + Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. - + idleTimeoutGroupBox Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. + + + backButtonBehaviorGroupBox + Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. + Never @@ -1088,16 +1113,6 @@ Always Always - - - backButtonBehaviorGroupBox - Back Button Behavior:\nAllows setting which part of the touchpad the back button will emulate a touch on. - - - - backButtonBehaviorGroupBox - Back Button Behavior:\nAllows setting which part of the touchpad the back button will emulate a touch on. - Touchpad Left @@ -1146,17 +1161,17 @@ gameFoldersBox - Game Folders: The list of folders to check for installed games. + Game Folders:\nThe list of folders to check for installed games. addFolderButton - Add: Add a folder to the list. + Add:\nAdd a folder to the list. removeFolderButton - Remove: Remove a folder from the list. + Remove:\nRemove a folder from the list. diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 0c6591a5f..1095cd9cd 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -414,6 +414,11 @@ Is PS4 Pro Modo PS4 Pro + + + Enable Discord Rich Presence + Habilitar Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Filtro de registro + + + Input + Entrada + + + + Cursor + Cursor + + + + Hide Cursor + Ocultar cursor + + + + Hide Cursor Idle Timeout + Tiempo de espera para ocultar cursor inactivo + + + + Controller + Controlador + + + + Back Button Behavior + Comportamiento del botón de retroceso + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Habilitar GPU NULL + + + Paths + Rutas + + + + Game Folders + Carpetas de juego + + + + Add... + Añadir... + + + + Remove + Eliminar + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Es PS4 Pro:\nHace que el emulador actúe como una PS4 PRO, lo que puede habilitar funciones especiales en los juegos que lo admitan. + + + discordRPCCheckbox + Habilitar Discord Rich Presence:\nMuestra el ícono del emulador y la información relevante en tu perfil de Discord. + userName @@ -1011,7 +1071,7 @@ logFilter - Filtro de Registro: Filtra el registro para imprimir solo información específica. Ejemplos: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveles: Trace, Debug, Info, Warning, Error, Critical - en este orden, un nivel específico silencia todos los niveles anteriores en la lista y registra cada nivel posterior. + Filtro de Registro:\nFiltra el registro para imprimir solo información específica.\nEjemplos: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveles: Trace, Debug, Info, Warning, Error, Critical - en este orden, un nivel específico silencia todos los niveles anteriores en la lista y registra cada nivel posterior. @@ -1023,6 +1083,56 @@ GUIgroupBox Reproducir Música del Título:\nSi un juego lo admite, habilita la reproducción de música especial al seleccionar el juego en la interfaz gráfica. + + + hideCursorGroupBox + Ocultar Cursor:\nElija cuándo desaparecerá el cursor:\nNunca: Siempre verá el mouse.\nInactivo: Establezca un tiempo para que desaparezca después de estar inactivo.\nSiempre: nunca verá el mouse. + + + + idleTimeoutGroupBox + Establezca un tiempo para que el mouse desaparezca después de estar inactivo. + + + + backButtonBehaviorGroupBox + Comportamiento del Botón Atrás:\nEstablece el botón atrás del controlador para emular el toque en la posición especificada en el touchpad del PS4. + + + + Never + Nunca + + + + Idle + Inactivo + + + + Always + Siempre + + + + Touchpad Left + Touchpad Izquierda + + + + Touchpad Right + Touchpad Derecha + + + + Touchpad Center + Centro del Touchpad + + + + None + Ninguno + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Habilitar GPU Nula:\nPor el bien de la depuración técnica, desactiva el renderizado del juego como si no hubiera tarjeta gráfica. + + + gameFoldersBox + Carpetas de Juegos:\nLa lista de carpetas para verificar los juegos instalados. + + + + addFolderButton + Añadir:\nAgregar una carpeta a la lista. + + + + removeFolderButton + Eliminar:\nEliminar una carpeta de la lista. + debugDump diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 60d8be89e..e86001b9a 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -414,6 +414,11 @@ Is PS4 Pro PS4 Pro حالت + + + Enable Discord Rich Presence + Discord Rich Presence را فعال کنید + Username @@ -434,6 +439,36 @@ Log Filter Log فیلتر + + + Input + ورودی + + + + Cursor + نشانگر + + + + Hide Cursor + پنهان کردن نشانگر + + + + Hide Cursor Idle Timeout + مخفی کردن زمان توقف مکان نما + + + + Controller + کنترل کننده + + + + Back Button Behavior + رفتار دکمه بازگشت + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU NULL GPU فعال کردن + + + Paths + مسیرها + + + + Game Folders + پوشه های بازی + + + + Add... + افزودن... + + + + Remove + حذف + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Is PS4 Pro:\nMakes the emulator act as a PS4 PRO, which may enable special features in games that support it. + + + discordRPCCheckbox + فعال کردن Discord Rich Presence:\nآیکون شبیه ساز و اطلاعات مربوطه را در نمایه Discord شما نمایش می دهد. + userName @@ -1011,7 +1071,7 @@ logFilter - Log Filter: Filters the log to only print specific information. Examples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Levels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Levels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. @@ -1023,6 +1083,56 @@ GUIgroupBox Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + + + hideCursorGroupBox + پنهان کردن نشانگر:\nانتخاب کنید که نشانگر چه زمانی ناپدید شود:\nهرگز: شما همیشه ماوس را خواهید دید.\nغیرفعال: زمانی را برای ناپدید شدن بعد از غیرفعالی تعیین کنید.\nهمیشه: شما هرگز ماوس را نخواهید دید. + + + + idleTimeoutGroupBox + زمانی را برای ناپدید شدن ماوس بعد از غیرفعالی تعیین کنید. + + + + backButtonBehaviorGroupBox + رفتار دکمه برگشت:\nدکمه برگشت کنترلر را طوری تنظیم می کند که ضربه زدن روی موقعیت مشخص شده روی صفحه لمسی PS4 را شبیه سازی کند. + + + + Never + هرگز + + + + Idle + بیکار + + + + Always + همیشه + + + + Touchpad Left + پد لمسی سمت چپ + + + + Touchpad Right + صفحه لمسی سمت راست + + + + Touchpad Center + مرکز تاچ پد + + + + None + هیچ کدام + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. + + + gameFoldersBox + پوشه های بازی:\nلیست پوشه هایی که باید بازی های نصب شده را بررسی کنید. + + + + addFolderButton + اضافه کردن:\nیک پوشه به لیست اضافه کنید. + + + + removeFolderButton + حذف:\nیک پوشه را از لیست حذف کنید. + debugDump diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts index 30c60af81..fe972de9a 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -414,6 +414,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Ota käyttöön Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Log Filter + + + Input + Syöttö + + + + Cursor + Kursori + + + + Hide Cursor + Piilota kursor + + + + Hide Cursor Idle Timeout + Inaktiivisuuden aikaraja kursorin piilottamiselle + + + + Controller + Ohjain + + + + Back Button Behavior + Takaisin-painikkeen käyttäytyminen + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Enable NULL GPU + + + Paths + Polut + + + + Game Folders + Pelihakemistot + + + + Add... + Lisää... + + + + Remove + Poista + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Onko PS4 Pro:\nAsettaa emulaattorin toimimaan PS4 PRO:na, mikä voi mahdollistaa erityisiä ominaisuuksia peleissä, jotka tukevat sitä. + + + discordRPCCheckbox + Ota käyttöön Discord Rich Presence:\nNäyttää emulaattorin kuvakkeen ja asiaankuuluvat tiedot Discord-profiilissasi. + userName @@ -1011,7 +1071,7 @@ logFilter - Lokifiltteri: Suodattaa lokia tulostamaan vain erityistä tietoa. Esimerkkejä: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Tasot: Jälki, Virheenkorjaus, Tieto, Varoitus, Virhe, Kriittinen - tällä järjestyksellä, tietty taso vaientaa kaikki edeltävät tasot luettelossa ja kirjaa kaikki tasot sen jälkeen. + Lokifiltteri:\nSuodattaa lokia tulostamaan vain erityistä tietoa.\nEsimerkkejä: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Tasot: Jälki, Virheenkorjaus, Tieto, Varoitus, Virhe, Kriittinen - tällä järjestyksellä, tietty taso vaientaa kaikki edeltävät tasot luettelossa ja kirjaa kaikki tasot sen jälkeen. @@ -1023,6 +1083,56 @@ GUIgroupBox Soita Otsikkomusiikkia:\nJos peli tukee sitä, ota käyttöön erityisen musiikin soittaminen pelin valinnan yhteydessä käyttöliittymässä. + + + hideCursorGroupBox + Piilota kursori:\nValitse, milloin kursori häviää:\nEi koskaan: Näet hiiren aina.\nAktiivinen: Aseta aika, jolloin se häviää oltuaan aktiivinen.\nAina: et koskaan näe hiirtä. + + + + idleTimeoutGroupBox + Aseta aika, jolloin hiiri häviää oltuaan aktiivinen. + + + + backButtonBehaviorGroupBox + Takaisin-napin käyttäytyminen:\nAsettaa ohjaimen takaisin-napin jäljittelemään kosketusta PS4:n kosketuslevyn määritettyyn kohtaan. + + + + Never + Ei koskaan + + + + Idle + Odotustila + + + + Always + aina + + + + Touchpad Left + Kosketuslevy Vasemmalla + + + + Touchpad Right + Kosketuslevy Oikealla + + + + Touchpad Center + Kosketuslevy Keskellä + + + + None + Ei mitään + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Ota Null GPU käyttöön:\nTeknistä vianetsintää varten pelin renderöinti estetään niin, että ikään kuin grafiikkakorttia ei olisi. + + + gameFoldersBox + Pelihakemistot:\nLuettelo hakemistoista asennettujen pelien tarkistamiseksi. + + + + addFolderButton + Lisää:\nLisää hakemisto luetteloon. + + + + removeFolderButton + Poista:\nPoista hakemisto luettelosta. + debugDump diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index 54e4b0e82..95158d73e 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -414,6 +414,11 @@ Is PS4 Pro Mode PS4 Pro + + + Enable Discord Rich Presence + Activer Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Filtre + + + Input + Entrée + + + + Cursor + Curseur + + + + Hide Cursor + Masquer le curseur + + + + Hide Cursor Idle Timeout + Délai d'inactivité pour masquer le curseur + + + + Controller + Manette + + + + Back Button Behavior + Comportement du bouton retour + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU NULL GPU + + + Paths + Chemins + + + + Game Folders + Dossiers de jeu + + + + Add... + Ajouter... + + + + Remove + Supprimer + Debug @@ -580,7 +635,7 @@ Games: - Jeux : + Jeux: @@ -615,7 +670,7 @@ PKG and Game versions match: - Les versions PKG et jeu correspondent : + Les versions PKG et jeu correspondent: @@ -625,17 +680,17 @@ PKG Version %1 is older than installed version: - La version PKG %1 est plus ancienne que la version installée : + La version PKG %1 est plus ancienne que la version installée: Game is installed: - Jeu installé : + Jeu installé: Would you like to install Patch: - Souhaitez-vous installer le patch : + Souhaitez-vous installer le patch: @@ -645,12 +700,12 @@ Would you like to install DLC: %1? - Souhaitez-vous installer le DLC : %1 ? + Souhaitez-vous installer le DLC: %1 ? DLC already installed: - DLC déjà installé : + DLC déjà installé: @@ -698,7 +753,7 @@ defaultTextEdit_MSG - Les Cheats/Patchs sont expérimentaux.\nUtilisez-les avec précaution.\n\nTéléchargez les Cheats individuellement en sélectionnant le dépôt et en cliquant sur le bouton de téléchargement.\nDans l'onglet Patchs, vous pouvez télécharger tous les patchs en une seule fois, choisir lesquels vous souhaitez utiliser et enregistrer votre sélection.\n\nComme nous ne développons pas les Cheats/Patches,\nmerci de signaler les problèmes à l'auteur du Cheat.\n\nVous avez créé un nouveau cheat ? Visitez :\nhttps://github.com/shadps4-emu/ps4_cheats + Les Cheats/Patchs sont expérimentaux.\nUtilisez-les avec précaution.\n\nTéléchargez les Cheats individuellement en sélectionnant le dépôt et en cliquant sur le bouton de téléchargement.\nDans l'onglet Patchs, vous pouvez télécharger tous les patchs en une seule fois, choisir lesquels vous souhaitez utiliser et enregistrer votre sélection.\n\nComme nous ne développons pas les Cheats/Patches,\nmerci de signaler les problèmes à l'auteur du Cheat.\n\nVous avez créé un nouveau cheat ? Visitez:\nhttps://github.com/shadps4-emu/ps4_cheats @@ -708,27 +763,27 @@ Serial: - Série : + Série: Version: - Version : + Version: Size: - Taille : + Taille: Select Cheat File: - Sélectionner le fichier de Cheat : + Sélectionner le fichier de Cheat: Repository: - Dépôt : + Dépôt: @@ -758,7 +813,7 @@ Select Patch File: - Sélectionner le fichier de patch : + Sélectionner le fichier de patch: @@ -813,7 +868,7 @@ Failed to parse XML: - Échec de l'analyse XML : + Échec de l'analyse XML: @@ -848,12 +903,12 @@ Failed to save file: - Échec de l'enregistrement du fichier : + Échec de l'enregistrement du fichier: Failed to download file: - Échec du téléchargement du fichier : + Échec du téléchargement du fichier: @@ -878,12 +933,12 @@ Failed to save: - Échec de l'enregistrement : + Échec de l'enregistrement: Failed to download: - Échec du téléchargement : + Échec du téléchargement: @@ -908,12 +963,12 @@ Failed to open file: - Échec de l'ouverture du fichier : + Échec de l'ouverture du fichier: XML ERROR: - Erreur XML : + Erreur XML: @@ -923,12 +978,12 @@ Author: - Auteur : + Auteur: Directory does not exist: - Répertoire n'existe pas : + Répertoire n'existe pas: @@ -938,7 +993,7 @@ Name: - Nom : + Nom: @@ -976,97 +1031,167 @@ consoleLanguageGroupBox - Langue de la console :\nDéfinit la langue utilisée par le jeu PS4.\nIl est recommandé de le définir sur une langue que le jeu prend en charge, ce qui variera selon la région. + Langue de la console:\nDéfinit la langue utilisée par le jeu PS4.\nIl est recommandé de le définir sur une langue que le jeu prend en charge, ce qui variera selon la région. emulatorLanguageGroupBox - Langue de l'émulateur :\nDéfinit la langue de l'interface utilisateur de l'émulateur. + Langue de l'émulateur:\nDéfinit la langue de l'interface utilisateur de l'émulateur. fullscreenCheckBox - Activer le mode plein écran :\nMet automatiquement la fenêtre du jeu en mode plein écran.\nCela peut être activé en appuyant sur la touche F11. + Activer le mode plein écran:\nMet automatiquement la fenêtre du jeu en mode plein écran.\nCela peut être activé en appuyant sur la touche F11. showSplashCheckBox - Afficher l'écran de démarrage :\nAffiche l'écran de démarrage du jeu (une image spéciale) lors du démarrage du jeu. + Afficher l'écran de démarrage:\nAffiche l'écran de démarrage du jeu (une image spéciale) lors du démarrage du jeu. ps4proCheckBox - Est-ce un PS4 Pro :\nFait en sorte que l'émulateur se comporte comme un PS4 PRO, ce qui peut activer des fonctionnalités spéciales dans les jeux qui le prennent en charge. + Est-ce un PS4 Pro:\nFait en sorte que l'émulateur se comporte comme un PS4 PRO, ce qui peut activer des fonctionnalités spéciales dans les jeux qui le prennent en charge. + + + + discordRPCCheckbox + Activer Discord Rich Presence:\nAffiche l'icône de l'émulateur et les informations pertinentes sur votre profil Discord. userName - Nom d'utilisateur :\nDéfinit le nom d'utilisateur du compte PS4, qui peut être affiché par certains jeux. + Nom d'utilisateur:\nDéfinit le nom d'utilisateur du compte PS4, qui peut être affiché par certains jeux. logTypeGroupBox - Type de journal :\nDétermine si la sortie de la fenêtre de journalisation est synchronisée pour des raisons de performance. Cela peut avoir un impact négatif sur l'émulation. + Type de journal:\nDétermine si la sortie de la fenêtre de journalisation est synchronisée pour des raisons de performance. Cela peut avoir un impact négatif sur l'émulation. logFilter - Filtre de journal : n'imprime que des informations spécifiques. Exemples : "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveaux : Trace, Debug, Info, Avertissement, Erreur, Critique - dans cet ordre, un niveau particulier désactive tous les niveaux précédents de la liste et enregistre tous les niveaux suivants. + Filtre de journal:\n n'imprime que des informations spécifiques.\nExemples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveaux: Trace, Debug, Info, Avertissement, Erreur, Critique - dans cet ordre, un niveau particulier désactive tous les niveaux précédents de la liste et enregistre tous les niveaux suivants. updaterGroupBox - Mise à jour :\nRelease: versions officielles publiées chaque mois qui peuvent être très anciennes, mais plus fiables et testées.\nNightly: versions de développement avec toutes les dernières fonctionnalités et correctifs, mais pouvant avoir des bogues et être moins stables. + Mise à jour:\nRelease: versions officielles publiées chaque mois qui peuvent être très anciennes, mais plus fiables et testées.\nNightly: versions de développement avec toutes les dernières fonctionnalités et correctifs, mais pouvant avoir des bogues et être moins stables. GUIgroupBox - Jouer de la musique de titre :\nSi le jeu le prend en charge, cela active la musique spéciale lorsque vous sélectionnez le jeu dans l'interface utilisateur. + Jouer de la musique de titre:\nSi le jeu le prend en charge, cela active la musique spéciale lorsque vous sélectionnez le jeu dans l'interface utilisateur. + + + + hideCursorGroupBox + Masquer le curseur:\nChoisissez quand le curseur disparaîtra:\nJamais: Vous verrez toujours la souris.\nInactif: Définissez un temps pour qu'il disparaisse après inactivité.\nToujours: vous ne verrez jamais la souris. + + + + idleTimeoutGroupBox + Définissez un temps pour que la souris disparaisse après être inactif. + + + + backButtonBehaviorGroupBox + Comportement du bouton retour:\nDéfinit le bouton de retour de la manette pour imiter le toucher de la position spécifiée sur le pavé tactile PS4. + + + + Never + Jamais + + + + Idle + Inactif + + + + Always + Toujours + + + + Touchpad Left + Pavé Tactile Gauche + + + + Touchpad Right + Pavé Tactile Droit + + + + Touchpad Center + Centre du Pavé Tactile + + + + None + Aucun graphicsAdapterGroupBox - Adaptateur graphique :\nSélectionnez le GPU que l'émulateur utilisera dans les systèmes multi-GPU à partir de la liste déroulante,\nou choisissez "Auto Select" pour le déterminer automatiquement. + Adaptateur graphique:\nSélectionnez le GPU que l'émulateur utilisera dans les systèmes multi-GPU à partir de la liste déroulante,\nou choisissez "Auto Select" pour le déterminer automatiquement. resolutionLayout - Largeur/Hauteur :\nDéfinit la taille de la fenêtre de l'émulateur au démarrage, qui peut être redimensionnée pendant le jeu.\nCela diffère de la résolution interne du jeu. + Largeur/Hauteur:\nDéfinit la taille de la fenêtre de l'émulateur au démarrage, qui peut être redimensionnée pendant le jeu.\nCela diffère de la résolution interne du jeu. heightDivider - Diviseur Vblank :\nLe taux de rafraîchissement de l'émulateur est multiplié par ce nombre. Changer cela peut avoir des effets négatifs, tels qu'une augmentation de la vitesse du jeu ou la rupture de fonctionnalités critiques du jeu qui ne s'attendent pas à ce changement ! + Diviseur Vblank:\nLe taux de rafraîchissement de l'émulateur est multiplié par ce nombre. Changer cela peut avoir des effets négatifs, tels qu'une augmentation de la vitesse du jeu ou la rupture de fonctionnalités critiques du jeu qui ne s'attendent pas à ce changement ! dumpShadersCheckBox - Activer l'exportation de shaders :\nPour le débogage technique, les shaders du jeu sont enregistrés dans un dossier lors du rendu. + Activer l'exportation de shaders:\nPour le débogage technique, les shaders du jeu sont enregistrés dans un dossier lors du rendu. nullGpuCheckBox - Activer le GPU nul :\nPour le débogage technique, désactive le rendu du jeu comme s'il n'y avait pas de carte graphique. + Activer le GPU nul:\nPour le débogage technique, désactive le rendu du jeu comme s'il n'y avait pas de carte graphique. + + + + gameFoldersBox + Dossiers de jeux:\nLa liste des dossiers à vérifier pour les jeux installés. + + + + addFolderButton + Ajouter:\nAjouter un dossier à la liste. + + + + removeFolderButton + Supprimer:\nSupprimer un dossier de la liste. debugDump - Activer l'exportation de débogage :\nEnregistre les symboles d'importation et d'exportation et les informations d'en-tête du fichier du programme PS4 actuel dans un répertoire. + Activer l'exportation de débogage:\nEnregistre les symboles d'importation et d'exportation et les informations d'en-tête du fichier du programme PS4 actuel dans un répertoire. vkValidationCheckBox - Activer les couches de validation Vulkan :\nActive un système qui valide l'état du rendu Vulkan et enregistre des informations sur son état interne. Cela réduit les performances et peut changer le comportement de l'émulation. + Activer les couches de validation Vulkan:\nActive un système qui valide l'état du rendu Vulkan et enregistre des informations sur son état interne. Cela réduit les performances et peut changer le comportement de l'émulation. vkSyncValidationCheckBox - Activer la validation de synchronisation Vulkan :\nActive un système qui valide la planification des tâches de rendu Vulkan. Cela réduit les performances et peut changer le comportement de l'émulation. + Activer la validation de synchronisation Vulkan:\nActive un système qui valide la planification des tâches de rendu Vulkan. Cela réduit les performances et peut changer le comportement de l'émulation. rdocCheckBox - Activer le débogage RenderDoc :\nS'il est activé, l'émulateur fournit une compatibilité avec Renderdoc, permettant d'enregistrer et d'analyser la trame rendue actuelle. + Activer le débogage RenderDoc:\nS'il est activé, l'émulateur fournit une compatibilité avec Renderdoc, permettant d'enregistrer et d'analyser la trame rendue actuelle. @@ -1132,7 +1257,7 @@ Network error: - Erreur réseau : + Erreur réseau: diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index a6481d5a2..7337b54c9 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -414,6 +414,11 @@ Is PS4 Pro PS4 Pro + + + Enable Discord Rich Presence + Engedélyezze a Discord Rich Presence-t + Username @@ -434,6 +439,36 @@ Log Filter Naplózási Filter + + + Input + Bemenet + + + + Cursor + Kurzor + + + + Hide Cursor + Kurzor elrejtése + + + + Hide Cursor Idle Timeout + Kurzor inaktivitási időtúllépés + + + + Controller + Kontroller + + + + Back Button Behavior + Vissza gomb viselkedése + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU NULL GPU Engedélyezése + + + Paths + Útvonalak + + + + Game Folders + Játékmappák + + + + Add... + Hozzáadás... + + + + Remove + Eltávolítás + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox PS4 Pro:\nAz emulátort PS4 PRO-ként kezeli, ami engedélyezheti a speciális funkciókat olyan játékokban, amelyek támogatják. + + + discordRPCCheckbox + Engedélyezze a Discord Rich Presence-t:\nMegjeleníti az emulator ikonját és a kapcsolódó információkat a Discord profilján. + userName @@ -1011,7 +1071,7 @@ logFilter - Napló szűrő: Csak bizonyos információk megjelenítésére szűri a naplót. Példák: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Szintek: Trace, Debug, Info, Warning, Error, Critical - ebben a sorrendben, egy konkrét szint elnémítja az előtte lévő összes szintet, és naplózza az utána következő szinteket. + Napló szűrő:\nCsak bizonyos információk megjelenítésére szűri a naplót.\nPéldák: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Szintek: Trace, Debug, Info, Warning, Error, Critical - ebben a sorrendben, egy konkrét szint elnémítja az előtte lévő összes szintet, és naplózza az utána következő szinteket. @@ -1023,6 +1083,56 @@ GUIgroupBox Játék címzene lejátszása:\nHa a játék támogatja, engedélyezze a speciális zene lejátszását, amikor a játékot kiválasztja a GUI-ban. + + + hideCursorGroupBox + Akurátor elrejtése:\nVálassza ki, mikor tűnjön el az egérkurzor:\nSoha: Az egér mindig látható.\nInaktív: Állítson be egy időt, amikor inaktív állapotban eltűnik.\nMindig: soha nem látja az egeret. + + + + idleTimeoutGroupBox + Állítson be egy időt, ameddig az egér inaktív állapot után eltűnik. + + + + backButtonBehaviorGroupBox + Vissza gomb viselkedés:\nBeállítja a vezérlő vissza gombját, hogy utánozza a PS4 érintőpadján megadott pozíció megérintését. + + + + Never + Soha + + + + Idle + Inaktív + + + + Always + Mindig + + + + Touchpad Left + Érintőpad Balra + + + + Touchpad Right + Érintőpad Jobbra + + + + Touchpad Center + Érintőpad Középen + + + + None + Semmi + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Null GPU engedélyezése:\nMűszaki hibaelhárítás céljából letiltja a játék renderelését, mintha nem lenne grafikus kártya. + + + gameFoldersBox + Játék mappák:\nA mappák listája az telepített játékok ellenőrzésére. + + + + addFolderButton + Hozzáadás:\nHozzon létre egy mappát a listában. + + + + removeFolderButton + Eltávolítás:\nTávolítson el egy mappát a listából. + debugDump diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts index 2fdc9b68c..f64762671 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -414,6 +414,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Aktifkan Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Log Filter + + + Input + Masukan + + + + Cursor + Kursor + + + + Hide Cursor + Sembunyikan kursor + + + + Hide Cursor Idle Timeout + Batas waktu sembunyikan kursor tidak aktif + + + + Controller + Pengontrol + + + + Back Button Behavior + Perilaku tombol kembali + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Enable NULL GPU + + + Paths + Jalur + + + + Game Folders + Folder Permainan + + + + Add... + Tambah... + + + + Remove + Hapus + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Adalah PS4 Pro:\nMembuat emulator berfungsi sebagai PS4 PRO, yang mungkin mengaktifkan fitur khusus dalam permainan yang mendukungnya. + + + discordRPCCheckbox + Aktifkan Discord Rich Presence:\nMenampilkan ikon emulator dan informasi relevan di profil Discord Anda. + userName @@ -1011,7 +1071,7 @@ logFilter - Filter Log: Menyaring log untuk hanya mencetak informasi tertentu. Contoh: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Tingkatan: Trace, Debug, Info, Warning, Error, Critical - dalam urutan ini, tingkat tertentu membungkam semua tingkat sebelumnya dalam daftar dan mencatat setiap tingkat setelahnya. + Filter Log:\nMenyaring log untuk hanya mencetak informasi tertentu.\nContoh: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Tingkatan: Trace, Debug, Info, Warning, Error, Critical - dalam urutan ini, tingkat tertentu membungkam semua tingkat sebelumnya dalam daftar dan mencatat setiap tingkat setelahnya. @@ -1023,6 +1083,56 @@ GUIgroupBox Putar Musik Judul Permainan:\nJika permainan mendukungnya, aktifkan pemutaran musik khusus saat memilih permainan di GUI. + + + hideCursorGroupBox + Sembunyikan Kursor:\nPilih kapan kursor akan menghilang:\nTidak Pernah: Anda akan selalu melihat mouse.\nTidak Aktif: Tetapkan waktu untuk menghilang setelah tidak aktif.\nSelalu: Anda tidak akan pernah melihat mouse. + + + + idleTimeoutGroupBox + Tetapkan waktu untuk mouse menghilang setelah tidak aktif. + + + + backButtonBehaviorGroupBox + Perilaku Tombol Kembali:\nMengatur tombol kembali pada pengontrol untuk meniru ketukan di posisi yang ditentukan di touchpad PS4. + + + + Never + Tidak Pernah + + + + Idle + Diam + + + + Always + Selalu + + + + Touchpad Left + Touchpad Kiri + + + + Touchpad Right + Touchpad Kanan + + + + Touchpad Center + Pusat Touchpad + + + + None + Tidak Ada + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Aktifkan GPU Null:\nUntuk tujuan debugging teknis, menonaktifkan rendering permainan seolah-olah tidak ada kartu grafis. + + + gameFoldersBox + Folder Permainan:\nDaftar folder untuk memeriksa permainan yang diinstal. + + + + addFolderButton + Tambah:\nTambahkan folder ke daftar. + + + + removeFolderButton + Hapus:\nHapus folder dari daftar. + debugDump diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts index aba38a290..1da50ec5b 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -414,6 +414,11 @@ Is PS4 Pro Modalità Ps4Pro + + + Enable Discord Rich Presence + Abilita Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Filtro Log + + + Input + Input + + + + Cursor + Cursore + + + + Hide Cursor + Nascondi cursore + + + + Hide Cursor Idle Timeout + Timeout inattivo per nascondere il cursore + + + + Controller + Controller + + + + Back Button Behavior + Comportamento del pulsante Indietro + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Abilita NULL GPU + + + Paths + Percorsi + + + + Game Folders + Cartelle di gioco + + + + Add... + Aggiungi... + + + + Remove + Rimuovi + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox È PS4 Pro:\nFa sì che l'emulatore si comporti come una PS4 PRO, il che può abilitare funzionalità speciali in giochi che la supportano. + + + discordRPCCheckbox + Abilita Discord Rich Presence:\nMostra l'icona dell'emulatore e informazioni pertinenti sul tuo profilo Discord. + userName @@ -1011,7 +1071,7 @@ logFilter - Filtro Log: Filtra il log per stampare solo informazioni specifiche. Esempi: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Livelli: Trace, Debug, Info, Warning, Error, Critical - in questo ordine, un livello specifico silenzia tutti i livelli precedenti nell'elenco e registra ogni livello successivo. + Filtro Log:\nFiltra il log per stampare solo informazioni specifiche.\nEsempi: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Livelli: Trace, Debug, Info, Warning, Error, Critical - in questo ordine, un livello specifico silenzia tutti i livelli precedenti nell'elenco e registra ogni livello successivo. @@ -1023,6 +1083,56 @@ GUIgroupBox Riproduci Musica del Titolo:\nSe un gioco lo supporta, attiva la riproduzione di musica speciale quando selezioni il gioco nell'interfaccia grafica. + + + hideCursorGroupBox + Nascondi cursore:\nScegli quando il cursore scomparirà:\nMai: Vedrai sempre il mouse.\nInattivo: Imposta un tempo per farlo scomparire dopo essere stato inattivo.\nSempre: non vedrai mai il mouse. + + + + idleTimeoutGroupBox + Imposta un tempo affinché il mouse scompaia dopo essere stato inattivo. + + + + backButtonBehaviorGroupBox + Comportamento del pulsante Indietro:\nImposta il pulsante Indietro del controller per emulare il tocco sulla posizione specificata sul touchpad PS4. + + + + Never + Mai + + + + Idle + Inattivo + + + + Always + Sempre + + + + Touchpad Left + Touchpad Sinistra + + + + Touchpad Right + Touchpad Destra + + + + Touchpad Center + Centro del Touchpad + + + + None + Nessuno + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Abilita GPU Null:\nPer scopi di debug tecnico, disabilita il rendering del gioco come se non ci fosse alcuna scheda grafica. + + + gameFoldersBox + Cartelle di Gioco:\nL'elenco delle cartelle da controllare per i giochi installati. + + + + addFolderButton + Aggiungi:\nAggiungi una cartella all'elenco. + + + + removeFolderButton + Rimuovi:\nRimuovi una cartella dall'elenco. + debugDump diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 7524250f5..4fd819452 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -414,6 +414,11 @@ Is PS4 Pro PS4 Proモード + + + Enable Discord Rich Presence + Discord Rich Presenceを有効にする + Username @@ -434,6 +439,36 @@ Log Filter ログフィルター + + + Input + 入力 + + + + Cursor + カーソル + + + + Hide Cursor + カーソルを隠す + + + + Hide Cursor Idle Timeout + カーソル非アクティブタイムアウト + + + + Controller + コントローラー + + + + Back Button Behavior + 戻るボタンの動作 + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU NULL GPUを有効にする + + + Paths + パス + + + + Game Folders + ゲームフォルダ + + + + Add... + 追加... + + + + Remove + 削除 + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox PS4 Proです:\nエミュレーターがPS4 PROとして動作するようにし、これをサポートするゲームで特別な機能を有効にする場合があります。 + + + discordRPCCheckbox + Discord Rich Presenceを有効にする:\nエミュレーターのアイコンと関連情報をDiscordプロフィールに表示します。 + userName @@ -1011,7 +1071,7 @@ logFilter - ログフィルター: 特定の情報のみを印刷するようにログをフィルタリングします。例: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" レベル: Trace, Debug, Info, Warning, Error, Critical - この順序で、特定のレベルはリスト内のすべての前のレベルをサイレンスし、その後のすべてのレベルをログに記録します。 + ログフィルター:\n特定の情報のみを印刷するようにログをフィルタリングします。\n例: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" レベル: Trace, Debug, Info, Warning, Error, Critical - この順序で、特定のレベルはリスト内のすべての前のレベルをサイレンスし、その後のすべてのレベルをログに記録します。 @@ -1023,6 +1083,56 @@ GUIgroupBox タイトルミュージックを再生:\nゲームがそれをサポートしている場合、GUIでゲームを選択したときに特別な音楽を再生することを有効にします。 + + + hideCursorGroupBox + カーソルを隠す:\nカーソルが消えるタイミングを選択してください:\n決して: いつでもマウスが見えます。\nアイドル: アイダルの後に消えるまでの時間を設定します。\n常に: マウスは決して見えません。 + + + + idleTimeoutGroupBox + アイドル後にマウスが消えるまでの時間を設定します。 + + + + backButtonBehaviorGroupBox + 戻るボタンの動作:\nコントローラーの戻るボタンを、PS4のタッチパッドの指定された位置をタッチするように設定します。 + + + + Never + 決して + + + + Idle + アイドル + + + + Always + 常に + + + + Touchpad Left + タッチパッド左 + + + + Touchpad Right + タッチパッド右 + + + + Touchpad Center + タッチパッド中央 + + + + None + なし + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Null GPUを有効にする:\n技術的なデバッグの目的で、グラフィックスカードがないかのようにゲームのレンダリングを無効にします。 + + + gameFoldersBox + ゲームフォルダ:\nインストールされたゲームを確認するためのフォルダのリスト。 + + + + addFolderButton + 追加:\nリストにフォルダを追加します。 + + + + removeFolderButton + 削除:\nリストからフォルダを削除します。 + debugDump diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 4805a4d0e..74d307c3e 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -414,6 +414,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Enable Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Log Filter + + + Input + Input + + + + Cursor + Cursor + + + + Hide Cursor + Hide Cursor + + + + Hide Cursor Idle Timeout + Hide Cursor Idle Timeout + + + + Controller + Controller + + + + Back Button Behavior + Back Button Behavior + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Enable NULL GPU + + + Paths + Paths + + + + Game Folders + Game Folders + + + + Add... + Add... + + + + Remove + Remove + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Is PS4 Pro:\nMakes the emulator act as a PS4 PRO, which may enable special features in games that support it. + + + discordRPCCheckbox + Discord Rich Presence 활성화:\nDiscord 프로필에 에뮬레이터 아이콘과 관련 정보를 표시합니다. + userName @@ -1011,7 +1071,7 @@ logFilter - Log Filter: Filters the log to only print specific information. Examples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Levels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Levels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. @@ -1023,6 +1083,56 @@ GUIgroupBox Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + + + hideCursorGroupBox + Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse. + + + + idleTimeoutGroupBox + Set a time for the mouse to disappear after being after being idle. + + + + backButtonBehaviorGroupBox + Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. + + + + Never + Never + + + + Idle + Idle + + + + Always + Always + + + + Touchpad Left + Touchpad Left + + + + Touchpad Right + Touchpad Right + + + + Touchpad Center + Touchpad Center + + + + None + None + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. + + + gameFoldersBox + Game Folders:\nThe list of folders to check for installed games. + + + + addFolderButton + Add:\nAdd a folder to the list. + + + + removeFolderButton + Remove:\nRemove a folder from the list. + debugDump diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index beaaa116a..2924d65ba 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -414,6 +414,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Įjungti Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Log Filter + + + Input + Įvestis + + + + Cursor + Žymeklis + + + + Hide Cursor + Slėpti žymeklį + + + + Hide Cursor Idle Timeout + Žymeklio paslėpimo neveikimo laikas + + + + Controller + Valdiklis + + + + Back Button Behavior + Atgal mygtuko elgsena + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Enable NULL GPU + + + Paths + Keliai + + + + Game Folders + Žaidimų aplankai + + + + Add... + Pridėti... + + + + Remove + Pašalinti + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Ar PS4 Pro:\nPadaro, kad emuliatorius veiktų kaip PS4 PRO, kas gali įjungti specialias funkcijas žaidimuose, kurie tai palaiko. + + + discordRPCCheckbox + Įjungti Discord Rich Presence:\nRodo emuliatoriaus ikoną ir susijusią informaciją jūsų Discord profilyje. + userName @@ -1011,7 +1071,7 @@ logFilter - Žurnalo filtras: Filtruojamas žurnalas, kad būtų spausdinama tik konkreti informacija. Pavyzdžiai: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Lygiai: Trace, Debug, Info, Warning, Error, Critical - šia tvarka, konkretus lygis nutildo visus ankstesnius lygius sąraše ir registruoja visus vėlesnius. + Žurnalo filtras:\nFiltruojamas žurnalas, kad būtų spausdinama tik konkreti informacija.\nPavyzdžiai: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Lygiai: Trace, Debug, Info, Warning, Error, Critical - šia tvarka, konkretus lygis nutildo visus ankstesnius lygius sąraše ir registruoja visus vėlesnius. @@ -1023,6 +1083,56 @@ GUIgroupBox Groti antraščių muziką:\nJei žaidimas tai palaiko, įjungia specialios muzikos grojimą, kai pasirinkite žaidimą GUI. + + + hideCursorGroupBox + Slėpti žymeklį:\nPasirinkite, kada žymeklis dings:\nNiekuomet: Visada matysite pelę.\nNeaktyvus: Nustatykite laiką, po kurio ji dings, kai bus neaktyvi.\nVisada: niekada nematysite pelės. + + + + idleTimeoutGroupBox + Nustatykite laiką, po kurio pelė dings, kai bus neaktyvi. + + + + backButtonBehaviorGroupBox + Atgal mygtuko elgesys:\nNustato valdiklio atgal mygtuką imituoti paspaudimą nurodytoje vietoje PS4 jutiklinėje plokštėje. + + + + Never + Niekada + + + + Idle + Neaktyvus + + + + Always + Visada + + + + Touchpad Left + Jutiklinis Paviršius Kairėje + + + + Touchpad Right + Jutiklinis Paviršius Dešinėje + + + + Touchpad Center + Jutiklinis Paviršius Centre + + + + None + Nieko + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Įjungti tuščią GPU:\nTechninio derinimo tikslais išjungia žaidimo renderiavimą, tarsi nebūtų grafikos plokštės. + + + gameFoldersBox + Žaidimų aplankai:\nAplankų sąrašas, kurį reikia patikrinti, ar yra įdiegtų žaidimų. + + + + addFolderButton + Pridėti:\nPridėti aplanką į sąrašą. + + + + removeFolderButton + Pašalinti:\nPašalinti aplanką iš sąrašo. + debugDump diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index c193e83dd..c224c4f8f 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -414,6 +414,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Aktiver Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Log Filter + + + Input + Inndata + + + + Cursor + Markør + + + + Hide Cursor + Skjul markør + + + + Hide Cursor Idle Timeout + Timeout for å skjule markør ved inaktivitet + + + + Controller + Kontroller + + + + Back Button Behavior + Tilbakeknapp atferd + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Enable NULL GPU + + + Paths + Stier + + + + Game Folders + Spillmapper + + + + Add... + Legg til... + + + + Remove + Fjern + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Er PS4 Pro:\nFår emulatoren til å fungere som en PS4 PRO, noe som kan aktivere spesielle funksjoner i spill som støtter det. + + + discordRPCCheckbox + Aktiver Discord Rich Presence:\nViser emulatorikonet og relevant informasjon på Discord-profilen din. + userName @@ -1011,7 +1071,7 @@ logFilter - Loggfilter: Filtrerer loggen for å kun skrive ut spesifikk informasjon. Eksempler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Nivåer: Trace, Debug, Info, Warning, Error, Critical - i denne rekkefølgen, et spesifikt nivå demper alle tidligere nivåer i listen og logger alle nivåer etter det. + Loggfilter:\nFiltrerer loggen for å kun skrive ut spesifikk informasjon.\nEksempler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Nivåer: Trace, Debug, Info, Warning, Error, Critical - i denne rekkefølgen, et spesifikt nivå demper alle tidligere nivåer i listen og logger alle nivåer etter det. @@ -1023,6 +1083,56 @@ GUIgroupBox Spille tittelmusikk:\nHvis et spill støtter det, aktiverer det å spille spesiell musikk når du velger spillet i GUI. + + + hideCursorGroupBox + Skjul musepeker:\nVelg når musepekeren skal forsvinne:\nAldri: Du vil alltid se musen.\nInaktiv: Sett en tid for at den skal forsvinne etter å ha vært inaktiv.\nAlltid: du vil aldri se musen. + + + + idleTimeoutGroupBox + Sett en tid for når musen forsvinner etter å ha vært inaktiv. + + + + backButtonBehaviorGroupBox + Atferd for tilbaketasten:\nSetter tilbaketasten på kontrolleren til å imitere et trykk på den angitte posisjonen på PS4s berøringsplate. + + + + Never + Aldri + + + + Idle + Inaktiv + + + + Always + Alltid + + + + Touchpad Left + Berøringsplate Venstre + + + + Touchpad Right + Berøringsplate Høyre + + + + Touchpad Center + Berøringsplate Midt + + + + None + Ingen + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Aktiver Null GPU:\nFor teknisk feilsøking deaktiverer spillrendering som om det ikke var noe grafikkort. + + + gameFoldersBox + Spillmapper:\nListen over mapper for å sjekke installerte spill. + + + + addFolderButton + Legg til:\nLegg til en mappe til listen. + + + + removeFolderButton + Fjern:\nFjern en mappe fra listen. + debugDump diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts index 6b0befa92..b733610d1 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -414,6 +414,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Discord Rich Presence inschakelen + Username @@ -434,6 +439,36 @@ Log Filter Log Filter + + + Input + Invoer + + + + Cursor + Cursor + + + + Hide Cursor + Cursor verbergen + + + + Hide Cursor Idle Timeout + Inactiviteit timeout voor het verbergen van de cursor + + + + Controller + Controller + + + + Back Button Behavior + Achterknop gedrag + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Enable NULL GPU + + + Paths + Pad + + + + Game Folders + Spelmappen + + + + Add... + Toevoegen... + + + + Remove + Verwijderen + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Is PS4 Pro:\nLaat de emulator zich gedragen als een PS4 PRO, wat speciale functies kan inschakelen in games die dit ondersteunen. + + + discordRPCCheckbox + Discord Rich Presence inschakelen:\nToont het emulatoricoon en relevante informatie op je Discord-profiel. + userName @@ -1011,7 +1071,7 @@ logFilter - Logfilter: Filtert het logboek om alleen specifieke informatie af te drukken. Voorbeelden: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveaus: Trace, Debug, Info, Waarschuwing, Fout, Kritiek - in deze volgorde, een specifiek niveau dempt alle voorgaande niveaus in de lijst en logt alle niveaus daarna. + Logfilter:\nFiltert het logboek om alleen specifieke informatie af te drukken.\nVoorbeelden: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveaus: Trace, Debug, Info, Waarschuwing, Fout, Kritiek - in deze volgorde, een specifiek niveau dempt alle voorgaande niveaus in de lijst en logt alle niveaus daarna. @@ -1023,6 +1083,56 @@ GUIgroupBox Speel titelsong:\nAls een game dit ondersteunt, wordt speciale muziek afgespeeld wanneer je het spel in de GUI selecteert. + + + hideCursorGroupBox + Verberg cursor:\nKies wanneer de cursor verdwijnt:\nNooit: Je ziet altijd de muis.\nInactief: Stel een tijd in waarna deze verdwijnt na inactiviteit.\nAltijd: je ziet de muis nooit. + + + + idleTimeoutGroupBox + Stel een tijd in voor wanneer de muis verdwijnt na inactiviteit. + + + + backButtonBehaviorGroupBox + Gedrag van de terugknop:\nStelt de terugknop van de controller in om een aanraking op de opgegeven positie op de PS4-touchpad na te bootsen. + + + + Never + Nooit + + + + Idle + Inactief + + + + Always + Altijd + + + + Touchpad Left + Touchpad Links + + + + Touchpad Right + Touchpad Rechts + + + + Touchpad Center + Touchpad Midden + + + + None + Geen + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Null GPU inschakelen:\nVoor technische foutopsporing schakelt de game-rendering uit alsof er geen grafische kaart is. + + + gameFoldersBox + Spelmap:\nDe lijst met mappen om te controleren op geïnstalleerde spellen. + + + + addFolderButton + Toevoegen:\nVoeg een map toe aan de lijst. + + + + removeFolderButton + Verwijderen:\nVerwijder een map uit de lijst. + debugDump diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index ba8b4f9e4..f81647f35 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -414,6 +414,11 @@ Is PS4 Pro Emulacja PS4 Pro + + + Enable Discord Rich Presence + Włącz Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Filtrowanie dziennika + + + Input + Wejście + + + + Cursor + Kursor + + + + Hide Cursor + Ukryj kursor + + + + Hide Cursor Idle Timeout + Czas oczekiwania na ukrycie kursora przy bezczynności + + + + Controller + Kontroler + + + + Back Button Behavior + Zachowanie przycisku wstecz + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Wyłącz kartę graficzną + + + Paths + Ścieżki + + + + Game Folders + Foldery gier + + + + Add... + Dodaj... + + + + Remove + Usuń + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Czy PS4 Pro:\nSprawia, że emulator działa jak PS4 PRO, co może aktywować specjalne funkcje w grach, które to obsługują. + + + discordRPCCheckbox + Włącz Discord Rich Presence:\nWyświetla ikonę emuladora i odpowiednie informacje na twoim profilu Discord. + userName @@ -1011,7 +1071,7 @@ logFilter - Filtr logu: Filtruje dziennik, aby drukować tylko określone informacje. Przykłady: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Poziomy: Trace, Debug, Info, Warning, Error, Critical - w tej kolejności, konkretny poziom wycisza wszystkie wcześniejsze poziomy w liście i rejestruje wszystkie poziomy później. + Filtr logu:\nFiltruje dziennik, aby drukować tylko określone informacje.\nPrzykłady: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Poziomy: Trace, Debug, Info, Warning, Error, Critical - w tej kolejności, konkretny poziom wycisza wszystkie wcześniejsze poziomy w liście i rejestruje wszystkie poziomy później. @@ -1023,6 +1083,56 @@ GUIgroupBox Odtwórz muzykę tytułową:\nJeśli gra to obsługuje, aktywuje odtwarzanie specjalnej muzyki podczas wybierania gry w GUI. + + + hideCursorGroupBox + Ukryj kursor:\nWybierz, kiedy kursor zniknie:\nNigdy: Zawsze będziesz widział myszkę.\nNieaktywny: Ustaw czas, po którym zniknie po bezczynności.\nZawsze: nigdy nie zobaczysz myszki. + + + + idleTimeoutGroupBox + Ustaw czas, po którym mysz zniknie po bezczynności. + + + + backButtonBehaviorGroupBox + Zachowanie przycisku Wstecz:\nUstawia przycisk Wstecz kontrolera tak, aby emulował dotknięcie określonego miejsca na panelu dotykowym PS4. + + + + Never + Nigdy + + + + Idle + Bezczynny + + + + Always + Zawsze + + + + Touchpad Left + Touchpad Lewy + + + + Touchpad Right + Touchpad Prawy + + + + Touchpad Center + Touchpad Środkowy + + + + None + Brak + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Włącz Null GPU:\nDla technicznego debugowania dezaktywuje renderowanie gry tak, jakby nie było karty graficznej. + + + gameFoldersBox + Foldery gier:\nLista folderów do sprawdzenia zainstalowanych gier. + + + + addFolderButton + Dodaj:\nDodaj folder do listy. + + + + removeFolderButton + Usuń:\nUsuń folder z listy. + debugDump diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 2e859f12b..cf673b298 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -414,6 +414,11 @@ Is PS4 Pro Modo PS4 Pro + + + Enable Discord Rich Presence + Ativar Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Filtro do Registro + + + Input + Entradas + + + + Cursor + Cursor + + + + Hide Cursor + Ocultar Cursor + + + + Hide Cursor Idle Timeout + Tempo de Inatividade para Ocultar Cursor + + + + Controller + Controle + + + + Back Button Behavior + Comportamento do botão Voltar + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Ativar GPU NULA + + + Paths + Pastas + + + + Game Folders + Pastas dos Jogos + + + + Add... + Adicionar... + + + + Remove + Remover + Debug @@ -986,17 +1041,22 @@ fullscreenCheckBox - Ativar modo tela cheia:\nMove automaticamente a janela do jogo para o modo tela cheia.\nIsso pode ser alterado pressionando a tecla F11. + Ativar Tela Cheia:\nMove automaticamente a janela do jogo para o modo tela cheia.\nIsso pode ser alterado pressionando a tecla F11. showSplashCheckBox - Mostrar tela inicial:\nExibe a tela inicial do jogo (imagem especial) ao iniciar o jogo. + Mostrar Splash Inicial:\nExibe a tela inicial do jogo (imagem especial) ao iniciar o jogo. ps4proCheckBox - É um PS4 Pro:\nFaz o emulador agir como um PS4 PRO, o que pode ativar recursos especiais em jogos que o suportam. + Modo PS4 Pro:\nFaz o emulador agir como um PS4 PRO, o que pode ativar recursos especiais em jogos que o suportam. + + + + discordRPCCheckbox + Ativar Discord Rich Presence:\nExibe o ícone do emulador e informações relevantes no seu perfil do Discord. @@ -1006,12 +1066,12 @@ logTypeGroupBox - Tipo de log:\nDefine se a saída da janela de log deve ser sincronizada para melhorar o desempenho. Isso pode impactar negativamente a emulação. + Tipo de Registro:\nDefine se a saída da janela de log deve ser sincronizada para melhorar o desempenho. Isso pode impactar negativamente a emulação. logFilter - Filtro de log: Imprime apenas informações específicas. Exemplos: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Níveis: Trace, Debug, Info, Warning, Error, Critical - assim, um nível específico desativa todos os níveis anteriores na lista e registra todos os níveis subsequentes. + Filtro de Registro:\nImprime apenas informações específicas.\nExemplos: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nNíveis: Trace, Debug, Info, Warning, Error, Critical - assim, um nível específico desativa todos os níveis anteriores na lista e registra todos os níveis subsequentes. @@ -1023,6 +1083,56 @@ GUIgroupBox Reproduzir música de abertura:\nSe o jogo suportar, ativa a reprodução de uma música especial ao selecionar o jogo na interface do menu. + + + hideCursorGroupBox + Ocultar Cursor:\nEscolha quando o cursor desaparecerá:\nNunca: Você sempre verá o mouse.\nParado: Defina um tempo para ele desaparecer após ficar inativo.\nSempre: Você nunca verá o mouse. + + + + idleTimeoutGroupBox + Defina um tempo em segundos para o mouse desaparecer após ficar inativo. + + + + backButtonBehaviorGroupBox + Comportamento do botão Voltar:\nDefine o botão Voltar do controle para emular o toque na posição especificada no touchpad do PS4. + + + + Never + Nunca + + + + Idle + Parado + + + + Always + Sempre + + + + Touchpad Left + Touchpad Esquerdo + + + + Touchpad Right + Touchpad Direito + + + + Touchpad Center + Touchpad Centro + + + + None + Nenhum + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Ativar GPU NULA:\nDesativa a renderização do jogo para fins de depuração técnica, como se não houvesse nenhuma placa gráfica. + + + gameFoldersBox + Pastas dos jogos:\nA lista de pastas para verificar se há jogos instalados. + + + + addFolderButton + Adicionar:\nAdicione uma pasta à lista. + + + + removeFolderButton + Remover:\nRemove uma pasta da lista. + debugDump @@ -1114,7 +1239,7 @@ Play Time - Horas Jogadas + Tempo Jogado diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index b36647c1c..33a57792f 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -414,6 +414,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Activați Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Log Filter + + + Input + Introducere + + + + Cursor + Cursor + + + + Hide Cursor + Ascunde cursorul + + + + Hide Cursor Idle Timeout + Timeout pentru ascunderea cursorului inactiv + + + + Controller + Controler + + + + Back Button Behavior + Comportament buton înapoi + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Enable NULL GPU + + + Paths + Trasee + + + + Game Folders + Dosare de joc + + + + Add... + Adaugă... + + + + Remove + Eliminare + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Este PS4 Pro:\nFace ca emulatorul să se comporte ca un PS4 PRO, ceea ce poate activa funcții speciale în jocurile care o suportă. + + + discordRPCCheckbox + Activați Discord Rich Presence:\nAfișează pictograma emulatorului și informații relevante pe profilul dumneavoastră Discord. + userName @@ -1011,7 +1071,7 @@ logFilter - Filtrul jurnalului: Filtrează jurnalul pentru a imprima doar informații specifice. Exemple: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveluri: Trace, Debug, Info, Warning, Error, Critical - în această ordine, un nivel specific reduce toate nivelurile anterioare din listă și înregistrează toate nivelurile ulterioare. + Filtrul jurnalului:\nFiltrează jurnalul pentru a imprima doar informații specifice.\nExemple: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Niveluri: Trace, Debug, Info, Warning, Error, Critical - în această ordine, un nivel specific reduce toate nivelurile anterioare din listă și înregistrează toate nivelurile ulterioare. @@ -1023,6 +1083,56 @@ GUIgroupBox Redă muzica titlului:\nDacă un joc o suportă, activează redarea muzicii speciale când selectezi jocul în GUI. + + + hideCursorGroupBox + Ascunde cursorul:\nAlegeți când va dispărea cursorul:\nNiciodată: Vei vedea întotdeauna mouse-ul.\nInactiv: Setează un timp pentru a dispărea după inactivitate.\nÎntotdeauna: nu vei vedea niciodată mouse-ul. + + + + idleTimeoutGroupBox + Setați un timp pentru ca mouse-ul să dispară după ce a fost inactiv. + + + + backButtonBehaviorGroupBox + Comportamentul butonului înapoi:\nSetează butonul înapoi al controlerului să imite atingerea poziției specificate pe touchpad-ul PS4. + + + + Never + Niciodată + + + + Idle + Inactiv + + + + Always + Întotdeauna + + + + Touchpad Left + Touchpad Stânga + + + + Touchpad Right + Touchpad Dreapta + + + + Touchpad Center + Centru Touchpad + + + + None + Niciunul + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Activează GPU Null:\nÎn scopuri de depanare tehnică, dezactivează redarea jocului ca și cum nu ar exista o placă grafică. + + + gameFoldersBox + Folderele jocurilor:\nLista folderelor pentru a verifica jocurile instalate. + + + + addFolderButton + Adăugați:\nAdăugați un folder la listă. + + + + removeFolderButton + Eliminați:\nÎndepărtați un folder din listă. + debugDump diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 224342f34..e53ec2bc8 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -414,6 +414,11 @@ Is PS4 Pro Режим PS4 Pro + + + Enable Discord Rich Presence + Включить Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Фильтр логов + + + Input + Ввод + + + + Cursor + Курсор + + + + Hide Cursor + Скрыть курсор + + + + Hide Cursor Idle Timeout + Тайм-аут скрытия курсора при неактивности + + + + Controller + Контроллер + + + + Back Button Behavior + Поведение кнопки назад + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Включить NULL GPU + + + Paths + Пути + + + + Game Folders + Игровые папки + + + + Add... + Добавить... + + + + Remove + Удалить + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Режим PS4 Pro:\nЗаставляет эмулятор работать как PS4 Pro, что может включить специальные функции в играх, поддерживающих это. + + + discordRPCCheckbox + Включить Discord Rich Presence:\nОтображает значок эмулятора и соответствующую информацию в вашем профиле Discord. + userName @@ -1011,7 +1071,7 @@ logFilter - Фильтр логов: Фильтрует логи, чтобы показывать только определенную информацию. Примеры: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Уровни: Trace, Debug, Info, Warning, Error, Critical - в этом порядке, конкретный уровень глушит все предыдущие уровни в списке и показывает все последующие уровни. + Фильтр логов:\nФильтрует логи, чтобы показывать только определенную информацию.\nПримеры: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Уровни: Trace, Debug, Info, Warning, Error, Critical - в этом порядке, конкретный уровень глушит все предыдущие уровни в списке и показывает все последующие уровни. @@ -1023,6 +1083,56 @@ GUIgroupBox Воспроизведение заглавной музыки:\nЕсли игра это поддерживает, включает воспроизведение специальной музыки при выборе игры в интерфейсе. + + + hideCursorGroupBox + Скрыть курсор:\nВыберите, когда курсор исчезнет:\nНикогда: Вы всегда будете видеть мышь.\nНеактивный: Yстановите время, через которое она исчезнет после бездействия.\nВсегда: вы никогда не увидите мышь. + + + + idleTimeoutGroupBox + Установите время, через которое курсор исчезнет после бездействия. + + + + backButtonBehaviorGroupBox + Поведение кнопки «Назад»:\nУстанавливает кнопку «Назад» на контроллере для имитации нажатия в указанной позиции на сенсорной панели PS4. + + + + Never + Никогда + + + + Idle + Неактивный + + + + Always + Всегда + + + + Touchpad Left + Тачпад Слева + + + + Touchpad Right + Тачпад Справа + + + + Touchpad Center + Центр Тачпада + + + + None + Нет + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Включить NULL GPU:\nДля технической отладки отключает рендеринг игры так, как будто графической карты нет. + + + gameFoldersBox + Игровые папки:\nСписок папок для проверки установленных игр. + + + + addFolderButton + Добавить:\nДобавить папку в список. + + + + removeFolderButton + Удалить:\nУдалить папку из списка. + debugDump diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index dae5525e8..09f1714e1 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -414,6 +414,11 @@ Is PS4 Pro Mënyra PS4 Pro + + + Enable Discord Rich Presence + Aktivizo Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Filtri i Ditarit + + + Input + Hyrje + + + + Cursor + Kursori + + + + Hide Cursor + Fshih kursorin + + + + Hide Cursor Idle Timeout + Koha e pritjes për fshehjen e kursorit + + + + Controller + Kontrollues + + + + Back Button Behavior + Sjellja e butonit të kthimit + Input @@ -509,6 +544,26 @@ Enable NULL GPU Aktivizo GPU-në NULL + + + Paths + Rrugët + + + + Game Folders + Dosjet e lojës + + + + Add... + Shtoni... + + + + Remove + Hiqni + Debug @@ -1033,6 +1088,11 @@ ps4proCheckBox Është PS4 Pro:\nBën që emulatori të veprojë si një PS4 PRO, gjë që mund të aktivizojë veçori të veçanta në lojrat që e mbështesin. + + + discordRPCCheckbox + Aktivizo Discord Rich Presence:\nShfaq ikonën e emulatorit dhe informacionin përkatës në profilin tuaj në Discord. + userName @@ -1046,7 +1106,7 @@ logFilter - Filtri i ditarit: Filtron ditarin për të shfaqur vetëm informacione specifike. Shembuj: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Nivelet: Trace, Debug, Info, Warning, Error, Critical - në këtë rend, një nivel specifik hesht të gjitha nivelet përpara në listë dhe regjistron çdo nivel pas atij. + Filtri i ditarit:\nFiltron ditarin për të shfaqur vetëm informacione specifike.\nShembuj: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Nivelet: Trace, Debug, Info, Warning, Error, Critical - në këtë rend, një nivel specifik hesht të gjitha nivelet përpara në listë dhe regjistron çdo nivel pas atij. @@ -1058,6 +1118,56 @@ GUIgroupBox Luaj muzikën e titullit:\nNëse një lojë e mbështet, aktivizohet luajtja e muzikës të veçantë kur të zgjidhësh lojën në GUI. + + + hideCursorGroupBox + Fsheh kursori:\nZgjidhni kur do të zhduket kursori:\nKurrë: Do ta shihni gjithmonë maus.\nInaktiv: Vendosni një kohë për të zhdukur pas inaktivitetit.\nGjithmonë: nuk do ta shihni kurrë maus. + + + + idleTimeoutGroupBox + Vendosni një kohë për të zhdukur maus pas inaktivitetit. + + + + backButtonBehaviorGroupBox + Sjellja e butonit mbrapa:\nVendos butonin mbrapa të kontrollorit për të imituar prekjen në pozicionin e caktuar në touchpad-in PS4. + + + + Never + Kurrë + + + + Idle + Pasiv + + + + Always + Gjithmonë + + + + Touchpad Left + Touchpad Majtas + + + + Touchpad Right + Touchpad Djathtas + + + + Touchpad Center + Qendra e Touchpad + + + + None + Asnjë + cursorGroupBox @@ -1146,17 +1256,17 @@ gameFoldersBox - Dosjet e lojërave: Lista e dosjeve për t'u kontrolluar për lojëra të instaluara. + Folderët e lojërave:\nLista e folderëve për të kontrolluar lojërat e instaluara. addFolderButton - Shto: Shto një dosje në listë. + Shto:\nShto një folder në listë. removeFolderButton - Hiq: Hiq një dosje nga lista. + Hiq:\nHiq një folder nga lista. @@ -1355,4 +1465,4 @@ Krijimi i skedarit skript të përditësimit dështoi - + \ No newline at end of file diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 3e9173c7c..d00f5fcf9 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -414,6 +414,11 @@ Is PS4 Pro PS4 Pro mu + + + Enable Discord Rich Presence + Discord Rich Presence'i etkinleştir + Username @@ -434,6 +439,36 @@ Log Filter Kayıt Filtresi + + + Input + Girdi + + + + Cursor + İmleç + + + + Hide Cursor + İmleci gizle + + + + Hide Cursor Idle Timeout + İmleç için hareketsizlik zaman aşımı + + + + Controller + Kontrolcü + + + + Back Button Behavior + Geri Dön Butonu Davranışı + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU NULL GPU'yu Etkinleştir + + + Paths + Yollar + + + + Game Folders + Oyun Klasörleri + + + + Add... + Ekle... + + + + Remove + Kaldır + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox PS4 Pro Mu:\nEmülatörü bir PS4 PRO gibi çalıştırır; bu, bunu destekleyen oyunlarda özel özellikleri etkinleştirebilir. + + + discordRPCCheckbox + Discord Rich Presence'i etkinleştir:\nEmülatör simgesini ve Discord profilinizdeki ilgili bilgileri gösterir. + userName @@ -1011,7 +1071,7 @@ logFilter - Günlük Filtre: Sadece belirli bilgileri yazdırmak için günlüğü filtreler. Örnekler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Düzeyler: Trace, Debug, Info, Warning, Error, Critical - bu sırada, belirli bir seviye listede önceki tüm seviyeleri susturur ve sonraki tüm seviyeleri kaydeder. + Günlük Filtre:\nSadece belirli bilgileri yazdırmak için günlüğü filtreler.\nÖrnekler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Düzeyler: Trace, Debug, Info, Warning, Error, Critical - bu sırada, belirli bir seviye listede önceki tüm seviyeleri susturur ve sonraki tüm seviyeleri kaydeder. @@ -1023,6 +1083,56 @@ GUIgroupBox Başlık Müziklerini Çal:\nEğer bir oyun bunu destekliyorsa, GUI'de oyunu seçtiğinizde özel müziklerin çalmasını etkinleştirir. + + + hideCursorGroupBox + İmleci gizle:\nİmlecin ne zaman kaybolacağını seçin:\nAsla: Fareyi her zaman göreceksiniz.\nPasif: Hareketsiz kaldıktan sonra kaybolması için bir süre belirleyin.\nHer zaman: fareyi asla göremeyeceksiniz. + + + + idleTimeoutGroupBox + Hareket etmeden sonra imlecin kaybolacağı süreyi ayarlayın. + + + + backButtonBehaviorGroupBox + Geri düğmesi davranışı:\nKontrol cihazındaki geri düğmesini, PS4'ün dokunmatik panelindeki belirlenen noktaya dokunmak için ayarlar. + + + + Never + Asla + + + + Idle + Boşta + + + + Always + Her zaman + + + + Touchpad Left + Dokunmatik Yüzey Sol + + + + Touchpad Right + Dokunmatik Yüzey Sağ + + + + Touchpad Center + Dokunmatik Yüzey Orta + + + + None + Yok + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Null GPU'yu Etkinleştir:\nTeknik hata ayıklama amacıyla, oyunun render edilmesini grafik kartı yokmuş gibi devre dışı bırakır. + + + gameFoldersBox + Oyun klasörleri:\nYüklenmiş oyunları kontrol etmek için klasörlerin listesi. + + + + addFolderButton + Ekle:\nListeye bir klasör ekle. + + + + removeFolderButton + Kaldır:\nListeden bir klasörü kaldır. + debugDump diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index af8d218a6..cf58ee5ba 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -414,6 +414,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + Bật Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Log Filter + + + Input + Đầu vào + + + + Cursor + Con trỏ + + + + Hide Cursor + Ẩn con trỏ + + + + Hide Cursor Idle Timeout + Thời gian chờ ẩn con trỏ + + + + Controller + Điều khiển + + + + Back Button Behavior + Hành vi nút quay lại + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Enable NULL GPU + + + Paths + Đường dẫn + + + + Game Folders + Thư mục trò chơi + + + + Add... + Thêm... + + + + Remove + Xóa + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox Là PS4 Pro:\nKhiến trình giả lập hoạt động như một PS4 PRO, điều này có thể kích hoạt các tính năng đặc biệt trong các trò chơi hỗ trợ điều này. + + + discordRPCCheckbox + Bật Discord Rich Presence:\nHiển thị biểu tượng trình giả lập và thông tin liên quan trên hồ sơ Discord của bạn. + userName @@ -1011,7 +1071,7 @@ logFilter - Bộ lọc nhật ký: Lọc nhật ký để in chỉ thông tin cụ thể. Ví dụ: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Các mức: Trace, Debug, Info, Warning, Error, Critical - theo thứ tự này, một mức cụ thể làm tắt tất cả các mức trước trong danh sách và ghi lại tất cả các mức sau đó. + Bộ lọc nhật ký:\nLọc nhật ký để in chỉ thông tin cụ thể.\nVí dụ: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Các mức: Trace, Debug, Info, Warning, Error, Critical - theo thứ tự này, một mức cụ thể làm tắt tất cả các mức trước trong danh sách và ghi lại tất cả các mức sau đó. @@ -1023,6 +1083,56 @@ GUIgroupBox Phát nhạc tiêu đề trò chơi:\nNếu một trò chơi hỗ trợ điều này, hãy kích hoạt phát nhạc đặc biệt khi bạn chọn trò chơi trong GUI. + + + hideCursorGroupBox + Ẩn con trỏ:\nChọn khi nào con trỏ sẽ biến mất:\nKhông bao giờ: Bạn sẽ luôn thấy chuột.\nKhông hoạt động: Đặt một khoảng thời gian để nó biến mất sau khi không hoạt động.\nLuôn luôn: bạn sẽ không bao giờ thấy chuột. + + + + idleTimeoutGroupBox + Đặt thời gian để chuột biến mất sau khi không hoạt động. + + + + backButtonBehaviorGroupBox + Hành vi nút quay lại:\nĐặt nút quay lại của tay cầm để mô phỏng việc chạm vào vị trí đã chỉ định trên touchpad của PS4. + + + + Never + Không bao giờ + + + + Idle + Nhàn rỗi + + + + Always + Luôn luôn + + + + Touchpad Left + Touchpad Trái + + + + Touchpad Right + Touchpad Phải + + + + Touchpad Center + Giữa Touchpad + + + + None + Không có + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox Bật GPU Null:\nĐể mục đích gỡ lỗi kỹ thuật, vô hiệu hóa việc kết xuất trò chơi như thể không có card đồ họa. + + + gameFoldersBox + Thư mục trò chơi:\nDanh sách các thư mục để kiểm tra các trò chơi đã cài đặt. + + + + addFolderButton + Thêm:\nThêm một thư mục vào danh sách. + + + + removeFolderButton + Xóa:\nXóa một thư mục khỏi danh sách. + debugDump diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 5eef55641..ac4117edc 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -414,6 +414,11 @@ Is PS4 Pro 是否是 PS4 Pro + + + Enable Discord Rich Presence + 启用 Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter 日志过滤 + + + Input + 输入 + + + + Cursor + 光标 + + + + Hide Cursor + 隐藏光标 + + + + Hide Cursor Idle Timeout + 光标空闲超时隐藏 + + + + Controller + 控制器 + + + + Back Button Behavior + 返回按钮行为 + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU 启用 NULL GPU + + + Paths + 路径 + + + + Game Folders + 游戏文件夹 + + + + Add... + 添加... + + + + Remove + 删除 + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox 这是 PS4 Pro:\n使模拟器作为 PS4 PRO 运行,可以在支持的游戏中激活特殊功能。 + + + discordRPCCheckbox + 启用 Discord Rich Presence:\n在您的 Discord 个人资料上显示仿真器图标和相关信息。 + userName @@ -1011,7 +1071,7 @@ logFilter - 日志过滤器: 过滤日志,仅打印特定信息。例如:"Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" 级别: Trace, Debug, Info, Warning, Error, Critical - 按此顺序,特定级别将静默列表中所有先前的级别,并记录所有后续级别。 + 日志过滤器:\n过滤日志,仅打印特定信息。\n例如:"Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" 级别: Trace, Debug, Info, Warning, Error, Critical - 按此顺序,特定级别将静默列表中所有先前的级别,并记录所有后续级别。 @@ -1023,6 +1083,56 @@ GUIgroupBox 播放标题音乐:\n如果游戏支持,在图形界面选择游戏时启用播放特殊音乐。 + + + hideCursorGroupBox + 隐藏光标:\n选择光标何时消失:\n从不: 您将始终看到鼠标。\n空闲: 设置光标在空闲后消失的时间。\n始终: 您将永远看不到鼠标。 + + + + idleTimeoutGroupBox + 设置鼠标在空闲后消失的时间。 + + + + backButtonBehaviorGroupBox + 返回按钮行为:\n设置控制器的返回按钮以模拟在 PS4 触控板上指定位置的点击。 + + + + Never + 从不 + + + + Idle + 空闲 + + + + Always + 始终 + + + + Touchpad Left + 触控板左侧 + + + + Touchpad Right + 触控板右侧 + + + + Touchpad Center + 触控板中间 + + + + None + + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox 启用空 GPU:\n为了技术调试,将游戏渲染禁用,仿佛没有图形卡。 + + + gameFoldersBox + 游戏文件夹:\n检查已安装游戏的文件夹列表。 + + + + addFolderButton + 添加:\n将文件夹添加到列表。 + + + + removeFolderButton + 移除:\n从列表中移除文件夹。 + debugDump diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 7e3585a07..c114c69c7 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -414,6 +414,11 @@ Is PS4 Pro Is PS4 Pro + + + Enable Discord Rich Presence + 啟用 Discord Rich Presence + Username @@ -434,6 +439,36 @@ Log Filter Log Filter + + + Input + 輸入 + + + + Cursor + 游標 + + + + Hide Cursor + 隱藏游標 + + + + Hide Cursor Idle Timeout + 游標空閒超時隱藏 + + + + Controller + 控制器 + + + + Back Button Behavior + 返回按鈕行為 + Graphics @@ -474,6 +509,26 @@ Enable NULL GPU Enable NULL GPU + + + Paths + 路徑 + + + + Game Folders + 遊戲資料夾 + + + + Add... + 添加... + + + + Remove + 刪除 + Debug @@ -998,6 +1053,11 @@ ps4proCheckBox 為PS4 Pro:\n讓模擬器像PS4 PRO一樣運作,這可能啟用支持此功能的遊戲中的特殊功能。 + + + discordRPCCheckbox + 啟用 Discord Rich Presence:\n在您的 Discord 個人檔案上顯示模擬器圖標和相關信息。 + userName @@ -1011,7 +1071,7 @@ logFilter - 日誌過濾器: 過濾日誌以僅打印特定信息。範例:"Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" 等級: Trace, Debug, Info, Warning, Error, Critical - 以此順序,特定級別靜音所有前面的級別,並記錄其後的每個級別。 + 日誌過濾器:\n過濾日誌以僅打印特定信息。\n範例:"Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" 等級: Trace, Debug, Info, Warning, Error, Critical - 以此順序,特定級別靜音所有前面的級別,並記錄其後的每個級別。 @@ -1023,6 +1083,56 @@ GUIgroupBox 播放標題音樂:\n如果遊戲支持,啟用在GUI中選擇遊戲時播放特殊音樂。 + + + hideCursorGroupBox + 隱藏游標:\n選擇游標何時消失:\n從不: 您將始終看到滑鼠。\n閒置: 設定在閒置後消失的時間。\n始終: 您將永遠看不到滑鼠。 + + + + idleTimeoutGroupBox + 設定滑鼠在閒置後消失的時間。 + + + + backButtonBehaviorGroupBox + 返回按鈕行為:\n設定控制器的返回按鈕模擬在 PS4 觸控板上指定位置的觸碰。 + + + + Never + 從不 + + + + Idle + 閒置 + + + + Always + 始終 + + + + Touchpad Left + 觸控板左側 + + + + Touchpad Right + 觸控板右側 + + + + Touchpad Center + 觸控板中間 + + + + None + + graphicsAdapterGroupBox @@ -1048,6 +1158,21 @@ nullGpuCheckBox 啟用空GPU:\n為了技術調試,禁用遊戲渲染,彷彿沒有顯示卡。 + + + gameFoldersBox + 遊戲資料夾:\n檢查已安裝遊戲的資料夾列表。 + + + + addFolderButton + 添加:\n將資料夾添加到列表。 + + + + removeFolderButton + 移除:\n從列表中移除資料夾。 + debugDump From b965b094b7174b91e38bd611d482db2e6d494696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quang=20Ng=C3=B4?= Date: Mon, 14 Oct 2024 18:34:52 +0700 Subject: [PATCH 07/10] test (#1371) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ee09163fd..6b87ec418 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -368,7 +368,7 @@ jobs: run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel3 - name: Run AppImage packaging script run: ./.github/linux-appimage-qt.sh From 9b54e8231409c3210405a38c03f11240d76b4c1e Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 14 Oct 2024 15:00:55 +0300 Subject: [PATCH 08/10] revert qt-issue fixed linux-qt build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b87ec418..ee09163fd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -368,7 +368,7 @@ jobs: run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel3 + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel - name: Run AppImage packaging script run: ./.github/linux-appimage-qt.sh From a3df2448ec848f8b264e058a002e841fee26959b Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Mon, 14 Oct 2024 15:11:21 +0300 Subject: [PATCH 09/10] Small Np + trophy fixes (#1363) * sceNpGetOnlineId returns sign out code * return -1 if trophy xml not found . Fixes undertale --- src/core/libraries/np_manager/np_manager.cpp | 6 +++--- src/core/libraries/np_manager/np_manager.h | 2 +- src/core/libraries/np_trophy/np_trophy.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/libraries/np_manager/np_manager.cpp b/src/core/libraries/np_manager/np_manager.cpp index e1aaee814..907244b22 100644 --- a/src/core/libraries/np_manager/np_manager.cpp +++ b/src/core/libraries/np_manager/np_manager.cpp @@ -986,9 +986,9 @@ int PS4_SYSV_ABI sceNpGetNpReachabilityState() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpGetOnlineId() { - LOG_ERROR(Lib_NpManager, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpGetOnlineId(s32 userId, OrbisNpOnlineId* onlineId) { + LOG_DEBUG(Lib_NpManager, "called returned sign out"); + return ORBIS_NP_ERROR_SIGNED_OUT; } int PS4_SYSV_ABI sceNpGetParentalControlInfo() { diff --git a/src/core/libraries/np_manager/np_manager.h b/src/core/libraries/np_manager/np_manager.h index 861d91e39..7e906cdc8 100644 --- a/src/core/libraries/np_manager/np_manager.h +++ b/src/core/libraries/np_manager/np_manager.h @@ -233,7 +233,7 @@ int PS4_SYSV_ABI sceNpGetGamePresenceStatus(); int PS4_SYSV_ABI sceNpGetGamePresenceStatusA(); int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId userId, OrbisNpId* npId); int PS4_SYSV_ABI sceNpGetNpReachabilityState(); -int PS4_SYSV_ABI sceNpGetOnlineId(); +int PS4_SYSV_ABI sceNpGetOnlineId(s32 userId, OrbisNpOnlineId* onlineId); int PS4_SYSV_ABI sceNpGetParentalControlInfo(); int PS4_SYSV_ABI sceNpGetParentalControlInfoA(); int PS4_SYSV_ABI sceNpGetState(s32 userId, OrbisNpState* state); diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp index 548d1af69..e8fd57ef1 100644 --- a/src/core/libraries/np_trophy/np_trophy.cpp +++ b/src/core/libraries/np_trophy/np_trophy.cpp @@ -520,7 +520,7 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context, if (!result) { LOG_ERROR(Lib_NpTrophy, "Failed to open trophy xml : {}", result.description()); - return ORBIS_OK; + return -1; } int num_trophies = 0; From 09725bd921088b73746605e672abf6ff40171880 Mon Sep 17 00:00:00 2001 From: psucien Date: Mon, 14 Oct 2024 22:33:06 +0200 Subject: [PATCH 10/10] hot-fix: unexpected pass break on indirect args buffer obtaining --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 293dfbe6a..b3c42fcb6 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -100,12 +100,12 @@ void Rasterizer::DrawIndirect(bool is_indexed, VAddr address, u32 offset, u32 si buffer_cache.BindVertexBuffers(vs_info); const u32 num_indices = buffer_cache.BindIndexBuffer(is_indexed, 0); - BeginRendering(*pipeline); - UpdateDynamicState(*pipeline); - const auto [buffer, base] = buffer_cache.ObtainBuffer(address, size, true); const auto total_offset = base + offset; + BeginRendering(*pipeline); + UpdateDynamicState(*pipeline); + // We can safely ignore both SGPR UD indices and results of fetch shader parsing, as vertex and // instance offsets will be automatically applied by Vulkan from indirect args buffer.