// Copyright (c) 2013- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include #include "android/jni/app-android.h" #include "Common/UI/View.h" #include "Common/UI/ViewGroup.h" #include "Common/System/OSD.h" #include "Common/GPU/OpenGL/GLFeatures.h" #include "Common/Data/Text/Parsers.h" #include "Common/Data/Encoding/Utf8.h" #include "Common/File/FileUtil.h" #include "Common/Render/Text/draw_text.h" #include "Common/StringUtils.h" #include "GPU/Common/TextureReplacer.h" #include "GPU/Common/PostShader.h" #include "Core/MIPS/MIPSTracer.h" #include "Core/ELF/ParamSFO.h" #include "Core/Config.h" #include "Core/HLE/HLE.h" #include "Core/Core.h" #include "Core/System.h" #include "Core/WebServer.h" #include "Core/Util/PathUtil.h" #include "UI/GPUDriverTestScreen.h" #include "UI/DeveloperToolsScreen.h" #include "UI/DevScreens.h" #include "UI/DriverManagerScreen.h" #include "UI/DisplayLayoutScreen.h" #include "UI/GameSettingsScreen.h" #include "UI/OnScreenDisplay.h" #include "UI/IconCache.h" #include "UI/MiscViews.h" #if PPSSPP_PLATFORM(ANDROID) static bool CheckKgslPresent() { constexpr auto KgslPath{ "/dev/kgsl-3d0" }; return access(KgslPath, F_OK) == 0; } static bool SupportsCustomDriver() { return android_get_device_api_level() >= 28 && CheckKgslPresent(); } #else static bool SupportsCustomDriver() { #ifdef _DEBUG return false; // change to true to debug driver installation on other platforms #else return false; #endif } #endif static std::string PostShaderTranslateName(std::string_view value) { const ShaderInfo *info = GetPostShaderInfo(value); if (info) { auto ps = GetI18NCategory(I18NCat::POSTSHADERS); return std::string(ps->T(value, info->name)); } else { return std::string(value); } } DeveloperToolsScreen::DeveloperToolsScreen(const Path &gamePath) : UITabbedBaseDialogScreen(gamePath, &g_Config.iDeveloperSettingsCurrentTab, TabDialogFlags::AddAutoTitles) {} void DeveloperToolsScreen::CreateTextureReplacementTab(UI::LinearLayout *list) { using namespace UI; auto dev = GetI18NCategory(I18NCat::DEVELOPER); auto di = GetI18NCategory(I18NCat::DIALOG); list->Add(new ItemHeader(dev->T("Texture Replacement"))); list->Add(new CheckBox(&g_Config.bSaveNewTextures, dev->T("Save new textures"))); list->Add(new CheckBox(&g_Config.bReplaceTextures, dev->T("Replace textures"))); Choice *createTextureIni = list->Add(new Choice(dev->T("Create/Open textures.ini file for current game"))); createTextureIni->OnClick.Handle(this, &DeveloperToolsScreen::OnOpenTexturesIniFile); createTextureIni->SetEnabledFunc([&] { if (!PSP_IsInited()) return false; // Disable the choice to Open/Create if the textures.ini file already exists, and we can't open it due to platform support limitations. if (!System_GetPropertyBool(SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR)) { if (hasTexturesIni_ == HasIni::MAYBE) hasTexturesIni_ = TextureReplacer::IniExists(g_paramSFO.GetDiscID()) ? HasIni::YES : HasIni::NO; return hasTexturesIni_ != HasIni::YES; } return true; }); if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) { // Best string we have list->Add(new Choice(di->T("Show in folder")))->OnClick.Add([=](UI::EventParams &) { Path path; if (PSP_IsInited()) { std::string gameID = g_paramSFO.GetDiscID(); path = GetSysDirectory(DIRECTORY_TEXTURES) / gameID; } else { // Just show the root textures directory. path = GetSysDirectory(DIRECTORY_TEXTURES); } System_ShowFileInFolder(path); }); } static const char *texLoadSpeeds[] = { "Slow (smooth)", "Medium", "Fast", "Instant (may stutter)" }; PopupMultiChoice *texLoadSpeed = list->Add(new PopupMultiChoice(&g_Config.iReplacementTextureLoadSpeed, dev->T("Replacement texture load speed"), texLoadSpeeds, 0, ARRAY_SIZE(texLoadSpeeds), I18NCat::DEVELOPER, screenManager())); texLoadSpeed->SetChoiceIcon(3, ImageID("I_WARNING")); } void DeveloperToolsScreen::CreateGeneralTab(UI::LinearLayout *list) { using namespace UI; auto dev = GetI18NCategory(I18NCat::DEVELOPER); auto sy = GetI18NCategory(I18NCat::SYSTEM); auto gr = GetI18NCategory(I18NCat::GRAPHICS); auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); list->Add(new ItemHeader(sy->T("CPU Core"))); bool canUseJit = System_GetPropertyBool(SYSPROP_CAN_JIT); // iOS can now use JIT on all modes, apparently. // The bool may come in handy for future non-jit platforms though (UWP XB1?) // In iOS App Store builds, we disable the JIT. static const char *cpuCores[] = { "Interpreter", "Dynarec/JIT (recommended)", "IR Interpreter", "JIT using IR" }; PopupMultiChoice *core = list->Add(new PopupMultiChoice(&g_Config.iCpuCore, sy->T("CPU Core"), cpuCores, 0, ARRAY_SIZE(cpuCores), I18NCat::SYSTEM, screenManager())); core->OnChoice.Add([=](UI::EventParams &e) { OnJitAffectingSetting(e); g_Config.NotifyUpdatedCpuCore(); }); if (!canUseJit) { core->HideChoice(1); core->HideChoice(3); } // TODO: Enable "JIT using IR" on more architectures. #if !PPSSPP_ARCH(X86) && !PPSSPP_ARCH(AMD64) && !PPSSPP_ARCH(ARM64) core->HideChoice(3); #endif list->Add(new Choice(dev->T("JIT debug tools")))->OnClick.Handle(this, &DeveloperToolsScreen::OnJitDebugTools); list->Add(new CheckBox(&g_Config.bShowDeveloperMenu, dev->T("Show in-game developer menu"))); AddOverlayList(list, screenManager()); list->Add(new ItemHeader(sy->T("General"))); list->Add(new CheckBox(&g_Config.bEnableLogging, dev->T("Enable Logging")))->OnClick.Handle(this, &DeveloperToolsScreen::OnLoggingChanged); list->Add(new Choice(dev->T("Logging Channels")))->OnClick.Add([this](UI::EventParams &e) { screenManager()->push(new LogConfigScreen()); }); list->Add(new CheckBox(&g_Config.bEnableFileLogging, dev->T("Log to file")))->SetEnabledPtr(&g_Config.bEnableLogging); if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_DESKTOP) { list->Add(new Choice(dev->T("Show log file in folder")))->OnClick.Add([](UI::EventParams &e) { Path logFilePath = g_logManager.GetLogFilePath(); if (logFilePath.empty()) { ERROR_LOG(Log::System, "No log file path configured."); return; } if (File::Exists(logFilePath)) { System_ShowFileInFolder(logFilePath); } else { System_LaunchUrl(LaunchUrlType::LOCAL_FILE, logFilePath.NavigateUp().ToString()); } }); } list->Add(new CheckBox(&g_Config.bLogFrameDrops, dev->T("Log Dropped Frame Statistics"))); if (GetGPUBackend() == GPUBackend::VULKAN) { list->Add(new CheckBox(&g_Config.bGpuLogProfiler, dev->T("GPU log profiler"))); } allowDebugger_ = !WebServerStopped(WebServerFlags::DEBUGGER); canAllowDebugger_ = !WebServerStopping(WebServerFlags::DEBUGGER); CheckBox *allowDebugger = new CheckBox(&allowDebugger_, dev->T("Allow remote debugger")); list->Add(allowDebugger)->OnClick.Handle(this, &DeveloperToolsScreen::OnRemoteDebugger); allowDebugger->SetEnabledPtr(&canAllowDebugger_); CheckBox *localDebugger = list->Add(new CheckBox(&g_Config.bRemoteDebuggerLocal, dev->T("Use locally hosted remote debugger"))); localDebugger->SetEnabledPtr(&allowDebugger_); list->Add(new Choice(dev->T("GPI/GPO switches/LEDs")))->OnClick.Add([=](UI::EventParams &e) { screenManager()->push(new GPIGPOScreen(dev->T("GPI/GPO switches/LEDs"))); }); list->Add(new CheckBox(&g_Config.bShowSaveLoadIndicator, dev->T("Show indicator when saving/loading"))); #if PPSSPP_PLATFORM(ANDROID) static const char *framerateModes[] = { "Default", "Request 60 Hz", "Force 60Hz" }; PopupMultiChoice *framerateMode = list->Add(new PopupMultiChoice(&g_Config.iDisplayFramerateMode, gr->T("Framerate mode"), framerateModes, 0, ARRAY_SIZE(framerateModes), I18NCat::GRAPHICS, screenManager())); framerateMode->SetEnabledFunc([]() { return System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 30; }); framerateMode->OnChoice.Add([](UI::EventParams &e) { System_Notify(SystemNotification::FORCE_RECREATE_ACTIVITY); }); #endif #if PPSSPP_PLATFORM(IOS) list->Add(new NoticeView(NoticeLevel::WARN, ms->T("Moving the memstick directory is NOT recommended on iOS"), ""))->SetWrapText(true); list->Add(new Choice(sy->T("Set Memory Stick folder")))->OnClick.Add( [=](UI::EventParams &) { SetMemStickDirDarwin(GetRequesterToken()); }); #endif // Makes it easy to get savestates out of an iOS device. The file listing shown in MacOS doesn't allow // you to descend into directories. #if PPSSPP_PLATFORM(IOS) list->Add(new Choice(dev->T("Copy savestates to memstick root")))->OnClick.Handle(this, &DeveloperToolsScreen::OnCopyStatesToRoot); #endif auto di = GetI18NCategory(I18NCat::DIALOG); // Reuse strings to the max, heh. list->Add(new Choice(ApplySafeSubstitutions("%1: ppsspp.ini", di->T("Copy to clipboard"))))->OnClick.Add([=](UI::EventParams &) { auto di = GetI18NCategory(I18NCat::DIALOG); std::string configStr = g_Config.GetConfigAsString(); if (!configStr.empty()) { System_CopyStringToClipboard(configStr); g_OSD.Show(OSDType::MESSAGE_INFO, ApplySafeSubstitutions(di->T("Copied to clipboard: %1"), "ppsspp.ini"), 0.0f, "copyToClip"); } }); } void DeveloperToolsScreen::CreateTestsTab(UI::LinearLayout *list) { using namespace UI; auto dev = GetI18NCategory(I18NCat::DEVELOPER); list->Add(new Choice(dev->T("Touchscreen Test")))->OnClick.Add([this](UI::EventParams &e) { screenManager()->push(new TouchTestScreen(gamePath_)); // Handle touchscreen test event }); // list->Add(new Choice(dev->T("Memstick Test")))->OnClick.Handle(this, &DeveloperToolsScreen::OnMemstickTest); Choice *frameDumpTests = list->Add(new Choice(dev->T("Framedump tests"))); frameDumpTests->OnClick.Add([this](UI::EventParams &e) { screenManager()->push(new FrameDumpTestScreen()); }); frameDumpTests->SetEnabled(!PSP_IsInited()); // For now, we only implement GPU driver tests for Vulkan and OpenGL. This is simply // because the D3D drivers are generally solid enough to not need this type of investigation. if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN || g_Config.iGPUBackend == (int)GPUBackend::OPENGL) { list->Add(new Choice(dev->T("GPU Driver Test")))->OnClick.Handle(this, &DeveloperToolsScreen::OnGPUDriverTest); } // Not useful enough to be made visible. /* auto memmapTest = list->Add(new Choice(dev->T("Memory map test"))); memmapTest->OnClick.Add([this](UI::EventParams &e) { MemoryMapTest(); }); memmapTest->SetEnabled(PSP_IsInited()); */ } void DeveloperToolsScreen::CreateDumpFileTab(UI::LinearLayout *list) { using namespace UI; auto dev = GetI18NCategory(I18NCat::DEVELOPER); list->Add(new ItemHeader(dev->T("Dump files"))); list->Add(new BitCheckBox(&g_Config.iDumpFileTypes, (int)DumpFileType::EBOOT, dev->T("Dump Decrypted Eboot", "Dump Decrypted EBOOT.BIN (If Encrypted) When Booting Game"))); list->Add(new BitCheckBox(&g_Config.iDumpFileTypes, (int)DumpFileType::PRX, dev->T("PRX"))); list->Add(new BitCheckBox(&g_Config.iDumpFileTypes, (int)DumpFileType::Atrac3, dev->T("Atrac3/3+"))); list->Add(new BitCheckBox(&g_Config.iDumpFileTypes, (int)DumpFileType::PBP_ISO, dev->T("ISO from PBP"))); } void DeveloperToolsScreen::CreateHLETab(UI::LinearLayout *list) { using namespace UI; auto dev = GetI18NCategory(I18NCat::DEVELOPER); list->Add(new CheckBox(&g_Config.bUseOldAtrac, dev->T("Use the old sceAtrac implementation"))); list->Add(new ItemHeader(dev->T("Disable HLE"))); for (int i = 0; i < (int)DisableHLEFlags::Count; i++) { DisableHLEFlags flag = (DisableHLEFlags)(1 << i); // Show a checkbox, unless the setting has graduated to always disabled. if (!(flag & AlwaysDisableHLEFlags())) { const HLEModuleMeta *meta = GetHLEModuleMetaByFlag(flag); if (meta) { BitCheckBox *checkBox = list->Add(new BitCheckBox(&g_Config.iDisableHLE, (int)flag, meta->modname)); checkBox->SetEnabled(!PSP_IsInited()); } } } list->Add(new ItemHeader(dev->T("Force-enable HLE"))); for (int i = 0; i < (int)DisableHLEFlags::Count; i++) { DisableHLEFlags flag = (DisableHLEFlags)(1 << i); // Show a checkbox, only if the setting has graduated to always disabled (and thus it makes sense to force-enable it). if (flag & AlwaysDisableHLEFlags()) { const HLEModuleMeta *meta = GetHLEModuleMetaByFlag(flag); if (meta) { BitCheckBox *checkBox = list->Add(new BitCheckBox(&g_Config.iForceEnableHLE, (int)flag, meta->modname)); checkBox->SetEnabled(!PSP_IsInited()); } } } } void DeveloperToolsScreen::CreateMIPSTracerTab(UI::LinearLayout *list) { using namespace UI; auto dev = GetI18NCategory(I18NCat::DEVELOPER); list->Add(new ItemHeader(dev->T("MIPSTracer"))); MIPSTracerEnabled_ = mipsTracer.tracing_enabled; CheckBox *MIPSTracerEnabled = new CheckBox(&MIPSTracerEnabled_, dev->T("MIPSTracer enabled")); list->Add(MIPSTracerEnabled)->OnClick.Handle(this, &DeveloperToolsScreen::OnMIPSTracerEnabled); MIPSTracerEnabled->SetEnabledFunc([]() { bool temp = g_Config.iCpuCore == static_cast(CPUCore::IR_INTERPRETER) && PSP_IsInited(); return temp && Core_IsStepping() && coreState != CORE_POWERDOWN; }); Choice *TraceDumpPath = list->Add(new Choice(dev->T("Select the file path for the trace"))); TraceDumpPath->OnClick.Handle(this, &DeveloperToolsScreen::OnMIPSTracerPathChanged); TraceDumpPath->SetEnabledFunc([]() { if (!PSP_IsInited()) return false; return true; }); MIPSTracerPath_ = mipsTracer.get_logging_path(); MIPSTracerPath = list->Add(new InfoItem(dev->T("Current log file"), MIPSTracerPath_)); PopupSliderChoice* storage_capacity = list->Add( new PopupSliderChoice( &mipsTracer.in_storage_capacity, 0x4'0000, 0x40'0000, 0x10'0000, dev->T("Storage capacity"), 0x10000, screenManager() ) ); storage_capacity->SetFormat("0x%x asm opcodes"); storage_capacity->OnChange.Add([&](UI::EventParams &) { INFO_LOG(Log::JIT, "User changed the tracer's storage capacity to 0x%x", mipsTracer.in_storage_capacity); }); PopupSliderChoice* trace_max_size = list->Add( new PopupSliderChoice( &mipsTracer.in_max_trace_size, 0x1'0000, 0x40'0000, 0x10'0000, dev->T("Max allowed trace size"), 0x10000, screenManager() ) ); trace_max_size->SetFormat("%d basic blocks"); trace_max_size->OnChange.Add([&](UI::EventParams &) { INFO_LOG(Log::JIT, "User changed the tracer's max trace size to %d", mipsTracer.in_max_trace_size); }); list->Add(new ItemHeader(dev->T("MIPSTracer actions"))); Choice *FlushTrace = list->Add(new Choice(dev->T("Flush the trace"))); FlushTrace->OnClick.Handle(this, &DeveloperToolsScreen::OnMIPSTracerFlushTrace); Choice *InvalidateJitCache = list->Add(new Choice(dev->T("Clear the JIT cache"))); InvalidateJitCache->OnClick.Handle(this, &DeveloperToolsScreen::OnMIPSTracerClearJitCache); Choice *ClearMIPSTracer = list->Add(new Choice(dev->T("Clear the MIPSTracer"))); ClearMIPSTracer->OnClick.Handle(this, &DeveloperToolsScreen::OnMIPSTracerClearTracer); } void DeveloperToolsScreen::CreateAudioTab(UI::LinearLayout *list) { using namespace UI; auto dev = GetI18NCategory(I18NCat::DEVELOPER); list->Add(new CheckBox(&g_Config.bForceFfmpegForAudioDec, dev->T("Use FFMPEG for all compressed audio"))); } void DeveloperToolsScreen::CreateUITab(UI::LinearLayout *list) { using namespace UI; auto dev = GetI18NCategory(I18NCat::DEVELOPER); UIContext *uiContext = screenManager()->getUIContext(); list->Add(new Choice(dev->T("Reload UI atlas")))->OnClick.Add([uiContext](UI::EventParams &) { uiContext->InvalidateAtlas(); }); auto di = GetI18NCategory(I18NCat::DIALOG); auto si = GetI18NCategory(I18NCat::SYSINFO); auto sy = GetI18NCategory(I18NCat::SYSTEM); auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS); // TODO: Move most of these strings out of the SysInfo category. list->Add(new ItemHeader(si->T("Icon cache"))); IconCacheStats iconStats = g_iconCache.GetStats(); list->Add(new InfoItem(si->T("Image data count"), StringFromFormat("%d", iconStats.cachedCount))); list->Add(new InfoItem(si->T("Texture count"), StringFromFormat("%d", iconStats.textureCount))); list->Add(new InfoItem(si->T("Data size"), NiceSizeFormat(iconStats.dataSize))); list->Add(new Choice(di->T("Clear")))->OnClick.Add([&](UI::EventParams &) { g_iconCache.ClearData(); RecreateViews(); }); list->Add(new ItemHeader(si->T("Font cache"))); const TextDrawer *text = screenManager()->getUIContext()->Text(); if (text) { list->Add(new InfoItem(si->T("Texture count"), StringFromFormat("%d", text->GetStringCacheSize()))); list->Add(new InfoItem(si->T("Data size"), NiceSizeFormat(text->GetCacheDataSize()))); } list->Add(new ItemHeader(si->T("Slider test"))); list->Add(new Slider(&testSliderValue_, 0, 100, 1, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); static const char *positions[] = {"Bottom Left", "Bottom Center", "Bottom Right", "Top Left", "Top Center", "Top Right", "Center Left", "Center Right", "None"}; list->Add(new ItemHeader(si->T("Notification tests"))); list->Add(new Choice(si->T("Error")))->OnClick.Add([&](UI::EventParams &) { std::string str = "Error " + CodepointToUTF8(0x1F41B) + CodepointToUTF8(0x1F41C) + CodepointToUTF8(0x1F914); g_OSD.Show(OSDType::MESSAGE_ERROR, str); }); list->Add(new Choice(si->T("Warning")))->OnClick.Add([&](UI::EventParams &) { g_OSD.Show(OSDType::MESSAGE_WARNING, "Warning, a pretty long warning heading", "Some\nAdditional\nDetail, some of which is very, very long and wide and will need line wrapping on most screens."); }); list->Add(new Choice(si->T("Info")))->OnClick.Add([&](UI::EventParams &) { g_OSD.Show(OSDType::MESSAGE_INFO, "Info, info info info info info info info info info info"); }); // This one is clickable list->Add(new Choice(si->T("Success")))->OnClick.Add([&](UI::EventParams &) { g_OSD.Show(OSDType::MESSAGE_SUCCESS, "Success", 0.0f, "clickable"); g_OSD.SetClickCallback("clickable", []() { System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.google.com/"); }); }); list->Add(new Choice(sy->T("RetroAchievements")))->OnClick.Add([&](UI::EventParams &) { g_OSD.Show(OSDType::MESSAGE_WARNING, "RetroAchievements warning", "", "I_RETROACHIEVEMENTS_LOGO"); }); list->Add(new ItemHeader(si->T("Progress tests"))); list->Add(new Choice(si->T("30%")))->OnClick.Add([&](UI::EventParams &) { g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 30, 0.0f); }); list->Add(new Choice(si->T("100%")))->OnClick.Add([&](UI::EventParams &) { g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 100, 1.0f); }); list->Add(new Choice(si->T("N/A%")))->OnClick.Add([&](UI::EventParams &) { g_OSD.SetProgressBar("testprogress", "Test Progress", 0, 0, 0, 0.0f); }); list->Add(new Choice(si->T("Success")))->OnClick.Add([&](UI::EventParams &) { g_OSD.RemoveProgressBar("testprogress", true, 0.5f); }); list->Add(new Choice(si->T("Failure")))->OnClick.Add([&](UI::EventParams &) { g_OSD.RemoveProgressBar("testprogress", false, 0.5f); }); list->Add(new ItemHeader(si->T("Achievement tests"))); list->Add(new Choice(si->T("Leaderboard tracker: Show")))->OnClick.Add([=](UI::EventParams &) { g_OSD.ShowLeaderboardTracker(1, "My leaderboard tracker", true); }); list->Add(new Choice(si->T("Leaderboard tracker: Update")))->OnClick.Add([=](UI::EventParams &) { g_OSD.ShowLeaderboardTracker(1, "Updated tracker", true); }); list->Add(new Choice(si->T("Leaderboard tracker: Hide")))->OnClick.Add([=](UI::EventParams &) { g_OSD.ShowLeaderboardTracker(1, "", false); }); list->Add(new ItemHeader(ac->T("Notifications"))); list->Add(new PopupMultiChoice(&g_Config.iNotificationPos, sy->T("Notification screen position"), positions, 0, ARRAY_SIZE(positions), I18NCat::DIALOG, screenManager())); list->Add(new PopupMultiChoice(&g_Config.iAchievementsLeaderboardTrackerPos, ac->T("Leaderboard tracker"), positions, 0, ARRAY_SIZE(positions), I18NCat::DIALOG, screenManager())); #ifdef _DEBUG list->Add(new CheckBox(&pretendIngame_, ac->T("Pretend to be in-game (for testing)"))); // Untranslated string because this is debug mode only, only for PPSSPP developers. list->Add(new ItemHeader(ac->T("Assert"))); list->Add(new Choice("Assert"))->OnClick.Add([=](UI::EventParams &) { _dbg_assert_msg_(false, "Test assert message"); }); #endif #if PPSSPP_PLATFORM(ANDROID) list->Add(new Choice(si->T("Exception")))->OnClick.Add([&](UI::EventParams &) { System_Notify(SystemNotification::TEST_JAVA_EXCEPTION); }); #endif } void DeveloperToolsScreen::CreateNetworkTab(UI::LinearLayout *list) { using namespace UI; auto dev = GetI18NCategory(I18NCat::DEVELOPER); auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); auto ri = GetI18NCategory(I18NCat::REMOTEISO); auto nw = GetI18NCategory(I18NCat::NETWORKING); list->Add(new ItemHeader(ms->T("Networking"))); list->Add(new CheckBox(&g_Config.bDontDownloadInfraJson, dev->T("Don't download infra-dns.json"))); // This is shared between RemoteISO and the remote debugger. list->Add(new PopupSliderChoice(&g_Config.iRemoteISOPort, 0, 65535, 0, ri->T("Local Server Port", "Local Server Port"), 100, screenManager())); list->Add(new ItemHeader(nw->T("AdHoc server"))); list->Add(new CheckBox(&g_Config.bAdhocServerShowPlayerPorts, nw->T("Show player port numbers"))); } // TODO: Make this generic extern int DefaultDepthRaster(); void DeveloperToolsScreen::CreateGraphicsTab(UI::LinearLayout *list) { using namespace UI; auto dev = GetI18NCategory(I18NCat::DEVELOPER); auto gr = GetI18NCategory(I18NCat::GRAPHICS); auto ps = GetI18NCategory(I18NCat::POSTSHADERS); auto sy = GetI18NCategory(I18NCat::SYSTEM); auto si = GetI18NCategory(I18NCat::SYSINFO); Draw::DrawContext *draw = screenManager()->getDrawContext(); list->Add(new ItemHeader(sy->T("General"))); list->Add(new CheckBox(&g_Config.bVendorBugChecksEnabled, dev->T("Enable driver bug workarounds"))); list->Add(new CheckBox(&g_Config.bShaderCache, dev->T("Enable shader cache"))); auto displayRefreshRate = list->Add(new PopupSliderChoice(&g_Config.iDisplayRefreshRate, 60, 1000, 60, dev->T("Display refresh rate"), 1, screenManager())); displayRefreshRate->SetFormat(si->T("%d Hz")); list->Add(new ItemHeader(dev->T("Vulkan"))); list->Add(new CheckBox(&g_Config.bVulkanDisableImplicitLayers, dev->T("Prevent loading overlays"))); if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) { list->Add(new CheckBox(&g_Config.bRenderMultiThreading, dev->T("Multi-threaded rendering"), ""))->OnClick.Add([](UI::EventParams &e) { // TODO: Not translating yet. Will combine with other translations of settings that need restart. g_OSD.Show(OSDType::MESSAGE_WARNING, "Restart required"); }); } if (GetGPUBackend() == GPUBackend::VULKAN && SupportsCustomDriver()) { auto driverChoice = list->Add(new Choice(gr->T("AdrenoTools driver manager"))); driverChoice->OnClick.Add([=](UI::EventParams &e) { screenManager()->push(new DriverManagerScreen(gamePath_)); }); } static const char *depthRasterModes[] = { "Auto", "Low", "Off", "Always on" }; PopupMultiChoice *depthRasterMode = list->Add(new PopupMultiChoice(&g_Config.iDepthRasterMode, gr->T("Lens flare occlusion"), depthRasterModes, 0, ARRAY_SIZE(depthRasterModes), I18NCat::GRAPHICS, screenManager())); depthRasterMode->SetDisabledPtr(&g_Config.bSoftwareRendering); depthRasterMode->SetDefault(DefaultDepthRaster()); depthRasterMode->SetChoiceIcon(3, ImageID("I_WARNING")); // It's a performance trap. list->Add(new ItemHeader(dev->T("Ubershaders"))); if (draw->GetShaderLanguageDesc().bitwiseOps && !draw->GetBugs().Has(Draw::Bugs::UNIFORM_INDEXING_BROKEN)) { // If the above if fails, the checkbox is redundant since it'll be force disabled anyway. list->Add(new CheckBox(&g_Config.bUberShaderVertex, dev->T("Vertex"))); } #if !PPSSPP_PLATFORM(UWP) if (g_Config.iGPUBackend != (int)GPUBackend::OPENGL || gl_extensions.GLES3) { #else { #endif list->Add(new CheckBox(&g_Config.bUberShaderFragment, dev->T("Fragment"))); } // Experimental, allow some VR features without OpenXR if (GetGPUBackend() == GPUBackend::OPENGL) { auto vr = GetI18NCategory(I18NCat::VR); list->Add(new ItemHeader(vr->T("Virtual reality"))); list->Add(new CheckBox(&g_Config.bForceVR, vr->T("VR camera"))); } // Experimental, will move to main graphics settings later. bool multiViewSupported = draw->GetDeviceCaps().multiViewSupported; auto enableStereo = [=]() -> bool { return g_Config.bStereoRendering && multiViewSupported; }; if (multiViewSupported) { list->Add(new ItemHeader(gr->T("Stereo rendering"))); list->Add(new CheckBox(&g_Config.bStereoRendering, gr->T("Stereo rendering"))); std::vector stereoShaderNames; ChoiceWithValueDisplay *stereoShaderChoice = list->Add(new ChoiceWithValueDisplay(&g_Config.sStereoToMonoShader, gr->T("Stereo display shader"), &PostShaderTranslateName)); stereoShaderChoice->SetEnabledFunc(enableStereo); stereoShaderChoice->OnClick.Add([=](EventParams &e) { auto gr = GetI18NCategory(I18NCat::GRAPHICS); auto procScreen = new PostProcScreen(gr->T("Stereo display shader"), 0, true); if (e.v) procScreen->SetPopupOrigin(e.v); screenManager()->push(procScreen); }); const ShaderInfo *shaderInfo = GetPostShaderInfo(g_Config.sStereoToMonoShader); if (shaderInfo) { for (size_t i = 0; i < ARRAY_SIZE(shaderInfo->settings); ++i) { auto &setting = shaderInfo->settings[i]; if (!setting.name.empty()) { std::string key = StringFromFormat("%sSettingCurrentValue%d", shaderInfo->section.c_str(), i + 1); bool keyExisted = g_Config.mPostShaderSetting.find(key) != g_Config.mPostShaderSetting.end(); auto &value = g_Config.mPostShaderSetting[key]; if (!keyExisted) value = setting.value; PopupSliderChoiceFloat *settingValue = list->Add(new PopupSliderChoiceFloat(&value, setting.minValue, setting.maxValue, setting.value, ps->T(setting.name), setting.step, screenManager())); settingValue->SetEnabledFunc([=] { return !g_Config.bSkipBufferEffects && enableStereo(); }); } } } } } void DeveloperToolsScreen::CreateCrashHistoryTab(UI::LinearLayout *list) { using namespace UI; auto dev = GetI18NCategory(I18NCat::DEVELOPER); auto di = GetI18NCategory(I18NCat::DIALOG); std::vector reports = Android_GetNativeCrashHistory(20); list->Add(new ItemHeader(dev->T("Crash history"))); if (reports.empty()) { list->Add(new TextView(di->T("None"))); return; } for (size_t i = 0; i < reports.size(); i++) { std::string name = StringFromFormat("Crash %d", (int)i); CollapsibleSection *section = list->Add(new CollapsibleSection(name)); const std::string report = reports[i]; if (report.size() > 150) { section->Add(new Choice(di->T("Copy to clipboard"), ImageID("I_FILE_COPY")))->OnClick.Add([report](UI::EventParams&) { System_CopyStringToClipboard(report); }); } section->Add(new TextView(report, FLAG_WRAP_TEXT | FLAG_DYNAMIC_ASCII, true)); } } void DeveloperToolsScreen::CreateTabs() { auto dev = GetI18NCategory(I18NCat::DEVELOPER); auto sy = GetI18NCategory(I18NCat::SYSTEM); auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); AddTab("General", sy->T("General"), [this](UI::LinearLayout *parent) { CreateGeneralTab(parent); }); AddTab("TextureReplacement", dev->T("Texture Replacement"), [this](UI::LinearLayout *parent) { CreateTextureReplacementTab(parent); }); AddTab("Graphics", ms->T("Graphics"), [this](UI::LinearLayout *parent) { CreateGraphicsTab(parent); }); AddTab("Networking", ms->T("Networking"), [this](UI::LinearLayout *parent) { CreateNetworkTab(parent); }); AddTab("Audio", ms->T("Audio"), [this](UI::LinearLayout *parent) { CreateAudioTab(parent); }); AddTab("Tests", dev->T("Tests"), [this](UI::LinearLayout *parent) { CreateTestsTab(parent); }); AddTab("UI", dev->T("UI"), [this](UI::LinearLayout *parent) { CreateUITab(parent); }); AddTab("DumpFiles", dev->T("Dump files"), [this](UI::LinearLayout *parent) { CreateDumpFileTab(parent); }); // Need a better title string. AddTab("HLE", dev->T("Disable HLE"), [this](UI::LinearLayout *parent) { CreateHLETab(parent); }); #if !PPSSPP_PLATFORM(ANDROID) && !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(SWITCH) AddTab("MIPSTracer", dev->T("MIPSTracer"), [this](UI::LinearLayout *parent) { CreateMIPSTracerTab(parent); }); #endif //#if PPSSPP_PLATFORM(ANDROID) if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 30) { AddTab("Crash history", dev->T("Crash history"), [this](UI::LinearLayout *parent) { CreateCrashHistoryTab(parent); }); } //#endif // Reconsider whenever recreating views. hasTexturesIni_ = HasIni::MAYBE; } void DeveloperToolsScreen::onFinish(DialogResult result) { UIScreen::onFinish(result); g_Config.Save("DeveloperToolsScreen::onFinish"); System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED); } void DeveloperToolsScreen::OnLoggingChanged(UI::EventParams &e) { System_Notify(SystemNotification::TOGGLE_DEBUG_CONSOLE); } void DeveloperToolsScreen::OnOpenTexturesIniFile(UI::EventParams &e) { std::string gameID = g_paramSFO.GetDiscID(); Path generatedFilename; if (TextureReplacer::GenerateIni(gameID, generatedFilename)) { if (System_GetPropertyBool(SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR)) { File::OpenFileInEditor(generatedFilename); } else { // Can't do much here, let's send a "toast" so the user sees that something happened. auto dev = GetI18NCategory(I18NCat::DEVELOPER); System_Toast((GetFriendlyPath(generatedFilename) + ": " + dev->T_cstr("Texture ini file created")).c_str()); } hasTexturesIni_ = HasIni::YES; } } void DeveloperToolsScreen::OnJitDebugTools(UI::EventParams &e) { screenManager()->push(new JitDebugScreen()); } void DeveloperToolsScreen::OnGPUDriverTest(UI::EventParams &e) { screenManager()->push(new GPUDriverTestScreen()); } void DeveloperToolsScreen::OnJitAffectingSetting(UI::EventParams &e) { System_PostUIMessage(UIMessage::REQUEST_CLEAR_JIT); } void DeveloperToolsScreen::OnCopyStatesToRoot(UI::EventParams &e) { Path savestate_dir = GetSysDirectory(DIRECTORY_SAVESTATE); Path root_dir = GetSysDirectory(DIRECTORY_MEMSTICK_ROOT); std::vector files; GetFilesInDir(savestate_dir, &files, nullptr, 0); for (const File::FileInfo &file : files) { Path src = file.fullName; Path dst = root_dir / file.name; INFO_LOG(Log::System, "Copying file '%s' to '%s'", src.c_str(), dst.c_str()); File::Copy(src, dst); } } void DeveloperToolsScreen::OnRemoteDebugger(UI::EventParams &e) { if (allowDebugger_) { StartWebServer(WebServerFlags::DEBUGGER); } else { StopWebServer(WebServerFlags::DEBUGGER); } // Persist the setting. Maybe should separate? g_Config.bRemoteDebuggerOnStartup = allowDebugger_; } void DeveloperToolsScreen::OnMIPSTracerEnabled(UI::EventParams &e) { if (MIPSTracerEnabled_) { u32 capacity = mipsTracer.in_storage_capacity; u32 trace_size = mipsTracer.in_max_trace_size; mipsTracer.initialize(capacity, trace_size); mipsTracer.start_tracing(); } else { mipsTracer.stop_tracing(); } } void DeveloperToolsScreen::OnMIPSTracerPathChanged(UI::EventParams &e) { auto dev = GetI18NCategory(I18NCat::DEVELOPER); System_BrowseForFileSave( GetRequesterToken(), dev->T("Select the log file"), "trace.txt", BrowseFileType::ANY, [this](std::string_view value, int) { mipsTracer.set_logging_path(std::string(value)); MIPSTracerPath_ = value; MIPSTracerPath->SetRightText(MIPSTracerPath_); } ); } void DeveloperToolsScreen::OnMIPSTracerFlushTrace(UI::EventParams &e) { mipsTracer.flush_to_file(); // The error logs are emitted inside the tracer } void DeveloperToolsScreen::OnMIPSTracerClearJitCache(UI::EventParams &e) { INFO_LOG(Log::JIT, "Clearing the jit cache..."); System_PostUIMessage(UIMessage::REQUEST_CLEAR_JIT); } void DeveloperToolsScreen::OnMIPSTracerClearTracer(UI::EventParams &e) { INFO_LOG(Log::JIT, "Clearing the MIPSTracer..."); mipsTracer.clear(); } void DeveloperToolsScreen::update() { UIBaseDialogScreen::update(); allowDebugger_ = !WebServerStopped(WebServerFlags::DEBUGGER); canAllowDebugger_ = !WebServerStopping(WebServerFlags::DEBUGGER); // For the UI tab's notification tests. if (pretendIngame_) { g_OSD.NudgeIngameNotifications(); } } void DeveloperToolsScreen::MemoryMapTest() { int sum = 0; for (uint64_t addr = 0; addr < 0x100000000ULL; addr += 0x1000) { const u32 addr32 = (u32)addr; if (Memory::IsValidAddress(addr32)) { sum += Memory::ReadUnchecked_U32(addr32); } } // Just to force the compiler to do things properly. INFO_LOG(Log::JIT, "Total sum: %08x", sum); } static bool RunMemstickTest(std::string *error) { Path testRoot = GetSysDirectory(PSPDirectories::DIRECTORY_CACHE) / "test"; *error = "N/A"; File::CreateDir(testRoot); if (!File::Exists(testRoot)) { return false; } Path testFilePath = testRoot / "temp.txt"; File::CreateEmptyFile(testFilePath); // Attempt to delete the test root. This should fail since it still contains files. File::DeleteDir(testRoot); if (!File::Exists(testRoot)) { *error = "testroot was deleted with a file in it!"; return false; } File::Delete(testFilePath); if (File::Exists(testFilePath)) { *error = "testfile wasn't deleted"; return false; } File::DeleteDir(testRoot); if (File::Exists(testRoot)) { *error = "testroot wasn't deleted, even when empty"; return false; } *error = "passed"; return true; } void DeveloperToolsScreen::OnMemstickTest(UI::EventParams &e) { std::string error; if (RunMemstickTest(&error)) { g_OSD.Show(OSDType::MESSAGE_SUCCESS, "Memstick test passed", error, 6.0f); } else { g_OSD.Show(OSDType::MESSAGE_ERROR, "Memstick test failed", error, 6.0f); } }