From 5ef736f6a77f1f6c80a267ff37f7b4ec230bed1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 27 Jul 2026 17:32:15 +0200 Subject: [PATCH] More command line improvements --- Common/TimeUtil.cpp | 16 +++++++++++++ Common/TimeUtil.h | 1 + Core/CmdLine.cpp | 18 +++++++++++---- Core/CmdLine.h | 2 +- Core/Config.cpp | 2 +- Windows/main.cpp | 3 ++- headless/Headless.cpp | 54 ++++++++++++++----------------------------- pspautotests | 2 +- 8 files changed, 52 insertions(+), 46 deletions(-) diff --git a/Common/TimeUtil.cpp b/Common/TimeUtil.cpp index de6979d9d1..789abbc5d3 100644 --- a/Common/TimeUtil.cpp +++ b/Common/TimeUtil.cpp @@ -65,6 +65,8 @@ void TimeInit() { // The timer will be automatically deleted on process destruction. Don't need to CloseHandle. Timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); + + // TODO: We probably don't need this anymore if we are using the high res waitable timers? #if !PPSSPP_PLATFORM(UWP) TIMECAPS caps; timeGetDevCaps(&caps, sizeof caps); @@ -73,6 +75,12 @@ void TimeInit() { #endif } +void TimeShutdown() { +#if PPSSPP_PLATFORM(WINDOWS) + timeEndPeriod(1); +#endif +} + double time_now_d() { LARGE_INTEGER time; QueryPerformanceCounter(&time); @@ -141,6 +149,10 @@ void TimeInit() { // Nothing to do. } +void TimeShutdown() { + // Nothing to do. +} + // The only intended use is to match the timings in VK_GOOGLE_display_timing uint64_t time_now_raw() { struct timespec tp; @@ -210,6 +222,10 @@ void TimeInit() { // Nothing to do. } +void TimeShutdown() { + // Nothing to do. +} + static time_t start; double time_now_d() { diff --git a/Common/TimeUtil.h b/Common/TimeUtil.h index 6f1a30a14c..95420e6e7f 100644 --- a/Common/TimeUtil.h +++ b/Common/TimeUtil.h @@ -2,6 +2,7 @@ #include void TimeInit(); +void TimeShutdown(); // Not really necessary to call, just for completeness. // Seconds. double time_now_d(); diff --git a/Core/CmdLine.cpp b/Core/CmdLine.cpp index e722bba378..225d60dc7d 100644 --- a/Core/CmdLine.cpp +++ b/Core/CmdLine.cpp @@ -186,6 +186,7 @@ static const CommandLineParam g_autoParams[] = { {POFF(memReadAction), CmdParamType::Enum, "memread", '\0', "Set the action for memory read exceptions", CmdLineMode::Both, g_ExceptionActionValues, ARRAY_SIZE(g_ExceptionActionValues)}, {POFF(memWriteAction), CmdParamType::Enum, "memwrite", '\0', "Set the action for memory write exceptions", CmdLineMode::Both, g_ExceptionActionValues, ARRAY_SIZE(g_ExceptionActionValues)}, {POFF(breakAction), CmdParamType::Enum, "break", '\0', "Set the action for break exceptions", CmdLineMode::Both, g_ExceptionActionValues, ARRAY_SIZE(g_ExceptionActionValues)}, + {POFF(verbose), CmdParamType::Bool, "verbose", '\0', "Enable verbose output", CmdLineMode::Both}, // TODO: At some point we should maybe simply expose all config settings to be set directly from the command line automatically? }; @@ -214,7 +215,7 @@ int CommandLineOptions::PrintUsage(const char *progname, const char *situationTe PRINT_STDOUT("Usage: %s [options] [FILE]\n\n", progname); PRINT_STDOUT("Launches FILE (e.g. ISO image) if present.\n"); - PRINT_STDOUT("Options (some of these are specific to SDL backend):\n"); + PRINT_STDOUT("Options:\n"); PRINT_STDOUT(" -h, --help show this message and exit\n"); PRINT_STDOUT(" --version show version information and exit\n"); @@ -234,17 +235,24 @@ int CommandLineOptions::PrintUsage(const char *progname, const char *situationTe // Skip mode-irrelevant parameters in help. continue; } - PRINT_STDOUT(" --%s%s%s\n", param.longName, + char key[25]{}; + snprintf(key, ARRAY_SIZE(key), " --%s%s%s", param.longName, param.shortName ? ", -" : "", param.shortName ? std::string(1, param.shortName).c_str() : ""); + // Fill key with spacing. + for (size_t j = strlen(key); j < ARRAY_SIZE(key) - 1; ++j) { + key[j] = ' '; + } if (param.type == CmdParamType::Enum) { + PRINT_STDOUT("%s%s\n", key, param.docString); PRINT_STDOUT(" options: "); for (int i = 0; i < param.enumCount; ++i) { PRINT_STDOUT("%s%s", param.enumValues[i], i + 1 < param.enumCount ? ", " : ""); } PRINT_STDOUT("\n"); + } else { + PRINT_STDOUT("%s%s\n", key, param.docString); } - PRINT_STDOUT(" %s\n", param.docString); } // These are only available in SDL. @@ -306,7 +314,7 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], C logLevel = LogLevel::LVERBOSE; break; case 'h': - PrintUsage(argv[0], mode, nullptr); + PrintUsage(argv[0], nullptr); return CommandLineParseResult::Exit; // Legacy cpucore options. case 'j': @@ -352,7 +360,7 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], C // We already incremented i. continue; } else if (equals(argv[i], "--help")) { - PrintUsage(argv[0], mode, nullptr); + PrintUsage(argv[0], nullptr); return CommandLineParseResult::Exit; } else if (equals(argv[i], "--version")) { printf("%s\n", PPSSPP_GIT_VERSION); diff --git a/Core/CmdLine.h b/Core/CmdLine.h index b2f1b34a53..9387b38632 100644 --- a/Core/CmdLine.h +++ b/Core/CmdLine.h @@ -87,8 +87,8 @@ struct CommandLineOptions { std::optional compare; std::optional bench; std::optional verbose; + std::optional timeout; std::optional screenshotFilename; std::optional screenshotFilenameSave; - std::optional timeout; }; diff --git a/Core/Config.cpp b/Core/Config.cpp index 5e0c52fd71..9d99b0d916 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -99,7 +99,7 @@ std::string DefaultLangRegion() { // Unfortunate default. There's no need to use bFirstRun, since this is only a default. static std::string defaultLangRegion = "en_US"; std::string langRegion = System_GetProperty(SYSPROP_LANGREGION); - if (g_i18nrepo.IniExists(langRegion)) { + if (!langRegion.empty() && g_i18nrepo.IniExists(langRegion)) { defaultLangRegion = langRegion; } else if (langRegion.length() >= 3) { // Don't give up. Let's try a fuzzy match - so nl_BE can match nl_NL. diff --git a/Windows/main.cpp b/Windows/main.cpp index 813c07ff33..a2940f7973 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -1310,7 +1310,8 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin MainWindow::DestroyDebugWindows(); DialogManager::DestroyAll(); - timeEndPeriod(1); + + TimeShutdown(); UnRegisterCMPTMFApis(); diff --git a/headless/Headless.cpp b/headless/Headless.cpp index ab29ab0c3d..dd7d75cc54 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -2,8 +2,12 @@ // Headless version of PPSSPP, for testing using http://code.google.com/p/pspautotests/ . // See headless.txt. // To build on non-windows systems, just run CMake in the SDL directory, it will build both a normal ppsspp and the headless version. +// // Example command line to run a test in the VS debugger (useful to debug failures): // > --root pspautotests/tests/../ --compare --timeout=5 --graphics=software pspautotests/tests/cpu/cpu_alu/cpu_alu.prx +// Example command line for taking screenshots from a frame dump: +// > -l --graphics=vulkan --screenshot-save=vt_ref.bmp "D:\PSP ISO\dump\Depth\11578 Virtua Tennis pause menu ULES00126_0002.zip" --resolution-scale=2 +// // NOTE: In MSVC, don't forget to set the working directory to $ProjectDir\.. in debug settings. #include "ppsspp_config.h" @@ -17,13 +21,11 @@ #include #include "Common/Profiler/Profiler.h" -#include "Common/System/NativeApp.h" #include "Common/System/Request.h" #include "Common/System/System.h" #include "Common/CommonWindows.h" #if PPSSPP_PLATFORM(WINDOWS) -#include #else #include #endif @@ -121,33 +123,18 @@ std::string NativeLoadSecret(std::string_view nameOfSecret) { return ""; } -int printUsage(const char *progname, const char *reason) { - if (reason != NULL) - fprintf(stderr, "Error: %s\n\n", reason); - fprintf(stderr, "PPSSPP Headless\n"); - fprintf(stderr, "This is primarily meant as a non-interactive test tool.\n\n"); - fprintf(stderr, "Usage: %s file.elf... [options]\n\n", progname); - fprintf(stderr, "Options:\n"); +int printUsage(const CommandLineOptions &options, const char *progname, const char *reason) { + options.PrintUsage(progname, reason); fprintf(stderr, " -m, --mount umd.cso mount iso on umd1:\n"); fprintf(stderr, " -r, --root some/path mount path on host0: (elfs must be in here)\n"); fprintf(stderr, " -l, --log full log output, not just emulated printfs\n"); - fprintf(stderr, " --debugger=PORT enable websocket debugger and break at start\n"); - - fprintf(stderr, " --graphics=BACKEND use a different gpu backend\n"); fprintf(stderr, " options: gles, software, directx9, etc.\n"); fprintf(stderr, " --screenshot=FILE compare against a screenshot\n"); fprintf(stderr, " --screenshot-save=FILE save rendered screenshot to a BMP file\n"); fprintf(stderr, " --max-mse=NUMBER maximum allowed MSE error for screenshot\n"); fprintf(stderr, " --timeout=SECONDS abort test it if takes longer than SECONDS\n"); - - fprintf(stderr, " -v, --verbose show the full passed/failed result\n"); - fprintf(stderr, " -i use the interpreter\n"); fprintf(stderr, " --ir use ir interpreter\n"); - fprintf(stderr, " -j use jit (default)\n"); - fprintf(stderr, " -c, --compare compare with output in file.expected\n"); - fprintf(stderr, " --bench run multiple times and output speed\n"); fprintf(stderr, "\nSee headless.txt for details.\n"); - return 1; } @@ -260,9 +247,9 @@ void System_SendDebugScreenshot(const uint8_t *data, int width, int height) { struct AutoTestOptions { double timeout; double maxScreenshotError; - bool compare : 1; - bool verbose : 1; - bool bench : 1; + bool compare; + bool verbose; + bool bench; }; bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const AutoTestOptions &opt) { @@ -464,6 +451,7 @@ int main(int argc, const char* argv[]) { testOptions.compare = cmdLineOptions.compare.value_or(false); testOptions.bench = cmdLineOptions.bench.value_or(false); testOptions.timeout = cmdLineOptions.timeout.value_or(std::numeric_limits::infinity()); + testOptions.verbose = cmdLineOptions.verbose.value_or(false); bool fullLog = false; const char *stateToLoad = 0; @@ -488,32 +476,26 @@ int main(int argc, const char* argv[]) { for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "-m") || !strcmp(argv[i], "--mount")) { if (++i >= argc) - return printUsage(argv[0], "Missing argument after -m"); + return printUsage(cmdLineOptions, argv[0], "Missing argument after -m"); mountIso = argv[i]; } else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--log")) fullLog = true; else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--odslog")) outputDebugStringLog = true; - else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) - testOptions.verbose = true; else if (!strncmp(argv[i], "--max-mse=", strlen("--max-mse=")) && strlen(argv[i]) > strlen("--max-mse=")) testOptions.maxScreenshotError = strtod(argv[i] + strlen("--max-mse="), nullptr); else if (!strncmp(argv[i], "--state=", strlen("--state=")) && strlen(argv[i]) > strlen("--state=")) stateToLoad = argv[i] + strlen("--state="); else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) - return printUsage(argv[0], NULL); + return printUsage(cmdLineOptions, argv[0], NULL); else if (!strcmp(argv[i], "--ignore")) { if (++i >= argc) - return printUsage(argv[0], "Missing argument after --ignore"); + return printUsage(cmdLineOptions, argv[0], "Missing argument after --ignore"); ignoredTests.push_back(argv[i]); } } - if (!fullLog) { - printf("Pass the -l flag to see full log output.\n"); - } - for (const std::string &filename : cmdLineOptions.bootFilenames) { AddToTestsByPath(&testFilenames, filename); } @@ -683,7 +665,7 @@ int main(int argc, const char* argv[]) { AddToTestsByPath(&testFilenames, (g_Config.flash0Directory / "vsh/module/vshmain.prx").ToString()); } if (testFilenames.empty()) { - return printUsage(argv[0], argc <= 1 ? NULL : "No executables specified"); + return printUsage(cmdLineOptions, argv[0], argc <= 1 ? NULL : "No executables specified"); } if (cmdLineOptions.debuggerPort.has_value()) { @@ -754,12 +736,10 @@ int main(int argc, const char* argv[]) { g_VFS.Clear(); g_logManager.Shutdown(); - if (cmdLineOptions.debuggerPort.has_value()) + if (cmdLineOptions.debuggerPort.has_value()) { net::Shutdown(); - -#if PPSSPP_PLATFORM(WINDOWS) - timeEndPeriod(1); -#endif + } + TimeShutdown(); g_threadManager.Teardown(); diff --git a/pspautotests b/pspautotests index 23ae650d05..3b281cf05e 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 23ae650d0537890247f49adb117618d715b75311 +Subproject commit 3b281cf05ec1ef5f604ba4f3a9ae0f773e73fee1