Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/UI/NativeApp.cpp
5668 views
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
// NativeApp implementation for platforms that will use that framework, like:
19
// Android, Linux, MacOSX.
20
//
21
// Native is a cross platform framework. It's not very mature and mostly
22
// just built according to the needs of my own apps.
23
//
24
// Windows has its own code that bypasses the framework entirely.
25
26
#include "ppsspp_config.h"
27
28
// Background worker threads should be spawned in NativeInit and joined
29
// in NativeShutdown.
30
#include <errno.h>
31
32
#include <clocale>
33
#include <algorithm>
34
#include <cstdlib>
35
#include <memory>
36
#include <mutex>
37
#include <thread>
38
39
#if defined(_WIN32)
40
#include "Windows/WindowsAudio.h"
41
#include "Windows/MainWindow.h"
42
#endif
43
44
#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
45
#include "Windows/CaptureDevice.h"
46
#endif
47
48
#include "Common/Net/HTTPClient.h"
49
#include "Common/Net/Resolve.h"
50
#include "Common/Net/URL.h"
51
#include "Common/Render/TextureAtlas.h"
52
#include "Common/Render/Text/draw_text.h"
53
#include "Common/GPU/OpenGL/GLFeatures.h"
54
#include "Common/GPU/thin3d.h"
55
#include "Common/UI/UI.h"
56
#include "Common/UI/Screen.h"
57
#include "Common/UI/Context.h"
58
#include "Common/UI/View.h"
59
#include "Common/UI/IconCache.h"
60
61
#include "android/jni/app-android.h"
62
63
#include "Common/System/Display.h"
64
#include "Common/System/Request.h"
65
#include "Common/System/System.h"
66
#include "Common/System/OSD.h"
67
#include "Common/System/NativeApp.h"
68
69
#include "Common/Data/Text/I18n.h"
70
#include "Common/Input/InputState.h"
71
#include "Common/Math/math_util.h"
72
#include "Common/Math/lin/matrix4x4.h"
73
#include "Common/Profiler/Profiler.h"
74
#include "Common/Data/Encoding/Utf8.h"
75
#include "Common/File/VFS/VFS.h"
76
#include "Common/File/VFS/ZipFileReader.h"
77
#include "Common/File/VFS/DirectoryReader.h"
78
#include "Common/CPUDetect.h"
79
#include "Common/File/FileUtil.h"
80
#include "Common/TimeUtil.h"
81
#include "Common/StringUtils.h"
82
#include "Common/Log/LogManager.h"
83
#include "Common/MemArena.h"
84
#include "Common/GraphicsContext.h"
85
#include "Common/OSVersion.h"
86
#include "Common/GPU/ShaderTranslation.h"
87
#include "Common/VR/PPSSPPVR.h"
88
#include "Common/Thread/ThreadManager.h"
89
#include "Common/Audio/AudioBackend.h"
90
#include "Common/UI/PopupScreens.h"
91
#include "Core/ControlMapper.h"
92
#include "Core/Config.h"
93
#include "Core/ConfigValues.h"
94
#include "Core/Core.h"
95
#include "Core/Debugger/Breakpoints.h"
96
#include "Core/FileLoaders/DiskCachingFileLoader.h"
97
#include "Core/FrameTiming.h"
98
#include "Core/KeyMap.h"
99
#include "Core/Reporting.h"
100
#include "Core/RetroAchievements.h"
101
#include "Core/SaveState.h"
102
#include "Core/Screenshot.h"
103
#include "Core/System.h"
104
#include "Core/HLE/__sceAudio.h"
105
#include "Core/HLE/sceCtrl.h"
106
#include "Core/HLE/sceUsbCam.h"
107
#include "Core/HLE/sceUsbGps.h"
108
#include "Core/HLE/proAdhoc.h"
109
#include "Core/HW/MemoryStick.h"
110
#include "Core/Util/GameManager.h"
111
#include "Core/Util/PortManager.h"
112
#include "Core/Util/AudioFormat.h"
113
#include "Core/Util/RecentFiles.h"
114
#include "Core/Util/PathUtil.h"
115
#include "Core/WebServer.h"
116
#include "Core/TiltEventProcessor.h"
117
118
#include "GPU/GPUCommon.h"
119
#include "GPU/Common/PresentationCommon.h"
120
#include "UI/AudioCommon.h"
121
#include "UI/Background.h"
122
#include "UI/BackgroundAudio.h"
123
#include "UI/ControlMappingScreen.h"
124
#include "UI/DevScreens.h"
125
#include "UI/DiscordIntegration.h"
126
#include "UI/EmuScreen.h"
127
#include "UI/GameInfoCache.h"
128
#include "UI/GameSettingsScreen.h"
129
#include "UI/DeveloperToolsScreen.h"
130
#include "UI/GPUDriverTestScreen.h"
131
#include "UI/MiscScreens.h"
132
#include "UI/MemStickScreen.h"
133
#include "UI/OnScreenDisplay.h"
134
#include "UI/RemoteISOScreen.h"
135
#include "UI/Theme.h"
136
#include "UI/UIAtlas.h"
137
138
#if PPSSPP_PLATFORM(UWP)
139
#include <dwrite_3.h>
140
#include "UWP/UWPHelpers/InputHelpers.h"
141
#endif
142
#if PPSSPP_PLATFORM(ANDROID)
143
#include "android/jni/app-android.h"
144
#endif
145
146
#if PPSSPP_ARCH(ARM) && defined(__ANDROID__)
147
#include "../../android/jni/ArmEmitterTest.h"
148
#elif PPSSPP_ARCH(ARM64) && defined(__ANDROID__)
149
#include "../../android/jni/Arm64EmitterTest.h"
150
#endif
151
152
#if PPSSPP_PLATFORM(IOS)
153
#include "ios/iOSCoreAudio.h"
154
#elif defined(__APPLE__)
155
#include <mach-o/dyld.h>
156
#endif
157
158
#if PPSSPP_PLATFORM(IOS) || PPSSPP_PLATFORM(MAC)
159
#include "UI/DarwinFileSystemServices.h"
160
#endif
161
162
#if !defined(__LIBRETRO__)
163
#include "Core/Util/GameDB.h"
164
#endif
165
166
#include <Core/HLE/Plugins.h>
167
168
bool HandleGlobalMessage(UIMessage message, const std::string &value);
169
static void ProcessWheelRelease(InputKeyCode keyCode, double now, bool keyPress);
170
void SaveFrameDump();
171
172
ScreenManager *g_screenManager;
173
std::string config_filename;
174
175
// Really need to clean this mess of globals up... but instead I add more :P
176
bool g_TakeScreenshot;
177
static bool resized = false;
178
static bool restarting = false;
179
180
static int renderCounter = 0;
181
182
struct PendingMessage {
183
UIMessage message;
184
std::string value;
185
};
186
187
static std::mutex g_pendingMutex;
188
static std::vector<PendingMessage> pendingMessages;
189
static Draw::DrawContext *g_draw;
190
static Draw::Pipeline *colorPipeline;
191
static Draw::Pipeline *texColorPipeline;
192
static UIContext *uiContext;
193
static int g_restartGraphics;
194
static bool g_windowHidden = false;
195
std::vector<std::function<void()>> g_pendingClosures;
196
197
AudioBackend *g_audioBackend = nullptr;
198
199
std::thread *graphicsLoadThread;
200
201
// globals
202
Path boot_filename;
203
204
// This is called before NativeInit so we do a little bit of initialization here.
205
void NativeGetAppInfo(std::string *app_dir_name, std::string *app_nice_name, bool *landscape, std::string *version) {
206
*app_nice_name = "PPSSPP";
207
*app_dir_name = "ppsspp";
208
*landscape = true;
209
*version = PPSSPP_GIT_VERSION;
210
211
#if PPSSPP_ARCH(ARM) && defined(__ANDROID__)
212
ArmEmitterTest();
213
#elif PPSSPP_ARCH(ARM64) && defined(__ANDROID__)
214
Arm64EmitterTest();
215
#endif
216
}
217
218
#if defined(USING_WIN_UI) && !PPSSPP_PLATFORM(UWP)
219
static bool CheckFontIsUsable(const wchar_t *fontFace) {
220
wchar_t actualFontFace[1024] = { 0 };
221
222
HFONT f = CreateFont(0, 0, 0, 0, FW_LIGHT, 0, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, VARIABLE_PITCH, fontFace);
223
if (f != nullptr) {
224
HDC hdc = CreateCompatibleDC(nullptr);
225
if (hdc != nullptr) {
226
SelectObject(hdc, f);
227
GetTextFace(hdc, 1024, actualFontFace);
228
DeleteDC(hdc);
229
}
230
DeleteObject(f);
231
}
232
233
// If we were able to get the font name, did it load?
234
if (actualFontFace[0] != 0) {
235
return wcsncmp(actualFontFace, fontFace, ARRAY_SIZE(actualFontFace)) == 0;
236
}
237
return false;
238
}
239
#endif
240
241
void PostLoadConfig() {
242
if (g_Config.currentDirectory.empty()) {
243
g_Config.currentDirectory = g_Config.defaultCurrentDirectory;
244
}
245
246
// Allow the lang directory to be overridden for testing purposes (e.g. Android, where it's hard to
247
// test new languages without recompiling the entire app, which is a hassle).
248
const Path langOverridePath = GetSysDirectory(DIRECTORY_SYSTEM) / "lang";
249
250
// If we run into the unlikely case that "lang" is actually a file, just use the built-in translations.
251
if (!File::Exists(langOverridePath) || !File::IsDirectory(langOverridePath))
252
g_i18nrepo.LoadIni(g_Config.sLanguageIni);
253
else
254
g_i18nrepo.LoadIni(g_Config.sLanguageIni, langOverridePath);
255
256
#if !PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(UWP)
257
CreateSysDirectories();
258
#endif
259
}
260
261
static void CheckFailedGPUBackends() {
262
#ifdef _DEBUG
263
// If you're in debug mode, you probably don't want a fallback. If you're in release mode, use IGNORE below.
264
NOTICE_LOG(Log::Loader, "Not checking for failed graphics backends in debug mode");
265
return;
266
#endif
267
268
#if PPSSPP_PLATFORM(ANDROID)
269
if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 30) {
270
// In Android 11 or later, Vulkan is as stable as OpenGL, so let's not even bother.
271
// Have also seen unexplained issues with random fallbacks to OpenGL for no good reason,
272
// especially when debugging.
273
return;
274
}
275
#endif
276
277
// We only want to do this once per process run and backend, to detect process crashes.
278
// If NativeShutdown is called before we finish, we might call this multiple times.
279
static int lastBackend = -1;
280
if (lastBackend == g_Config.iGPUBackend) {
281
return;
282
}
283
lastBackend = g_Config.iGPUBackend;
284
285
const Path failedBackendsDir = GetFailedBackendsDir();
286
const Path failedBackendsFile = failedBackendsDir / "FailedGraphicsBackends.txt";
287
288
std::string data;
289
if (File::ReadTextFileToString(failedBackendsFile, &data)) {
290
g_Config.sFailedGPUBackends = data;
291
}
292
293
// Use this if you want to debug a graphics crash...
294
if (g_Config.sFailedGPUBackends == "IGNORE")
295
return;
296
else if (!g_Config.sFailedGPUBackends.empty()) {
297
ERROR_LOG(Log::Loader, "Failed graphics backends: %s", g_Config.sFailedGPUBackends.c_str());
298
}
299
300
// Okay, let's not try a backend in the failed list.
301
g_Config.iGPUBackend = g_Config.NextValidBackend();
302
if (lastBackend != g_Config.iGPUBackend) {
303
// This is the expected path.
304
std::string param = GPUBackendToString((GPUBackend)lastBackend) + " -> " + GPUBackendToString((GPUBackend)g_Config.iGPUBackend);
305
System_GraphicsBackendFailedAlert(param);
306
INFO_LOG(Log::Loader, "Failed graphics backend switched from %s (%d to %d)", param.c_str(), lastBackend, g_Config.iGPUBackend);
307
} else {
308
WARN_LOG(Log::Loader, "Did not switch failed backend! %d", g_Config.iGPUBackend);
309
}
310
311
// And then let's - for now - add the current to the failed list, in case it fails - we'll clear it again once it succeeds.
312
const std::string curBackend = GPUBackendToString((GPUBackend)g_Config.iGPUBackend);
313
if (g_Config.sFailedGPUBackends.empty()) {
314
g_Config.sFailedGPUBackends = curBackend;
315
} else if (g_Config.sFailedGPUBackends.find(curBackend) != std::string::npos) {
316
// Backend already listed!
317
ERROR_LOG(Log::Loader, "Unexpected: Backend already in failed backends. Should not have been attempted");
318
} else if (g_Config.sFailedGPUBackends.find("ALL") == std::string::npos) {
319
g_Config.sFailedGPUBackends += "," + GPUBackendToString((GPUBackend)g_Config.iGPUBackend);
320
}
321
322
// Let's try to create, in case it doesn't exist.
323
File::CreateFullPath(failedBackendsDir);
324
File::WriteStringToFile(true, g_Config.sFailedGPUBackends, failedBackendsFile);
325
}
326
327
static void ClearFailedGPUBackends() {
328
if (g_Config.sFailedGPUBackends == "IGNORE")
329
return;
330
331
const Path failedBackendsDir = GetFailedBackendsDir();
332
const Path failedBackendsFile = failedBackendsDir / "FailedGraphicsBackends.txt";
333
// We've successfully started graphics without crashing, hurray.
334
// In case they update drivers and have totally different problems much later, clear the failed list.
335
g_Config.sFailedGPUBackends.clear();
336
File::Delete(failedBackendsFile);
337
}
338
339
void NativeInit(int argc, const char *argv[], const char *savegame_dir, const char *external_dir, const char *cache_dir) {
340
net::Init(); // This needs to happen before we load the config. So on Windows we also run it in Main. It's fine to call multiple times.
341
342
g_Config.Init();
343
344
IncrementDebugCounter(DebugCounter::APP_BOOT);
345
346
// Probably an excessive timeout. it only causes delays on shutdown, though.
347
__UPnPInit(2000);
348
349
ShaderTranslationInit();
350
351
g_threadManager.Init(cpu_info.num_cores, cpu_info.logical_cpu_count);
352
353
g_recentFiles.EnsureThread();
354
355
// Make sure UI state is MENU.
356
ResetUIState();
357
358
bool skipLogo = false;
359
setlocale( LC_ALL, "C" );
360
std::string user_data_path = savegame_dir;
361
pendingMessages.clear();
362
g_pendingClosures.clear();
363
g_requestManager.Clear();
364
365
// external_dir has all kinds of meanings depending on platform.
366
// on iOS it's even the path to bundled app assets. It's a mess.
367
368
// We want this to be FIRST.
369
#if PPSSPP_PLATFORM(IOS) || PPSSPP_PLATFORM(MAC)
370
// Packed assets are included in app
371
g_VFS.Register("", new DirectoryReader(Path(external_dir)));
372
#endif
373
#if defined(ASSETS_DIR)
374
g_VFS.Register("", new DirectoryReader(Path(ASSETS_DIR)));
375
#endif
376
#if !defined(MOBILE_DEVICE) && !defined(_WIN32) && !PPSSPP_PLATFORM(SWITCH)
377
g_VFS.Register("", new DirectoryReader(File::GetExeDirectory() / "assets"));
378
g_VFS.Register("", new DirectoryReader(File::GetExeDirectory()));
379
g_VFS.Register("", new DirectoryReader(Path("/usr/local/share/ppsspp/assets")));
380
g_VFS.Register("", new DirectoryReader(Path("/usr/local/share/games/ppsspp/assets")));
381
g_VFS.Register("", new DirectoryReader(Path("/usr/share/ppsspp/assets")));
382
g_VFS.Register("", new DirectoryReader(Path("/usr/share/games/ppsspp/assets")));
383
#endif
384
385
#if PPSSPP_PLATFORM(SWITCH)
386
Path assetPath = Path(user_data_path) / "assets";
387
g_VFS.Register("", new DirectoryReader(assetPath));
388
#else
389
g_VFS.Register("", new DirectoryReader(Path("assets")));
390
#endif
391
g_VFS.Register("", new DirectoryReader(Path(savegame_dir)));
392
393
#if PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(MAC)
394
g_Config.defaultCurrentDirectory = Path(System_GetProperty(SYSPROP_USER_DOCUMENTS_DIR));
395
#else
396
g_Config.defaultCurrentDirectory = Path("/");
397
#endif
398
399
#if !PPSSPP_PLATFORM(UWP)
400
g_Config.internalDataDirectory = Path(savegame_dir);
401
#endif
402
403
#if PPSSPP_PLATFORM(ANDROID)
404
// In Android 12 with scoped storage, due to the above, the external directory
405
// is no longer the plain root of external storage, but it's an app specific directory
406
// on external storage (g_extFilesDir).
407
if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {
408
// There's no sensible default directory. Let the user browse for files.
409
g_Config.defaultCurrentDirectory.clear();
410
} else {
411
g_Config.memStickDirectory = Path(external_dir);
412
g_Config.defaultCurrentDirectory = Path(external_dir);
413
}
414
415
// Might also add an option to move it to internal / non-visible storage, but there's
416
// little point, really.
417
418
g_Config.flash0Directory = Path(external_dir) / "flash0";
419
420
Path memstickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt";
421
if (File::Exists(memstickDirFile)) {
422
INFO_LOG(Log::System, "Reading '%s' to find memstick dir.", memstickDirFile.c_str());
423
std::string memstickDir;
424
if (File::ReadTextFileToString(memstickDirFile, &memstickDir)) {
425
Path memstickPath(memstickDir);
426
if (!memstickPath.empty() && File::Exists(memstickPath)) {
427
g_Config.memStickDirectory = memstickPath;
428
INFO_LOG(Log::System, "Memstick Directory from memstick_dir.txt: '%s'", g_Config.memStickDirectory.c_str());
429
} else {
430
ERROR_LOG(Log::System, "Couldn't read directory '%s' specified by memstick_dir.txt.", memstickDir.c_str());
431
if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {
432
// Ask the user to configure a memstick directory.
433
INFO_LOG(Log::System, "Asking the user.");
434
g_Config.memStickDirectory.clear();
435
}
436
}
437
}
438
} else {
439
INFO_LOG(Log::System, "No memstick directory file found (tried to open '%s')", memstickDirFile.c_str());
440
}
441
442
// Attempt to create directories after reading the path.
443
if (!System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {
444
CreateSysDirectories();
445
}
446
#elif PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__)
447
Path memstickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt";
448
if (File::Exists(memstickDirFile)) {
449
INFO_LOG(Log::System, "Reading '%s' to find memstick dir.", memstickDirFile.c_str());
450
std::string memstickDir;
451
if (File::ReadTextFileToString(memstickDirFile, &memstickDir)) {
452
Path memstickPath(memstickDir);
453
if (!memstickPath.empty() && File::Exists(memstickPath)) {
454
g_Config.memStickDirectory = memstickPath;
455
g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM));
456
g_Config.Reload();
457
INFO_LOG(Log::System, "Memstick Directory from memstick_dir.txt: '%s'", g_Config.memStickDirectory.c_str());
458
} else {
459
ERROR_LOG(Log::System, "Couldn't read directory '%s' specified by memstick_dir.txt.", memstickDir.c_str());
460
g_Config.memStickDirectory.clear();
461
}
462
}
463
}
464
else {
465
INFO_LOG(Log::System, "No memstick directory file found (tried to open '%s')", memstickDirFile.c_str());
466
}
467
#elif PPSSPP_PLATFORM(IOS)
468
g_Config.defaultCurrentDirectory = g_Config.internalDataDirectory;
469
g_Config.memStickDirectory = DarwinFileSystemServices::appropriateMemoryStickDirectoryToUse();
470
g_Config.flash0Directory = Path(external_dir) / "flash0";
471
#elif PPSSPP_PLATFORM(MAC)
472
g_Config.memStickDirectory = DarwinFileSystemServices::appropriateMemoryStickDirectoryToUse();
473
g_Config.flash0Directory = Path(external_dir) / "flash0";
474
#elif PPSSPP_PLATFORM(SWITCH)
475
g_Config.memStickDirectory = g_Config.internalDataDirectory / "config/ppsspp";
476
g_Config.flash0Directory = g_Config.internalDataDirectory / "assets/flash0";
477
#elif !PPSSPP_PLATFORM(WINDOWS)
478
std::string config;
479
if (getenv("XDG_CONFIG_HOME") != NULL)
480
config = getenv("XDG_CONFIG_HOME");
481
else if (getenv("HOME") != NULL)
482
config = getenv("HOME") + std::string("/.config");
483
else // Just in case
484
config = "./config";
485
486
g_Config.memStickDirectory = Path(config) / "ppsspp";
487
g_Config.flash0Directory = File::GetExeDirectory() / "assets/flash0";
488
if (getenv("HOME") != nullptr) {
489
g_Config.defaultCurrentDirectory = Path(getenv("HOME"));
490
} else {
491
// Hm, should probably actually explicitly set the current directory..
492
// Though it's not many platforms that'll land us here.
493
g_Config.currentDirectory = Path(".");
494
}
495
#endif
496
497
if (g_Config.currentDirectory.empty()) {
498
g_Config.currentDirectory = g_Config.defaultCurrentDirectory;
499
}
500
501
if (cache_dir && strlen(cache_dir)) {
502
g_Config.appCacheDirectory = Path(cache_dir);
503
DiskCachingFileLoaderCache::SetCacheDir(g_Config.appCacheDirectory);
504
}
505
506
g_logManager.Init(&g_Config.bEnableLogging);
507
508
#if !PPSSPP_PLATFORM(WINDOWS)
509
g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM));
510
511
// Note that if we don't have storage permission here, loading the config will
512
// fail and it will be set to the default. Later, we load again when we get permission.
513
g_Config.Load();
514
#endif
515
516
const char *fileToLog = nullptr;
517
Path stateToLoad;
518
519
bool gotBootFilename = false;
520
bool gotoGameSettings = false;
521
bool gotoTouchScreenTest = false;
522
bool gotoDeveloperTools = false;
523
boot_filename.clear();
524
525
// Parse command line
526
LogLevel logLevel = LogLevel::LINFO;
527
bool forceLogLevel = false;
528
const auto setLogLevel = [&logLevel, &forceLogLevel](LogLevel level) {
529
logLevel = level;
530
forceLogLevel = true;
531
};
532
533
// TODO: Need a much better command line argument parser.
534
535
for (int i = 1; i < argc; i++) {
536
if (argv[i][0] == '-') {
537
#if defined(__APPLE__)
538
// On Apple system debugged executable may get -NSDocumentRevisionsDebugMode YES in argv.
539
if (!strcmp(argv[i], "-NSDocumentRevisionsDebugMode") && argc - 1 > i) {
540
i++;
541
continue;
542
}
543
#endif
544
switch (argv[i][1]) {
545
case 'd':
546
// Enable debug logging
547
// Note that you must also change the max log level in Log.h.
548
setLogLevel(LogLevel::LDEBUG);
549
break;
550
case 'v':
551
// Enable verbose logging
552
// Note that you must also change the max log level in Log.h.
553
setLogLevel(LogLevel::LVERBOSE);
554
break;
555
case 'j':
556
g_Config.iCpuCore = (int)CPUCore::JIT;
557
g_Config.bSaveSettings = false;
558
break;
559
case 'i':
560
g_Config.iCpuCore = (int)CPUCore::INTERPRETER;
561
g_Config.bSaveSettings = false;
562
break;
563
case 'r':
564
g_Config.iCpuCore = (int)CPUCore::IR_INTERPRETER;
565
g_Config.bSaveSettings = false;
566
break;
567
case 'J':
568
g_Config.iCpuCore = (int)CPUCore::JIT_IR;
569
g_Config.bSaveSettings = false;
570
break;
571
case '-':
572
if (!strncmp(argv[i], "--loglevel=", strlen("--loglevel=")) && strlen(argv[i]) > strlen("--loglevel="))
573
setLogLevel(static_cast<LogLevel>(std::atoi(argv[i] + strlen("--loglevel="))));
574
if (!strncmp(argv[i], "--log=", strlen("--log=")) && strlen(argv[i]) > strlen("--log="))
575
fileToLog = argv[i] + strlen("--log=");
576
if (!strncmp(argv[i], "--state=", strlen("--state=")) && strlen(argv[i]) > strlen("--state="))
577
stateToLoad = Path(argv[i] + strlen("--state="));
578
if (!strncmp(argv[i], "--escape-exit", strlen("--escape-exit")))
579
g_Config.bPauseExitsEmulator = true;
580
if (!strncmp(argv[i], "--pause-menu-exit", strlen("--pause-menu-exit")))
581
g_Config.bPauseMenuExitsEmulator = true;
582
if (!strcmp(argv[i], "--fullscreen")) {
583
g_Config.DoNotSaveSetting(&g_Config.bFullScreen);
584
g_Config.bFullScreen = true;
585
}
586
if (!strncmp(argv[i], "--root=", strlen("--root=")) && strlen(argv[i]) > strlen("--root=")) {
587
g_Config.mountRoot = Path(argv[i] + strlen("--root="));
588
}
589
if (!strcmp(argv[i], "--windowed")) {
590
g_Config.DoNotSaveSetting(&g_Config.bFullScreen);
591
g_Config.bFullScreen = false;
592
}
593
if (!strcmp(argv[i], "--touchscreentest"))
594
gotoTouchScreenTest = true;
595
if (!strcmp(argv[i], "--gamesettings"))
596
gotoGameSettings = true;
597
if (!strcmp(argv[i], "--developertools"))
598
gotoDeveloperTools = true;
599
if (!strncmp(argv[i], "--appendconfig=", strlen("--appendconfig=")) && strlen(argv[i]) > strlen("--appendconfig=")) {
600
g_Config.SetAppendedConfigIni(Path(argv[i] + strlen("--appendconfig=")));
601
g_Config.LoadAppendedConfig();
602
}
603
break;
604
}
605
} else {
606
// This parameter should be a boot filename. Only accept it if we
607
// don't already have one.
608
if (!gotBootFilename) {
609
gotBootFilename = true;
610
INFO_LOG(Log::System, "Boot filename found in args: '%s'", argv[i]);
611
612
bool okToLoad = true;
613
bool okToCheck = true;
614
if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) {
615
PermissionStatus status = System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE);
616
if (status == PERMISSION_STATUS_DENIED) {
617
ERROR_LOG(Log::IO, "Storage permission denied. Launching without argument.");
618
okToLoad = false;
619
okToCheck = false;
620
} else if (status != PERMISSION_STATUS_GRANTED) {
621
ERROR_LOG(Log::IO, "Storage permission not granted. Launching without argument check.");
622
okToCheck = false;
623
} else {
624
INFO_LOG(Log::IO, "Storage permission granted.");
625
}
626
}
627
if (okToLoad) {
628
std::string str = std::string(argv[i]);
629
// Handle file:/// URIs, since you get those when creating shortcuts on some Android systems.
630
if (startsWith(str, "file:///")) {
631
str = UriDecode(str.substr(7));
632
INFO_LOG(Log::IO, "Decoding '%s' to '%s'", argv[i], str.c_str());
633
}
634
635
boot_filename = Path(str);
636
skipLogo = true;
637
}
638
if (okToLoad && okToCheck) {
639
std::unique_ptr<FileLoader> fileLoader(ConstructFileLoader(boot_filename));
640
if (!fileLoader->Exists()) {
641
fprintf(stderr, "File not found: %s\n", boot_filename.c_str());
642
#if defined(_WIN32) || defined(__ANDROID__)
643
// Ignore and proceed.
644
boot_filename.clear();
645
#else
646
// Bail.
647
exit(1);
648
#endif
649
}
650
}
651
} else {
652
fprintf(stderr, "Syntax error: Can only boot one file.\nNote: Many command line args need a =, like --appendconfig=FILENAME.ini.\n");
653
#if defined(_WIN32) || defined(__ANDROID__)
654
// Ignore and proceed.
655
#else
656
// Bail.
657
exit(1);
658
#endif
659
}
660
}
661
}
662
663
if (fileToLog) {
664
g_logManager.EnableOutput(LogOutput::File);
665
g_logManager.SetFileLogPath(Path(fileToLog));
666
} else {
667
// Set a default file logging path, in case the user enables it with the checkbox later.
668
g_logManager.SetFileLogPath(GetSysDirectory(DIRECTORY_DUMP) / "log.txt");
669
}
670
671
if (forceLogLevel) {
672
NOTICE_LOG(Log::System, "Setting log level to %d due to command line override", (int)logLevel);
673
g_logManager.SetAllLogLevels(logLevel);
674
}
675
676
PostLoadConfig();
677
678
#if PPSSPP_PLATFORM(ANDROID)
679
// Stdio is used for Android logging too.
680
g_logManager.EnableOutput(LogOutput::Stdio);
681
#elif (defined(MOBILE_DEVICE) && !defined(_DEBUG))
682
// Enable basic logging for any kind of mobile device, since LogManager doesn't.
683
// The MOBILE_DEVICE/_DEBUG condition matches LogManager.cpp.
684
// TODO: Why not use stdio?
685
g_logManager.EnableOutput(LogOutput::Printf);
686
#endif
687
688
if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) {
689
if (System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE) != PERMISSION_STATUS_GRANTED) {
690
System_AskForPermission(SYSTEM_PERMISSION_STORAGE);
691
}
692
}
693
694
g_BackgroundAudio.SFX().Init();
695
696
if (!boot_filename.empty() && stateToLoad.Valid()) {
697
SaveState::Load(stateToLoad, -1, [](SaveState::Status status, std::string_view message) {
698
if (!message.empty() && (!g_Config.bDumpFrames || !g_Config.bDumpVideoOutput)) {
699
g_OSD.Show(status == SaveState::Status::SUCCESS ? OSDType::MESSAGE_SUCCESS : OSDType::MESSAGE_ERROR,
700
message, status == SaveState::Status::SUCCESS ? 2.0 : 5.0);
701
}
702
});
703
}
704
705
if (g_Config.bAchievementsEnable) {
706
FILE *iconCacheFile = File::OpenCFile(GetSysDirectory(DIRECTORY_CACHE) / "icon.cache", "rb");
707
if (iconCacheFile) {
708
g_iconCache.LoadFromFile(iconCacheFile);
709
fclose(iconCacheFile);
710
}
711
}
712
713
g_DownloadManager.SetCacheDir(GetSysDirectory(DIRECTORY_APP_CACHE));
714
715
DEBUG_LOG(Log::System, "ScreenManager!");
716
g_screenManager = new ScreenManager();
717
if (g_Config.memStickDirectory.empty()) {
718
INFO_LOG(Log::System, "No memstick directory! Asking for one to be configured.");
719
g_screenManager->switchScreen(new LogoScreen(AfterLogoScreen::MEMSTICK_SCREEN_INITIAL_SETUP));
720
} else if (gotoGameSettings) {
721
g_screenManager->switchScreen(new LogoScreen(AfterLogoScreen::TO_GAME_SETTINGS));
722
} else if (gotoTouchScreenTest) {
723
g_screenManager->switchScreen(new MainScreen());
724
g_screenManager->push(new TouchTestScreen(Path()));
725
} else if (gotoDeveloperTools) {
726
g_screenManager->switchScreen(new MainScreen());
727
g_screenManager->push(new DeveloperToolsScreen(Path()));
728
} else if (skipLogo && !boot_filename.empty()) {
729
INFO_LOG(Log::System, "Launching EmuScreen with boot filename '%s'", boot_filename.c_str());
730
g_screenManager->switchScreen(new EmuScreen(boot_filename));
731
} else {
732
g_screenManager->switchScreen(new LogoScreen(AfterLogoScreen::DEFAULT));
733
}
734
735
g_screenManager->SetBackgroundOverlayScreens(new BackgroundScreen(), new OSDOverlayScreen());
736
737
// Easy testing
738
// screenManager->push(new GPUDriverTestScreen());
739
740
WebServerFlags flags = (WebServerFlags)0;
741
if (g_Config.bRemoteShareOnStartup) {
742
flags |= WebServerFlags::DISCS;
743
}
744
if (g_Config.bRemoteDebuggerOnStartup) {
745
flags |= WebServerFlags::DEBUGGER;
746
}
747
if (flags != WebServerFlags::NONE) {
748
StartWebServer(WebServerFlags::ALL);
749
}
750
751
std::string sysName = System_GetProperty(SYSPROP_NAME);
752
753
// We do this here, instead of in NativeInitGraphics, because the display may be reset.
754
// When it's reset we don't want to forget all our managed things.
755
CheckFailedGPUBackends();
756
SetGPUBackend((GPUBackend)g_Config.iGPUBackend);
757
renderCounter = 0;
758
759
// Initialize retro achievements runtime.
760
Achievements::Initialize();
761
762
// Must be done restarting by now.
763
restarting = false;
764
}
765
766
void CallbackPostRender(UIContext *dc, void *userdata);
767
bool CreateGlobalPipelines();
768
769
// TODO: Add faster special case for channels == 2.
770
static void NativeMixWrapper(float *dest, int framesToWrite, int sampleRateHz, void *userdata) {
771
static int16_t *buffer;
772
static int bufSize;
773
if (bufSize < framesToWrite * 2) {
774
buffer = new int16_t[framesToWrite * 2];
775
bufSize = framesToWrite * 2;
776
}
777
778
NativeMix(buffer, framesToWrite, sampleRateHz, userdata);
779
780
for (int i = 0; i < framesToWrite * 2; i++) {
781
dest[i] = (float)buffer[i] * (float)(1.0f / 32767.0f);
782
}
783
}
784
785
bool NativeInitGraphics(GraphicsContext *graphicsContext) {
786
INFO_LOG(Log::System, "NativeInitGraphics");
787
788
_assert_msg_(g_screenManager, "No screenmanager, bad init order. Backend = %d", g_Config.iGPUBackend);
789
790
// We set this now so any resize during init is processed later.
791
resized = false;
792
793
Core_SetGraphicsContext(graphicsContext);
794
g_draw = graphicsContext->GetDrawContext();
795
796
_assert_(g_draw);
797
798
if (!CreateGlobalPipelines()) {
799
ERROR_LOG(Log::G3D, "Failed to create global pipelines");
800
return false;
801
}
802
803
ui_draw2d.SetAtlas(GetUIAtlas());
804
ui_draw2d.SetFontAtlas(GetFontAtlas());
805
806
uiContext = new UIContext();
807
uiContext->SetTheme(GetTheme());
808
uiContext->SetAtlasProvider(&AtlasProvider);
809
UpdateTheme();
810
811
ui_draw2d.Init(g_draw, texColorPipeline);
812
813
uiContext->Init(g_draw, texColorPipeline, colorPipeline, &ui_draw2d);
814
if (uiContext->Text()) {
815
// This seems unnecessary.
816
// uiContext->Text()->SetOrCreateFont(FontStyle(FontID::invalid(), FontFamily::SansSerif, 20, FontStyleFlags::Default));
817
}
818
819
g_screenManager->setUIContext(uiContext);
820
g_screenManager->setPostRenderCallback(&CallbackPostRender, nullptr);
821
g_screenManager->deviceRestored(g_draw);
822
823
g_audioBackend = System_CreateAudioBackend();
824
if (g_audioBackend) {
825
g_audioBackend->SetRenderCallback(&NativeMixWrapper, nullptr);
826
bool reverted = false;
827
g_audioBackend->InitOutputDevice(g_Config.sAudioDevice, LatencyMode::Aggressive, &reverted);
828
if (reverted) {
829
g_Config.sAudioDevice.clear();
830
}
831
}
832
833
#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
834
if (IsWin7OrHigher()) {
835
winCamera = new WindowsCaptureDevice(CAPTUREDEVIDE_TYPE::VIDEO);
836
winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::INITIALIZE, nullptr });
837
winMic = new WindowsCaptureDevice(CAPTUREDEVIDE_TYPE::Audio);
838
winMic->sendMessage({ CAPTUREDEVIDE_COMMAND::INITIALIZE, nullptr });
839
}
840
#endif
841
842
// Warn about low refresh rates on desktop. Might add other platforms later.
843
#if PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(MAC)
844
const double displayHz = System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE);
845
if (displayHz < 55.0f) {
846
// This is a warning, not an error.
847
auto g = GetI18NCategory(I18NCat::GRAPHICS);
848
g_OSD.Show(OSDType::MESSAGE_WARNING, ApplySafeSubstitutions(g->T("Your display is set to a low refresh rate: %1 Hz. 60 Hz or higher is recommended."), (int)displayHz), 8.0f, "low_refresh");
849
g_OSD.SetClickCallback("low_refresh", [](bool clicked, void *) {
850
if (clicked) {
851
// Open the display settings.
852
System_OpenDisplaySettings();
853
}
854
}, nullptr);
855
}
856
#endif
857
858
g_gameInfoCache = new GameInfoCache();
859
860
if (gpu) {
861
PSP_CoreParameter().pixelWidth = g_display.pixel_xres;
862
PSP_CoreParameter().pixelHeight = g_display.pixel_yres;
863
gpu->DeviceRestore(g_draw);
864
}
865
866
INFO_LOG(Log::System, "NativeInitGraphics completed");
867
868
return true;
869
}
870
871
bool CreateGlobalPipelines() {
872
using namespace Draw;
873
874
ShaderModule *vs_color_2d = g_draw->GetVshaderPreset(VS_COLOR_2D);
875
ShaderModule *fs_color_2d = g_draw->GetFshaderPreset(FS_COLOR_2D);
876
ShaderModule *vs_texture_color_2d = g_draw->GetVshaderPreset(VS_TEXTURE_COLOR_2D);
877
ShaderModule *fs_texture_color_2d = g_draw->GetFshaderPreset(FS_TEXTURE_COLOR_2D);
878
879
if (!vs_color_2d || !fs_color_2d || !vs_texture_color_2d || !fs_texture_color_2d) {
880
ERROR_LOG(Log::G3D, "Failed to get shader preset");
881
return false;
882
}
883
884
InputLayout *inputLayout = ui_draw2d.CreateInputLayout(g_draw);
885
BlendState *blendNormal = g_draw->CreateBlendState({ true, 0xF, BlendFactor::ONE, BlendFactor::ONE_MINUS_SRC_ALPHA });
886
DepthStencilState *depth = g_draw->CreateDepthStencilState({ false, false, Comparison::LESS });
887
RasterState *rasterNoCull = g_draw->CreateRasterState({});
888
889
PipelineDesc colorDesc{
890
Primitive::TRIANGLE_LIST,
891
{ vs_color_2d, fs_color_2d },
892
inputLayout, depth, blendNormal, rasterNoCull, &vsColBufDesc,
893
};
894
PipelineDesc texColorDesc{
895
Primitive::TRIANGLE_LIST,
896
{ vs_texture_color_2d, fs_texture_color_2d },
897
inputLayout, depth, blendNormal, rasterNoCull, &vsTexColBufDesc,
898
};
899
900
colorPipeline = g_draw->CreateGraphicsPipeline(colorDesc, "global_color");
901
if (!colorPipeline) {
902
// Something really critical is wrong, don't care much about correct releasing of the states.
903
return false;
904
}
905
906
texColorPipeline = g_draw->CreateGraphicsPipeline(texColorDesc, "global_texcolor");
907
if (!texColorPipeline) {
908
// Something really critical is wrong, don't care much about correct releasing of the states.
909
return false;
910
}
911
912
// Release these now, reference counting should ensure that they get completely released
913
// once we delete both pipelines.
914
inputLayout->Release();
915
rasterNoCull->Release();
916
blendNormal->Release();
917
depth->Release();
918
return true;
919
}
920
921
void NativeShutdownGraphics() {
922
INFO_LOG(Log::System, "NativeShutdownGraphics begin");
923
924
if (g_screenManager) {
925
g_screenManager->deviceLost();
926
}
927
g_iconCache.ClearTextures();
928
929
// TODO: This is not really necessary with Vulkan on Android - could keep shaders etc in memory
930
if (gpu)
931
gpu->DeviceLost();
932
933
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
934
if (winCamera) {
935
winCamera->waitShutDown();
936
delete winCamera;
937
winCamera = nullptr;
938
}
939
if (winMic) {
940
winMic->waitShutDown();
941
delete winMic;
942
winMic = nullptr;
943
}
944
#endif
945
946
if (g_audioBackend) {
947
delete g_audioBackend;
948
g_audioBackend = nullptr;
949
}
950
951
UIBackgroundShutdown();
952
953
delete g_gameInfoCache;
954
g_gameInfoCache = nullptr;
955
956
delete uiContext;
957
uiContext = nullptr;
958
959
ui_draw2d.Shutdown();
960
961
if (colorPipeline) {
962
colorPipeline->Release();
963
colorPipeline = nullptr;
964
}
965
if (texColorPipeline) {
966
texColorPipeline->Release();
967
texColorPipeline = nullptr;
968
}
969
970
INFO_LOG(Log::System, "NativeShutdownGraphics end");
971
}
972
973
static void TakeScreenshot(Draw::DrawContext *draw) {
974
Path path = GetSysDirectory(DIRECTORY_SCREENSHOT);
975
if (!File::Exists(path)) {
976
File::CreateDir(path);
977
}
978
979
// First, find a free filename.
980
//
981
// NOTE: On Android, the old approach of checking filenames one by one doesn't scale.
982
// So let's just grab the full file listing, and then find a name that's not in it.
983
//
984
// TODO: Also, we could do this on a thread too. Not sure if worth it.
985
986
const std::string gameId = g_paramSFO.GetDiscID();
987
988
// TODO: Make something like IterateFileInDir instead.
989
std::vector<File::FileInfo> files;
990
const std::string prefix = gameId + "_";
991
File::GetFilesInDir(path, &files, nullptr, 0, prefix);
992
std::set<std::string> existingNames;
993
for (auto &file : files) {
994
existingNames.insert(file.name);
995
}
996
997
Path filename;
998
int i = 0;
999
for (int i = 0; i < 20000; i++) {
1000
const std::string pngName = prefix + StringFromFormat("%05d.png", i);
1001
const std::string jpgName = prefix + StringFromFormat("%05d.jpg", i);
1002
if (existingNames.find(pngName) == existingNames.end() && existingNames.find(jpgName) == existingNames.end()) {
1003
filename = path / (g_Config.bScreenshotsAsPNG ? pngName : jpgName);
1004
break;
1005
}
1006
}
1007
1008
if (filename.empty()) {
1009
// Overwrite this one over and over.
1010
filename = path / (prefix + (g_Config.bScreenshotsAsPNG ? "20000.png" : "20000.jpg"));
1011
}
1012
1013
const ScreenshotType type = g_Config.iScreenshotMode == (int)ScreenshotMode::GameImage ? SCREENSHOT_DISPLAY : SCREENSHOT_OUTPUT;
1014
1015
const ScreenshotResult result = TakeGameScreenshot(draw, filename, g_Config.bScreenshotsAsPNG ? ScreenshotFormat::PNG : ScreenshotFormat::JPG, type, -1, [filename](bool success) {
1016
if (success) {
1017
g_OSD.Show(OSDType::MESSAGE_FILE_LINK, filename.ToVisualString(), 0.0f, "screenshot_link");
1018
if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) {
1019
g_OSD.SetClickCallback("screenshot_link", [](bool clicked, void *data) -> void {
1020
Path *path = reinterpret_cast<Path *>(data);
1021
if (clicked) {
1022
System_ShowFileInFolder(*path);
1023
} else {
1024
delete path;
1025
}
1026
}, new Path(filename));
1027
}
1028
} else {
1029
auto err = GetI18NCategory(I18NCat::ERRORS);
1030
g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Could not save screenshot file"));
1031
WARN_LOG(Log::System, "Failed to take screenshot.");
1032
}
1033
});
1034
}
1035
1036
void CallbackPostRender(UIContext *dc, void *userdata) {
1037
if (g_TakeScreenshot) {
1038
TakeScreenshot(dc->GetDrawContext());
1039
g_TakeScreenshot = false;
1040
}
1041
}
1042
1043
static void SendMouseDeltaAxis();
1044
1045
void NativeFrame(GraphicsContext *graphicsContext) {
1046
PROFILE_END_FRAME();
1047
1048
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_DESKTOP) {
1049
if (g_windowHidden && g_Config.bPauseWhenMinimized) {
1050
sleep_ms(16, "window-hidden");
1051
return;
1052
}
1053
}
1054
1055
// This can only be accessed from Windows currently, and causes linking errors with headless etc.
1056
if (g_restartGraphics == 1) {
1057
// Used for debugging only.
1058
NativeShutdownGraphics();
1059
g_restartGraphics++;
1060
return;
1061
}
1062
else if (g_restartGraphics == 2) {
1063
NativeInitGraphics(graphicsContext);
1064
g_restartGraphics = 0;
1065
}
1066
1067
double startTime = time_now_d();
1068
1069
ProcessWheelRelease(NKCODE_EXT_MOUSEWHEEL_UP, startTime, false);
1070
ProcessWheelRelease(NKCODE_EXT_MOUSEWHEEL_DOWN, startTime, false);
1071
1072
SetOverrideScreenFrame(nullptr);
1073
1074
// it's ok to call this redundantly with DoFrame from EmuScreen
1075
Achievements::Idle();
1076
1077
g_DownloadManager.Update();
1078
1079
g_Discord.Update();
1080
1081
g_OSD.Update();
1082
1083
_dbg_assert_(graphicsContext != nullptr);
1084
_dbg_assert_(g_screenManager != nullptr);
1085
1086
g_GameManager.Update();
1087
1088
if (GetUIState() != UISTATE_INGAME) {
1089
// Note: We do this from NativeFrame so that the graphics context is
1090
// guaranteed valid, to be safe - g_gameInfoCache messes around with textures.
1091
g_BackgroundAudio.Update();
1092
}
1093
1094
g_iconCache.FrameUpdate();
1095
1096
g_screenManager->update();
1097
1098
if (g_audioBackend) {
1099
g_audioBackend->FrameUpdate(g_Config.bAutoAudioDevice);
1100
}
1101
1102
// Do this after g_screenManager.update() so we can receive setting changes before rendering.
1103
{
1104
std::vector<PendingMessage> toProcess;
1105
std::vector<std::function<void()>> toRun;
1106
{
1107
std::lock_guard<std::mutex> lock(g_pendingMutex);
1108
toProcess = std::move(pendingMessages);
1109
toRun = std::move(g_pendingClosures);
1110
pendingMessages.clear();
1111
g_pendingClosures.clear();
1112
}
1113
1114
for (auto &item : toRun) {
1115
item();
1116
}
1117
1118
for (const auto &item : toProcess) {
1119
// Hack.
1120
if (item.message == UIMessage::WINDOW_RESTORED && graphicsContext) {
1121
graphicsContext->NotifyWindowRestored();
1122
}
1123
1124
if (HandleGlobalMessage(item.message, item.value)) {
1125
// TODO: Add a to-string thingy.
1126
VERBOSE_LOG(Log::System, "Handled global message: %d / %s", (int)item.message, item.value.c_str());
1127
}
1128
g_screenManager->sendMessage(item.message, item.value.c_str());
1129
}
1130
}
1131
1132
g_requestManager.ProcessRequests();
1133
1134
g_breakpoints.Frame();
1135
1136
// Apply the UIContext bounds as a 2D transformation matrix.
1137
Matrix4x4 ortho = ComputeOrthoMatrix(g_display.dp_xres, g_display.dp_yres, graphicsContext->GetDrawContext()->GetDeviceCaps().coordConvention);
1138
1139
Draw::DebugFlags debugFlags = Draw::DebugFlags::NONE;
1140
if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::GPU_PROFILE)
1141
debugFlags |= Draw::DebugFlags::PROFILE_TIMESTAMPS;
1142
if (g_Config.bGpuLogProfiler)
1143
debugFlags |= Draw::DebugFlags::PROFILE_SCOPES;
1144
1145
// Can be overridden by sceDisplay which may pass true for the second argument.
1146
g_frameTiming.ComputePresentMode(g_draw, false);
1147
1148
g_draw->BeginFrame(debugFlags);
1149
1150
ui_draw2d.PushDrawMatrix(ortho);
1151
1152
g_screenManager->getUIContext()->SetTintSaturation(g_Config.fUITint, g_Config.fUISaturation);
1153
1154
// All actual rendering happen in here.
1155
ScreenRenderFlags renderFlags = g_screenManager->render();
1156
if (g_screenManager->getUIContext()->Text()) {
1157
g_screenManager->getUIContext()->Text()->OncePerFrame();
1158
}
1159
1160
ui_draw2d.PopDrawMatrix();
1161
1162
g_draw->EndFrame();
1163
1164
// This, between EndFrame and Present, is where we should actually wait to do present time management.
1165
// There might not be a meaningful distinction here for all backends..
1166
g_frameTiming.PostSubmit();
1167
1168
if (renderCounter < 10 && ++renderCounter == 10) {
1169
// We're rendering fine, clear out failure info.
1170
ClearFailedGPUBackends();
1171
}
1172
1173
g_draw->Present(g_frameTiming.PresentMode());
1174
1175
if (resized) {
1176
INFO_LOG(Log::G3D, "Resized flag set - recalculating bounds");
1177
resized = false;
1178
1179
if (uiContext) {
1180
// Modifying the bounds here can be used to "inset" the whole image to gain borders for TV overscan etc.
1181
// The UI now supports any offset but not the EmuScreen yet.
1182
uiContext->SetBounds(Bounds(0, 0, g_display.dp_xres, g_display.dp_yres));
1183
1184
// OSX 10.6 and SDL 1.2 bug.
1185
#if defined(__APPLE__) && !defined(USING_QT_UI)
1186
static int dp_xres_old = g_display.dp_xres;
1187
if (g_display.dp_xres != dp_xres_old) {
1188
dp_xres_old = g_display.dp_xres;
1189
}
1190
#endif
1191
}
1192
1193
graphicsContext->Resize();
1194
g_screenManager->resized();
1195
1196
// TODO: Move this to the GraphicsContext objects for each backend.
1197
#if !PPSSPP_PLATFORM(WINDOWS) && !defined(ANDROID)
1198
PSP_CoreParameter().pixelWidth = g_display.pixel_xres;
1199
PSP_CoreParameter().pixelHeight = g_display.pixel_yres;
1200
System_PostUIMessage(UIMessage::GPU_DISPLAY_RESIZED);
1201
#endif
1202
} else {
1203
// INFO_LOG(Log::G3D, "Polling graphics context");
1204
graphicsContext->Poll();
1205
}
1206
1207
SendMouseDeltaAxis();
1208
1209
if (!(renderFlags & ScreenRenderFlags::HANDLED_THROTTLING)) {
1210
// TODO: We should ideally mix this with game audio.
1211
g_BackgroundAudio.Play();
1212
1213
float refreshRate = System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE);
1214
static double lastTime = 0.0;
1215
if (lastTime > 0.0) {
1216
double now = time_now_d();
1217
// Simple throttling to not burn the GPU in the menu.
1218
// TODO: This should move into NativeFrame.
1219
double diffTime = now - lastTime;
1220
int sleepTimeUs = (int)(1000000 * ((1.0 / refreshRate) - diffTime));
1221
// printf("sleep: %0.3f ms (diff: %0.3f) %f\n", (double)sleepTimeUs / 1000, diffTime * 1000.0, refreshRate);
1222
1223
// If presentation mode is FIFO, we don't need to sleep a lot, we'll be throttled by
1224
// presentation. But still, let's sleep a bit.
1225
// Actually, for some reason this increases latency a lot, to the degree that the UI
1226
// gets hard to use.. Commenting out for now.
1227
// if (g_frameTiming.PresentMode() == Draw::PresentMode::FIFO) {
1228
// sleepTimeUs = std::min(2000, sleepTimeUs); // 2 ms
1229
// }
1230
1231
if (sleepTimeUs > 0)
1232
sleep_us(sleepTimeUs, "fallback-throttle");
1233
}
1234
lastTime = time_now_d();
1235
}
1236
}
1237
1238
bool HandleGlobalMessage(UIMessage message, const std::string &value) {
1239
if (message == UIMessage::RESTART_GRAPHICS) {
1240
g_restartGraphics = 1;
1241
return true;
1242
} else if (message == UIMessage::SAVESTATE_DISPLAY_SLOT) {
1243
auto sy = GetI18NCategory(I18NCat::SYSTEM);
1244
std::string msg = StringFromFormat("%s: %d", sy->T_cstr("Savestate Slot"), SaveState::GetCurrentSlot() + 1);
1245
// Show for the same duration as the preview.
1246
g_OSD.Show(OSDType::MESSAGE_INFO, msg, 2.0f, "savestate_slot");
1247
return true;
1248
}
1249
else if (message == UIMessage::GPU_DISPLAY_RESIZED) {
1250
if (gpu) {
1251
gpu->NotifyDisplayResized();
1252
}
1253
return true;
1254
}
1255
else if (message == UIMessage::GPU_RENDER_RESIZED) {
1256
if (gpu) {
1257
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
1258
gpu->NotifyRenderResized(config);
1259
}
1260
return true;
1261
}
1262
else if (message == UIMessage::GPU_CONFIG_CHANGED) {
1263
if (gpu) {
1264
gpu->NotifyConfigChanged();
1265
}
1266
Reporting::UpdateConfig();
1267
return true;
1268
}
1269
else if (message == UIMessage::POWER_SAVING) {
1270
if (value != "false") {
1271
auto sy = GetI18NCategory(I18NCat::SYSTEM);
1272
#if PPSSPP_PLATFORM(ANDROID)
1273
g_OSD.Show(OSDType::MESSAGE_WARNING, sy->T("WARNING: Android battery save mode is on"), 2.0f, "core_powerSaving");
1274
#else
1275
g_OSD.Show(OSDType::MESSAGE_WARNING, sy->T("WARNING: Battery save mode is on"), 2.0f, "core_powerSaving");
1276
#endif
1277
}
1278
Core_SetPowerSaving(value != "false");
1279
return true;
1280
}
1281
else if (message == UIMessage::PERMISSION_GRANTED && value == "storage") {
1282
CreateSysDirectories();
1283
// We must have failed to load the config before, so load it now to avoid overwriting the old config
1284
// with a freshly generated one.
1285
// NOTE: If graphics backend isn't what's in the config (due to error fallback, or not matching the default
1286
// and then getting permission), it will get out of sync. So we save and restore g_Config.iGPUBackend.
1287
// Ideally we should simply reinitialize graphics to the mode from the config, but there are potential issues.
1288
int gpuBackend = g_Config.iGPUBackend;
1289
INFO_LOG(Log::IO, "Reloading config after storage permission grant.");
1290
g_Config.Reload();
1291
PostLoadConfig();
1292
g_Config.iGPUBackend = gpuBackend;
1293
return true;
1294
} else if (message == UIMessage::APP_RESUMED || message == UIMessage::GOT_FOCUS) {
1295
// Assume that the user may have modified things.
1296
MemoryStick_NotifyWrite();
1297
return true;
1298
} else if (message == UIMessage::SAVE_FRAME_DUMP) {
1299
SaveFrameDump();
1300
return true;
1301
} else {
1302
return false;
1303
}
1304
}
1305
1306
bool NativeIsAtTopLevel() {
1307
// This might need some synchronization?
1308
if (!g_screenManager) {
1309
ERROR_LOG(Log::System, "No screen manager active");
1310
return false;
1311
}
1312
Screen *currentScreen = g_screenManager->topScreen();
1313
if (currentScreen) {
1314
bool top = currentScreen->isTopLevel();
1315
return currentScreen->isTopLevel();
1316
} else {
1317
ERROR_LOG(Log::System, "No current screen");
1318
return false;
1319
}
1320
}
1321
1322
void NativeTouch(const TouchInput &touch) {
1323
if (!g_screenManager) {
1324
return;
1325
}
1326
1327
// Brute force prevent NaNs from getting into the UI system.
1328
// Don't think this is actually necessary in practice.
1329
if (my_isnan(touch.x) || my_isnan(touch.y)) {
1330
return;
1331
}
1332
g_screenManager->touch(touch);
1333
}
1334
1335
// up, down
1336
static double g_wheelReleaseTime[2]{};
1337
1338
static void ProcessWheelRelease(InputKeyCode keyCode, double now, bool keyPress) {
1339
int dir = keyCode - NKCODE_EXT_MOUSEWHEEL_UP;
1340
if (g_wheelReleaseTime[dir] != 0.0 && (keyPress || now >= g_wheelReleaseTime[dir])) {
1341
g_wheelReleaseTime[dir] = 0.0;
1342
KeyInput key{};
1343
key.deviceId = DEVICE_ID_MOUSE;
1344
key.keyCode = keyCode;
1345
key.flags = KeyInputFlags::UP;
1346
NativeKey(key);
1347
}
1348
1349
if (keyPress) {
1350
float releaseTime = (float)g_Config.iMouseWheelUpDelayMs * (1.0f / 1000.0f);
1351
g_wheelReleaseTime[dir] = now + releaseTime;
1352
}
1353
}
1354
1355
bool NativeKey(const KeyInput &key) {
1356
double now = time_now_d();
1357
1358
// VR actions
1359
if ((IsVREnabled() || g_Config.bForceVR) && !UpdateVRKeys(key)) {
1360
return false;
1361
}
1362
1363
#if PPSSPP_PLATFORM(UWP)
1364
// Ignore if key sent from OnKeyDown/OnKeyUp/XInput while text edit active
1365
// it's already handled by `OnCharacterReceived`
1366
if (IgnoreInput(key.keyCode) && !(key.flags & KeyInputFlags::CHAR)) {
1367
return false;
1368
}
1369
#endif
1370
1371
// INFO_LOG(Log::System, "Key code: %i flags: %i", key.keyCode, key.flags);
1372
#if !defined(MOBILE_DEVICE)
1373
if (g_Config.bPauseExitsEmulator) {
1374
std::vector<int> pspKeys;
1375
pspKeys.clear();
1376
if (KeyMap::InputMappingToPspButton(InputMapping(key.deviceId, key.keyCode), &pspKeys)) {
1377
if (std::find(pspKeys.begin(), pspKeys.end(), VIRTKEY_PAUSE) != pspKeys.end()) {
1378
System_ExitApp();
1379
return true;
1380
}
1381
}
1382
}
1383
#endif
1384
1385
#ifdef _DEBUG
1386
// Debug hack: Randomize the language with F9!
1387
if ((key.keyCode == NKCODE_F9 && (key.flags & KeyInputFlags::DOWN))) {
1388
std::vector<File::FileInfo> tempLangs;
1389
g_VFS.GetFileListing("lang", &tempLangs, "ini");
1390
int x = rand() % tempLangs.size();
1391
std::string_view code, part2;
1392
if (SplitStringOnce(tempLangs[x].name, &code, &part2, '.')) {
1393
g_Config.sLanguageIni = code;
1394
INFO_LOG(Log::System, "Switching to random language: %s", g_Config.sLanguageIni.c_str());
1395
if (g_i18nrepo.LoadIni(g_Config.sLanguageIni)) {
1396
g_screenManager->RecreateAllViews();
1397
System_Notify(SystemNotification::UI);
1398
}
1399
}
1400
}
1401
#endif
1402
1403
if (!g_screenManager) {
1404
return false;
1405
}
1406
1407
// Handle releases of mousewheel keys.
1408
if ((key.flags & KeyInputFlags::DOWN) && key.deviceId == DEVICE_ID_MOUSE && (key.keyCode == NKCODE_EXT_MOUSEWHEEL_UP || key.keyCode == NKCODE_EXT_MOUSEWHEEL_DOWN)) {
1409
ProcessWheelRelease(key.keyCode, now, true);
1410
}
1411
1412
HLEPlugins::SetKey(key.keyCode, (key.flags & KeyInputFlags::DOWN) ? 1 : 0);
1413
// Dispatch the key event.
1414
bool retval = g_screenManager->key(key);
1415
1416
// The Mode key can have weird consequences on some devices, see #17245.
1417
if (key.keyCode == NKCODE_BUTTON_MODE) {
1418
// Tell the caller that we handled the key.
1419
retval = true;
1420
}
1421
1422
return retval;
1423
}
1424
1425
void NativeAxis(const AxisInput *axes, size_t count) {
1426
// VR actions
1427
if ((IsVREnabled() || g_Config.bForceVR) && !UpdateVRAxis(axes, count)) {
1428
return;
1429
}
1430
1431
if (!g_screenManager) {
1432
// Too early.
1433
return;
1434
}
1435
1436
g_screenManager->axis(axes, count);
1437
1438
for (size_t i = 0; i < count; i++) {
1439
const AxisInput &axis = axes[i];
1440
HLEPlugins::PluginDataAxis[axis.axisId] = axis.value;
1441
}
1442
}
1443
1444
// Called from NativeFrame and from NativeMouseDelta.
1445
static void SendMouseDeltaAxis() {
1446
float mx, my;
1447
MouseEventProcessor::MouseDeltaToAxes(time_now_d(), &mx, &my);
1448
1449
AxisInput axis[2];
1450
axis[0].axisId = JOYSTICK_AXIS_MOUSE_REL_X;
1451
axis[0].deviceId = DEVICE_ID_MOUSE;
1452
axis[0].value = mx;
1453
axis[1].axisId = JOYSTICK_AXIS_MOUSE_REL_Y;
1454
axis[1].deviceId = DEVICE_ID_MOUSE;
1455
axis[1].value = my;
1456
1457
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_X] = mx;
1458
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_Y] = my;
1459
1460
//NOTICE_LOG(Log::System, "delta: %0.2f %0.2f mx/my: %0.2f %0.2f dpi: %f sens: %f ",
1461
// g_mouseDeltaX, g_mouseDeltaY, mx, my, g_display.dpi_scale_x, g_Config.fMouseSensitivity);
1462
1463
if (GetUIState() == UISTATE_INGAME || g_IsMappingMouseInput) {
1464
NativeAxis(axis, 2);
1465
}
1466
}
1467
1468
void NativeMouseDelta(float dx, float dy) {
1469
if (!g_Config.bMouseControl)
1470
return;
1471
1472
// Remap, shared code. Then send it as a regular axis event.
1473
MouseEventProcessor::ProcessDelta(time_now_d(), dx, dy);
1474
1475
SendMouseDeltaAxis();
1476
}
1477
1478
// TODO: Should include a device ID here, since accelerometers can be on pads for example (DualSense).
1479
void NativeAccelerometer(float tiltX, float tiltY, float tiltZ) {
1480
if (g_Config.iTiltInputType == TILT_NULL) {
1481
// if tilt events are disabled, don't do anything special.
1482
return;
1483
}
1484
1485
// create the base coordinate tilt system from the calibration data.
1486
float tiltBaseAngleY = g_Config.fTiltBaseAngleY;
1487
1488
// Figure out the sensitivity of the tilt. (sensitivity is originally 0 - 100)
1489
// We divide by 50, so that the rest of the 50 units can be used to overshoot the
1490
// target. If you want precise control, you'd keep the sensitivity ~50.
1491
// For games that don't need much control but need fast reactions,
1492
// then a value of 70-80 is the way to go.
1493
float xSensitivity = g_Config.iTiltSensitivityX / 50.0;
1494
float ySensitivity = g_Config.iTiltSensitivityY / 50.0;
1495
1496
// x and y are flipped if we are in landscape orientation. The events are
1497
// sent with respect to the portrait coordinate system, while we
1498
// take all events in landscape.
1499
// see [http://developer.android.com/guide/topics/sensors/sensors_overview.html] for details
1500
bool landscape = g_display.dp_yres < g_display.dp_xres;
1501
// now transform out current tilt to the calibrated coordinate system
1502
TiltEventProcessor::ProcessTilt(landscape, tiltBaseAngleY, tiltX, tiltY, tiltZ,
1503
g_Config.bInvertTiltX, g_Config.bInvertTiltY,
1504
xSensitivity, ySensitivity);
1505
1506
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_ACCELEROMETER_X] = tiltX;
1507
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_ACCELEROMETER_Y] = tiltY;
1508
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_ACCELEROMETER_Z] = tiltZ;
1509
}
1510
1511
void System_PostUIMessage(UIMessage message, std::string_view param) {
1512
std::lock_guard<std::mutex> lock(g_pendingMutex);
1513
PendingMessage pendingMessage;
1514
pendingMessage.message = message;
1515
pendingMessage.value = param;
1516
pendingMessages.push_back(pendingMessage);
1517
}
1518
1519
void System_RunOnMainThread(std::function<void()> func) {
1520
std::lock_guard<std::mutex> lock(g_pendingMutex);
1521
g_pendingClosures.push_back(std::move(func));
1522
}
1523
1524
void NativeResized() {
1525
// NativeResized can come from any thread so we just set a flag, then process it later.
1526
VERBOSE_LOG(Log::G3D, "NativeResized - setting flag");
1527
resized = true;
1528
}
1529
1530
void NativeSetRestarting() {
1531
restarting = true;
1532
}
1533
1534
bool NativeIsRestarting() {
1535
return restarting;
1536
}
1537
1538
void NativeShutdown() {
1539
INFO_LOG(Log::System, "NativeShutdown begin");
1540
1541
Achievements::Shutdown();
1542
1543
if (g_Config.bAchievementsEnable) {
1544
FILE *iconCacheFile = File::OpenCFile(GetSysDirectory(DIRECTORY_CACHE) / "icon.cache", "wb");
1545
if (iconCacheFile) {
1546
g_iconCache.SaveToFile(iconCacheFile);
1547
fclose(iconCacheFile);
1548
}
1549
}
1550
1551
if (g_screenManager) {
1552
g_screenManager->shutdown();
1553
delete g_screenManager;
1554
g_screenManager = nullptr;
1555
}
1556
1557
g_Config.Save("NativeShutdown");
1558
1559
g_i18nrepo.LogMissingKeys();
1560
1561
ShutdownWebServer();
1562
1563
__UPnPShutdown();
1564
1565
g_PortManager.Shutdown();
1566
1567
net::Shutdown();
1568
1569
g_Discord.Shutdown();
1570
1571
ShaderTranslationShutdown();
1572
1573
// Avoid shutting this down when restarting core.
1574
if (!restarting) {
1575
g_logManager.Shutdown();
1576
}
1577
1578
g_threadManager.Teardown();
1579
1580
#if !PPSSPP_PLATFORM(IOS)
1581
System_ExitApp();
1582
#endif
1583
1584
// Previously we did exit() here on Android but that makes it hard to do things like restart on backend change.
1585
// I think we handle most globals correctly or correct-enough now.
1586
INFO_LOG(Log::System, "NativeShutdown end");
1587
}
1588
1589
// In the future, we might make this more sophisticated, such as storing in the app private directory on Android.
1590
// Right now we just store secrets in separate files next to ppsspp.ini. The important thing is keeping them out of it
1591
// since we often ask people to post or send the ini for debugging.
1592
static Path GetSecretPath(std::string_view nameOfSecret) {
1593
return GetSysDirectory(DIRECTORY_SYSTEM) / ("ppsspp_" + std::string(nameOfSecret) + ".dat");
1594
}
1595
1596
// name should be simple alphanumerics to avoid problems on Windows.
1597
bool NativeSaveSecret(std::string_view nameOfSecret, std::string_view data) {
1598
Path path = GetSecretPath(nameOfSecret);
1599
if (data.empty() && File::Exists(path)) {
1600
return File::Delete(path);
1601
} else if (!File::WriteDataToFile(false, data.data(), data.size(), path)) {
1602
WARN_LOG(Log::System, "Failed to write secret '%.*s' to path '%s'", (int)nameOfSecret.size(), nameOfSecret.data(), path.c_str());
1603
return false;
1604
}
1605
return true;
1606
}
1607
1608
// On failure, returns an empty string. Good enough since any real secret is non-empty.
1609
std::string NativeLoadSecret(std::string_view nameOfSecret) {
1610
Path path = GetSecretPath(nameOfSecret);
1611
std::string data;
1612
if (!File::ReadBinaryFileToString(path, &data)) {
1613
data.clear(); // just to be sure.
1614
}
1615
return data;
1616
}
1617
1618
void Native_NotifyWindowHidden(bool hidden) {
1619
g_windowHidden = hidden;
1620
// TODO: Wait until we can react?
1621
}
1622
1623
bool Native_IsWindowHidden() {
1624
return g_windowHidden;
1625
}
1626
1627
static bool IsWindowSmall(int pixelWidth, int pixelHeight) {
1628
if (!g_Config.bShrinkIfWindowSmall) {
1629
return false;
1630
}
1631
1632
// Can't take this from config as it will not be set if windows is maximized.
1633
int w = (int)(pixelWidth * g_display.dpi_scale_real_x);
1634
int h = (int)(pixelHeight * g_display.dpi_scale_real_y);
1635
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
1636
return config.InternalRotationIsPortrait() ? (h < 480 + 80) : (w < 480 + 80);
1637
}
1638
1639
bool Native_UpdateScreenScale(int pixel_width, int pixel_height, float customScale) {
1640
_dbg_assert_(customScale > 0.1f);
1641
float g_logical_dpi = System_GetPropertyFloat(SYSPROP_DISPLAY_LOGICAL_DPI);
1642
float dpi = System_GetPropertyFloat(SYSPROP_DISPLAY_DPI);
1643
1644
if (dpi < 0.0f) {
1645
dpi = 96.0f;
1646
}
1647
if (g_logical_dpi < 0.0f) {
1648
g_logical_dpi = 96.0f;
1649
}
1650
1651
bool smallWindow = IsWindowSmall(pixel_width, pixel_height);
1652
if (smallWindow) {
1653
customScale *= 0.5f;
1654
} else {
1655
customScale = UIScaleFactorToMultiplier(g_Config.iUIScaleFactor);
1656
}
1657
1658
if (g_display.Recalculate(pixel_width, pixel_height, g_logical_dpi / dpi, g_logical_dpi / dpi, customScale)) {
1659
NativeResized();
1660
return true;
1661
} else {
1662
return false;
1663
}
1664
}
1665
1666