CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/SDL/SDLMain.cpp
Views: 1401
// SDL/EGL implementation of the framework.1// This is quite messy due to platform-specific implementations and #ifdef's.2// If your platform is not supported, it is suggested to use Qt instead.34#include <cstdlib>5#include <unistd.h>6#include <pwd.h>78#include "ppsspp_config.h"9#if PPSSPP_PLATFORM(MAC)10#include "SDL2/SDL.h"11#include "SDL2/SDL_syswm.h"12#else13#include "SDL.h"14#include "SDL_syswm.h"15#endif16#include "SDL/SDLJoystick.h"17SDLJoystick *joystick = NULL;1819#if PPSSPP_PLATFORM(RPI)20#include <bcm_host.h>21#endif2223#include <atomic>24#include <algorithm>25#include <cmath>26#include <csignal>27#include <thread>28#include <locale>2930#include "Common/System/Display.h"31#include "Common/System/System.h"32#include "Common/System/Request.h"33#include "Common/System/NativeApp.h"34#include "ext/glslang/glslang/Public/ShaderLang.h"35#include "Common/Data/Format/PNGLoad.h"36#include "Common/Net/Resolve.h"37#include "Common/File/FileUtil.h"38#include "NKCodeFromSDL.h"39#include "Common/Math/math_util.h"40#include "Common/GPU/OpenGL/GLRenderManager.h"41#include "Common/Profiler/Profiler.h"4243#if defined(VK_USE_PLATFORM_XLIB_KHR)44#include <X11/Xlib.h>45#include <X11/Xutil.h>46#elif defined(VK_USE_PLATFORM_XCB_KHR)47#include <X11/Xlib.h>48#include <X11/Xutil.h>49#include <X11/Xlib-xcb.h>50#endif5152#include "Common/GraphicsContext.h"53#include "Common/TimeUtil.h"54#include "Common/Input/InputState.h"55#include "Common/Input/KeyCodes.h"56#include "Common/Data/Collections/ConstMap.h"57#include "Common/Data/Encoding/Utf8.h"58#include "Common/Thread/ThreadUtil.h"59#include "Core/System.h"60#include "Core/Core.h"61#include "Core/Config.h"62#include "Core/ConfigValues.h"63#include "SDLGLGraphicsContext.h"64#include "SDLVulkanGraphicsContext.h"6566#if PPSSPP_PLATFORM(MAC)67#include "SDL2/SDL_vulkan.h"68#else69#include "SDL_vulkan.h"70#endif7172#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)73#include "UI/DarwinFileSystemServices.h"74#endif7576#if PPSSPP_PLATFORM(MAC)77#include "CocoaBarItems.h"78#endif7980#if PPSSPP_PLATFORM(SWITCH)81#define LIBNX_SWKBD_LIMIT 500 // enforced by HOS82extern u32 __nx_applet_type; // Not exposed through a header?83#endif8485GlobalUIState lastUIState = UISTATE_MENU;86GlobalUIState GetUIState();8788static bool g_QuitRequested = false;89static bool g_RestartRequested = false;9091static int g_DesktopWidth = 0;92static int g_DesktopHeight = 0;93static float g_DesktopDPI = 1.0f;94static float g_ForcedDPI = 0.0f; // if this is 0.0f, use g_DesktopDPI95static float g_RefreshRate = 60.f;96static int g_sampleRate = 44100;9798static bool g_rebootEmuThread = false;99100static SDL_AudioSpec g_retFmt;101102static bool g_textFocusChanged;103static bool g_textFocus;104105106// Window state to be transferred to the main SDL thread.107static std::mutex g_mutexWindow;108struct WindowState {109std::string title;110bool toggleFullScreenNextFrame;111int toggleFullScreenType;112bool clipboardDataAvailable;113std::string clipboardString;114bool update;115};116static WindowState g_windowState;117118int getDisplayNumber(void) {119int displayNumber = 0;120char * displayNumberStr;121122//get environment123displayNumberStr=getenv("SDL_VIDEO_FULLSCREEN_HEAD");124125if (displayNumberStr) {126displayNumber = atoi(displayNumberStr);127}128129return displayNumber;130}131132void sdl_mixaudio_callback(void *userdata, Uint8 *stream, int len) {133NativeMix((short *)stream, len / (2 * 2), g_sampleRate);134}135136static SDL_AudioDeviceID audioDev = 0;137138// Must be called after NativeInit().139static void InitSDLAudioDevice(const std::string &name = "") {140SDL_AudioSpec fmt;141memset(&fmt, 0, sizeof(fmt));142fmt.freq = g_sampleRate;143fmt.format = AUDIO_S16;144fmt.channels = 2;145fmt.samples = 256;146fmt.callback = &sdl_mixaudio_callback;147fmt.userdata = nullptr;148149std::string startDevice = name;150if (startDevice.empty()) {151startDevice = g_Config.sAudioDevice;152}153154audioDev = 0;155if (!startDevice.empty()) {156audioDev = SDL_OpenAudioDevice(startDevice.c_str(), 0, &fmt, &g_retFmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);157if (audioDev <= 0) {158WARN_LOG(Log::Audio, "Failed to open audio device: %s", startDevice.c_str());159}160}161if (audioDev <= 0) {162INFO_LOG(Log::Audio, "SDL: Trying a different audio device");163audioDev = SDL_OpenAudioDevice(nullptr, 0, &fmt, &g_retFmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);164}165if (audioDev <= 0) {166ERROR_LOG(Log::Audio, "Failed to open audio device: %s", SDL_GetError());167} else {168if (g_retFmt.samples != fmt.samples) // Notify, but still use it169ERROR_LOG(Log::Audio, "Output audio samples: %d (requested: %d)", g_retFmt.samples, fmt.samples);170if (g_retFmt.format != fmt.format || g_retFmt.channels != fmt.channels) {171ERROR_LOG(Log::Audio, "Sound buffer format does not match requested format.");172ERROR_LOG(Log::Audio, "Output audio freq: %d (requested: %d)", g_retFmt.freq, fmt.freq);173ERROR_LOG(Log::Audio, "Output audio format: %d (requested: %d)", g_retFmt.format, fmt.format);174ERROR_LOG(Log::Audio, "Output audio channels: %d (requested: %d)", g_retFmt.channels, fmt.channels);175ERROR_LOG(Log::Audio, "Provided output format does not match requirement, turning audio off");176SDL_CloseAudioDevice(audioDev);177}178SDL_PauseAudioDevice(audioDev, 0);179}180}181182static void StopSDLAudioDevice() {183if (audioDev > 0) {184SDL_PauseAudioDevice(audioDev, 1);185SDL_CloseAudioDevice(audioDev);186}187}188189static void UpdateScreenDPI(SDL_Window *window) {190int drawable_width, window_width;191SDL_GetWindowSize(window, &window_width, NULL);192193if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL)194SDL_GL_GetDrawableSize(window, &drawable_width, NULL);195else if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN)196SDL_Vulkan_GetDrawableSize(window, &drawable_width, NULL);197198// Round up a little otherwise there would be a gap sometimes199// in fractional scaling200g_DesktopDPI = ((float) drawable_width + 1.0f) / window_width;201202// Temporary hack203#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)204if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {205g_DesktopDPI = 1.0f;206}207#endif208}209210// Simple implementations of System functions211212void System_Toast(std::string_view text) {213#ifdef _WIN32214std::wstring str = ConvertUTF8ToWString(text);215MessageBox(0, str.c_str(), L"Toast!", MB_ICONINFORMATION);216#else217printf("%*.s", (int)text.length(), text.data());218#endif219}220221void System_ShowKeyboard() {222// Irrelevant on PC223}224225void System_Vibrate(int length_ms) {226// Ignore on PC227}228229bool System_MakeRequest(SystemRequestType type, int requestId, const std::string ¶m1, const std::string ¶m2, int64_t param3, int64_t param4) {230switch (type) {231case SystemRequestType::RESTART_APP:232g_RestartRequested = true;233// TODO: Also save param1 and then split it into an argv.234return true;235case SystemRequestType::EXIT_APP:236// Do a clean exit237g_QuitRequested = true;238return true;239#if PPSSPP_PLATFORM(SWITCH)240case SystemRequestType::INPUT_TEXT_MODAL:241{242// swkbd only works on "real" titles243if (__nx_applet_type != AppletType_Application && __nx_applet_type != AppletType_SystemApplication) {244g_requestManager.PostSystemFailure(requestId);245return true;246}247248SwkbdConfig kbd;249Result rc = swkbdCreate(&kbd, 0);250251if (R_SUCCEEDED(rc)) {252char buf[LIBNX_SWKBD_LIMIT] = {'\0'};253swkbdConfigMakePresetDefault(&kbd);254255swkbdConfigSetHeaderText(&kbd, param1.c_str());256swkbdConfigSetInitialText(&kbd, param2.c_str());257258rc = swkbdShow(&kbd, buf, sizeof(buf));259260swkbdClose(&kbd);261262g_requestManager.PostSystemSuccess(requestId, buf);263return true;264}265266g_requestManager.PostSystemFailure(requestId);267return true;268}269#endif // PPSSPP_PLATFORM(SWITCH)270#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)271case SystemRequestType::BROWSE_FOR_FILE:272{273DarwinDirectoryPanelCallback callback = [requestId] (bool success, Path path) {274if (success) {275g_requestManager.PostSystemSuccess(requestId, path.c_str());276} else {277g_requestManager.PostSystemFailure(requestId);278}279};280BrowseFileType fileType = (BrowseFileType)param3;281DarwinFileSystemServices::presentDirectoryPanel(callback, /* allowFiles = */ true, /* allowDirectories = */ false, fileType);282return true;283}284case SystemRequestType::BROWSE_FOR_FOLDER:285{286DarwinDirectoryPanelCallback callback = [requestId] (bool success, Path path) {287if (success) {288g_requestManager.PostSystemSuccess(requestId, path.c_str());289} else {290g_requestManager.PostSystemFailure(requestId);291}292};293DarwinFileSystemServices::presentDirectoryPanel(callback, /* allowFiles = */ false, /* allowDirectories = */ true);294return true;295}296#endif297case SystemRequestType::TOGGLE_FULLSCREEN_STATE:298{299std::lock_guard<std::mutex> guard(g_mutexWindow);300g_windowState.update = true;301g_windowState.toggleFullScreenNextFrame = true;302if (param1 == "1") {303g_windowState.toggleFullScreenType = 1;304} else if (param1 == "0") {305g_windowState.toggleFullScreenType = 0;306} else {307// Just toggle.308g_windowState.toggleFullScreenType = -1;309}310return true;311}312case SystemRequestType::SET_WINDOW_TITLE:313{314std::lock_guard<std::mutex> guard(g_mutexWindow);315const char *app_name = System_GetPropertyBool(SYSPROP_APP_GOLD) ? "PPSSPP Gold" : "PPSSPP";316g_windowState.title = param1.empty() ? app_name : param1;317g_windowState.update = true;318return true;319}320case SystemRequestType::COPY_TO_CLIPBOARD:321{322std::lock_guard<std::mutex> guard(g_mutexWindow);323g_windowState.clipboardString = param1;324g_windowState.clipboardDataAvailable = true;325g_windowState.update = true;326return true;327}328case SystemRequestType::SHOW_FILE_IN_FOLDER:329{330#if PPSSPP_PLATFORM(WINDOWS)331SFGAOF flags;332PIDLIST_ABSOLUTE pidl = nullptr;333HRESULT hr = SHParseDisplayName(ConvertUTF8ToWString(ReplaceAll(path, "/", "\\")).c_str(), nullptr, &pidl, 0, &flags);334if (pidl) {335if (SUCCEEDED(hr))336SHOpenFolderAndSelectItems(pidl, 0, NULL, 0);337CoTaskMemFree(pidl);338}339#elif PPSSPP_PLATFORM(MAC)340OSXShowInFinder(param1.c_str());341#elif (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))342pid_t pid = fork();343if (pid < 0)344return true;345346if (pid == 0) {347execlp("xdg-open", "xdg-open", param1.c_str(), nullptr);348exit(1);349}350#endif /* PPSSPP_PLATFORM(WINDOWS) */351return true;352}353case SystemRequestType::NOTIFY_UI_EVENT:354{355switch ((UIEventNotification)param3) {356case UIEventNotification::TEXT_GOTFOCUS:357g_textFocus = true;358g_textFocusChanged = true;359break;360case UIEventNotification::POPUP_CLOSED:361case UIEventNotification::TEXT_LOSTFOCUS:362g_textFocus = false;363g_textFocusChanged = true;364break;365default:366break;367}368return true;369}370default:371return false;372}373}374375void System_AskForPermission(SystemPermission permission) {}376PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; }377378void System_LaunchUrl(LaunchUrlType urlType, const char *url) {379switch (urlType) {380case LaunchUrlType::BROWSER_URL:381case LaunchUrlType::MARKET_URL:382{383#if PPSSPP_PLATFORM(SWITCH)384Uuid uuid = { 0 };385WebWifiConfig conf;386webWifiCreate(&conf, NULL, url, uuid, 0);387webWifiShow(&conf, NULL);388#elif defined(MOBILE_DEVICE)389INFO_LOG(Log::System, "Would have gone to %s but LaunchBrowser is not implemented on this platform", url);390#elif defined(_WIN32)391std::wstring wurl = ConvertUTF8ToWString(url);392ShellExecute(NULL, L"open", wurl.c_str(), NULL, NULL, SW_SHOWNORMAL);393#elif defined(__APPLE__)394OSXOpenURL(url);395#else396std::string command = std::string("xdg-open ") + url;397int err = system(command.c_str());398if (err) {399INFO_LOG(Log::System, "Would have gone to %s but xdg-utils seems not to be installed", url);400}401#endif402break;403}404case LaunchUrlType::EMAIL_ADDRESS:405{406#if defined(MOBILE_DEVICE)407INFO_LOG(Log::System, "Would have opened your email client for %s but LaunchEmail is not implemented on this platform", url);408#elif defined(_WIN32)409std::wstring mailto = std::wstring(L"mailto:") + ConvertUTF8ToWString(url);410ShellExecute(NULL, L"open", mailto.c_str(), NULL, NULL, SW_SHOWNORMAL);411#elif defined(__APPLE__)412std::string mailToURL = std::string("mailto:") + url;413OSXOpenURL(mailToURL.c_str());414#else415std::string command = std::string("xdg-email ") + url;416int err = system(command.c_str());417if (err) {418INFO_LOG(Log::System, "Would have gone to %s but xdg-utils seems not to be installed", url);419}420#endif421break;422}423}424}425426std::string System_GetProperty(SystemProperty prop) {427switch (prop) {428case SYSPROP_NAME:429#ifdef _WIN32430return "SDL:Windows";431#elif __linux__432return "SDL:Linux";433#elif __APPLE__434return "SDL:macOS";435#elif PPSSPP_PLATFORM(SWITCH)436return "SDL:Horizon";437#else438return "SDL:";439#endif440case SYSPROP_LANGREGION: {441// Get user-preferred locale from OS442setlocale(LC_ALL, "");443std::string locale(setlocale(LC_ALL, NULL));444// Set c and c++ strings back to POSIX445std::locale::global(std::locale("POSIX"));446if (!locale.empty()) {447// Technically, this is an opaque string, but try to find the locale code.448size_t messagesPos = locale.find("LC_MESSAGES=");449if (messagesPos != std::string::npos) {450messagesPos += strlen("LC_MESSAGES=");451size_t semi = locale.find(';', messagesPos);452locale = locale.substr(messagesPos, semi - messagesPos);453}454455if (locale.find("_", 0) != std::string::npos) {456if (locale.find(".", 0) != std::string::npos) {457return locale.substr(0, locale.find(".",0));458}459return locale;460}461}462return "en_US";463}464case SYSPROP_CLIPBOARD_TEXT:465return SDL_HasClipboardText() ? SDL_GetClipboardText() : "";466case SYSPROP_AUDIO_DEVICE_LIST:467{468std::string result;469for (int i = 0; i < SDL_GetNumAudioDevices(0); ++i) {470const char *name = SDL_GetAudioDeviceName(i, 0);471if (!name) {472continue;473}474475if (i == 0) {476result = name;477} else {478result.append(1, '\0');479result.append(name);480}481}482return result;483}484case SYSPROP_BUILD_VERSION:485return PPSSPP_GIT_VERSION;486case SYSPROP_USER_DOCUMENTS_DIR:487{488const char *home = getenv("HOME");489return home ? std::string(home) : "/";490}491default:492return "";493}494}495496std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {497std::vector<std::string> result;498499switch (prop) {500case SYSPROP_TEMP_DIRS:501if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0)502result.push_back(getenv("TMPDIR"));503if (getenv("TMP") && strlen(getenv("TMP")) != 0)504result.push_back(getenv("TMP"));505if (getenv("TEMP") && strlen(getenv("TEMP")) != 0)506result.push_back(getenv("TEMP"));507return result;508509default:510return result;511}512}513514int64_t System_GetPropertyInt(SystemProperty prop) {515switch (prop) {516case SYSPROP_AUDIO_SAMPLE_RATE:517return g_retFmt.freq;518case SYSPROP_AUDIO_FRAMES_PER_BUFFER:519return g_retFmt.samples;520case SYSPROP_DEVICE_TYPE:521#if defined(MOBILE_DEVICE)522return DEVICE_TYPE_MOBILE;523#else524return DEVICE_TYPE_DESKTOP;525#endif526case SYSPROP_DISPLAY_COUNT:527return SDL_GetNumVideoDisplays();528case SYSPROP_KEYBOARD_LAYOUT:529{530char q, w, y;531q = SDL_GetKeyFromScancode(SDL_SCANCODE_Q);532w = SDL_GetKeyFromScancode(SDL_SCANCODE_W);533y = SDL_GetKeyFromScancode(SDL_SCANCODE_Y);534if (q == 'a' && w == 'z' && y == 'y')535return KEYBOARD_LAYOUT_AZERTY;536else if (q == 'q' && w == 'w' && y == 'z')537return KEYBOARD_LAYOUT_QWERTZ;538return KEYBOARD_LAYOUT_QWERTY;539}540case SYSPROP_DISPLAY_XRES:541return g_DesktopWidth;542case SYSPROP_DISPLAY_YRES:543return g_DesktopHeight;544default:545return -1;546}547}548549float System_GetPropertyFloat(SystemProperty prop) {550switch (prop) {551case SYSPROP_DISPLAY_REFRESH_RATE:552return g_RefreshRate;553case SYSPROP_DISPLAY_DPI:554return (g_ForcedDPI == 0.0f ? g_DesktopDPI : g_ForcedDPI) * 96.0;555case SYSPROP_DISPLAY_SAFE_INSET_LEFT:556case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:557case SYSPROP_DISPLAY_SAFE_INSET_TOP:558case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:559return 0.0f;560default:561return -1;562}563}564565bool System_GetPropertyBool(SystemProperty prop) {566switch (prop) {567case SYSPROP_HAS_TEXT_CLIPBOARD:568case SYSPROP_CAN_SHOW_FILE:569#if PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(MAC) || (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))570return true;571#else572return false;573#endif574case SYSPROP_HAS_OPEN_DIRECTORY:575#if PPSSPP_PLATFORM(WINDOWS)576return true;577#elif PPSSPP_PLATFORM(MAC) || (PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID))578return true;579#endif580case SYSPROP_HAS_BACK_BUTTON:581return true;582#if PPSSPP_PLATFORM(SWITCH)583case SYSPROP_HAS_TEXT_INPUT_DIALOG:584return __nx_applet_type == AppletType_Application || __nx_applet_type != AppletType_SystemApplication;585#endif586case SYSPROP_HAS_KEYBOARD:587return true;588case SYSPROP_APP_GOLD:589#ifdef GOLD590return true;591#else592return false;593#endif594case SYSPROP_CAN_JIT:595return true;596case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR:597return true; // FileUtil.cpp: OpenFileInEditor598#ifndef HTTPS_NOT_AVAILABLE599case SYSPROP_SUPPORTS_HTTPS:600return !g_Config.bDisableHTTPS;601#endif602#if PPSSPP_PLATFORM(MAC)603case SYSPROP_HAS_FOLDER_BROWSER:604case SYSPROP_HAS_FILE_BROWSER:605return true;606#endif607case SYSPROP_HAS_ACCELEROMETER:608#if defined(MOBILE_DEVICE)609return true;610#else611return false;612#endif613default:614return false;615}616}617618void System_Notify(SystemNotification notification) {619switch (notification) {620case SystemNotification::AUDIO_RESET_DEVICE:621StopSDLAudioDevice();622InitSDLAudioDevice();623break;624625default:626break;627}628}629630// returns -1 on failure631static int parseInt(const char *str) {632int val;633int retval = sscanf(str, "%d", &val);634printf("%i = scanf %s\n", retval, str);635if (retval != 1) {636return -1;637} else {638return val;639}640}641642static float parseFloat(const char *str) {643float val;644int retval = sscanf(str, "%f", &val);645printf("%i = sscanf %s\n", retval, str);646if (retval != 1) {647return -1.0f;648} else {649return val;650}651}652653void UpdateWindowState(SDL_Window *window) {654SDL_SetWindowTitle(window, g_windowState.title.c_str());655if (g_windowState.toggleFullScreenNextFrame) {656g_windowState.toggleFullScreenNextFrame = false;657658Uint32 window_flags = SDL_GetWindowFlags(window);659if (g_windowState.toggleFullScreenType == -1) {660window_flags ^= SDL_WINDOW_FULLSCREEN_DESKTOP;661} else if (g_windowState.toggleFullScreenType == 1) {662window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;663} else {664window_flags &= ~SDL_WINDOW_FULLSCREEN_DESKTOP;665}666SDL_SetWindowFullscreen(window, window_flags);667}668if (g_windowState.clipboardDataAvailable) {669SDL_SetClipboardText(g_windowState.clipboardString.c_str());670g_windowState.clipboardDataAvailable = false;671g_windowState.clipboardString.clear();672}673g_windowState.update = false;674}675676enum class EmuThreadState {677DISABLED,678START_REQUESTED,679RUNNING,680QUIT_REQUESTED,681STOPPED,682};683684static std::thread emuThread;685static std::atomic<int> emuThreadState((int)EmuThreadState::DISABLED);686687static void EmuThreadFunc(GraphicsContext *graphicsContext) {688SetCurrentThreadName("Emu");689690// There's no real requirement that NativeInit happen on this thread.691// We just call the update/render loop here.692emuThreadState = (int)EmuThreadState::RUNNING;693694NativeInitGraphics(graphicsContext);695696while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {697UpdateRunLoop(graphicsContext);698}699emuThreadState = (int)EmuThreadState::STOPPED;700graphicsContext->StopThread();701702NativeShutdownGraphics();703}704705static void EmuThreadStart(GraphicsContext *context) {706emuThreadState = (int)EmuThreadState::START_REQUESTED;707emuThread = std::thread(&EmuThreadFunc, context);708}709710static void EmuThreadStop(const char *reason) {711emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;712}713714static void EmuThreadJoin() {715emuThread.join();716emuThread = std::thread();717}718719struct InputStateTracker {720void MouseCaptureControl() {721bool captureMouseCondition = g_Config.bMouseControl && ((GetUIState() == UISTATE_INGAME && g_Config.bMouseConfine) || g_Config.bMapMouse);722if (mouseCaptured != captureMouseCondition) {723mouseCaptured = captureMouseCondition;724if (captureMouseCondition)725SDL_SetRelativeMouseMode(SDL_TRUE);726else727SDL_SetRelativeMouseMode(SDL_FALSE);728}729}730731bool mouseDown;732bool mouseCaptured;733};734735static void ProcessSDLEvent(SDL_Window *window, const SDL_Event &event, InputStateTracker *inputTracker) {736// We have to juggle around 3 kinds of "DPI spaces" if a logical DPI is737// provided (through --dpi, it is equal to system DPI if unspecified):738// - SDL gives us motion events in "system DPI" points739// - UpdateScreenScale expects pixels, so in a way "96 DPI" points740// - The UI code expects motion events in "logical DPI" points741float mx = event.motion.x * g_DesktopDPI * g_display.dpi_scale_x;742float my = event.motion.y * g_DesktopDPI * g_display.dpi_scale_x;743744switch (event.type) {745case SDL_QUIT:746g_QuitRequested = 1;747break;748749#if !defined(MOBILE_DEVICE)750case SDL_WINDOWEVENT:751switch (event.window.event) {752case SDL_WINDOWEVENT_SIZE_CHANGED: // better than RESIZED, more general753{754int new_width = event.window.data1;755int new_height = event.window.data2;756757// The size given by SDL is in point-units, convert these to758// pixels before passing to UpdateScreenScale()759int new_width_px = new_width * g_DesktopDPI;760int new_height_px = new_height * g_DesktopDPI;761762Core_NotifyWindowHidden(false);763764Uint32 window_flags = SDL_GetWindowFlags(window);765bool fullscreen = (window_flags & SDL_WINDOW_FULLSCREEN);766767// This one calls NativeResized if the size changed.768UpdateScreenScale(new_width_px, new_height_px);769770// Set variable here in case fullscreen was toggled by hotkey771if (g_Config.UseFullScreen() != fullscreen) {772g_Config.bFullScreen = fullscreen;773g_Config.iForceFullScreen = -1;774} else {775// It is possible for the monitor to change DPI, so recalculate776// DPI on each resize event.777UpdateScreenDPI(window);778}779780if (!g_Config.bFullScreen) {781g_Config.iWindowWidth = new_width;782g_Config.iWindowHeight = new_height;783}784// Hide/Show cursor correctly toggling fullscreen785if (lastUIState == UISTATE_INGAME && fullscreen && !g_Config.bShowTouchControls) {786SDL_ShowCursor(SDL_DISABLE);787} else if (lastUIState != UISTATE_INGAME || !fullscreen) {788SDL_ShowCursor(SDL_ENABLE);789}790break;791}792793case SDL_WINDOWEVENT_MOVED:794{795Uint32 window_flags = SDL_GetWindowFlags(window);796bool fullscreen = (window_flags & SDL_WINDOW_FULLSCREEN);797if (!fullscreen) {798g_Config.iWindowX = (int)event.window.data1;799g_Config.iWindowY = (int)event.window.data2;800}801break;802}803804case SDL_WINDOWEVENT_MINIMIZED:805case SDL_WINDOWEVENT_HIDDEN:806Core_NotifyWindowHidden(true);807break;808case SDL_WINDOWEVENT_EXPOSED:809case SDL_WINDOWEVENT_SHOWN:810Core_NotifyWindowHidden(false);811break;812default:813break;814}815break;816#endif817case SDL_KEYDOWN:818{819if (event.key.repeat > 0) { break;}820int k = event.key.keysym.sym;821KeyInput key;822key.flags = KEY_DOWN;823auto mapped = KeyMapRawSDLtoNative.find(k);824if (mapped == KeyMapRawSDLtoNative.end() || mapped->second == NKCODE_UNKNOWN) {825break;826}827key.keyCode = mapped->second;828key.deviceId = DEVICE_ID_KEYBOARD;829NativeKey(key);830831#ifdef _DEBUG832if (k == SDLK_F7) {833printf("f7 pressed - rebooting emuthread\n");834g_rebootEmuThread = true;835}836#endif837// Convenience subset of what838// "Enable standard shortcut keys"839// does on Windows.840if(g_Config.bSystemControls) {841bool ctrl = bool(event.key.keysym.mod & KMOD_CTRL);842if (ctrl && (k == SDLK_w))843{844if (Core_IsStepping())845Core_EnableStepping(false);846Core_Stop();847System_PostUIMessage(UIMessage::REQUEST_GAME_STOP);848// NOTE: Unlike Windows version, this849// does not need Core_WaitInactive();850// since SDL does not have a separate851// UI thread.852}853if (ctrl && (k == SDLK_b))854{855System_PostUIMessage(UIMessage::REQUEST_GAME_RESET);856Core_EnableStepping(false);857}858}859break;860}861case SDL_KEYUP:862{863if (event.key.repeat > 0) { break;}864int k = event.key.keysym.sym;865KeyInput key;866key.flags = KEY_UP;867auto mapped = KeyMapRawSDLtoNative.find(k);868if (mapped == KeyMapRawSDLtoNative.end() || mapped->second == NKCODE_UNKNOWN) {869break;870}871key.keyCode = mapped->second;872key.deviceId = DEVICE_ID_KEYBOARD;873NativeKey(key);874break;875}876case SDL_TEXTINPUT:877{878int pos = 0;879int c = u8_nextchar(event.text.text, &pos, strlen(event.text.text));880KeyInput key;881key.flags = KEY_CHAR;882key.unicodeChar = c;883key.deviceId = DEVICE_ID_KEYBOARD;884NativeKey(key);885break;886}887// This behavior doesn't feel right on a macbook with a touchpad.888#if !PPSSPP_PLATFORM(MAC)889case SDL_FINGERMOTION:890{891int w, h;892SDL_GetWindowSize(window, &w, &h);893TouchInput input;894input.id = event.tfinger.fingerId;895input.x = event.tfinger.x * w * g_DesktopDPI * g_display.dpi_scale_x;896input.y = event.tfinger.y * h * g_DesktopDPI * g_display.dpi_scale_x;897input.flags = TOUCH_MOVE;898input.timestamp = event.tfinger.timestamp;899NativeTouch(input);900break;901}902case SDL_FINGERDOWN:903{904int w, h;905SDL_GetWindowSize(window, &w, &h);906TouchInput input;907input.id = event.tfinger.fingerId;908input.x = event.tfinger.x * w * g_DesktopDPI * g_display.dpi_scale_x;909input.y = event.tfinger.y * h * g_DesktopDPI * g_display.dpi_scale_x;910input.flags = TOUCH_DOWN;911input.timestamp = event.tfinger.timestamp;912NativeTouch(input);913914KeyInput key;915key.deviceId = DEVICE_ID_MOUSE;916key.keyCode = NKCODE_EXT_MOUSEBUTTON_1;917key.flags = KEY_DOWN;918NativeKey(key);919break;920}921case SDL_FINGERUP:922{923int w, h;924SDL_GetWindowSize(window, &w, &h);925TouchInput input;926input.id = event.tfinger.fingerId;927input.x = event.tfinger.x * w * g_DesktopDPI * g_display.dpi_scale_x;928input.y = event.tfinger.y * h * g_DesktopDPI * g_display.dpi_scale_x;929input.flags = TOUCH_UP;930input.timestamp = event.tfinger.timestamp;931NativeTouch(input);932933KeyInput key;934key.deviceId = DEVICE_ID_MOUSE;935key.keyCode = NKCODE_EXT_MOUSEBUTTON_1;936key.flags = KEY_UP;937NativeKey(key);938break;939}940#endif941case SDL_MOUSEBUTTONDOWN:942switch (event.button.button) {943case SDL_BUTTON_LEFT:944{945inputTracker->mouseDown = true;946TouchInput input{};947input.x = mx;948input.y = my;949input.flags = TOUCH_DOWN | TOUCH_MOUSE;950input.id = 0;951NativeTouch(input);952KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KEY_DOWN);953NativeKey(key);954}955break;956case SDL_BUTTON_RIGHT:957{958// Right button only emits mouse move events. This is weird,959// but consistent with Windows. Needs cleanup.960TouchInput input{};961input.x = mx;962input.y = my;963input.flags = TOUCH_MOVE | TOUCH_MOUSE;964input.id = 0;965NativeTouch(input);966KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KEY_DOWN);967NativeKey(key);968}969break;970case SDL_BUTTON_MIDDLE:971{972KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_3, KEY_DOWN);973NativeKey(key);974}975break;976case SDL_BUTTON_X1:977{978KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_4, KEY_DOWN);979NativeKey(key);980}981break;982case SDL_BUTTON_X2:983{984KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_5, KEY_DOWN);985NativeKey(key);986}987break;988}989break;990case SDL_MOUSEWHEEL:991{992KeyInput key;993key.deviceId = DEVICE_ID_MOUSE;994key.flags = KEY_DOWN;995#if SDL_VERSION_ATLEAST(2, 0, 18)996if (event.wheel.preciseY != 0.0f) {997// Should the scale be DPI-driven?998const float scale = 30.0f;999key.keyCode = event.wheel.preciseY > 0 ? NKCODE_EXT_MOUSEWHEEL_UP : NKCODE_EXT_MOUSEWHEEL_DOWN;1000key.flags |= KEY_HASWHEELDELTA;1001int wheelDelta = event.wheel.preciseY * scale;1002if (event.wheel.preciseY < 0) {1003wheelDelta = -wheelDelta;1004}1005key.flags |= wheelDelta << 16;1006NativeKey(key);1007break;1008}1009#endif1010if (event.wheel.y > 0) {1011key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP;1012NativeKey(key);1013} else if (event.wheel.y < 0) {1014key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN;1015NativeKey(key);1016}1017break;1018}1019case SDL_MOUSEMOTION:1020if (inputTracker->mouseDown) {1021TouchInput input{};1022input.x = mx;1023input.y = my;1024input.flags = TOUCH_MOVE | TOUCH_MOUSE;1025input.id = 0;1026NativeTouch(input);1027}1028NativeMouseDelta(event.motion.xrel, event.motion.yrel);1029break;1030case SDL_MOUSEBUTTONUP:1031switch (event.button.button) {1032case SDL_BUTTON_LEFT:1033{1034inputTracker->mouseDown = false;1035TouchInput input{};1036input.x = mx;1037input.y = my;1038input.flags = TOUCH_UP | TOUCH_MOUSE;1039NativeTouch(input);1040KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_1, KEY_UP);1041NativeKey(key);1042}1043break;1044case SDL_BUTTON_RIGHT:1045{1046// Right button only emits mouse move events. This is weird,1047// but consistent with Windows. Needs cleanup.1048TouchInput input{};1049input.x = mx;1050input.y = my;1051input.flags = TOUCH_MOVE | TOUCH_MOUSE;1052NativeTouch(input);1053KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_2, KEY_UP);1054NativeKey(key);1055}1056break;1057case SDL_BUTTON_MIDDLE:1058{1059KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_3, KEY_UP);1060NativeKey(key);1061}1062break;1063case SDL_BUTTON_X1:1064{1065KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_4, KEY_UP);1066NativeKey(key);1067}1068break;1069case SDL_BUTTON_X2:1070{1071KeyInput key(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_5, KEY_UP);1072NativeKey(key);1073}1074break;1075}1076break;10771078#if SDL_VERSION_ATLEAST(2, 0, 4)1079case SDL_AUDIODEVICEADDED:1080// Automatically switch to the new device.1081if (event.adevice.iscapture == 0) {1082const char *name = SDL_GetAudioDeviceName(event.adevice.which, 0);1083if (!name) {1084break;1085}1086// Don't start auto switching for a second, because some devices init on start.1087bool doAutoSwitch = g_Config.bAutoAudioDevice && time_now_d() > 1.0f;1088if (doAutoSwitch || g_Config.sAudioDevice == name) {1089StopSDLAudioDevice();1090InitSDLAudioDevice(name ? name : "");1091}1092}1093break;1094case SDL_AUDIODEVICEREMOVED:1095if (event.adevice.iscapture == 0 && event.adevice.which == audioDev) {1096StopSDLAudioDevice();1097InitSDLAudioDevice();1098}1099break;1100#endif11011102default:1103if (joystick) {1104joystick->ProcessInput(event);1105}1106break;1107}1108}11091110void UpdateTextFocus() {1111if (g_textFocusChanged) {1112INFO_LOG(Log::System, "Updating text focus: %d", g_textFocus);1113if (g_textFocus) {1114SDL_StartTextInput();1115} else {1116SDL_StopTextInput();1117}1118g_textFocusChanged = false;1119}1120}11211122void UpdateSDLCursor() {1123#if !defined(MOBILE_DEVICE)1124if (lastUIState != GetUIState()) {1125lastUIState = GetUIState();1126if (lastUIState == UISTATE_INGAME && g_Config.UseFullScreen() && !g_Config.bShowTouchControls)1127SDL_ShowCursor(SDL_DISABLE);1128if (lastUIState != UISTATE_INGAME || !g_Config.UseFullScreen())1129SDL_ShowCursor(SDL_ENABLE);1130}1131#endif1132}11331134#ifdef _WIN321135#undef main1136#endif1137int main(int argc, char *argv[]) {1138for (int i = 1; i < argc; i++) {1139if (!strcmp(argv[i], "--version")) {1140printf("%s\n", PPSSPP_GIT_VERSION);1141return 0;1142}1143}11441145TimeInit();11461147#ifdef HAVE_LIBNX1148socketInitializeDefault();1149nxlinkStdio();1150#else // HAVE_LIBNX1151// Ignore sigpipe.1152if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {1153perror("Unable to ignore SIGPIPE");1154}1155#endif // HAVE_LIBNX11561157PROFILE_INIT();1158glslang::InitializeProcess();11591160#if PPSSPP_PLATFORM(RPI)1161bcm_host_init();1162#endif1163putenv((char*)"SDL_VIDEO_CENTERED=1");1164SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");11651166#ifdef SDL_HINT_TOUCH_MOUSE_EVENTS1167SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");1168#endif11691170bool vulkanMayBeAvailable = false;1171if (VulkanMayBeAvailable()) {1172printf("DEBUG: Vulkan might be available.\n");1173vulkanMayBeAvailable = true;1174} else {1175printf("DEBUG: Vulkan is not available, not using Vulkan.\n");1176}11771178SDL_version compiled;1179SDL_version linked;1180int set_xres = -1;1181int set_yres = -1;1182bool portrait = false;1183bool set_ipad = false;1184float set_dpi = 0.0f;1185float set_scale = 1.0f;11861187// Produce a new set of arguments with the ones we skip.1188int remain_argc = 1;1189const char *remain_argv[256] = { argv[0] };11901191Uint32 mode = 0;1192for (int i = 1; i < argc; i++) {1193if (!strcmp(argv[i],"--fullscreen")) {1194mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;1195g_Config.iForceFullScreen = 1;1196} else if (set_xres == -2)1197set_xres = parseInt(argv[i]);1198else if (set_yres == -2)1199set_yres = parseInt(argv[i]);1200else if (set_dpi == -2)1201set_dpi = parseFloat(argv[i]);1202else if (set_scale == -2)1203set_scale = parseFloat(argv[i]);1204else if (!strcmp(argv[i],"--xres"))1205set_xres = -2;1206else if (!strcmp(argv[i],"--yres"))1207set_yres = -2;1208else if (!strcmp(argv[i],"--dpi"))1209set_dpi = -2;1210else if (!strcmp(argv[i],"--scale"))1211set_scale = -2;1212else if (!strcmp(argv[i],"--ipad"))1213set_ipad = true;1214else if (!strcmp(argv[i],"--portrait"))1215portrait = true;1216else {1217remain_argv[remain_argc++] = argv[i];1218}1219}12201221std::string app_name;1222std::string app_name_nice;1223std::string version;1224bool landscape;1225NativeGetAppInfo(&app_name, &app_name_nice, &landscape, &version);12261227bool joystick_enabled = true;1228if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) < 0) {1229fprintf(stderr, "Failed to initialize SDL with joystick support. Retrying without.\n");1230joystick_enabled = false;1231if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {1232fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError());1233return 1;1234}1235}12361237SDL_VERSION(&compiled);1238SDL_GetVersion(&linked);1239printf("Info: We compiled against SDL version %d.%d.%d", compiled.major, compiled.minor, compiled.patch);1240if (compiled.minor != linked.minor || compiled.patch != linked.patch) {1241printf(", 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);1242} else {1243printf(" and we are linking against SDL version %d.%d.%d. :)\n", linked.major, linked.minor, linked.patch);1244}12451246// Get the video info before doing anything else, so we don't get skewed resolution results.1247// TODO: support multiple displays correctly1248SDL_DisplayMode displayMode;1249int should_be_zero = SDL_GetCurrentDisplayMode(0, &displayMode);1250if (should_be_zero != 0) {1251fprintf(stderr, "Could not get display mode: %s\n", SDL_GetError());1252return 1;1253}1254g_DesktopWidth = displayMode.w;1255g_DesktopHeight = displayMode.h;1256g_RefreshRate = displayMode.refresh_rate;12571258SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);1259SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);1260SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);1261SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);1262SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);1263SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);12641265// Force fullscreen if the resolution is too low to run windowed.1266if (g_DesktopWidth < 480 * 2 && g_DesktopHeight < 272 * 2) {1267mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;1268}12691270// If we're on mobile, don't try for windowed either.1271#if defined(MOBILE_DEVICE) && !PPSSPP_PLATFORM(SWITCH)1272mode |= SDL_WINDOW_FULLSCREEN;1273#elif defined(USING_FBDEV) || PPSSPP_PLATFORM(SWITCH)1274mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;1275#else1276mode |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;1277#endif12781279if (mode & SDL_WINDOW_FULLSCREEN_DESKTOP) {1280g_display.pixel_xres = g_DesktopWidth;1281g_display.pixel_yres = g_DesktopHeight;1282if (g_Config.iForceFullScreen == -1)1283g_Config.bFullScreen = true;1284} else {1285// set a sensible default resolution (2x)1286g_display.pixel_xres = 480 * 2 * set_scale;1287g_display.pixel_yres = 272 * 2 * set_scale;1288if (portrait) {1289std::swap(g_display.pixel_xres, g_display.pixel_yres);1290}1291if (g_Config.iForceFullScreen == -1)1292g_Config.bFullScreen = false;1293}12941295if (set_ipad) {1296g_display.pixel_xres = 1024;1297g_display.pixel_yres = 768;1298}1299if (!landscape) {1300std::swap(g_display.pixel_xres, g_display.pixel_yres);1301}13021303if (set_xres > 0) {1304g_display.pixel_xres = set_xres;1305}1306if (set_yres > 0) {1307g_display.pixel_yres = set_yres;1308}1309if (set_dpi > 0) {1310g_ForcedDPI = set_dpi;1311}13121313// Mac / Linux1314char path[2048];1315#if PPSSPP_PLATFORM(SWITCH)1316strcpy(path, "/switch/ppsspp/");1317#else1318const char *the_path = getenv("HOME");1319if (!the_path) {1320struct passwd *pwd = getpwuid(getuid());1321if (pwd)1322the_path = pwd->pw_dir;1323}1324if (the_path)1325strcpy(path, the_path);1326#endif1327if (strlen(path) > 0 && path[strlen(path) - 1] != '/')1328strcat(path, "/");13291330#if PPSSPP_PLATFORM(MAC)1331std::string external_dir_str;1332if (SDL_GetBasePath())1333external_dir_str = std::string(SDL_GetBasePath()) + "/assets";1334else1335external_dir_str = "/tmp";1336const char *external_dir = external_dir_str.c_str();1337#else1338const char *external_dir = "/tmp";1339#endif1340NativeInit(remain_argc, (const char **)remain_argv, path, external_dir, nullptr);13411342// Use the setting from the config when initing the window.1343if (g_Config.UseFullScreen())1344mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;13451346int x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(getDisplayNumber());1347int y = SDL_WINDOWPOS_UNDEFINED;1348int w = g_display.pixel_xres;1349int h = g_display.pixel_yres;13501351if (!g_Config.bFullScreen) {1352if (g_Config.iWindowX != -1)1353x = g_Config.iWindowX;1354if (g_Config.iWindowY != -1)1355y = g_Config.iWindowY;1356if (g_Config.iWindowWidth > 0 && set_xres <= 0)1357w = g_Config.iWindowWidth;1358if (g_Config.iWindowHeight > 0 && set_yres <= 0)1359h = g_Config.iWindowHeight;1360}13611362GraphicsContext *graphicsContext = nullptr;1363SDL_Window *window = nullptr;13641365// Switch away from Vulkan if not available.1366if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN && !vulkanMayBeAvailable) {1367g_Config.iGPUBackend = (int)GPUBackend::OPENGL;1368}13691370std::string error_message;1371if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {1372SDLGLGraphicsContext *glctx = new SDLGLGraphicsContext();1373if (glctx->Init(window, x, y, w, h, mode, &error_message) != 0) {1374// Let's try the fallback once per process run.1375printf("GL init error '%s' - falling back to Vulkan\n", error_message.c_str());1376g_Config.iGPUBackend = (int)GPUBackend::VULKAN;1377SetGPUBackend((GPUBackend)g_Config.iGPUBackend);1378delete glctx;13791380// NOTE : This should match the lines below in the Vulkan case.1381SDLVulkanGraphicsContext *vkctx = new SDLVulkanGraphicsContext();1382if (!vkctx->Init(window, x, y, w, h, mode | SDL_WINDOW_VULKAN, &error_message)) {1383printf("Vulkan fallback failed: %s\n", error_message.c_str());1384return 1;1385}1386graphicsContext = vkctx;1387} else {1388graphicsContext = glctx;1389}1390#if !PPSSPP_PLATFORM(SWITCH)1391} else if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {1392SDLVulkanGraphicsContext *vkctx = new SDLVulkanGraphicsContext();1393if (!vkctx->Init(window, x, y, w, h, mode | SDL_WINDOW_VULKAN, &error_message)) {1394// Let's try the fallback once per process run.13951396printf("Vulkan init error '%s' - falling back to GL\n", error_message.c_str());1397g_Config.iGPUBackend = (int)GPUBackend::OPENGL;1398SetGPUBackend((GPUBackend)g_Config.iGPUBackend);1399delete vkctx;14001401// NOTE : This should match the three lines above in the OpenGL case.1402SDLGLGraphicsContext *glctx = new SDLGLGraphicsContext();1403if (glctx->Init(window, x, y, w, h, mode, &error_message) != 0) {1404printf("GL fallback failed: %s\n", error_message.c_str());1405return 1;1406}1407graphicsContext = glctx;1408} else {1409graphicsContext = vkctx;1410}1411#endif1412}1413#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)1414if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {1415g_ForcedDPI = 1.0f;1416}1417#endif14181419UpdateScreenDPI(window);14201421float dpi_scale = 1.0f / (g_ForcedDPI == 0.0f ? g_DesktopDPI : g_ForcedDPI);14221423UpdateScreenScale(w * g_DesktopDPI, h * g_DesktopDPI);14241425bool mainThreadIsRender = g_Config.iGPUBackend == (int)GPUBackend::OPENGL;14261427SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str());14281429char iconPath[PATH_MAX];1430#if defined(ASSETS_DIR)1431snprintf(iconPath, PATH_MAX, "%sicon_regular_72.png", ASSETS_DIR);1432if (access(iconPath, F_OK) != 0)1433snprintf(iconPath, PATH_MAX, "%sassets/icon_regular_72.png", SDL_GetBasePath() ? SDL_GetBasePath() : "");1434#else1435snprintf(iconPath, PATH_MAX, "%sassets/icon_regular_72.png", SDL_GetBasePath() ? SDL_GetBasePath() : "");1436#endif1437int width = 0, height = 0;1438unsigned char *imageData;1439if (pngLoad(iconPath, &width, &height, &imageData) == 1) {1440SDL_Surface *surface = SDL_CreateRGBSurface(0, width, height, 32,14410x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);1442memcpy(surface->pixels, imageData, width*height*4);1443SDL_SetWindowIcon(window, surface);1444SDL_FreeSurface(surface);1445free(imageData);1446imageData = NULL;1447}14481449// Since we render from the main thread, there's nothing done here, but we call it to avoid confusion.1450if (!graphicsContext->InitFromRenderThread(&error_message)) {1451printf("Init from thread error: '%s'\n", error_message.c_str());1452return 1;1453}14541455// OK, we have a valid graphics backend selected. Let's clear the failures.1456g_Config.sFailedGPUBackends.clear();14571458#ifdef MOBILE_DEVICE1459SDL_ShowCursor(SDL_DISABLE);1460#endif14611462// Avoid the IME popup when holding keys. This doesn't affect all versions of SDL.1463// Note: We re-enable it in text input fields! This is necessary otherwise we don't receive1464// KEY_CHAR events.1465SDL_StopTextInput();14661467InitSDLAudioDevice();14681469if (joystick_enabled) {1470joystick = new SDLJoystick();1471} else {1472joystick = nullptr;1473}1474EnableFZ();14751476EmuThreadStart(graphicsContext);14771478graphicsContext->ThreadStart();14791480InputStateTracker inputTracker{};14811482#if PPSSPP_PLATFORM(MAC)1483// setup menu items for macOS1484initializeOSXExtras();1485#endif14861487bool waitOnExit = g_Config.iGPUBackend == (int)GPUBackend::OPENGL;14881489if (!mainThreadIsRender) {1490// Vulkan mode uses this.1491// We should only be a message pump. This allows for lower latency1492// input events, and so on.1493while (true) {1494SDL_Event event;1495while (SDL_WaitEventTimeout(&event, 100)) {1496ProcessSDLEvent(window, event, &inputTracker);1497}1498if (g_QuitRequested || g_RestartRequested)1499break;15001501UpdateTextFocus();1502UpdateSDLCursor();15031504inputTracker.MouseCaptureControl();15051506{1507std::lock_guard<std::mutex> guard(g_mutexWindow);1508if (g_windowState.update) {1509UpdateWindowState(window);1510}1511}1512}1513} else while (true) {1514{1515SDL_Event event;1516while (SDL_PollEvent(&event)) {1517ProcessSDLEvent(window, event, &inputTracker);1518}1519}1520if (g_QuitRequested || g_RestartRequested)1521break;1522if (emuThreadState == (int)EmuThreadState::DISABLED) {1523UpdateRunLoop(graphicsContext);1524}1525if (g_QuitRequested || g_RestartRequested)1526break;15271528UpdateTextFocus();1529UpdateSDLCursor();15301531inputTracker.MouseCaptureControl();15321533bool renderThreadPaused = Core_IsWindowHidden() && g_Config.bPauseWhenMinimized && emuThreadState != (int)EmuThreadState::DISABLED;1534if (emuThreadState != (int)EmuThreadState::DISABLED && !renderThreadPaused) {1535if (!graphicsContext->ThreadFrame())1536break;1537}15381539{1540std::lock_guard<std::mutex> guard(g_mutexWindow);1541if (g_windowState.update) {1542UpdateWindowState(window);1543}1544}15451546if (g_rebootEmuThread) {1547printf("rebooting emu thread");1548g_rebootEmuThread = false;1549EmuThreadStop("shutdown");1550// Skipping GL calls, the old context is gone.1551while (graphicsContext->ThreadFrame()) {1552INFO_LOG(Log::System, "graphicsContext->ThreadFrame executed to clear buffers");1553}1554EmuThreadJoin();1555graphicsContext->ThreadEnd();1556graphicsContext->ShutdownFromRenderThread();15571558printf("OK, shutdown complete. starting up graphics again.\n");15591560if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {1561SDLGLGraphicsContext *ctx = (SDLGLGraphicsContext *)graphicsContext;1562if (!ctx->Init(window, x, y, w, h, mode, &error_message)) {1563printf("Failed to reinit graphics.\n");1564}1565}15661567if (!graphicsContext->InitFromRenderThread(&error_message)) {1568System_Toast("Graphics initialization failed. Quitting.");1569return 1;1570}15711572EmuThreadStart(graphicsContext);1573graphicsContext->ThreadStart();1574}1575}15761577EmuThreadStop("shutdown");15781579if (waitOnExit) {1580while (graphicsContext->ThreadFrame()) {1581// Need to keep eating frames to allow the EmuThread to exit correctly.1582continue;1583}1584}15851586EmuThreadJoin();15871588delete joystick;15891590graphicsContext->ThreadEnd();15911592NativeShutdown();15931594// Destroys Draw, which is used in NativeShutdown to shutdown.1595graphicsContext->ShutdownFromRenderThread();1596graphicsContext->Shutdown();1597delete graphicsContext;15981599if (audioDev > 0) {1600SDL_PauseAudioDevice(audioDev, 1);1601SDL_CloseAudioDevice(audioDev);1602}1603SDL_Quit();1604#if PPSSPP_PLATFORM(RPI)1605bcm_host_deinit();1606#endif16071608glslang::FinalizeProcess();1609printf("Leaving main\n");1610#ifdef HAVE_LIBNX1611socketExit();1612#endif16131614// If a restart was requested (and supported on this platform), respawn the executable.1615if (g_RestartRequested) {1616#if PPSSPP_PLATFORM(MAC)1617RestartMacApp();1618#elif PPSSPP_PLATFORM(LINUX)1619// Hackery from https://unix.stackexchange.com/questions/207935/how-to-restart-or-reset-a-running-process-in-linux,1620char *exec_argv[] = { argv[0], nullptr };1621execv("/proc/self/exe", exec_argv);1622#endif1623}1624return 0;1625}162616271628