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/SDL/SDLMain.cpp
Views: 1401
1
// SDL/EGL implementation of the framework.
2
// This is quite messy due to platform-specific implementations and #ifdef's.
3
// If your platform is not supported, it is suggested to use Qt instead.
4
5
#include <cstdlib>
6
#include <unistd.h>
7
#include <pwd.h>
8
9
#include "ppsspp_config.h"
10
#if PPSSPP_PLATFORM(MAC)
11
#include "SDL2/SDL.h"
12
#include "SDL2/SDL_syswm.h"
13
#else
14
#include "SDL.h"
15
#include "SDL_syswm.h"
16
#endif
17
#include "SDL/SDLJoystick.h"
18
SDLJoystick *joystick = NULL;
19
20
#if PPSSPP_PLATFORM(RPI)
21
#include <bcm_host.h>
22
#endif
23
24
#include <atomic>
25
#include <algorithm>
26
#include <cmath>
27
#include <csignal>
28
#include <thread>
29
#include <locale>
30
31
#include "Common/System/Display.h"
32
#include "Common/System/System.h"
33
#include "Common/System/Request.h"
34
#include "Common/System/NativeApp.h"
35
#include "ext/glslang/glslang/Public/ShaderLang.h"
36
#include "Common/Data/Format/PNGLoad.h"
37
#include "Common/Net/Resolve.h"
38
#include "Common/File/FileUtil.h"
39
#include "NKCodeFromSDL.h"
40
#include "Common/Math/math_util.h"
41
#include "Common/GPU/OpenGL/GLRenderManager.h"
42
#include "Common/Profiler/Profiler.h"
43
44
#if defined(VK_USE_PLATFORM_XLIB_KHR)
45
#include <X11/Xlib.h>
46
#include <X11/Xutil.h>
47
#elif defined(VK_USE_PLATFORM_XCB_KHR)
48
#include <X11/Xlib.h>
49
#include <X11/Xutil.h>
50
#include <X11/Xlib-xcb.h>
51
#endif
52
53
#include "Common/GraphicsContext.h"
54
#include "Common/TimeUtil.h"
55
#include "Common/Input/InputState.h"
56
#include "Common/Input/KeyCodes.h"
57
#include "Common/Data/Collections/ConstMap.h"
58
#include "Common/Data/Encoding/Utf8.h"
59
#include "Common/Thread/ThreadUtil.h"
60
#include "Core/System.h"
61
#include "Core/Core.h"
62
#include "Core/Config.h"
63
#include "Core/ConfigValues.h"
64
#include "SDLGLGraphicsContext.h"
65
#include "SDLVulkanGraphicsContext.h"
66
67
#if PPSSPP_PLATFORM(MAC)
68
#include "SDL2/SDL_vulkan.h"
69
#else
70
#include "SDL_vulkan.h"
71
#endif
72
73
#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
74
#include "UI/DarwinFileSystemServices.h"
75
#endif
76
77
#if PPSSPP_PLATFORM(MAC)
78
#include "CocoaBarItems.h"
79
#endif
80
81
#if PPSSPP_PLATFORM(SWITCH)
82
#define LIBNX_SWKBD_LIMIT 500 // enforced by HOS
83
extern u32 __nx_applet_type; // Not exposed through a header?
84
#endif
85
86
GlobalUIState lastUIState = UISTATE_MENU;
87
GlobalUIState GetUIState();
88
89
static bool g_QuitRequested = false;
90
static bool g_RestartRequested = false;
91
92
static int g_DesktopWidth = 0;
93
static int g_DesktopHeight = 0;
94
static float g_DesktopDPI = 1.0f;
95
static float g_ForcedDPI = 0.0f; // if this is 0.0f, use g_DesktopDPI
96
static float g_RefreshRate = 60.f;
97
static int g_sampleRate = 44100;
98
99
static bool g_rebootEmuThread = false;
100
101
static SDL_AudioSpec g_retFmt;
102
103
static bool g_textFocusChanged;
104
static bool g_textFocus;
105
106
107
// Window state to be transferred to the main SDL thread.
108
static std::mutex g_mutexWindow;
109
struct WindowState {
110
std::string title;
111
bool toggleFullScreenNextFrame;
112
int toggleFullScreenType;
113
bool clipboardDataAvailable;
114
std::string clipboardString;
115
bool update;
116
};
117
static WindowState g_windowState;
118
119
int getDisplayNumber(void) {
120
int displayNumber = 0;
121
char * displayNumberStr;
122
123
//get environment
124
displayNumberStr=getenv("SDL_VIDEO_FULLSCREEN_HEAD");
125
126
if (displayNumberStr) {
127
displayNumber = atoi(displayNumberStr);
128
}
129
130
return displayNumber;
131
}
132
133
void sdl_mixaudio_callback(void *userdata, Uint8 *stream, int len) {
134
NativeMix((short *)stream, len / (2 * 2), g_sampleRate);
135
}
136
137
static SDL_AudioDeviceID audioDev = 0;
138
139
// Must be called after NativeInit().
140
static void InitSDLAudioDevice(const std::string &name = "") {
141
SDL_AudioSpec fmt;
142
memset(&fmt, 0, sizeof(fmt));
143
fmt.freq = g_sampleRate;
144
fmt.format = AUDIO_S16;
145
fmt.channels = 2;
146
fmt.samples = 256;
147
fmt.callback = &sdl_mixaudio_callback;
148
fmt.userdata = nullptr;
149
150
std::string startDevice = name;
151
if (startDevice.empty()) {
152
startDevice = g_Config.sAudioDevice;
153
}
154
155
audioDev = 0;
156
if (!startDevice.empty()) {
157
audioDev = SDL_OpenAudioDevice(startDevice.c_str(), 0, &fmt, &g_retFmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
158
if (audioDev <= 0) {
159
WARN_LOG(Log::Audio, "Failed to open audio device: %s", startDevice.c_str());
160
}
161
}
162
if (audioDev <= 0) {
163
INFO_LOG(Log::Audio, "SDL: Trying a different audio device");
164
audioDev = SDL_OpenAudioDevice(nullptr, 0, &fmt, &g_retFmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
165
}
166
if (audioDev <= 0) {
167
ERROR_LOG(Log::Audio, "Failed to open audio device: %s", SDL_GetError());
168
} else {
169
if (g_retFmt.samples != fmt.samples) // Notify, but still use it
170
ERROR_LOG(Log::Audio, "Output audio samples: %d (requested: %d)", g_retFmt.samples, fmt.samples);
171
if (g_retFmt.format != fmt.format || g_retFmt.channels != fmt.channels) {
172
ERROR_LOG(Log::Audio, "Sound buffer format does not match requested format.");
173
ERROR_LOG(Log::Audio, "Output audio freq: %d (requested: %d)", g_retFmt.freq, fmt.freq);
174
ERROR_LOG(Log::Audio, "Output audio format: %d (requested: %d)", g_retFmt.format, fmt.format);
175
ERROR_LOG(Log::Audio, "Output audio channels: %d (requested: %d)", g_retFmt.channels, fmt.channels);
176
ERROR_LOG(Log::Audio, "Provided output format does not match requirement, turning audio off");
177
SDL_CloseAudioDevice(audioDev);
178
}
179
SDL_PauseAudioDevice(audioDev, 0);
180
}
181
}
182
183
static void StopSDLAudioDevice() {
184
if (audioDev > 0) {
185
SDL_PauseAudioDevice(audioDev, 1);
186
SDL_CloseAudioDevice(audioDev);
187
}
188
}
189
190
static void UpdateScreenDPI(SDL_Window *window) {
191
int drawable_width, window_width;
192
SDL_GetWindowSize(window, &window_width, NULL);
193
194
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL)
195
SDL_GL_GetDrawableSize(window, &drawable_width, NULL);
196
else if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN)
197
SDL_Vulkan_GetDrawableSize(window, &drawable_width, NULL);
198
199
// Round up a little otherwise there would be a gap sometimes
200
// in fractional scaling
201
g_DesktopDPI = ((float) drawable_width + 1.0f) / window_width;
202
203
// Temporary hack
204
#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
205
if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
206
g_DesktopDPI = 1.0f;
207
}
208
#endif
209
}
210
211
// Simple implementations of System functions
212
213
void System_Toast(std::string_view text) {
214
#ifdef _WIN32
215
std::wstring str = ConvertUTF8ToWString(text);
216
MessageBox(0, str.c_str(), L"Toast!", MB_ICONINFORMATION);
217
#else
218
printf("%*.s", (int)text.length(), text.data());
219
#endif
220
}
221
222
void System_ShowKeyboard() {
223
// Irrelevant on PC
224
}
225
226
void System_Vibrate(int length_ms) {
227
// Ignore on PC
228
}
229
230
bool System_MakeRequest(SystemRequestType type, int requestId, const std::string &param1, const std::string &param2, int64_t param3, int64_t param4) {
231
switch (type) {
232
case SystemRequestType::RESTART_APP:
233
g_RestartRequested = true;
234
// TODO: Also save param1 and then split it into an argv.
235
return true;
236
case SystemRequestType::EXIT_APP:
237
// Do a clean exit
238
g_QuitRequested = true;
239
return true;
240
#if PPSSPP_PLATFORM(SWITCH)
241
case SystemRequestType::INPUT_TEXT_MODAL:
242
{
243
// swkbd only works on "real" titles
244
if (__nx_applet_type != AppletType_Application && __nx_applet_type != AppletType_SystemApplication) {
245
g_requestManager.PostSystemFailure(requestId);
246
return true;
247
}
248
249
SwkbdConfig kbd;
250
Result rc = swkbdCreate(&kbd, 0);
251
252
if (R_SUCCEEDED(rc)) {
253
char buf[LIBNX_SWKBD_LIMIT] = {'\0'};
254
swkbdConfigMakePresetDefault(&kbd);
255
256
swkbdConfigSetHeaderText(&kbd, param1.c_str());
257
swkbdConfigSetInitialText(&kbd, param2.c_str());
258
259
rc = swkbdShow(&kbd, buf, sizeof(buf));
260
261
swkbdClose(&kbd);
262
263
g_requestManager.PostSystemSuccess(requestId, buf);
264
return true;
265
}
266
267
g_requestManager.PostSystemFailure(requestId);
268
return true;
269
}
270
#endif // PPSSPP_PLATFORM(SWITCH)
271
#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
272
case SystemRequestType::BROWSE_FOR_FILE:
273
{
274
DarwinDirectoryPanelCallback callback = [requestId] (bool success, Path path) {
275
if (success) {
276
g_requestManager.PostSystemSuccess(requestId, path.c_str());
277
} else {
278
g_requestManager.PostSystemFailure(requestId);
279
}
280
};
281
BrowseFileType fileType = (BrowseFileType)param3;
282
DarwinFileSystemServices::presentDirectoryPanel(callback, /* allowFiles = */ true, /* allowDirectories = */ false, fileType);
283
return true;
284
}
285
case SystemRequestType::BROWSE_FOR_FOLDER:
286
{
287
DarwinDirectoryPanelCallback callback = [requestId] (bool success, Path path) {
288
if (success) {
289
g_requestManager.PostSystemSuccess(requestId, path.c_str());
290
} else {
291
g_requestManager.PostSystemFailure(requestId);
292
}
293
};
294
DarwinFileSystemServices::presentDirectoryPanel(callback, /* allowFiles = */ false, /* allowDirectories = */ true);
295
return true;
296
}
297
#endif
298
case SystemRequestType::TOGGLE_FULLSCREEN_STATE:
299
{
300
std::lock_guard<std::mutex> guard(g_mutexWindow);
301
g_windowState.update = true;
302
g_windowState.toggleFullScreenNextFrame = true;
303
if (param1 == "1") {
304
g_windowState.toggleFullScreenType = 1;
305
} else if (param1 == "0") {
306
g_windowState.toggleFullScreenType = 0;
307
} else {
308
// Just toggle.
309
g_windowState.toggleFullScreenType = -1;
310
}
311
return true;
312
}
313
case SystemRequestType::SET_WINDOW_TITLE:
314
{
315
std::lock_guard<std::mutex> guard(g_mutexWindow);
316
const char *app_name = System_GetPropertyBool(SYSPROP_APP_GOLD) ? "PPSSPP Gold" : "PPSSPP";
317
g_windowState.title = param1.empty() ? app_name : param1;
318
g_windowState.update = true;
319
return true;
320
}
321
case SystemRequestType::COPY_TO_CLIPBOARD:
322
{
323
std::lock_guard<std::mutex> guard(g_mutexWindow);
324
g_windowState.clipboardString = param1;
325
g_windowState.clipboardDataAvailable = true;
326
g_windowState.update = true;
327
return true;
328
}
329
case SystemRequestType::SHOW_FILE_IN_FOLDER:
330
{
331
#if PPSSPP_PLATFORM(WINDOWS)
332
SFGAOF flags;
333
PIDLIST_ABSOLUTE pidl = nullptr;
334
HRESULT hr = SHParseDisplayName(ConvertUTF8ToWString(ReplaceAll(path, "/", "\\")).c_str(), nullptr, &pidl, 0, &flags);
335
if (pidl) {
336
if (SUCCEEDED(hr))
337
SHOpenFolderAndSelectItems(pidl, 0, NULL, 0);
338
CoTaskMemFree(pidl);
339
}
340
#elif PPSSPP_PLATFORM(MAC)
341
OSXShowInFinder(param1.c_str());
342
#elif (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))
343
pid_t pid = fork();
344
if (pid < 0)
345
return true;
346
347
if (pid == 0) {
348
execlp("xdg-open", "xdg-open", param1.c_str(), nullptr);
349
exit(1);
350
}
351
#endif /* PPSSPP_PLATFORM(WINDOWS) */
352
return true;
353
}
354
case SystemRequestType::NOTIFY_UI_EVENT:
355
{
356
switch ((UIEventNotification)param3) {
357
case UIEventNotification::TEXT_GOTFOCUS:
358
g_textFocus = true;
359
g_textFocusChanged = true;
360
break;
361
case UIEventNotification::POPUP_CLOSED:
362
case UIEventNotification::TEXT_LOSTFOCUS:
363
g_textFocus = false;
364
g_textFocusChanged = true;
365
break;
366
default:
367
break;
368
}
369
return true;
370
}
371
default:
372
return false;
373
}
374
}
375
376
void System_AskForPermission(SystemPermission permission) {}
377
PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; }
378
379
void System_LaunchUrl(LaunchUrlType urlType, const char *url) {
380
switch (urlType) {
381
case LaunchUrlType::BROWSER_URL:
382
case LaunchUrlType::MARKET_URL:
383
{
384
#if PPSSPP_PLATFORM(SWITCH)
385
Uuid uuid = { 0 };
386
WebWifiConfig conf;
387
webWifiCreate(&conf, NULL, url, uuid, 0);
388
webWifiShow(&conf, NULL);
389
#elif defined(MOBILE_DEVICE)
390
INFO_LOG(Log::System, "Would have gone to %s but LaunchBrowser is not implemented on this platform", url);
391
#elif defined(_WIN32)
392
std::wstring wurl = ConvertUTF8ToWString(url);
393
ShellExecute(NULL, L"open", wurl.c_str(), NULL, NULL, SW_SHOWNORMAL);
394
#elif defined(__APPLE__)
395
OSXOpenURL(url);
396
#else
397
std::string command = std::string("xdg-open ") + url;
398
int err = system(command.c_str());
399
if (err) {
400
INFO_LOG(Log::System, "Would have gone to %s but xdg-utils seems not to be installed", url);
401
}
402
#endif
403
break;
404
}
405
case LaunchUrlType::EMAIL_ADDRESS:
406
{
407
#if defined(MOBILE_DEVICE)
408
INFO_LOG(Log::System, "Would have opened your email client for %s but LaunchEmail is not implemented on this platform", url);
409
#elif defined(_WIN32)
410
std::wstring mailto = std::wstring(L"mailto:") + ConvertUTF8ToWString(url);
411
ShellExecute(NULL, L"open", mailto.c_str(), NULL, NULL, SW_SHOWNORMAL);
412
#elif defined(__APPLE__)
413
std::string mailToURL = std::string("mailto:") + url;
414
OSXOpenURL(mailToURL.c_str());
415
#else
416
std::string command = std::string("xdg-email ") + url;
417
int err = system(command.c_str());
418
if (err) {
419
INFO_LOG(Log::System, "Would have gone to %s but xdg-utils seems not to be installed", url);
420
}
421
#endif
422
break;
423
}
424
}
425
}
426
427
std::string System_GetProperty(SystemProperty prop) {
428
switch (prop) {
429
case SYSPROP_NAME:
430
#ifdef _WIN32
431
return "SDL:Windows";
432
#elif __linux__
433
return "SDL:Linux";
434
#elif __APPLE__
435
return "SDL:macOS";
436
#elif PPSSPP_PLATFORM(SWITCH)
437
return "SDL:Horizon";
438
#else
439
return "SDL:";
440
#endif
441
case SYSPROP_LANGREGION: {
442
// Get user-preferred locale from OS
443
setlocale(LC_ALL, "");
444
std::string locale(setlocale(LC_ALL, NULL));
445
// Set c and c++ strings back to POSIX
446
std::locale::global(std::locale("POSIX"));
447
if (!locale.empty()) {
448
// Technically, this is an opaque string, but try to find the locale code.
449
size_t messagesPos = locale.find("LC_MESSAGES=");
450
if (messagesPos != std::string::npos) {
451
messagesPos += strlen("LC_MESSAGES=");
452
size_t semi = locale.find(';', messagesPos);
453
locale = locale.substr(messagesPos, semi - messagesPos);
454
}
455
456
if (locale.find("_", 0) != std::string::npos) {
457
if (locale.find(".", 0) != std::string::npos) {
458
return locale.substr(0, locale.find(".",0));
459
}
460
return locale;
461
}
462
}
463
return "en_US";
464
}
465
case SYSPROP_CLIPBOARD_TEXT:
466
return SDL_HasClipboardText() ? SDL_GetClipboardText() : "";
467
case SYSPROP_AUDIO_DEVICE_LIST:
468
{
469
std::string result;
470
for (int i = 0; i < SDL_GetNumAudioDevices(0); ++i) {
471
const char *name = SDL_GetAudioDeviceName(i, 0);
472
if (!name) {
473
continue;
474
}
475
476
if (i == 0) {
477
result = name;
478
} else {
479
result.append(1, '\0');
480
result.append(name);
481
}
482
}
483
return result;
484
}
485
case SYSPROP_BUILD_VERSION:
486
return PPSSPP_GIT_VERSION;
487
case SYSPROP_USER_DOCUMENTS_DIR:
488
{
489
const char *home = getenv("HOME");
490
return home ? std::string(home) : "/";
491
}
492
default:
493
return "";
494
}
495
}
496
497
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
498
std::vector<std::string> result;
499
500
switch (prop) {
501
case SYSPROP_TEMP_DIRS:
502
if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0)
503
result.push_back(getenv("TMPDIR"));
504
if (getenv("TMP") && strlen(getenv("TMP")) != 0)
505
result.push_back(getenv("TMP"));
506
if (getenv("TEMP") && strlen(getenv("TEMP")) != 0)
507
result.push_back(getenv("TEMP"));
508
return result;
509
510
default:
511
return result;
512
}
513
}
514
515
int64_t System_GetPropertyInt(SystemProperty prop) {
516
switch (prop) {
517
case SYSPROP_AUDIO_SAMPLE_RATE:
518
return g_retFmt.freq;
519
case SYSPROP_AUDIO_FRAMES_PER_BUFFER:
520
return g_retFmt.samples;
521
case SYSPROP_DEVICE_TYPE:
522
#if defined(MOBILE_DEVICE)
523
return DEVICE_TYPE_MOBILE;
524
#else
525
return DEVICE_TYPE_DESKTOP;
526
#endif
527
case SYSPROP_DISPLAY_COUNT:
528
return SDL_GetNumVideoDisplays();
529
case SYSPROP_KEYBOARD_LAYOUT:
530
{
531
char q, w, y;
532
q = SDL_GetKeyFromScancode(SDL_SCANCODE_Q);
533
w = SDL_GetKeyFromScancode(SDL_SCANCODE_W);
534
y = SDL_GetKeyFromScancode(SDL_SCANCODE_Y);
535
if (q == 'a' && w == 'z' && y == 'y')
536
return KEYBOARD_LAYOUT_AZERTY;
537
else if (q == 'q' && w == 'w' && y == 'z')
538
return KEYBOARD_LAYOUT_QWERTZ;
539
return KEYBOARD_LAYOUT_QWERTY;
540
}
541
case SYSPROP_DISPLAY_XRES:
542
return g_DesktopWidth;
543
case SYSPROP_DISPLAY_YRES:
544
return g_DesktopHeight;
545
default:
546
return -1;
547
}
548
}
549
550
float System_GetPropertyFloat(SystemProperty prop) {
551
switch (prop) {
552
case SYSPROP_DISPLAY_REFRESH_RATE:
553
return g_RefreshRate;
554
case SYSPROP_DISPLAY_DPI:
555
return (g_ForcedDPI == 0.0f ? g_DesktopDPI : g_ForcedDPI) * 96.0;
556
case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
557
case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
558
case SYSPROP_DISPLAY_SAFE_INSET_TOP:
559
case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:
560
return 0.0f;
561
default:
562
return -1;
563
}
564
}
565
566
bool System_GetPropertyBool(SystemProperty prop) {
567
switch (prop) {
568
case SYSPROP_HAS_TEXT_CLIPBOARD:
569
case SYSPROP_CAN_SHOW_FILE:
570
#if PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(MAC) || (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))
571
return true;
572
#else
573
return false;
574
#endif
575
case SYSPROP_HAS_OPEN_DIRECTORY:
576
#if PPSSPP_PLATFORM(WINDOWS)
577
return true;
578
#elif PPSSPP_PLATFORM(MAC) || (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))
579
return true;
580
#endif
581
case SYSPROP_HAS_BACK_BUTTON:
582
return true;
583
#if PPSSPP_PLATFORM(SWITCH)
584
case SYSPROP_HAS_TEXT_INPUT_DIALOG:
585
return __nx_applet_type == AppletType_Application || __nx_applet_type != AppletType_SystemApplication;
586
#endif
587
case SYSPROP_HAS_KEYBOARD:
588
return true;
589
case SYSPROP_APP_GOLD:
590
#ifdef GOLD
591
return true;
592
#else
593
return false;
594
#endif
595
case SYSPROP_CAN_JIT:
596
return true;
597
case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR:
598
return true; // FileUtil.cpp: OpenFileInEditor
599
#ifndef HTTPS_NOT_AVAILABLE
600
case SYSPROP_SUPPORTS_HTTPS:
601
return !g_Config.bDisableHTTPS;
602
#endif
603
#if PPSSPP_PLATFORM(MAC)
604
case SYSPROP_HAS_FOLDER_BROWSER:
605
case SYSPROP_HAS_FILE_BROWSER:
606
return true;
607
#endif
608
case SYSPROP_HAS_ACCELEROMETER:
609
#if defined(MOBILE_DEVICE)
610
return true;
611
#else
612
return false;
613
#endif
614
default:
615
return false;
616
}
617
}
618
619
void System_Notify(SystemNotification notification) {
620
switch (notification) {
621
case SystemNotification::AUDIO_RESET_DEVICE:
622
StopSDLAudioDevice();
623
InitSDLAudioDevice();
624
break;
625
626
default:
627
break;
628
}
629
}
630
631
// returns -1 on failure
632
static int parseInt(const char *str) {
633
int val;
634
int retval = sscanf(str, "%d", &val);
635
printf("%i = scanf %s\n", retval, str);
636
if (retval != 1) {
637
return -1;
638
} else {
639
return val;
640
}
641
}
642
643
static float parseFloat(const char *str) {
644
float val;
645
int retval = sscanf(str, "%f", &val);
646
printf("%i = sscanf %s\n", retval, str);
647
if (retval != 1) {
648
return -1.0f;
649
} else {
650
return val;
651
}
652
}
653
654
void UpdateWindowState(SDL_Window *window) {
655
SDL_SetWindowTitle(window, g_windowState.title.c_str());
656
if (g_windowState.toggleFullScreenNextFrame) {
657
g_windowState.toggleFullScreenNextFrame = false;
658
659
Uint32 window_flags = SDL_GetWindowFlags(window);
660
if (g_windowState.toggleFullScreenType == -1) {
661
window_flags ^= SDL_WINDOW_FULLSCREEN_DESKTOP;
662
} else if (g_windowState.toggleFullScreenType == 1) {
663
window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
664
} else {
665
window_flags &= ~SDL_WINDOW_FULLSCREEN_DESKTOP;
666
}
667
SDL_SetWindowFullscreen(window, window_flags);
668
}
669
if (g_windowState.clipboardDataAvailable) {
670
SDL_SetClipboardText(g_windowState.clipboardString.c_str());
671
g_windowState.clipboardDataAvailable = false;
672
g_windowState.clipboardString.clear();
673
}
674
g_windowState.update = false;
675
}
676
677
enum class EmuThreadState {
678
DISABLED,
679
START_REQUESTED,
680
RUNNING,
681
QUIT_REQUESTED,
682
STOPPED,
683
};
684
685
static std::thread emuThread;
686
static std::atomic<int> emuThreadState((int)EmuThreadState::DISABLED);
687
688
static void EmuThreadFunc(GraphicsContext *graphicsContext) {
689
SetCurrentThreadName("Emu");
690
691
// There's no real requirement that NativeInit happen on this thread.
692
// We just call the update/render loop here.
693
emuThreadState = (int)EmuThreadState::RUNNING;
694
695
NativeInitGraphics(graphicsContext);
696
697
while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
698
UpdateRunLoop(graphicsContext);
699
}
700
emuThreadState = (int)EmuThreadState::STOPPED;
701
graphicsContext->StopThread();
702
703
NativeShutdownGraphics();
704
}
705
706
static void EmuThreadStart(GraphicsContext *context) {
707
emuThreadState = (int)EmuThreadState::START_REQUESTED;
708
emuThread = std::thread(&EmuThreadFunc, context);
709
}
710
711
static void EmuThreadStop(const char *reason) {
712
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
713
}
714
715
static void EmuThreadJoin() {
716
emuThread.join();
717
emuThread = std::thread();
718
}
719
720
struct InputStateTracker {
721
void MouseCaptureControl() {
722
bool captureMouseCondition = g_Config.bMouseControl && ((GetUIState() == UISTATE_INGAME && g_Config.bMouseConfine) || g_Config.bMapMouse);
723
if (mouseCaptured != captureMouseCondition) {
724
mouseCaptured = captureMouseCondition;
725
if (captureMouseCondition)
726
SDL_SetRelativeMouseMode(SDL_TRUE);
727
else
728
SDL_SetRelativeMouseMode(SDL_FALSE);
729
}
730
}
731
732
bool mouseDown;
733
bool mouseCaptured;
734
};
735
736
static void ProcessSDLEvent(SDL_Window *window, const SDL_Event &event, InputStateTracker *inputTracker) {
737
// We have to juggle around 3 kinds of "DPI spaces" if a logical DPI is
738
// provided (through --dpi, it is equal to system DPI if unspecified):
739
// - SDL gives us motion events in "system DPI" points
740
// - UpdateScreenScale expects pixels, so in a way "96 DPI" points
741
// - The UI code expects motion events in "logical DPI" points
742
float mx = event.motion.x * g_DesktopDPI * g_display.dpi_scale_x;
743
float my = event.motion.y * g_DesktopDPI * g_display.dpi_scale_x;
744
745
switch (event.type) {
746
case SDL_QUIT:
747
g_QuitRequested = 1;
748
break;
749
750
#if !defined(MOBILE_DEVICE)
751
case SDL_WINDOWEVENT:
752
switch (event.window.event) {
753
case SDL_WINDOWEVENT_SIZE_CHANGED: // better than RESIZED, more general
754
{
755
int new_width = event.window.data1;
756
int new_height = event.window.data2;
757
758
// The size given by SDL is in point-units, convert these to
759
// pixels before passing to UpdateScreenScale()
760
int new_width_px = new_width * g_DesktopDPI;
761
int new_height_px = new_height * g_DesktopDPI;
762
763
Core_NotifyWindowHidden(false);
764
765
Uint32 window_flags = SDL_GetWindowFlags(window);
766
bool fullscreen = (window_flags & SDL_WINDOW_FULLSCREEN);
767
768
// This one calls NativeResized if the size changed.
769
UpdateScreenScale(new_width_px, new_height_px);
770
771
// Set variable here in case fullscreen was toggled by hotkey
772
if (g_Config.UseFullScreen() != fullscreen) {
773
g_Config.bFullScreen = fullscreen;
774
g_Config.iForceFullScreen = -1;
775
} else {
776
// It is possible for the monitor to change DPI, so recalculate
777
// DPI on each resize event.
778
UpdateScreenDPI(window);
779
}
780
781
if (!g_Config.bFullScreen) {
782
g_Config.iWindowWidth = new_width;
783
g_Config.iWindowHeight = new_height;
784
}
785
// Hide/Show cursor correctly toggling fullscreen
786
if (lastUIState == UISTATE_INGAME && fullscreen && !g_Config.bShowTouchControls) {
787
SDL_ShowCursor(SDL_DISABLE);
788
} else if (lastUIState != UISTATE_INGAME || !fullscreen) {
789
SDL_ShowCursor(SDL_ENABLE);
790
}
791
break;
792
}
793
794
case SDL_WINDOWEVENT_MOVED:
795
{
796
Uint32 window_flags = SDL_GetWindowFlags(window);
797
bool fullscreen = (window_flags & SDL_WINDOW_FULLSCREEN);
798
if (!fullscreen) {
799
g_Config.iWindowX = (int)event.window.data1;
800
g_Config.iWindowY = (int)event.window.data2;
801
}
802
break;
803
}
804
805
case SDL_WINDOWEVENT_MINIMIZED:
806
case SDL_WINDOWEVENT_HIDDEN:
807
Core_NotifyWindowHidden(true);
808
break;
809
case SDL_WINDOWEVENT_EXPOSED:
810
case SDL_WINDOWEVENT_SHOWN:
811
Core_NotifyWindowHidden(false);
812
break;
813
default:
814
break;
815
}
816
break;
817
#endif
818
case SDL_KEYDOWN:
819
{
820
if (event.key.repeat > 0) { break;}
821
int k = event.key.keysym.sym;
822
KeyInput key;
823
key.flags = KEY_DOWN;
824
auto mapped = KeyMapRawSDLtoNative.find(k);
825
if (mapped == KeyMapRawSDLtoNative.end() || mapped->second == NKCODE_UNKNOWN) {
826
break;
827
}
828
key.keyCode = mapped->second;
829
key.deviceId = DEVICE_ID_KEYBOARD;
830
NativeKey(key);
831
832
#ifdef _DEBUG
833
if (k == SDLK_F7) {
834
printf("f7 pressed - rebooting emuthread\n");
835
g_rebootEmuThread = true;
836
}
837
#endif
838
// Convenience subset of what
839
// "Enable standard shortcut keys"
840
// does on Windows.
841
if(g_Config.bSystemControls) {
842
bool ctrl = bool(event.key.keysym.mod & KMOD_CTRL);
843
if (ctrl && (k == SDLK_w))
844
{
845
if (Core_IsStepping())
846
Core_EnableStepping(false);
847
Core_Stop();
848
System_PostUIMessage(UIMessage::REQUEST_GAME_STOP);
849
// NOTE: Unlike Windows version, this
850
// does not need Core_WaitInactive();
851
// since SDL does not have a separate
852
// UI thread.
853
}
854
if (ctrl && (k == SDLK_b))
855
{
856
System_PostUIMessage(UIMessage::REQUEST_GAME_RESET);
857
Core_EnableStepping(false);
858
}
859
}
860
break;
861
}
862
case SDL_KEYUP:
863
{
864
if (event.key.repeat > 0) { break;}
865
int k = event.key.keysym.sym;
866
KeyInput key;
867
key.flags = KEY_UP;
868
auto mapped = KeyMapRawSDLtoNative.find(k);
869
if (mapped == KeyMapRawSDLtoNative.end() || mapped->second == NKCODE_UNKNOWN) {
870
break;
871
}
872
key.keyCode = mapped->second;
873
key.deviceId = DEVICE_ID_KEYBOARD;
874
NativeKey(key);
875
break;
876
}
877
case SDL_TEXTINPUT:
878
{
879
int pos = 0;
880
int c = u8_nextchar(event.text.text, &pos, strlen(event.text.text));
881
KeyInput key;
882
key.flags = KEY_CHAR;
883
key.unicodeChar = c;
884
key.deviceId = DEVICE_ID_KEYBOARD;
885
NativeKey(key);
886
break;
887
}
888
// This behavior doesn't feel right on a macbook with a touchpad.
889
#if !PPSSPP_PLATFORM(MAC)
890
case SDL_FINGERMOTION:
891
{
892
int w, h;
893
SDL_GetWindowSize(window, &w, &h);
894
TouchInput input;
895
input.id = event.tfinger.fingerId;
896
input.x = event.tfinger.x * w * g_DesktopDPI * g_display.dpi_scale_x;
897
input.y = event.tfinger.y * h * g_DesktopDPI * g_display.dpi_scale_x;
898
input.flags = TOUCH_MOVE;
899
input.timestamp = event.tfinger.timestamp;
900
NativeTouch(input);
901
break;
902
}
903
case SDL_FINGERDOWN:
904
{
905
int w, h;
906
SDL_GetWindowSize(window, &w, &h);
907
TouchInput input;
908
input.id = event.tfinger.fingerId;
909
input.x = event.tfinger.x * w * g_DesktopDPI * g_display.dpi_scale_x;
910
input.y = event.tfinger.y * h * g_DesktopDPI * g_display.dpi_scale_x;
911
input.flags = TOUCH_DOWN;
912
input.timestamp = event.tfinger.timestamp;
913
NativeTouch(input);
914
915
KeyInput key;
916
key.deviceId = DEVICE_ID_MOUSE;
917
key.keyCode = NKCODE_EXT_MOUSEBUTTON_1;
918
key.flags = KEY_DOWN;
919
NativeKey(key);
920
break;
921
}
922
case SDL_FINGERUP:
923
{
924
int w, h;
925
SDL_GetWindowSize(window, &w, &h);
926
TouchInput input;
927
input.id = event.tfinger.fingerId;
928
input.x = event.tfinger.x * w * g_DesktopDPI * g_display.dpi_scale_x;
929
input.y = event.tfinger.y * h * g_DesktopDPI * g_display.dpi_scale_x;
930
input.flags = TOUCH_UP;
931
input.timestamp = event.tfinger.timestamp;
932
NativeTouch(input);
933
934
KeyInput key;
935
key.deviceId = DEVICE_ID_MOUSE;
936
key.keyCode = NKCODE_EXT_MOUSEBUTTON_1;
937
key.flags = KEY_UP;
938
NativeKey(key);
939
break;
940
}
941
#endif
942
case SDL_MOUSEBUTTONDOWN:
943
switch (event.button.button) {
944
case SDL_BUTTON_LEFT:
945
{
946
inputTracker->mouseDown = true;
947
TouchInput input{};
948
input.x = mx;
949
input.y = my;
950
input.flags = TOUCH_DOWN | TOUCH_MOUSE;
951
input.id = 0;
952
NativeTouch(input);
953
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KEY_DOWN);
954
NativeKey(key);
955
}
956
break;
957
case SDL_BUTTON_RIGHT:
958
{
959
// Right button only emits mouse move events. This is weird,
960
// but consistent with Windows. Needs cleanup.
961
TouchInput input{};
962
input.x = mx;
963
input.y = my;
964
input.flags = TOUCH_MOVE | TOUCH_MOUSE;
965
input.id = 0;
966
NativeTouch(input);
967
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KEY_DOWN);
968
NativeKey(key);
969
}
970
break;
971
case SDL_BUTTON_MIDDLE:
972
{
973
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_3, KEY_DOWN);
974
NativeKey(key);
975
}
976
break;
977
case SDL_BUTTON_X1:
978
{
979
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_4, KEY_DOWN);
980
NativeKey(key);
981
}
982
break;
983
case SDL_BUTTON_X2:
984
{
985
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_5, KEY_DOWN);
986
NativeKey(key);
987
}
988
break;
989
}
990
break;
991
case SDL_MOUSEWHEEL:
992
{
993
KeyInput key;
994
key.deviceId = DEVICE_ID_MOUSE;
995
key.flags = KEY_DOWN;
996
#if SDL_VERSION_ATLEAST(2, 0, 18)
997
if (event.wheel.preciseY != 0.0f) {
998
// Should the scale be DPI-driven?
999
const float scale = 30.0f;
1000
key.keyCode = event.wheel.preciseY > 0 ? NKCODE_EXT_MOUSEWHEEL_UP : NKCODE_EXT_MOUSEWHEEL_DOWN;
1001
key.flags |= KEY_HASWHEELDELTA;
1002
int wheelDelta = event.wheel.preciseY * scale;
1003
if (event.wheel.preciseY < 0) {
1004
wheelDelta = -wheelDelta;
1005
}
1006
key.flags |= wheelDelta << 16;
1007
NativeKey(key);
1008
break;
1009
}
1010
#endif
1011
if (event.wheel.y > 0) {
1012
key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP;
1013
NativeKey(key);
1014
} else if (event.wheel.y < 0) {
1015
key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN;
1016
NativeKey(key);
1017
}
1018
break;
1019
}
1020
case SDL_MOUSEMOTION:
1021
if (inputTracker->mouseDown) {
1022
TouchInput input{};
1023
input.x = mx;
1024
input.y = my;
1025
input.flags = TOUCH_MOVE | TOUCH_MOUSE;
1026
input.id = 0;
1027
NativeTouch(input);
1028
}
1029
NativeMouseDelta(event.motion.xrel, event.motion.yrel);
1030
break;
1031
case SDL_MOUSEBUTTONUP:
1032
switch (event.button.button) {
1033
case SDL_BUTTON_LEFT:
1034
{
1035
inputTracker->mouseDown = false;
1036
TouchInput input{};
1037
input.x = mx;
1038
input.y = my;
1039
input.flags = TOUCH_UP | TOUCH_MOUSE;
1040
NativeTouch(input);
1041
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KEY_UP);
1042
NativeKey(key);
1043
}
1044
break;
1045
case SDL_BUTTON_RIGHT:
1046
{
1047
// Right button only emits mouse move events. This is weird,
1048
// but consistent with Windows. Needs cleanup.
1049
TouchInput input{};
1050
input.x = mx;
1051
input.y = my;
1052
input.flags = TOUCH_MOVE | TOUCH_MOUSE;
1053
NativeTouch(input);
1054
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KEY_UP);
1055
NativeKey(key);
1056
}
1057
break;
1058
case SDL_BUTTON_MIDDLE:
1059
{
1060
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_3, KEY_UP);
1061
NativeKey(key);
1062
}
1063
break;
1064
case SDL_BUTTON_X1:
1065
{
1066
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_4, KEY_UP);
1067
NativeKey(key);
1068
}
1069
break;
1070
case SDL_BUTTON_X2:
1071
{
1072
KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_5, KEY_UP);
1073
NativeKey(key);
1074
}
1075
break;
1076
}
1077
break;
1078
1079
#if SDL_VERSION_ATLEAST(2, 0, 4)
1080
case SDL_AUDIODEVICEADDED:
1081
// Automatically switch to the new device.
1082
if (event.adevice.iscapture == 0) {
1083
const char *name = SDL_GetAudioDeviceName(event.adevice.which, 0);
1084
if (!name) {
1085
break;
1086
}
1087
// Don't start auto switching for a second, because some devices init on start.
1088
bool doAutoSwitch = g_Config.bAutoAudioDevice && time_now_d() > 1.0f;
1089
if (doAutoSwitch || g_Config.sAudioDevice == name) {
1090
StopSDLAudioDevice();
1091
InitSDLAudioDevice(name ? name : "");
1092
}
1093
}
1094
break;
1095
case SDL_AUDIODEVICEREMOVED:
1096
if (event.adevice.iscapture == 0 && event.adevice.which == audioDev) {
1097
StopSDLAudioDevice();
1098
InitSDLAudioDevice();
1099
}
1100
break;
1101
#endif
1102
1103
default:
1104
if (joystick) {
1105
joystick->ProcessInput(event);
1106
}
1107
break;
1108
}
1109
}
1110
1111
void UpdateTextFocus() {
1112
if (g_textFocusChanged) {
1113
INFO_LOG(Log::System, "Updating text focus: %d", g_textFocus);
1114
if (g_textFocus) {
1115
SDL_StartTextInput();
1116
} else {
1117
SDL_StopTextInput();
1118
}
1119
g_textFocusChanged = false;
1120
}
1121
}
1122
1123
void UpdateSDLCursor() {
1124
#if !defined(MOBILE_DEVICE)
1125
if (lastUIState != GetUIState()) {
1126
lastUIState = GetUIState();
1127
if (lastUIState == UISTATE_INGAME && g_Config.UseFullScreen() && !g_Config.bShowTouchControls)
1128
SDL_ShowCursor(SDL_DISABLE);
1129
if (lastUIState != UISTATE_INGAME || !g_Config.UseFullScreen())
1130
SDL_ShowCursor(SDL_ENABLE);
1131
}
1132
#endif
1133
}
1134
1135
#ifdef _WIN32
1136
#undef main
1137
#endif
1138
int main(int argc, char *argv[]) {
1139
for (int i = 1; i < argc; i++) {
1140
if (!strcmp(argv[i], "--version")) {
1141
printf("%s\n", PPSSPP_GIT_VERSION);
1142
return 0;
1143
}
1144
}
1145
1146
TimeInit();
1147
1148
#ifdef HAVE_LIBNX
1149
socketInitializeDefault();
1150
nxlinkStdio();
1151
#else // HAVE_LIBNX
1152
// Ignore sigpipe.
1153
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
1154
perror("Unable to ignore SIGPIPE");
1155
}
1156
#endif // HAVE_LIBNX
1157
1158
PROFILE_INIT();
1159
glslang::InitializeProcess();
1160
1161
#if PPSSPP_PLATFORM(RPI)
1162
bcm_host_init();
1163
#endif
1164
putenv((char*)"SDL_VIDEO_CENTERED=1");
1165
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
1166
1167
#ifdef SDL_HINT_TOUCH_MOUSE_EVENTS
1168
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
1169
#endif
1170
1171
bool vulkanMayBeAvailable = false;
1172
if (VulkanMayBeAvailable()) {
1173
printf("DEBUG: Vulkan might be available.\n");
1174
vulkanMayBeAvailable = true;
1175
} else {
1176
printf("DEBUG: Vulkan is not available, not using Vulkan.\n");
1177
}
1178
1179
SDL_version compiled;
1180
SDL_version linked;
1181
int set_xres = -1;
1182
int set_yres = -1;
1183
bool portrait = false;
1184
bool set_ipad = false;
1185
float set_dpi = 0.0f;
1186
float set_scale = 1.0f;
1187
1188
// Produce a new set of arguments with the ones we skip.
1189
int remain_argc = 1;
1190
const char *remain_argv[256] = { argv[0] };
1191
1192
Uint32 mode = 0;
1193
for (int i = 1; i < argc; i++) {
1194
if (!strcmp(argv[i],"--fullscreen")) {
1195
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1196
g_Config.iForceFullScreen = 1;
1197
} else if (set_xres == -2)
1198
set_xres = parseInt(argv[i]);
1199
else if (set_yres == -2)
1200
set_yres = parseInt(argv[i]);
1201
else if (set_dpi == -2)
1202
set_dpi = parseFloat(argv[i]);
1203
else if (set_scale == -2)
1204
set_scale = parseFloat(argv[i]);
1205
else if (!strcmp(argv[i],"--xres"))
1206
set_xres = -2;
1207
else if (!strcmp(argv[i],"--yres"))
1208
set_yres = -2;
1209
else if (!strcmp(argv[i],"--dpi"))
1210
set_dpi = -2;
1211
else if (!strcmp(argv[i],"--scale"))
1212
set_scale = -2;
1213
else if (!strcmp(argv[i],"--ipad"))
1214
set_ipad = true;
1215
else if (!strcmp(argv[i],"--portrait"))
1216
portrait = true;
1217
else {
1218
remain_argv[remain_argc++] = argv[i];
1219
}
1220
}
1221
1222
std::string app_name;
1223
std::string app_name_nice;
1224
std::string version;
1225
bool landscape;
1226
NativeGetAppInfo(&app_name, &app_name_nice, &landscape, &version);
1227
1228
bool joystick_enabled = true;
1229
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) < 0) {
1230
fprintf(stderr, "Failed to initialize SDL with joystick support. Retrying without.\n");
1231
joystick_enabled = false;
1232
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
1233
fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError());
1234
return 1;
1235
}
1236
}
1237
1238
SDL_VERSION(&compiled);
1239
SDL_GetVersion(&linked);
1240
printf("Info: We compiled against SDL version %d.%d.%d", compiled.major, compiled.minor, compiled.patch);
1241
if (compiled.minor != linked.minor || compiled.patch != linked.patch) {
1242
printf(", but we are linking against SDL version %d.%d.%d., be aware that this can lead to unexpected behaviors\n", linked.major, linked.minor, linked.patch);
1243
} else {
1244
printf(" and we are linking against SDL version %d.%d.%d. :)\n", linked.major, linked.minor, linked.patch);
1245
}
1246
1247
// Get the video info before doing anything else, so we don't get skewed resolution results.
1248
// TODO: support multiple displays correctly
1249
SDL_DisplayMode displayMode;
1250
int should_be_zero = SDL_GetCurrentDisplayMode(0, &displayMode);
1251
if (should_be_zero != 0) {
1252
fprintf(stderr, "Could not get display mode: %s\n", SDL_GetError());
1253
return 1;
1254
}
1255
g_DesktopWidth = displayMode.w;
1256
g_DesktopHeight = displayMode.h;
1257
g_RefreshRate = displayMode.refresh_rate;
1258
1259
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
1260
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
1261
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
1262
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
1263
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
1264
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
1265
1266
// Force fullscreen if the resolution is too low to run windowed.
1267
if (g_DesktopWidth < 480 * 2 && g_DesktopHeight < 272 * 2) {
1268
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1269
}
1270
1271
// If we're on mobile, don't try for windowed either.
1272
#if defined(MOBILE_DEVICE) && !PPSSPP_PLATFORM(SWITCH)
1273
mode |= SDL_WINDOW_FULLSCREEN;
1274
#elif defined(USING_FBDEV) || PPSSPP_PLATFORM(SWITCH)
1275
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1276
#else
1277
mode |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
1278
#endif
1279
1280
if (mode & SDL_WINDOW_FULLSCREEN_DESKTOP) {
1281
g_display.pixel_xres = g_DesktopWidth;
1282
g_display.pixel_yres = g_DesktopHeight;
1283
if (g_Config.iForceFullScreen == -1)
1284
g_Config.bFullScreen = true;
1285
} else {
1286
// set a sensible default resolution (2x)
1287
g_display.pixel_xres = 480 * 2 * set_scale;
1288
g_display.pixel_yres = 272 * 2 * set_scale;
1289
if (portrait) {
1290
std::swap(g_display.pixel_xres, g_display.pixel_yres);
1291
}
1292
if (g_Config.iForceFullScreen == -1)
1293
g_Config.bFullScreen = false;
1294
}
1295
1296
if (set_ipad) {
1297
g_display.pixel_xres = 1024;
1298
g_display.pixel_yres = 768;
1299
}
1300
if (!landscape) {
1301
std::swap(g_display.pixel_xres, g_display.pixel_yres);
1302
}
1303
1304
if (set_xres > 0) {
1305
g_display.pixel_xres = set_xres;
1306
}
1307
if (set_yres > 0) {
1308
g_display.pixel_yres = set_yres;
1309
}
1310
if (set_dpi > 0) {
1311
g_ForcedDPI = set_dpi;
1312
}
1313
1314
// Mac / Linux
1315
char path[2048];
1316
#if PPSSPP_PLATFORM(SWITCH)
1317
strcpy(path, "/switch/ppsspp/");
1318
#else
1319
const char *the_path = getenv("HOME");
1320
if (!the_path) {
1321
struct passwd *pwd = getpwuid(getuid());
1322
if (pwd)
1323
the_path = pwd->pw_dir;
1324
}
1325
if (the_path)
1326
strcpy(path, the_path);
1327
#endif
1328
if (strlen(path) > 0 && path[strlen(path) - 1] != '/')
1329
strcat(path, "/");
1330
1331
#if PPSSPP_PLATFORM(MAC)
1332
std::string external_dir_str;
1333
if (SDL_GetBasePath())
1334
external_dir_str = std::string(SDL_GetBasePath()) + "/assets";
1335
else
1336
external_dir_str = "/tmp";
1337
const char *external_dir = external_dir_str.c_str();
1338
#else
1339
const char *external_dir = "/tmp";
1340
#endif
1341
NativeInit(remain_argc, (const char **)remain_argv, path, external_dir, nullptr);
1342
1343
// Use the setting from the config when initing the window.
1344
if (g_Config.UseFullScreen())
1345
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1346
1347
int x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(getDisplayNumber());
1348
int y = SDL_WINDOWPOS_UNDEFINED;
1349
int w = g_display.pixel_xres;
1350
int h = g_display.pixel_yres;
1351
1352
if (!g_Config.bFullScreen) {
1353
if (g_Config.iWindowX != -1)
1354
x = g_Config.iWindowX;
1355
if (g_Config.iWindowY != -1)
1356
y = g_Config.iWindowY;
1357
if (g_Config.iWindowWidth > 0 && set_xres <= 0)
1358
w = g_Config.iWindowWidth;
1359
if (g_Config.iWindowHeight > 0 && set_yres <= 0)
1360
h = g_Config.iWindowHeight;
1361
}
1362
1363
GraphicsContext *graphicsContext = nullptr;
1364
SDL_Window *window = nullptr;
1365
1366
// Switch away from Vulkan if not available.
1367
if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN && !vulkanMayBeAvailable) {
1368
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
1369
}
1370
1371
std::string error_message;
1372
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
1373
SDLGLGraphicsContext *glctx = new SDLGLGraphicsContext();
1374
if (glctx->Init(window, x, y, w, h, mode, &error_message) != 0) {
1375
// Let's try the fallback once per process run.
1376
printf("GL init error '%s' - falling back to Vulkan\n", error_message.c_str());
1377
g_Config.iGPUBackend = (int)GPUBackend::VULKAN;
1378
SetGPUBackend((GPUBackend)g_Config.iGPUBackend);
1379
delete glctx;
1380
1381
// NOTE : This should match the lines below in the Vulkan case.
1382
SDLVulkanGraphicsContext *vkctx = new SDLVulkanGraphicsContext();
1383
if (!vkctx->Init(window, x, y, w, h, mode | SDL_WINDOW_VULKAN, &error_message)) {
1384
printf("Vulkan fallback failed: %s\n", error_message.c_str());
1385
return 1;
1386
}
1387
graphicsContext = vkctx;
1388
} else {
1389
graphicsContext = glctx;
1390
}
1391
#if !PPSSPP_PLATFORM(SWITCH)
1392
} else if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
1393
SDLVulkanGraphicsContext *vkctx = new SDLVulkanGraphicsContext();
1394
if (!vkctx->Init(window, x, y, w, h, mode | SDL_WINDOW_VULKAN, &error_message)) {
1395
// Let's try the fallback once per process run.
1396
1397
printf("Vulkan init error '%s' - falling back to GL\n", error_message.c_str());
1398
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
1399
SetGPUBackend((GPUBackend)g_Config.iGPUBackend);
1400
delete vkctx;
1401
1402
// NOTE : This should match the three lines above in the OpenGL case.
1403
SDLGLGraphicsContext *glctx = new SDLGLGraphicsContext();
1404
if (glctx->Init(window, x, y, w, h, mode, &error_message) != 0) {
1405
printf("GL fallback failed: %s\n", error_message.c_str());
1406
return 1;
1407
}
1408
graphicsContext = glctx;
1409
} else {
1410
graphicsContext = vkctx;
1411
}
1412
#endif
1413
}
1414
#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
1415
if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
1416
g_ForcedDPI = 1.0f;
1417
}
1418
#endif
1419
1420
UpdateScreenDPI(window);
1421
1422
float dpi_scale = 1.0f / (g_ForcedDPI == 0.0f ? g_DesktopDPI : g_ForcedDPI);
1423
1424
UpdateScreenScale(w * g_DesktopDPI, h * g_DesktopDPI);
1425
1426
bool mainThreadIsRender = g_Config.iGPUBackend == (int)GPUBackend::OPENGL;
1427
1428
SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str());
1429
1430
char iconPath[PATH_MAX];
1431
#if defined(ASSETS_DIR)
1432
snprintf(iconPath, PATH_MAX, "%sicon_regular_72.png", ASSETS_DIR);
1433
if (access(iconPath, F_OK) != 0)
1434
snprintf(iconPath, PATH_MAX, "%sassets/icon_regular_72.png", SDL_GetBasePath() ? SDL_GetBasePath() : "");
1435
#else
1436
snprintf(iconPath, PATH_MAX, "%sassets/icon_regular_72.png", SDL_GetBasePath() ? SDL_GetBasePath() : "");
1437
#endif
1438
int width = 0, height = 0;
1439
unsigned char *imageData;
1440
if (pngLoad(iconPath, &width, &height, &imageData) == 1) {
1441
SDL_Surface *surface = SDL_CreateRGBSurface(0, width, height, 32,
1442
0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
1443
memcpy(surface->pixels, imageData, width*height*4);
1444
SDL_SetWindowIcon(window, surface);
1445
SDL_FreeSurface(surface);
1446
free(imageData);
1447
imageData = NULL;
1448
}
1449
1450
// Since we render from the main thread, there's nothing done here, but we call it to avoid confusion.
1451
if (!graphicsContext->InitFromRenderThread(&error_message)) {
1452
printf("Init from thread error: '%s'\n", error_message.c_str());
1453
return 1;
1454
}
1455
1456
// OK, we have a valid graphics backend selected. Let's clear the failures.
1457
g_Config.sFailedGPUBackends.clear();
1458
1459
#ifdef MOBILE_DEVICE
1460
SDL_ShowCursor(SDL_DISABLE);
1461
#endif
1462
1463
// Avoid the IME popup when holding keys. This doesn't affect all versions of SDL.
1464
// Note: We re-enable it in text input fields! This is necessary otherwise we don't receive
1465
// KEY_CHAR events.
1466
SDL_StopTextInput();
1467
1468
InitSDLAudioDevice();
1469
1470
if (joystick_enabled) {
1471
joystick = new SDLJoystick();
1472
} else {
1473
joystick = nullptr;
1474
}
1475
EnableFZ();
1476
1477
EmuThreadStart(graphicsContext);
1478
1479
graphicsContext->ThreadStart();
1480
1481
InputStateTracker inputTracker{};
1482
1483
#if PPSSPP_PLATFORM(MAC)
1484
// setup menu items for macOS
1485
initializeOSXExtras();
1486
#endif
1487
1488
bool waitOnExit = g_Config.iGPUBackend == (int)GPUBackend::OPENGL;
1489
1490
if (!mainThreadIsRender) {
1491
// Vulkan mode uses this.
1492
// We should only be a message pump. This allows for lower latency
1493
// input events, and so on.
1494
while (true) {
1495
SDL_Event event;
1496
while (SDL_WaitEventTimeout(&event, 100)) {
1497
ProcessSDLEvent(window, event, &inputTracker);
1498
}
1499
if (g_QuitRequested || g_RestartRequested)
1500
break;
1501
1502
UpdateTextFocus();
1503
UpdateSDLCursor();
1504
1505
inputTracker.MouseCaptureControl();
1506
1507
{
1508
std::lock_guard<std::mutex> guard(g_mutexWindow);
1509
if (g_windowState.update) {
1510
UpdateWindowState(window);
1511
}
1512
}
1513
}
1514
} else while (true) {
1515
{
1516
SDL_Event event;
1517
while (SDL_PollEvent(&event)) {
1518
ProcessSDLEvent(window, event, &inputTracker);
1519
}
1520
}
1521
if (g_QuitRequested || g_RestartRequested)
1522
break;
1523
if (emuThreadState == (int)EmuThreadState::DISABLED) {
1524
UpdateRunLoop(graphicsContext);
1525
}
1526
if (g_QuitRequested || g_RestartRequested)
1527
break;
1528
1529
UpdateTextFocus();
1530
UpdateSDLCursor();
1531
1532
inputTracker.MouseCaptureControl();
1533
1534
bool renderThreadPaused = Core_IsWindowHidden() && g_Config.bPauseWhenMinimized && emuThreadState != (int)EmuThreadState::DISABLED;
1535
if (emuThreadState != (int)EmuThreadState::DISABLED && !renderThreadPaused) {
1536
if (!graphicsContext->ThreadFrame())
1537
break;
1538
}
1539
1540
{
1541
std::lock_guard<std::mutex> guard(g_mutexWindow);
1542
if (g_windowState.update) {
1543
UpdateWindowState(window);
1544
}
1545
}
1546
1547
if (g_rebootEmuThread) {
1548
printf("rebooting emu thread");
1549
g_rebootEmuThread = false;
1550
EmuThreadStop("shutdown");
1551
// Skipping GL calls, the old context is gone.
1552
while (graphicsContext->ThreadFrame()) {
1553
INFO_LOG(Log::System, "graphicsContext->ThreadFrame executed to clear buffers");
1554
}
1555
EmuThreadJoin();
1556
graphicsContext->ThreadEnd();
1557
graphicsContext->ShutdownFromRenderThread();
1558
1559
printf("OK, shutdown complete. starting up graphics again.\n");
1560
1561
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
1562
SDLGLGraphicsContext *ctx = (SDLGLGraphicsContext *)graphicsContext;
1563
if (!ctx->Init(window, x, y, w, h, mode, &error_message)) {
1564
printf("Failed to reinit graphics.\n");
1565
}
1566
}
1567
1568
if (!graphicsContext->InitFromRenderThread(&error_message)) {
1569
System_Toast("Graphics initialization failed. Quitting.");
1570
return 1;
1571
}
1572
1573
EmuThreadStart(graphicsContext);
1574
graphicsContext->ThreadStart();
1575
}
1576
}
1577
1578
EmuThreadStop("shutdown");
1579
1580
if (waitOnExit) {
1581
while (graphicsContext->ThreadFrame()) {
1582
// Need to keep eating frames to allow the EmuThread to exit correctly.
1583
continue;
1584
}
1585
}
1586
1587
EmuThreadJoin();
1588
1589
delete joystick;
1590
1591
graphicsContext->ThreadEnd();
1592
1593
NativeShutdown();
1594
1595
// Destroys Draw, which is used in NativeShutdown to shutdown.
1596
graphicsContext->ShutdownFromRenderThread();
1597
graphicsContext->Shutdown();
1598
delete graphicsContext;
1599
1600
if (audioDev > 0) {
1601
SDL_PauseAudioDevice(audioDev, 1);
1602
SDL_CloseAudioDevice(audioDev);
1603
}
1604
SDL_Quit();
1605
#if PPSSPP_PLATFORM(RPI)
1606
bcm_host_deinit();
1607
#endif
1608
1609
glslang::FinalizeProcess();
1610
printf("Leaving main\n");
1611
#ifdef HAVE_LIBNX
1612
socketExit();
1613
#endif
1614
1615
// If a restart was requested (and supported on this platform), respawn the executable.
1616
if (g_RestartRequested) {
1617
#if PPSSPP_PLATFORM(MAC)
1618
RestartMacApp();
1619
#elif PPSSPP_PLATFORM(LINUX)
1620
// Hackery from https://unix.stackexchange.com/questions/207935/how-to-restart-or-reset-a-running-process-in-linux,
1621
char *exec_argv[] = { argv[0], nullptr };
1622
execv("/proc/self/exe", exec_argv);
1623
#endif
1624
}
1625
return 0;
1626
}
1627
1628