Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/headless/Headless.cpp
5659 views
1
2
// Headless version of PPSSPP, for testing using http://code.google.com/p/pspautotests/ .
3
// See headless.txt.
4
// To build on non-windows systems, just run CMake in the SDL directory, it will build both a normal ppsspp and the headless version.
5
// Example command line to run a test in the VS debugger (useful to debug failures):
6
// > --root pspautotests/tests/../ --compare --timeout=5 --graphics=software pspautotests/tests/cpu/cpu_alu/cpu_alu.prx
7
// NOTE: In MSVC, don't forget to set the working directory to $ProjectDir\.. in debug settings.
8
9
#include "ppsspp_config.h"
10
#include <cstdio>
11
#include <cstdlib>
12
#include <limits>
13
#if PPSSPP_PLATFORM(ANDROID)
14
#include <jni.h>
15
#endif
16
17
#include <algorithm>
18
19
#include "Common/Profiler/Profiler.h"
20
#include "Common/System/NativeApp.h"
21
#include "Common/System/Request.h"
22
#include "Common/System/System.h"
23
24
#include "Common/CommonWindows.h"
25
#if PPSSPP_PLATFORM(WINDOWS)
26
#include <timeapi.h>
27
#else
28
#include <csignal>
29
#endif
30
#include "Common/CPUDetect.h"
31
#include "Common/File/VFS/VFS.h"
32
#include "Common/File/VFS/ZipFileReader.h"
33
#include "Common/File/VFS/DirectoryReader.h"
34
#include "Common/File/FileUtil.h"
35
#include "Common/GraphicsContext.h"
36
#include "Common/TimeUtil.h"
37
#include "Common/StringUtils.h"
38
#include "Common/Thread/ThreadManager.h"
39
#include "Core/Config.h"
40
#include "Core/ConfigValues.h"
41
#include "Core/Core.h"
42
#include "Core/CoreTiming.h"
43
#include "Core/System.h"
44
#include "Core/WebServer.h"
45
#include "Core/HLE/sceUtility.h"
46
#include "Core/SaveState.h"
47
#include "GPU/GPUCommon.h"
48
#include "GPU/Common/FramebufferManagerCommon.h"
49
#include "Common/Log.h"
50
#include "Common/Log/LogManager.h"
51
52
#include "Compare.h"
53
#include "HeadlessHost.h"
54
#if defined(_WIN32)
55
#include "WindowsHeadlessHost.h"
56
#elif defined(SDL)
57
#include "SDLHeadlessHost.h"
58
#endif
59
60
static HeadlessHost *g_headlessHost;
61
62
#if PPSSPP_PLATFORM(ANDROID)
63
JNIEnv *getEnv() {
64
return nullptr;
65
}
66
67
jclass findClass(const char *name) {
68
return nullptr;
69
}
70
71
bool System_AudioRecordingIsAvailable() { return false; }
72
bool System_AudioRecordingState() { return false; }
73
#endif
74
75
// Temporary hacks around annoying linking errors.
76
void NativeFrame(GraphicsContext *graphicsContext) { }
77
void NativeResized() { }
78
79
std::string System_GetProperty(SystemProperty prop) { return ""; }
80
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) { return std::vector<std::string>(); }
81
int64_t System_GetPropertyInt(SystemProperty prop) {
82
if (prop == SYSPROP_SYSTEMVERSION)
83
return 31;
84
return -1;
85
}
86
float System_GetPropertyFloat(SystemProperty prop) { return -1.0f; }
87
bool System_GetPropertyBool(SystemProperty prop) {
88
switch (prop) {
89
case SYSPROP_CAN_JIT:
90
return true;
91
case SYSPROP_SKIP_UI:
92
return true;
93
default:
94
return false;
95
}
96
}
97
void System_Notify(SystemNotification notification) {}
98
void System_PostUIMessage(UIMessage message, std::string_view param) {}
99
void System_RunOnMainThread(std::function<void()>) {}
100
bool System_MakeRequest(SystemRequestType type, int requestId, const std::string &param1, const std::string &param2, int64_t param3, int64_t param4) {
101
switch (type) {
102
case SystemRequestType::SEND_DEBUG_OUTPUT:
103
if (g_headlessHost) {
104
g_headlessHost->SendDebugOutput(param1);
105
return true;
106
}
107
return false;
108
case SystemRequestType::SEND_DEBUG_SCREENSHOT:
109
if (g_headlessHost) {
110
g_headlessHost->SendDebugScreenshot((const u8 *)param1.data(), (uint32_t)(param1.size() / param3), param3);
111
return true;
112
}
113
return false;
114
default:
115
return false;
116
}
117
}
118
void System_AskForPermission(SystemPermission permission) {}
119
PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; }
120
void System_AudioGetDebugStats(char *buf, size_t bufSize) { if (buf) buf[0] = '\0'; }
121
void System_AudioClear() {}
122
void System_AudioPushSamples(const s32 *audio, int numSamples, float volume) {}
123
124
// TODO: To avoid having to define these here, these should probably be turned into system "requests".
125
bool NativeSaveSecret(std::string_view nameOfSecret, std::string_view data) { return false; }
126
std::string NativeLoadSecret(std::string_view nameOfSecret) {
127
return "";
128
}
129
130
int printUsage(const char *progname, const char *reason)
131
{
132
if (reason != NULL)
133
fprintf(stderr, "Error: %s\n\n", reason);
134
fprintf(stderr, "PPSSPP Headless\n");
135
fprintf(stderr, "This is primarily meant as a non-interactive test tool.\n\n");
136
fprintf(stderr, "Usage: %s file.elf... [options]\n\n", progname);
137
fprintf(stderr, "Options:\n");
138
fprintf(stderr, " -m, --mount umd.cso mount iso on umd1:\n");
139
fprintf(stderr, " -r, --root some/path mount path on host0: (elfs must be in here)\n");
140
fprintf(stderr, " -l, --log full log output, not just emulated printfs\n");
141
fprintf(stderr, " --debugger=PORT enable websocket debugger and break at start\n");
142
143
fprintf(stderr, " --graphics=BACKEND use a different gpu backend\n");
144
fprintf(stderr, " options: gles, software, directx9, etc.\n");
145
fprintf(stderr, " --screenshot=FILE compare against a screenshot\n");
146
fprintf(stderr, " --max-mse=NUMBER maximum allowed MSE error for screenshot\n");
147
fprintf(stderr, " --timeout=SECONDS abort test it if takes longer than SECONDS\n");
148
149
fprintf(stderr, " -v, --verbose show the full passed/failed result\n");
150
fprintf(stderr, " -i use the interpreter\n");
151
fprintf(stderr, " --ir use ir interpreter\n");
152
fprintf(stderr, " -j use jit (default)\n");
153
fprintf(stderr, " -c, --compare compare with output in file.expected\n");
154
fprintf(stderr, " --bench run multiple times and output speed\n");
155
fprintf(stderr, "\nSee headless.txt for details.\n");
156
157
return 1;
158
}
159
160
static HeadlessHost *getHost(GPUCore gpuCore) {
161
switch (gpuCore) {
162
case GPUCORE_SOFTWARE:
163
return new HeadlessHost();
164
#ifdef HEADLESSHOST_CLASS
165
default:
166
return new HEADLESSHOST_CLASS();
167
#else
168
default:
169
return new HeadlessHost();
170
#endif
171
}
172
}
173
174
struct AutoTestOptions {
175
double timeout;
176
double maxScreenshotError;
177
bool compare : 1;
178
bool verbose : 1;
179
bool bench : 1;
180
};
181
182
bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const AutoTestOptions &opt) {
183
// Kinda ugly, trying to guesstimate the test name from filename...
184
currentTestName = GetTestName(coreParameter.fileToStart);
185
186
std::string output;
187
if (opt.compare || opt.bench)
188
coreParameter.collectDebugOutput = &output;
189
190
if (!PSP_InitStart(coreParameter)) {
191
// Shouldn't really happen anymore, the errors happen later in PSP_InitUpdate.
192
fprintf(stderr, "Failed to start '%s'.\n", coreParameter.fileToStart.c_str());
193
printf("TESTERROR\n");
194
TeamCityPrint("testIgnored name='%s' message='PRX/ELF missing'", currentTestName.c_str());
195
GitHubActionsPrint("error", "PRX/ELF missing for %s", currentTestName.c_str());
196
return false;
197
}
198
199
TeamCityPrint("testStarted name='%s' captureStandardOutput='true'", currentTestName.c_str());
200
201
if (opt.compare)
202
headlessHost->SetComparisonScreenshot(ExpectedScreenshotFromFilename(coreParameter.fileToStart), opt.maxScreenshotError);
203
204
std::string error_string;
205
while (PSP_InitUpdate(&error_string) == BootState::Booting) {
206
sleep_ms(1, "auto-test");
207
}
208
209
if (!PSP_IsInited()) {
210
TeamCityPrint("%s", error_string.c_str());
211
TeamCityPrint("testFailed name='%s' message='Startup failed'", currentTestName.c_str());
212
TeamCityPrint("testFinished name='%s'", currentTestName.c_str());
213
GitHubActionsPrint("error", "Test init failed for %s", currentTestName.c_str());
214
return false;
215
}
216
217
System_Notify(SystemNotification::BOOT_DONE);
218
219
PSP_UpdateDebugStats((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::DEBUG_STATS || g_Config.bLogFrameDrops);
220
221
if (gpu) {
222
gpu->BeginHostFrame(g_Config.GetDisplayLayoutConfig(DeviceOrientation::Landscape));
223
}
224
Draw::DrawContext *draw = coreParameter.graphicsContext ? coreParameter.graphicsContext->GetDrawContext() : nullptr;
225
if (draw) {
226
draw->BeginFrame(Draw::DebugFlags::NONE);
227
}
228
229
bool passed = true;
230
double deadline = time_now_d() + opt.timeout;
231
coreState = coreParameter.startBreak ? CORE_STEPPING_CPU : CORE_RUNNING_CPU;
232
while (coreState == CORE_RUNNING_CPU || coreState == CORE_STEPPING_CPU)
233
{
234
int blockTicks = (int)usToCycles(1000000 / 10);
235
PSP_RunLoopFor(blockTicks);
236
237
// If we were rendering, this might be a nice time to do something about it.
238
if (coreState == CORE_NEXTFRAME) {
239
coreState = CORE_RUNNING_CPU;
240
headlessHost->SwapBuffers();
241
}
242
if (coreState == CORE_STEPPING_CPU && !coreParameter.startBreak) {
243
break;
244
}
245
bool debugger = false;
246
#ifdef _WIN32
247
if (IsDebuggerPresent())
248
debugger = true;
249
#endif
250
if (time_now_d() > deadline && !debugger) {
251
// Don't compare, print the output at least up to this point, and bail.
252
if (!opt.bench) {
253
printf("%s", output.c_str());
254
255
System_SendDebugOutput("TIMEOUT\n");
256
TeamCityPrint("testFailed name='%s' message='Test timeout'", currentTestName.c_str());
257
GitHubActionsPrint("error", "Test timeout for %s", currentTestName.c_str());
258
}
259
260
passed = false;
261
Core_Stop();
262
}
263
}
264
if (gpu) {
265
gpu->EndHostFrame();
266
}
267
268
if (draw) {
269
draw->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Headless");
270
// Vulkan may get angry if we don't do a final present.
271
if (gpu)
272
gpu->CopyDisplayToOutput(g_Config.GetDisplayLayoutConfig(DeviceOrientation::Landscape), true);
273
274
draw->EndFrame();
275
}
276
277
PSP_Shutdown(true);
278
279
if (!opt.bench)
280
headlessHost->FlushDebugOutput();
281
282
if (opt.compare && passed)
283
passed = CompareOutput(coreParameter.fileToStart, output, opt.verbose);
284
285
TeamCityPrint("testFinished name='%s'", currentTestName.c_str());
286
287
return passed;
288
}
289
290
std::vector<std::string> ReadFromListFile(const std::string &listFilename) {
291
std::vector<std::string> testFilenames;
292
char temp[2048]{};
293
294
if (listFilename == "-") {
295
// If you get stuck here in the debugger, you accidentally passed '@-' on the command line, meaning we expect
296
// a list of files on stdin.
297
while (scanf("%2047s", temp) == 1)
298
testFilenames.push_back(temp);
299
} else {
300
FILE *fp = File::OpenCFile(Path(listFilename), "rt");
301
if (!fp) {
302
fprintf(stderr, "Unable to open '%s' as a list file\n", listFilename.c_str());
303
return testFilenames;
304
}
305
306
while (fscanf(fp, "%2047s", temp) == 1)
307
testFilenames.push_back(temp);
308
fclose(fp);
309
}
310
311
return testFilenames;
312
}
313
314
static void AddRecursively(std::vector<std::string> *tests, Path actualPath) {
315
// TODO: Some file systems can optimize this.
316
std::vector<File::FileInfo> fileInfo;
317
if (!File::GetFilesInDir(actualPath, &fileInfo, "prx")) {
318
return;
319
}
320
for (const auto &file : fileInfo) {
321
if (file.isDirectory) {
322
AddRecursively(tests, actualPath / file.name);
323
} else if (file.name != "Makefile") { // hack around filter problem
324
tests->push_back((actualPath / file.name).ToString());
325
}
326
}
327
}
328
329
static void AddTestsByPath(std::vector<std::string> *tests, std::string_view path) {
330
if (endsWith(path, "/...")) {
331
path = path.substr(0, path.size() - 4);
332
// Recurse for tests
333
AddRecursively(tests, Path(path));
334
} /* else if (File::IsDirectory(Path(path))) {
335
// Alternate syntax - just specify the path.
336
AddRecursively(tests, Path(path));
337
} */ else {
338
tests->push_back(std::string(path));
339
}
340
}
341
342
int main(int argc, const char* argv[])
343
{
344
PROFILE_INIT();
345
TimeInit();
346
#if PPSSPP_PLATFORM(WINDOWS)
347
if (!IsDebuggerPresent()) {
348
SetCleanExitOnAssert();
349
}
350
#else
351
// Ignore sigpipe.
352
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
353
perror("Unable to ignore SIGPIPE");
354
}
355
#endif
356
357
#if defined(_DEBUG) && defined(_MSC_VER)
358
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
359
#endif
360
361
AutoTestOptions testOptions{};
362
testOptions.timeout = std::numeric_limits<double>::infinity();
363
bool fullLog = false;
364
const char *stateToLoad = 0;
365
GPUCore gpuCore = GPUCORE_SOFTWARE;
366
CPUCore cpuCore = CPUCore::JIT;
367
int debuggerPort = -1;
368
bool oldAtrac = false;
369
bool outputDebugStringLog = false;
370
371
std::vector<std::string> testFilenames;
372
std::vector<std::string> ignoredTests;
373
const char *mountIso = nullptr;
374
const char *mountRoot = nullptr;
375
const char *screenshotFilename = nullptr;
376
377
for (int i = 1; i < argc; i++)
378
{
379
if (!strcmp(argv[i], "-m") || !strcmp(argv[i], "--mount"))
380
{
381
if (++i >= argc)
382
return printUsage(argv[0], "Missing argument after -m");
383
mountIso = argv[i];
384
}
385
else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--root"))
386
{
387
if (++i >= argc)
388
return printUsage(argv[0], "Missing argument after -r");
389
mountRoot = argv[i];
390
}
391
else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--log"))
392
fullLog = true;
393
else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--odslog"))
394
outputDebugStringLog = true;
395
else if (!strcmp(argv[i], "-i"))
396
cpuCore = CPUCore::INTERPRETER;
397
else if (!strcmp(argv[i], "-j"))
398
cpuCore = CPUCore::JIT;
399
else if (!strcmp(argv[i], "--jit-ir"))
400
cpuCore = CPUCore::JIT_IR;
401
else if (!strcmp(argv[i], "--ir"))
402
cpuCore = CPUCore::IR_INTERPRETER;
403
else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--compare"))
404
testOptions.compare = true;
405
else if (!strcmp(argv[i], "--bench"))
406
testOptions.bench = true;
407
else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
408
testOptions.verbose = true;
409
else if (!strcmp(argv[i], "--old-atrac"))
410
oldAtrac = true;
411
else if (!strncmp(argv[i], "--graphics=", strlen("--graphics=")) && strlen(argv[i]) > strlen("--graphics="))
412
{
413
const char *gpuName = argv[i] + strlen("--graphics=");
414
if (!strcasecmp(gpuName, "gles"))
415
gpuCore = GPUCORE_GLES;
416
// There used to be a separate "null" rendering core - just use software.
417
else if (!strcasecmp(gpuName, "software") || !strcasecmp(gpuName, "null"))
418
gpuCore = GPUCORE_SOFTWARE;
419
else if (!strcasecmp(gpuName, "directx11"))
420
gpuCore = GPUCORE_DIRECTX11;
421
else if (!strcasecmp(gpuName, "vulkan"))
422
gpuCore = GPUCORE_VULKAN;
423
else
424
return printUsage(argv[0], "Unknown gpu backend specified after --graphics=. Allowed: software, directx9, directx11, vulkan, gles, null.");
425
}
426
// Default to GLES if no value selected.
427
else if (!strcmp(argv[i], "--graphics")) {
428
#if PPSSPP_API(ANY_GL)
429
gpuCore = GPUCORE_GLES;
430
#else
431
gpuCore = GPUCORE_DIRECTX11;
432
#endif
433
} else if (!strncmp(argv[i], "--screenshot=", strlen("--screenshot=")) && strlen(argv[i]) > strlen("--screenshot="))
434
screenshotFilename = argv[i] + strlen("--screenshot=");
435
else if (!strncmp(argv[i], "--timeout=", strlen("--timeout=")) && strlen(argv[i]) > strlen("--timeout="))
436
testOptions.timeout = strtod(argv[i] + strlen("--timeout="), nullptr);
437
else if (!strncmp(argv[i], "--max-mse=", strlen("--max-mse=")) && strlen(argv[i]) > strlen("--max-mse="))
438
testOptions.maxScreenshotError = strtod(argv[i] + strlen("--max-mse="), nullptr);
439
else if (!strncmp(argv[i], "--debugger=", strlen("--debugger=")) && strlen(argv[i]) > strlen("--debugger="))
440
debuggerPort = (int)strtoul(argv[i] + strlen("--debugger="), NULL, 10);
441
else if (!strcmp(argv[i], "--teamcity"))
442
teamCityMode = true;
443
else if (!strncmp(argv[i], "--state=", strlen("--state=")) && strlen(argv[i]) > strlen("--state="))
444
stateToLoad = argv[i] + strlen("--state=");
445
else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h"))
446
return printUsage(argv[0], NULL);
447
else if (!strcmp(argv[i], "--ignore")) {
448
if (++i >= argc)
449
return printUsage(argv[0], "Missing argument after --ignore");
450
ignoredTests.push_back(argv[i]);
451
} else {
452
AddTestsByPath(&testFilenames, argv[i]);
453
}
454
}
455
456
if (testFilenames.size() == 1 && testFilenames[0][0] == '@')
457
testFilenames = ReadFromListFile(testFilenames[0].substr(1));
458
459
// Remove any ignored tests.
460
testFilenames.erase(
461
std::remove_if(
462
testFilenames.begin(),
463
testFilenames.end(),
464
[&ignoredTests](const std::string& item) { return std::find(ignoredTests.begin(), ignoredTests.end(), item) != ignoredTests.end(); }
465
),
466
testFilenames.end()
467
);
468
469
if (testFilenames.empty())
470
return printUsage(argv[0], argc <= 1 ? NULL : "No executables specified");
471
472
g_Config.bEnableLogging = (fullLog || outputDebugStringLog);
473
g_logManager.Init(&g_Config.bEnableLogging, outputDebugStringLog);
474
475
for (int i = 0; i < (int)Log::NUMBER_OF_LOGS; i++) {
476
Log type = (Log)i;
477
g_logManager.SetEnabled(type, (fullLog || outputDebugStringLog));
478
g_logManager.SetLogLevel(type, LogLevel::LDEBUG);
479
}
480
if (fullLog) {
481
// Only with --log, add the printfLogger.
482
g_logManager.EnableOutput(LogOutput::Printf);
483
}
484
485
// Needs to be after log so we don't interfere with test output.
486
g_threadManager.Init(cpu_info.num_cores, cpu_info.logical_cpu_count);
487
488
HeadlessHost *headlessHost = getHost(gpuCore);
489
g_headlessHost = headlessHost;
490
491
std::string error_string;
492
GraphicsContext *graphicsContext = nullptr;
493
bool glWorking = headlessHost->InitGraphics(&error_string, &graphicsContext, gpuCore);
494
495
CoreParameter coreParameter;
496
coreParameter.cpuCore = cpuCore; // apprently this gets overwritten somehow by g_Config below.
497
coreParameter.gpuCore = glWorking ? gpuCore : GPUCORE_SOFTWARE;
498
coreParameter.graphicsContext = graphicsContext;
499
coreParameter.enableSound = false;
500
coreParameter.mountIso = mountIso ? Path(mountIso) : Path();
501
coreParameter.mountRoot = mountRoot ? Path(mountRoot) : Path();
502
coreParameter.startBreak = false;
503
coreParameter.headLess = true;
504
coreParameter.renderScaleFactor = 1;
505
coreParameter.renderWidth = 480;
506
coreParameter.renderHeight = 272;
507
coreParameter.pixelWidth = 480;
508
coreParameter.pixelHeight = 272;
509
coreParameter.fastForward = true;
510
511
g_Config.RestoreDefaults(RestoreSettingsBits::SETTINGS | RestoreSettingsBits::CONTROLS, true);
512
513
// Somehow this affects the test execution of pspautotests/tests/gpu/vertices/morph.prx, even though
514
// we actually set the cpu core in CoreParameter above. Probably because we end up using the JIT vs non-JIT
515
// vertex decoder.
516
g_Config.iCpuCore = 0;
517
518
// NOTE: In headless mode, we never save the config. This is just for this run.
519
g_Config.iDumpFileTypes = 0;
520
g_Config.bEnableSound = false;
521
g_Config.bFirstRun = false;
522
g_Config.bIgnoreBadMemAccess = true; // NOTE: A few tests rely on this, which is BAD: threads/mbx/refer/refer , threads/mbx/send/send, threads/vtimers/interrupt
523
// Never report from tests.
524
g_Config.sReportHost.clear();
525
g_Config.bAutoSaveSymbolMap = false;
526
g_Config.bSkipBufferEffects = false;
527
g_Config.iSkipGPUReadbackMode = (int)SkipGPUReadbackMode::NO_SKIP;
528
g_Config.bHardwareTransform = true;
529
g_Config.iAnisotropyLevel = 0; // When testing mipmapping we really don't want this.
530
g_Config.iMultiSampleLevel = 0;
531
g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH;
532
g_Config.iTimeFormat = PSP_SYSTEMPARAM_TIME_FORMAT_24HR;
533
g_Config.bEncryptSave = true;
534
g_Config.sNickName = "shadow";
535
g_Config.iTimeZone = 60;
536
g_Config.iDateFormat = PSP_SYSTEMPARAM_DATE_FORMAT_DDMMYYYY;
537
g_Config.iButtonPreference = PSP_SYSTEMPARAM_BUTTON_CROSS;
538
g_Config.iLockParentalLevel = 9;
539
g_Config.iInternalResolution = 1;
540
g_Config.bEnableLogging = (fullLog || outputDebugStringLog);
541
g_Config.bSoftwareSkinning = true;
542
g_Config.bVertexDecoderJit = true;
543
g_Config.bSoftwareRendering = coreParameter.gpuCore == GPUCORE_SOFTWARE;
544
g_Config.bSoftwareRenderingJit = true;
545
g_Config.iSplineBezierQuality = 2;
546
g_Config.bHighQualityDepth = true;
547
g_Config.bMemStickInserted = true;
548
g_Config.iMemStickSizeGB = 16;
549
g_Config.bEnableWlan = true;
550
g_Config.sMACAddress = "12:34:56:78:9A:BC";
551
g_Config.iFirmwareVersion = PSP_DEFAULT_FIRMWARE;
552
g_Config.iPSPModel = PSP_MODEL_SLIM;
553
g_Config.iGameVolume = VOLUMEHI_FULL;
554
g_Config.iReverbVolume = VOLUMEHI_FULL;
555
g_Config.internalDataDirectory.clear();
556
g_Config.bUseOldAtrac = oldAtrac;
557
g_Config.iForceEnableHLE = 0xFFFFFFFF; // Run all modules as HLE. We don't have anything to load in this context.
558
559
// g_Config.bUseOldAtrac = true;
560
561
Path exePath = File::GetExeDirectory();
562
g_Config.flash0Directory = exePath / "assets/flash0";
563
564
#if PPSSPP_PLATFORM(WINDOWS)
565
// Mount a filesystem
566
g_Config.memStickDirectory = exePath / "memstick";
567
File::CreateDir(g_Config.memStickDirectory);
568
CreateSysDirectories();
569
#elif !PPSSPP_PLATFORM(ANDROID)
570
g_Config.memStickDirectory = Path(std::string(getenv("HOME"))) / ".ppsspp";
571
#endif
572
573
// Try to find the flash0 directory. Often this is from a subdirectory.
574
Path nextPath = exePath;
575
for (int i = 0; i < 5; ++i) {
576
if (File::Exists(nextPath / "assets/flash0")) {
577
g_Config.flash0Directory = nextPath / "assets/flash0";
578
#if !PPSSPP_PLATFORM(ANDROID)
579
g_VFS.Register("", new DirectoryReader(nextPath / "assets"));
580
#endif
581
break;
582
}
583
584
if (!nextPath.CanNavigateUp())
585
break;
586
nextPath = nextPath.NavigateUp();
587
}
588
589
if (screenshotFilename)
590
headlessHost->SetComparisonScreenshot(Path(std::string(screenshotFilename)), testOptions.maxScreenshotError);
591
headlessHost->SetWriteFailureScreenshot(!teamCityMode && !getenv("GITHUB_ACTIONS") && !testOptions.bench);
592
headlessHost->SetWriteDebugOutput(!testOptions.compare && !testOptions.bench);
593
594
#if PPSSPP_PLATFORM(ANDROID)
595
// For some reason the debugger installs it with this name?
596
if (File::Exists(Path("/data/app/org.ppsspp.ppsspp-2.apk"))) {
597
g_VFS.Register("", ZipFileReader::Create(Path("/data/app/org.ppsspp.ppsspp-2.apk"), "assets/"));
598
}
599
if (File::Exists(Path("/data/app/org.ppsspp.ppsspp.apk"))) {
600
g_VFS.Register("", ZipFileReader::Create(Path("/data/app/org.ppsspp.ppsspp.apk"), "assets/"));
601
}
602
#elif PPSSPP_PLATFORM(LINUX)
603
g_VFS.Register("", new DirectoryReader(Path("/usr/local/share/ppsspp/assets")));
604
g_VFS.Register("", new DirectoryReader(Path("/usr/local/share/games/ppsspp/assets")));
605
g_VFS.Register("", new DirectoryReader(Path("/usr/share/ppsspp/assets")));
606
g_VFS.Register("", new DirectoryReader(Path("/usr/share/games/ppsspp/assets")));
607
#endif
608
609
UpdateUIState(UISTATE_INGAME);
610
611
if (debuggerPort > 0) {
612
g_Config.iRemoteISOPort = debuggerPort;
613
coreParameter.startBreak = true;
614
StartWebServer(WebServerFlags::DEBUGGER);
615
}
616
617
if (stateToLoad != NULL)
618
SaveState::Load(Path(stateToLoad), -1);
619
620
std::vector<std::string> failedTests;
621
std::vector<std::string> passedTests;
622
for (size_t i = 0; i < testFilenames.size(); ++i)
623
{
624
coreParameter.fileToStart = Path(testFilenames[i]);
625
if (testOptions.compare)
626
printf("%s:\n", coreParameter.fileToStart.c_str());
627
bool passed = RunAutoTest(headlessHost, coreParameter, testOptions);
628
if (testOptions.bench) {
629
double st = time_now_d();
630
double deadline = st + testOptions.timeout;
631
double runs = 0.0;
632
for (int i = 0; i < 100; ++i) {
633
RunAutoTest(headlessHost, coreParameter, testOptions);
634
runs++;
635
636
if (time_now_d() > deadline)
637
break;
638
}
639
double et = time_now_d();
640
641
std::string testName = GetTestName(coreParameter.fileToStart);
642
printf(" %s - %f seconds average\n", testName.c_str(), (et - st) / runs);
643
}
644
if (testOptions.compare) {
645
std::string testName = GetTestName(coreParameter.fileToStart);
646
if (passed) {
647
passedTests.push_back(testName);
648
printf(" %s - passed!\n", testName.c_str());
649
}
650
else
651
failedTests.push_back(testName);
652
}
653
}
654
655
if (testOptions.compare) {
656
printf("%d tests passed, %d tests failed.\n", (int)passedTests.size(), (int)failedTests.size());
657
if (!failedTests.empty())
658
{
659
printf("Failed tests:\n");
660
for (size_t i = 0; i < failedTests.size(); ++i) {
661
printf(" %s\n", failedTests[i].c_str());
662
}
663
}
664
}
665
666
if (debuggerPort > 0) {
667
ShutdownWebServer();
668
}
669
670
headlessHost->ShutdownGraphics();
671
delete headlessHost;
672
headlessHost = nullptr;
673
g_headlessHost = nullptr;
674
675
g_VFS.Clear();
676
g_logManager.Shutdown();
677
678
#if PPSSPP_PLATFORM(WINDOWS)
679
timeEndPeriod(1);
680
#endif
681
682
g_threadManager.Teardown();
683
684
if (!failedTests.empty() && !teamCityMode)
685
return 1;
686
return 0;
687
}
688
689