CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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