mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 12:07:31 +01:00
Each tab is now a SettingsPage: a list of items (headers, settings, buttons, hints, links to other pages) that a function builds and views are created from. Settings are added with page->Setting(&g_Config.x), which takes the label, the kind of view, the range or choices, units, labels for special values and the default from the config. The pages keep what depends on the UI (which settings to show and when, enable rules, and behavior that needs the screen), and reach the screen through the page's SettingsPageHost. A page can link to other pages, which open on top of it in the same tab, under a bar showing where you are, like Tools > Developer tools > Graphics, where the earlier pages can be clicked to go back to them. The bar has back, search and help buttons, and the back key also goes back a page (or out of a search) before leaving the screen. Each page remembers its scroll position. In portrait there's no tab strip: a page links to the tabs, and the bar shows just the current page's title. Rotating keeps showing the same page. Moving between pages only focuses an item when navigating with a keyboard or controller. The developer tools are now pages linked from the Tools tab, instead of a screen of their own. The in-game developer menu and --start-screen=developertools open the settings on that page. The toggles of live state (the remote debugger and the MIPS tracer) show and change it through functions, and pretending to be in-game for the notification tests is a flag on the OSD. The memory map and memstick tests, only reachable from commented-out code, are removed. Search lists the matching settings from all tabs and the pages they link to, grouped by where they are. Each result goes to the setting on its page, scrolled to and highlighted (outlined, when picked with a mouse or touch), or for a page, opens it. This replaces the search tab and the old filter that hid the views that didn't match, and with them ShowSearchControls. Changing tabs leaves the search. The pages, the bar and search are in SettingsPagesDialogScreen, between the tabbed dialog screen and the settings screen, so the other tabbed screens keep plain tabs. Other visible changes: - Reset buttons that went to the wrong value now go to the real default: the touch control auto-hide delay (was 20 seconds instead of 8), the mouse wheel delay (1 ms, below its own range, instead of 80) and the UI size (0 instead of -1 on desktop). The alternative speeds get a reset button. - Units are shown on more sliders (frames, MHz, seconds, Hz), and popups no longer spell out what 0 means in the units, since the zero label does. Units of up to three characters (%, x, ms, MHz) aren't translated. - The Android HW scaler hides the scales the display is too small for, instead of cutting the list short. - Choices are translated in the setting's own section. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
773 lines
28 KiB
C++
773 lines
28 KiB
C++
// 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 <string>
|
|
|
|
#include "android/jni/app-android.h"
|
|
#include "Common/UI/View.h"
|
|
#include "Common/UI/ViewGroup.h"
|
|
#include "Common/System/OSD.h"
|
|
#include "Common/System/Request.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 "Common/UI/ScreenManager.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 "Core/FileSystems/VirtualDiscFileSystem.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"
|
|
#include "UI/SettingsPage.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);
|
|
}
|
|
}
|
|
|
|
enum class HasIni {
|
|
NO,
|
|
YES,
|
|
MAYBE,
|
|
};
|
|
|
|
// The pages' handlers, defined after them.
|
|
static void OnLoggingChanged(UI::EventParams &e);
|
|
static void OpenTexturesIniFile(HasIni *hasIni);
|
|
static void OnJitDebugTools(SettingsPageHost *host);
|
|
static void OnGPUDriverTest(SettingsPageHost *host);
|
|
static void OnJitAffectingSetting(UI::EventParams &e);
|
|
#if PPSSPP_PLATFORM(IOS)
|
|
static void OnCopyStatesToRoot(UI::EventParams &e);
|
|
#endif
|
|
static void SetRemoteDebuggerAllowed(bool allow);
|
|
#if !PPSSPP_PLATFORM(ANDROID) && !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(SWITCH)
|
|
static void SetMIPSTracerEnabled(bool enabled);
|
|
static void OnMIPSTracerPathChanged(SettingsPageHost *host);
|
|
static void OnMIPSTracerFlushTrace(UI::EventParams &e);
|
|
static void OnMIPSTracerClearJitCache(UI::EventParams &e);
|
|
static void OnMIPSTracerClearTracer(UI::EventParams &e);
|
|
#endif
|
|
|
|
static void BuildTextureReplacementPage(SettingsPage *page) {
|
|
using namespace UI;
|
|
|
|
page->Header("Texture Replacement");
|
|
page->Setting(&g_Config.bSaveNewTextures);
|
|
page->Setting(&g_Config.bReplaceTextures);
|
|
|
|
// Checked once per build of the page, when first needed.
|
|
std::shared_ptr<HasIni> hasTexturesIni = std::make_shared<HasIni>(HasIni::MAYBE);
|
|
page->Choice("Create/Open textures.ini file for current game").OnClick([hasTexturesIni](UI::EventParams &e) {
|
|
OpenTexturesIniFile(hasTexturesIni.get());
|
|
}).EnabledFunc([hasTexturesIni] {
|
|
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
|
|
page->Choice({ I18NCat::DIALOG, "Show in folder" }).OnClick([](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);
|
|
});
|
|
}
|
|
|
|
page->Setting(&g_Config.iReplacementTextureLoadSpeed).Setup([](View *view) {
|
|
static_cast<PopupMultiChoice *>(view)->SetChoiceIcon(3, ImageID("I_WARNING"));
|
|
});
|
|
}
|
|
|
|
static void BuildGeneralPage(SettingsPage *page) {
|
|
using namespace UI;
|
|
SettingsPageHost *host = page->Host();
|
|
|
|
page->Header({ I18NCat::SYSTEM, "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.
|
|
|
|
page->Setting(&g_Config.iCpuCore).OnChange([](UI::EventParams &e) {
|
|
OnJitAffectingSetting(e);
|
|
g_Config.NotifyUpdatedCpuCore();
|
|
}).Setup([canUseJit](View *view) {
|
|
PopupMultiChoice *core = static_cast<PopupMultiChoice *>(view);
|
|
if (!canUseJit) {
|
|
core->HideChoice(1);
|
|
}
|
|
// Every native JIT is IR-based, so "JIT using IR" is the same as "JIT".
|
|
core->HideChoice(3);
|
|
});
|
|
|
|
// Only the interpreter raises these, but don't hide it behind the CPU core choice - it'd just
|
|
// look like the setting had vanished.
|
|
page->Setting(&g_Config.bEnableFPUExceptionTraps);
|
|
// Experimental, so not translated yet.
|
|
page->Setting(&g_Config.bEmulateMediaEngine);
|
|
|
|
page->Choice("JIT debug tools").OnClick([host](UI::EventParams &e) {
|
|
OnJitDebugTools(host);
|
|
});
|
|
page->Setting(&g_Config.bShowDeveloperMenu);
|
|
|
|
page->Custom("Debug overlay", [host](UI::ViewGroup *parent) -> UI::View * {
|
|
AddOverlayList(parent, host->PageScreenManager());
|
|
return parent->GetViewByIndex(parent->GetNumSubviews() - 1);
|
|
});
|
|
|
|
page->Header({ I18NCat::SYSTEM, "General" });
|
|
|
|
page->Bool(&g_Config.bEnableLogging, "Enable Logging").OnChange(&OnLoggingChanged);
|
|
page->Choice("Logging Channels").OnClick([host](UI::EventParams &e) {
|
|
host->PageScreenManager()->push(new LogConfigScreen());
|
|
});
|
|
page->Setting(&g_Config.bEnableFileLogging).Enabled(&g_Config.bEnableLogging);
|
|
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_DESKTOP) {
|
|
page->Choice("Show log file in folder").OnClick([](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());
|
|
}
|
|
});
|
|
}
|
|
|
|
page->Setting(&g_Config.bLogFrameDrops);
|
|
if (GetGPUBackend() == GPUBackend::VULKAN) {
|
|
page->Setting(&g_Config.bGpuLogProfiler);
|
|
}
|
|
|
|
auto debuggerAllowed = [] {
|
|
return !WebServerStopped(WebServerFlags::DEBUGGER);
|
|
};
|
|
page->Bool("Allow remote debugger", debuggerAllowed, &SetRemoteDebuggerAllowed).EnabledFunc([] {
|
|
return !WebServerStopping(WebServerFlags::DEBUGGER);
|
|
});
|
|
page->Setting(&g_Config.bRemoteDebuggerLocal).EnabledFunc(debuggerAllowed);
|
|
|
|
page->Setting(&g_Config.bAutoSaveLoadSymbols);
|
|
|
|
page->Choice("GPI/GPO switches/LEDs").OnClick([host](UI::EventParams &e) {
|
|
host->PageScreenManager()->push(new GPIGPOScreen(T(I18NCat::DEVELOPER, "GPI/GPO switches/LEDs")));
|
|
});
|
|
|
|
page->Setting(&g_Config.bShowSaveLoadIndicator);
|
|
|
|
#if PPSSPP_PLATFORM(ANDROID)
|
|
page->Setting(&g_Config.iDisplayFramerateMode).EnabledFunc([]() {
|
|
return System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 30;
|
|
}).OnChange([](UI::EventParams &e) {
|
|
System_Notify(SystemNotification::FORCE_RECREATE_ACTIVITY);
|
|
});
|
|
#endif
|
|
|
|
#if PPSSPP_PLATFORM(IOS)
|
|
page->Custom({ I18NCat::MAINSETTINGS, "Moving the memstick directory is NOT recommended on iOS" }, [](UI::ViewGroup *parent) -> UI::View * {
|
|
NoticeView *notice = parent->Add(new NoticeView(NoticeLevel::WARN, T(I18NCat::MAINSETTINGS, "Moving the memstick directory is NOT recommended on iOS"), ""));
|
|
notice->SetWrapText(true);
|
|
return notice;
|
|
});
|
|
page->Choice({ I18NCat::SYSTEM, "Set Memory Stick folder" }).OnClick([host](UI::EventParams &) {
|
|
SetMemStickDirDarwin(host->PageRequesterToken());
|
|
});
|
|
#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)
|
|
page->Choice("Copy savestates to memstick root").OnClick(&OnCopyStatesToRoot);
|
|
#endif
|
|
|
|
// Reuse strings to the max, heh.
|
|
page->Custom({ I18NCat::DIALOG, "Copy to clipboard" }, [](UI::ViewGroup *parent) -> UI::View * {
|
|
Choice *copy = parent->Add(new Choice(ApplySafeSubstitutions("%1: ppsspp.ini", T(I18NCat::DIALOG, "Copy to clipboard"))));
|
|
copy->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");
|
|
}
|
|
});
|
|
return copy;
|
|
});
|
|
|
|
#if PLATFORM_SUPPORTS_FILE_HANDLER_PLUGINS
|
|
page->Setting(&g_Config.bEnableFileHandlerPlugins);
|
|
#endif
|
|
}
|
|
|
|
static void BuildTestsPage(SettingsPage *page) {
|
|
using namespace UI;
|
|
SettingsPageHost *host = page->Host();
|
|
|
|
page->Choice("Touchscreen Test").OnClick([host](UI::EventParams &e) {
|
|
host->PageScreenManager()->push(new TouchTestScreen(host->PageGamePath()));
|
|
});
|
|
page->Choice("Framedump tests").OnClick([host](UI::EventParams &e) {
|
|
host->PageScreenManager()->push(new FrameDumpTestScreen());
|
|
}).EnabledFunc([] {
|
|
return !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) {
|
|
page->Choice("GPU Driver Test").OnClick([host](UI::EventParams &e) {
|
|
OnGPUDriverTest(host);
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
static void BuildDumpFilePage(SettingsPage *page) {
|
|
using namespace UI;
|
|
|
|
auto addDumpType = [page](DumpFileType type, SettingText text) {
|
|
page->Custom(text, [type, text](UI::ViewGroup *parent) -> UI::View * {
|
|
return parent->Add(new BitCheckBox(&g_Config.iDumpFileTypes, (int)type, text.Translate(I18NCat::DEVELOPER)));
|
|
});
|
|
};
|
|
|
|
page->Header("Dump files");
|
|
addDumpType(DumpFileType::EBOOT, { I18NCat::DEVELOPER, "Dump Decrypted Eboot", "Dump Decrypted EBOOT.BIN (If Encrypted) When Booting Game" });
|
|
addDumpType(DumpFileType::PRX, "PRX");
|
|
addDumpType(DumpFileType::Atrac3, "Atrac3/3+");
|
|
addDumpType(DumpFileType::PBP_ISO, "ISO from PBP");
|
|
}
|
|
|
|
static void BuildHLEPage(SettingsPage *page) {
|
|
using namespace UI;
|
|
|
|
page->Setting(&g_Config.bUseOldAtrac);
|
|
|
|
auto addModule = [page](int *flags, DisableHLEFlags flag, const char *modname) {
|
|
page->Custom(SettingText::Raw(modname), [flags, flag, modname](UI::ViewGroup *parent) -> UI::View * {
|
|
return parent->Add(new BitCheckBox(flags, (int)flag, modname));
|
|
}).EnabledFunc([] {
|
|
return !PSP_IsInited();
|
|
});
|
|
};
|
|
|
|
page->Header("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) {
|
|
addModule(&g_Config.iDisableHLE, flag, meta->modname);
|
|
}
|
|
}
|
|
}
|
|
|
|
page->Header("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) {
|
|
addModule(&g_Config.iForceEnableHLE, flag, meta->modname);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !PPSSPP_PLATFORM(ANDROID) && !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(SWITCH)
|
|
static void BuildMIPSTracerPage(SettingsPage *page) {
|
|
using namespace UI;
|
|
SettingsPageHost *host = page->Host();
|
|
|
|
page->Header("MIPSTracer");
|
|
|
|
page->Bool("MIPSTracer enabled", [] { return mipsTracer.tracing_enabled; }, &SetMIPSTracerEnabled).EnabledFunc([]() {
|
|
bool temp = g_Config.iCpuCore == static_cast<int>(CPUCore::IR_INTERPRETER) && PSP_IsInited();
|
|
return temp && Core_IsStepping() && coreState != CORE_POWERDOWN;
|
|
});
|
|
|
|
page->Choice("Select the file path for the trace").OnClick([host](UI::EventParams &e) {
|
|
OnMIPSTracerPathChanged(host);
|
|
}).EnabledFunc([]() {
|
|
if (!PSP_IsInited())
|
|
return false;
|
|
return true;
|
|
});
|
|
|
|
page->Info("Current log file", mipsTracer.get_logging_path());
|
|
|
|
page->IntSlider(&mipsTracer.in_storage_capacity, 0x4'0000, 0x40'0000, "Storage capacity").Default(0x10'0000).Step(0x10000).Format("0x%x asm opcodes").OnChange([](UI::EventParams &) {
|
|
INFO_LOG(Log::JIT, "User changed the tracer's storage capacity to 0x%x", mipsTracer.in_storage_capacity);
|
|
});
|
|
page->IntSlider(&mipsTracer.in_max_trace_size, 0x1'0000, 0x40'0000, "Max allowed trace size").Default(0x10'0000).Step(0x10000).Format("%d basic blocks").OnChange([](UI::EventParams &) {
|
|
INFO_LOG(Log::JIT, "User changed the tracer's max trace size to %d", mipsTracer.in_max_trace_size);
|
|
});
|
|
|
|
page->Header("MIPSTracer actions");
|
|
page->Choice("Flush the trace").OnClick(&OnMIPSTracerFlushTrace);
|
|
page->Choice("Clear the JIT cache").OnClick(&OnMIPSTracerClearJitCache);
|
|
page->Choice("Clear the MIPSTracer").OnClick(&OnMIPSTracerClearTracer);
|
|
}
|
|
#endif
|
|
|
|
static void BuildAudioPage(SettingsPage *page) {
|
|
page->Bool(&g_Config.bForceFfmpegForAudioDec, "Use FFMPEG for all compressed audio");
|
|
}
|
|
|
|
static void BuildUIPage(SettingsPage *page) {
|
|
using namespace UI;
|
|
SettingsPageHost *host = page->Host();
|
|
|
|
UIContext *uiContext = host->PageScreenManager()->getUIContext();
|
|
page->Choice("Reload UI atlas").OnClick([uiContext](UI::EventParams &) {
|
|
uiContext->InvalidateAtlas();
|
|
});
|
|
|
|
// TODO: Move most of these strings out of the SysInfo category.
|
|
const I18NCat si = I18NCat::SYSINFO;
|
|
|
|
page->Header({ si, "Icon cache" });
|
|
IconCacheStats iconStats = g_iconCache.GetStats();
|
|
page->Info({ si, "Image data count" }, StringFromFormat("%d", iconStats.cachedCount));
|
|
page->Info({ si, "Texture count" }, StringFromFormat("%d", iconStats.textureCount));
|
|
page->Info({ si, "Data size" }, NiceSizeFormat(iconStats.dataSize));
|
|
page->Choice({ I18NCat::DIALOG, "Clear" }).OnClick([host](UI::EventParams &) {
|
|
g_iconCache.ClearData();
|
|
host->RecreatePageViews();
|
|
});
|
|
|
|
page->Header({ si, "Font cache" });
|
|
const TextDrawer *text = host->PageScreenManager()->getUIContext()->Text();
|
|
if (text) {
|
|
page->Info({ si, "Texture count" }, StringFromFormat("%d", text->GetStringCacheSize()));
|
|
page->Info({ si, "Data size" }, NiceSizeFormat(text->GetCacheDataSize()));
|
|
}
|
|
|
|
page->Header({ si, "Slider test" });
|
|
page->Custom({ si, "Slider test" }, [](UI::ViewGroup *parent) -> UI::View * {
|
|
static int testSliderValue = 0;
|
|
return parent->Add(new Slider(&testSliderValue, 0, 100, 1, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
|
|
});
|
|
|
|
page->Header({ si, "Notification tests" });
|
|
page->Choice({ si, "Error" }).OnClick([](UI::EventParams &) {
|
|
std::string str = "Error " + CodepointToUTF8(0x1F41B) + CodepointToUTF8(0x1F41C) + CodepointToUTF8(0x1F914);
|
|
g_OSD.Show(OSDType::MESSAGE_ERROR, str);
|
|
});
|
|
page->Choice({ si, "Warning" }).OnClick([](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.");
|
|
});
|
|
page->Choice({ si, "Info" }).OnClick([](UI::EventParams &) {
|
|
g_OSD.Show(OSDType::MESSAGE_INFO, "Info, info info info info info info info info info info");
|
|
});
|
|
// This one is clickable
|
|
page->Choice({ si, "Success" }).OnClick([](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/");
|
|
});
|
|
});
|
|
page->Choice({ I18NCat::SYSTEM, "RetroAchievements" }).OnClick([](UI::EventParams &) {
|
|
g_OSD.Show(OSDType::MESSAGE_WARNING, "RetroAchievements warning", "", "I_RETROACHIEVEMENTS_LOGO");
|
|
});
|
|
page->Header({ si, "Progress tests" });
|
|
page->Choice({ si, "30%" }).OnClick([](UI::EventParams &) {
|
|
g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 30, 0.0f);
|
|
});
|
|
page->Choice({ si, "100%" }).OnClick([](UI::EventParams &) {
|
|
g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 100, 1.0f);
|
|
});
|
|
page->Choice({ si, "N/A%" }).OnClick([](UI::EventParams &) {
|
|
g_OSD.SetProgressBar("testprogress", "Test Progress", 0, 0, 0, 0.0f);
|
|
});
|
|
page->Choice({ si, "Success" }).OnClick([](UI::EventParams &) {
|
|
g_OSD.RemoveProgressBar("testprogress", true, 0.5f);
|
|
});
|
|
page->Choice({ si, "Failure" }).OnClick([](UI::EventParams &) {
|
|
g_OSD.RemoveProgressBar("testprogress", false, 0.5f);
|
|
});
|
|
page->Header({ si, "Achievement tests" });
|
|
page->Choice({ si, "Leaderboard tracker: Show" }).OnClick([](UI::EventParams &) {
|
|
g_OSD.ShowLeaderboardTracker(1, "My leaderboard tracker", true);
|
|
});
|
|
page->Choice({ si, "Leaderboard tracker: Update" }).OnClick([](UI::EventParams &) {
|
|
g_OSD.ShowLeaderboardTracker(1, "Updated tracker", true);
|
|
});
|
|
page->Choice({ si, "Leaderboard tracker: Hide" }).OnClick([](UI::EventParams &) {
|
|
g_OSD.ShowLeaderboardTracker(1, "", false);
|
|
});
|
|
|
|
page->Header({ I18NCat::ACHIEVEMENTS, "Notifications" });
|
|
page->Setting(&g_Config.iNotificationPos);
|
|
page->Setting(&g_Config.iAchievementsLeaderboardTrackerPos);
|
|
|
|
#ifdef _DEBUG
|
|
page->Bool({ I18NCat::ACHIEVEMENTS, "Pretend to be in-game (for testing)" }, [] {
|
|
return g_OSD.PretendIngame();
|
|
}, [](bool pretend) {
|
|
g_OSD.SetPretendIngame(pretend);
|
|
});
|
|
// Untranslated string because this is debug mode only, only for PPSSPP developers.
|
|
page->Header({ I18NCat::ACHIEVEMENTS, "Assert" });
|
|
page->Choice(SettingText::Raw("Assert")).OnClick([](UI::EventParams &) {
|
|
_dbg_assert_msg_(false, "Test assert message");
|
|
});
|
|
#endif
|
|
#if PPSSPP_PLATFORM(ANDROID)
|
|
page->Choice({ si, "Exception" }).OnClick([](UI::EventParams &) {
|
|
System_Notify(SystemNotification::TEST_JAVA_EXCEPTION);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
static void BuildNetworkPage(SettingsPage *page) {
|
|
page->Header({ I18NCat::MAINSETTINGS, "Networking" });
|
|
page->Setting(&g_Config.bDontDownloadInfraJson);
|
|
// This is shared between RemoteISO and the remote debugger.
|
|
page->Setting(&g_Config.iRemoteISOPort);
|
|
page->Header({ I18NCat::NETWORKING, "AdHoc server" });
|
|
page->Setting(&g_Config.bAdhocServerShowPlayerPorts);
|
|
}
|
|
|
|
// TODO: Make this generic
|
|
extern int DefaultDepthRaster();
|
|
|
|
static void BuildGraphicsPage(SettingsPage *page) {
|
|
using namespace UI;
|
|
SettingsPageHost *host = page->Host();
|
|
|
|
Draw::DrawContext *draw = host->PageScreenManager()->getDrawContext();
|
|
|
|
page->Header({ I18NCat::SYSTEM, "General" });
|
|
page->Setting(&g_Config.bVendorBugChecksEnabled);
|
|
page->Setting(&g_Config.bShaderCache);
|
|
page->Setting(&g_Config.bHardwareTransform);
|
|
|
|
page->Setting(&g_Config.iDisplayRefreshRate);
|
|
|
|
page->Header("Vulkan");
|
|
page->Setting(&g_Config.bVulkanDisableImplicitLayers);
|
|
|
|
if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
|
|
page->Setting(&g_Config.bRenderMultiThreading).OnChange([](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()) {
|
|
page->Choice({ I18NCat::GRAPHICS, "AdrenoTools driver manager" }).OnClick([host](UI::EventParams &e) {
|
|
host->PageScreenManager()->push(new DriverManagerScreen(host->PageGamePath()));
|
|
});
|
|
}
|
|
|
|
page->Setting(&g_Config.iDepthRasterMode).Disabled(&g_Config.bSoftwareRendering).Setup([](View *view) {
|
|
PopupMultiChoice *depthRasterMode = static_cast<PopupMultiChoice *>(view);
|
|
depthRasterMode->SetDefault(DefaultDepthRaster());
|
|
depthRasterMode->SetChoiceIcon(3, ImageID("I_WARNING")); // It's a performance trap.
|
|
});
|
|
|
|
page->Header("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.
|
|
page->Setting(&g_Config.bUberShaderVertex);
|
|
}
|
|
|
|
// Experimental, allow some VR features without OpenXR
|
|
if (GetGPUBackend() == GPUBackend::OPENGL) {
|
|
page->Header({ I18NCat::VR, "Virtual reality" });
|
|
page->Setting(&g_Config.bForceVR);
|
|
}
|
|
|
|
// Experimental, will move to main graphics settings later.
|
|
bool multiViewSupported = draw->GetDeviceCaps().multiViewSupported;
|
|
|
|
auto enableStereo = [=]() -> bool {
|
|
return g_Config.bStereoRendering && multiViewSupported;
|
|
};
|
|
|
|
if (multiViewSupported) {
|
|
page->Header({ I18NCat::GRAPHICS, "Stereo rendering" });
|
|
page->Setting(&g_Config.bStereoRendering);
|
|
|
|
page->Custom({ I18NCat::GRAPHICS, "Stereo display shader" }, [host](UI::ViewGroup *parent) -> UI::View * {
|
|
ChoiceWithValueDisplay *stereoShaderChoice = parent->Add(new ChoiceWithValueDisplay(&g_Config.sStereoToMonoShader, T(I18NCat::GRAPHICS, "Stereo display shader"), &PostShaderTranslateName));
|
|
stereoShaderChoice->OnClick.Add([host](EventParams &e) {
|
|
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
|
|
auto procScreen = new PostProcScreen(gr->T("Stereo display shader"), 0, true);
|
|
procScreen->OnChoice.Add([host](EventParams &) {
|
|
host->RecreatePageViews();
|
|
});
|
|
if (e.v)
|
|
procScreen->SetPopupOrigin(e.v);
|
|
host->PageScreenManager()->push(procScreen);
|
|
});
|
|
return stereoShaderChoice;
|
|
}).EnabledFunc(enableStereo);
|
|
|
|
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()) {
|
|
continue;
|
|
}
|
|
// The value is looked up (and added if missing) when the slider is created, not when the page is built.
|
|
page->Custom({ I18NCat::POSTSHADERS, setting.name.c_str() }, [host, shaderInfo, i](UI::ViewGroup *parent) -> UI::View * {
|
|
auto &setting = shaderInfo->settings[i];
|
|
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;
|
|
return parent->Add(new PopupSliderChoiceFloat(&value, setting.minValue, setting.maxValue, setting.value, T(I18NCat::POSTSHADERS, setting.name), setting.step, host->PageScreenManager()));
|
|
}).EnabledFunc([=] {
|
|
return !g_Config.bSkipBufferEffects && enableStereo();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void CreateCrashHistoryList(UI::ViewGroup *crashHistory) {
|
|
using namespace UI;
|
|
auto di = GetI18NCategory(I18NCat::DIALOG);
|
|
std::vector<std::string> reports = Android_GetNativeCrashHistory(20);
|
|
if (reports.empty()) {
|
|
crashHistory->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 = crashHistory->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));
|
|
}
|
|
}
|
|
|
|
static void BuildCrashHistoryPage(SettingsPage *page) {
|
|
// A list of reports rather than settings.
|
|
page->Custom({ I18NCat::DEVELOPER, "Crash history" }, [](UI::ViewGroup *parent) -> UI::View * {
|
|
CreateCrashHistoryList(parent);
|
|
return parent->GetViewByIndex(parent->GetNumSubviews() - 1);
|
|
});
|
|
}
|
|
|
|
void BuildDeveloperToolsPage(SettingsPage *page) {
|
|
auto link = [page](SettingText title, void (*build)(SettingsPage *)) {
|
|
page->Link(title, I18NCat::DEVELOPER, build);
|
|
};
|
|
|
|
link({ I18NCat::SYSTEM, "General" }, &BuildGeneralPage);
|
|
link({ I18NCat::DEVELOPER, "Texture Replacement" }, &BuildTextureReplacementPage);
|
|
link({ I18NCat::MAINSETTINGS, "Graphics" }, &BuildGraphicsPage);
|
|
link({ I18NCat::MAINSETTINGS, "Networking" }, &BuildNetworkPage);
|
|
link({ I18NCat::MAINSETTINGS, "Audio" }, &BuildAudioPage);
|
|
link({ I18NCat::DEVELOPER, "Tests" }, &BuildTestsPage);
|
|
link({ I18NCat::DEVELOPER, "UI" }, &BuildUIPage);
|
|
link({ I18NCat::DEVELOPER, "Dump files" }, &BuildDumpFilePage);
|
|
link({ I18NCat::DEVELOPER, "HLE" }, &BuildHLEPage);
|
|
#if !PPSSPP_PLATFORM(ANDROID) && !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(SWITCH)
|
|
link({ I18NCat::DEVELOPER, "MIPSTracer" }, &BuildMIPSTracerPage);
|
|
#endif
|
|
if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 30) {
|
|
link({ I18NCat::DEVELOPER, "Crash history" }, &BuildCrashHistoryPage);
|
|
}
|
|
}
|
|
|
|
static void OnLoggingChanged(UI::EventParams &e) {
|
|
System_Notify(SystemNotification::TOGGLE_DEBUG_CONSOLE);
|
|
}
|
|
|
|
static void OpenTexturesIniFile(HasIni *hasIni) {
|
|
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());
|
|
}
|
|
|
|
*hasIni = HasIni::YES;
|
|
}
|
|
}
|
|
|
|
static void OnJitDebugTools(SettingsPageHost *host) {
|
|
host->PageScreenManager()->push(new JitDebugScreen());
|
|
}
|
|
|
|
static void OnGPUDriverTest(SettingsPageHost *host) {
|
|
host->PageScreenManager()->push(new GPUDriverTestScreen());
|
|
}
|
|
|
|
static void OnJitAffectingSetting(UI::EventParams &e) {
|
|
System_PostUIMessage(UIMessage::REQUEST_CLEAR_JIT);
|
|
}
|
|
|
|
#if PPSSPP_PLATFORM(IOS)
|
|
static void OnCopyStatesToRoot(UI::EventParams &e) {
|
|
Path savestate_dir = GetSysDirectory(DIRECTORY_SAVESTATE);
|
|
Path root_dir = GetSysDirectory(DIRECTORY_MEMSTICK_ROOT);
|
|
|
|
std::vector<File::FileInfo> 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);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void SetRemoteDebuggerAllowed(bool allow) {
|
|
if (allow) {
|
|
// Only needed for connecting from another device, but that's the usual case.
|
|
System_RequestLocalNetworkPermission();
|
|
StartWebServer(WebServerFlags::DEBUGGER);
|
|
} else {
|
|
StopWebServer(WebServerFlags::DEBUGGER);
|
|
}
|
|
// Persist the setting. Maybe should separate?
|
|
g_Config.bRemoteDebuggerOnStartup = allow;
|
|
}
|
|
|
|
#if !PPSSPP_PLATFORM(ANDROID) && !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(SWITCH)
|
|
static void SetMIPSTracerEnabled(bool enabled) {
|
|
if (enabled) {
|
|
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();
|
|
}
|
|
}
|
|
|
|
static void OnMIPSTracerPathChanged(SettingsPageHost *host) {
|
|
auto dev = GetI18NCategory(I18NCat::DEVELOPER);
|
|
System_BrowseForFileSave(
|
|
host->PageRequesterToken(),
|
|
dev->T("Select the log file"),
|
|
"trace.txt",
|
|
BrowseFileType::ANY,
|
|
[host](std::string_view value, int) {
|
|
mipsTracer.set_logging_path(std::string(value));
|
|
// The views may have been recreated while the dialog was open, so don't keep a pointer to the path view.
|
|
host->RecreatePageViews();
|
|
}
|
|
);
|
|
}
|
|
|
|
static void OnMIPSTracerFlushTrace(UI::EventParams &e) {
|
|
mipsTracer.flush_to_file();
|
|
// The error logs are emitted inside the tracer
|
|
}
|
|
|
|
static void OnMIPSTracerClearJitCache(UI::EventParams &e) {
|
|
INFO_LOG(Log::JIT, "Clearing the jit cache...");
|
|
System_PostUIMessage(UIMessage::REQUEST_CLEAR_JIT);
|
|
}
|
|
|
|
static void OnMIPSTracerClearTracer(UI::EventParams &e) {
|
|
INFO_LOG(Log::JIT, "Clearing the MIPSTracer...");
|
|
mipsTracer.clear();
|
|
}
|
|
#endif
|