diff --git a/Common/ConsoleListener.cpp b/Common/ConsoleListener.cpp index b5285e1f25..6b50ae0742 100644 --- a/Common/ConsoleListener.cpp +++ b/Common/ConsoleListener.cpp @@ -68,7 +68,7 @@ ConsoleListener::ConsoleListener() : bHidden(true) bUseColor = false; #elif defined(IOS) bUseColor = false; -#elif PPSSPP_PLATFORM(UWP) +#elif PPSSPP_PLATFORM(UWP) || PPSSPP_PLATFORM(SWITCH) bUseColor = false; #elif defined(_MSC_VER) bUseColor = false; diff --git a/Common/FileUtil.cpp b/Common/FileUtil.cpp index 7b9e8259b7..04ae730c1c 100644 --- a/Common/FileUtil.cpp +++ b/Common/FileUtil.cpp @@ -136,6 +136,9 @@ static bool ResolvePathVista(const std::wstring &path, wchar_t *buf, DWORD bufSi #endif std::string ResolvePath(const std::string &path) { + if (startsWith(path, "http://") || startsWith(path, "https://")) { + return path; + } #ifdef _WIN32 static const int BUF_SIZE = 32768; wchar_t *buf = new wchar_t[BUF_SIZE] {}; diff --git a/Core/Config.cpp b/Core/Config.cpp index b8fd0923c0..0ac8411c42 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1160,8 +1160,9 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { vPinnedPaths.clear(); for (auto it = pinnedPaths.begin(), end = pinnedPaths.end(); it != end; ++it) { // Unpin paths that are deleted automatically. - if (File::Exists(it->second)) { - vPinnedPaths.push_back(File::ResolvePath(it->second)); + const std::string &path = it->second; + if (startsWith(path, "http://") || startsWith(path, "https://") || File::Exists(path)) { + vPinnedPaths.push_back(File::ResolvePath(path)); } } diff --git a/Core/FileLoaders/CachingFileLoader.cpp b/Core/FileLoaders/CachingFileLoader.cpp index 61a7459045..f161574916 100644 --- a/Core/FileLoaders/CachingFileLoader.cpp +++ b/Core/FileLoaders/CachingFileLoader.cpp @@ -109,9 +109,11 @@ void CachingFileLoader::ShutdownCache() { // TODO: Maybe add some hint that deletion is coming soon? // We can't delete while the thread is running, so have to wait. // This should only happen from the menu. - while (aheadThread_) { + while (aheadThreadRunning_) { sleep_ms(1); } + if (aheadThread_.joinable()) + aheadThread_.join(); std::lock_guard guard(blocksMutex_); for (auto block : blocks_) { @@ -252,7 +254,7 @@ bool CachingFileLoader::MakeCacheSpaceFor(size_t blocks, bool readingAhead) { void CachingFileLoader::StartReadAhead(s64 pos) { std::lock_guard guard(blocksMutex_); - if (aheadThread_) { + if (aheadThreadRunning_) { // Already going. return; } @@ -261,8 +263,10 @@ void CachingFileLoader::StartReadAhead(s64 pos) { return; } - aheadThread_ = true; - std::thread th([this, pos] { + aheadThreadRunning_ = true; + if (aheadThread_.joinable()) + aheadThread_.join(); + aheadThread_ = std::thread([this, pos] { setCurrentThreadName("FileLoaderReadAhead"); std::unique_lock guard(blocksMutex_); @@ -278,7 +282,6 @@ void CachingFileLoader::StartReadAhead(s64 pos) { } } - aheadThread_ = false; + aheadThreadRunning_ = false; }); - th.detach(); } diff --git a/Core/FileLoaders/CachingFileLoader.h b/Core/FileLoaders/CachingFileLoader.h index 97fe0aa682..e778265cce 100644 --- a/Core/FileLoaders/CachingFileLoader.h +++ b/Core/FileLoaders/CachingFileLoader.h @@ -19,6 +19,7 @@ #include #include +#include #include "Common/CommonTypes.h" #include "Core/Loaders.h" @@ -75,6 +76,7 @@ private: std::map blocks_; std::recursive_mutex blocksMutex_; - bool aheadThread_ = false; + bool aheadThreadRunning_ = false; + std::thread aheadThread_; std::once_flag preparedFlag_; }; diff --git a/Core/FileLoaders/RamCachingFileLoader.cpp b/Core/FileLoaders/RamCachingFileLoader.cpp index 4b934cebdf..9ea0c2e61f 100644 --- a/Core/FileLoaders/RamCachingFileLoader.cpp +++ b/Core/FileLoaders/RamCachingFileLoader.cpp @@ -105,9 +105,11 @@ void RamCachingFileLoader::ShutdownCache() { // We can't delete while the thread is running, so have to wait. // This should only happen from the menu. - while (aheadThread_) { + while (aheadThreadRunning_) { sleep_ms(1); } + if (aheadThread_.joinable()) + aheadThread_.join(); std::lock_guard guard(blocksMutex_); blocks_.clear(); @@ -118,7 +120,7 @@ void RamCachingFileLoader::ShutdownCache() { } void RamCachingFileLoader::Cancel() { - if (aheadThread_) { + if (aheadThreadRunning_) { std::lock_guard guard(blocksMutex_); aheadCancel_ = true; } @@ -213,14 +215,16 @@ void RamCachingFileLoader::StartReadAhead(s64 pos) { std::lock_guard guard(blocksMutex_); aheadPos_ = pos; - if (aheadThread_) { + if (aheadThreadRunning_) { // Already going. return; } - aheadThread_ = true; + aheadThreadRunning_ = true; aheadCancel_ = false; - std::thread th([this] { + if (aheadThread_.joinable()) + aheadThread_.join(); + aheadThread_ = std::thread([this] { setCurrentThreadName("FileLoaderReadAhead"); while (aheadRemaining_ != 0 && !aheadCancel_) { @@ -243,9 +247,8 @@ void RamCachingFileLoader::StartReadAhead(s64 pos) { } } - aheadThread_ = false; + aheadThreadRunning_ = false; }); - th.detach(); } u32 RamCachingFileLoader::NextAheadBlock() { diff --git a/Core/FileLoaders/RamCachingFileLoader.h b/Core/FileLoaders/RamCachingFileLoader.h index 60ca2afc91..44c375d442 100644 --- a/Core/FileLoaders/RamCachingFileLoader.h +++ b/Core/FileLoaders/RamCachingFileLoader.h @@ -19,6 +19,7 @@ #include #include +#include #include "Common/CommonTypes.h" #include "Core/Loaders.h" @@ -65,6 +66,7 @@ private: std::mutex blocksMutex_; u32 aheadRemaining_; s64 aheadPos_; - bool aheadThread_ = false; + std::thread aheadThread_; + bool aheadThreadRunning_ = false; bool aheadCancel_ = false; }; diff --git a/Core/FileSystems/VirtualDiscFileSystem.cpp b/Core/FileSystems/VirtualDiscFileSystem.cpp index b07946ebf9..e76b5c4dee 100644 --- a/Core/FileSystems/VirtualDiscFileSystem.cpp +++ b/Core/FileSystems/VirtualDiscFileSystem.cpp @@ -35,8 +35,10 @@ #include #include #include +#if !PPSSPP_PLATFORM(SWITCH) #include #endif +#endif const std::string INDEX_FILENAME = ".ppsspp-index.lst"; @@ -809,6 +811,7 @@ void VirtualDiscFileSystem::HandlerLogger(void *arg, HandlerHandle handle, LogTy } VirtualDiscFileSystem::Handler::Handler(const char *filename, VirtualDiscFileSystem *const sys) { +#if !PPSSPP_PLATFORM(SWITCH) #ifdef _WIN32 #if PPSSPP_PLATFORM(UWP) #define dlopen(name, ignore) (void *)LoadPackagedLibrary(ConvertUTF8ToWString(name).c_str(), 0) @@ -847,13 +850,14 @@ VirtualDiscFileSystem::Handler::Handler(const char *filename, VirtualDiscFileSys #undef dlsym #undef dlclose #endif +#endif } VirtualDiscFileSystem::Handler::~Handler() { if (library != NULL) { Shutdown(); -#if !PPSSPP_PLATFORM(UWP) +#if !PPSSPP_PLATFORM(UWP) && !PPSSPP_PLATFORM(SWITCH) #ifdef _WIN32 FreeLibrary((HMODULE)library); #else diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index ac041bc58b..925bf21e70 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -654,7 +654,6 @@ void __IoInit() { if (ioManagerThreadEnabled) { Core_ListenLifecycle(&__IoWakeManager); ioManagerThread = new std::thread(&__IoManagerThread); - ioManagerThread->detach(); } __KernelRegisterWaitTypeFuncs(WAITTYPE_ASYNCIO, __IoAsyncBeginCallback, __IoAsyncEndCallback); @@ -731,9 +730,10 @@ void __IoShutdown() { ioManagerThreadEnabled = false; ioManager.SyncThread(); ioManager.FinishEventLoop(); - if (ioManagerThread != NULL) { + if (ioManagerThread != nullptr) { + ioManagerThread->join(); delete ioManagerThread; - ioManagerThread = NULL; + ioManagerThread = nullptr; ioManager.Shutdown(); } diff --git a/Core/MIPS/MIPSDebugInterface.cpp b/Core/MIPS/MIPSDebugInterface.cpp index ae1a9a7a6c..e99e86073c 100644 --- a/Core/MIPS/MIPSDebugInterface.cpp +++ b/Core/MIPS/MIPSDebugInterface.cpp @@ -18,6 +18,10 @@ #include #include +#ifndef _MSC_VER +#include +#endif + #include "Core/Debugger/Breakpoints.h" #include "Core/Debugger/SymbolMap.h" #include "Core/Debugger/DebugInterface.h" diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index 2f2122bb4c..b00cd3496d 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -57,6 +57,8 @@ #include "Core/HLE/sceKernelModule.h" #include "Core/HLE/sceKernelMemory.h" +static std::thread loadingThread; + static void UseLargeMem(int memsize) { if (memsize != 1) { // Nothing requested. @@ -256,7 +258,11 @@ bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) { host->SendUIMessage("config_loaded", ""); INFO_LOG(LOADER,"Loading %s...", bootpath.c_str()); - std::thread th([bootpath] { + PSPLoaders_Shutdown(); + // Note: this thread reads the game binary, loads caches, and links HLE while UI spins. + // To do something deterministically when the game starts, disabling this thread won't be enough. + // Instead: Use Core_ListenLifecycle() or watch coreState. + loadingThread = std::thread([bootpath] { setCurrentThreadName("ExecLoader"); PSP_LoadingLock guard; if (coreState != CORE_POWERUP) @@ -273,7 +279,6 @@ bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) { PSP_CoreParameter().fileToStart = ""; } }); - th.detach(); return true; } @@ -379,7 +384,9 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { } // End of temporary code - std::thread th([finalName] { + PSPLoaders_Shutdown(); + // Note: See Load_PSP_ISO for notes about this thread. + loadingThread = std::thread([finalName] { setCurrentThreadName("ExecLoader"); PSP_LoadingLock guard; if (coreState != CORE_POWERUP) @@ -394,7 +401,6 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { PSP_CoreParameter().fileToStart = ""; } }); - th.detach(); return true; } @@ -402,7 +408,9 @@ bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) { BlobFileSystem *umd = new BlobFileSystem(&pspFileSystem, fileLoader, "data.ppdmp"); pspFileSystem.Mount("disc0:", umd); - std::thread th([] { + PSPLoaders_Shutdown(); + // Note: See Load_PSP_ISO for notes about this thread. + loadingThread = std::thread([] { setCurrentThreadName("ExecLoader"); PSP_LoadingLock guard; if (coreState != CORE_POWERUP) @@ -417,6 +425,10 @@ bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) { PSP_CoreParameter().fileToStart = ""; } }); - th.detach(); return true; } + +void PSPLoaders_Shutdown() { + if (loadingThread.joinable()) + loadingThread.join(); +} diff --git a/Core/PSPLoaders.h b/Core/PSPLoaders.h index 34f33ca177..f344008460 100644 --- a/Core/PSPLoaders.h +++ b/Core/PSPLoaders.h @@ -26,3 +26,4 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string); bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string); void InitMemoryForGameISO(FileLoader *fileLoader); void InitMemoryForGamePBP(FileLoader *fileLoader); +void PSPLoaders_Shutdown(); diff --git a/Core/Reporting.cpp b/Core/Reporting.cpp index 34431ff61f..388086ad76 100644 --- a/Core/Reporting.cpp +++ b/Core/Reporting.cpp @@ -15,6 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include #include #include #include @@ -70,6 +71,13 @@ namespace Reporting // The latest compatibility result from the server. static std::vector lastCompatResult; + static std::mutex pendingMessageLock; + static std::condition_variable pendingMessageCond; + static std::deque pendingMessages; + static bool pendingMessagesDone = false; + static std::thread messageThread; + static std::thread compatThread; + enum class RequestType { NONE, @@ -93,6 +101,7 @@ namespace Reporting static std::condition_variable crcCond; static std::string crcFilename; static std::map crcResults; + static std::thread crcThread; static int CalculateCRCThread() { setCurrentThreadName("ReportCRC"); @@ -133,8 +142,7 @@ namespace Reporting } crcFilename = gamePath; - std::thread th(CalculateCRCThread); - th.detach(); + crcThread = std::thread(CalculateCRCThread); } u32 RetrieveCRC() { @@ -148,6 +156,8 @@ namespace Reporting it = crcResults.find(gamePath); } + if (crcThread.joinable()) + crcThread.join(); return it->second; } @@ -269,6 +279,8 @@ namespace Reporting return "Mac"; #elif defined(LOONGSON) return "Loongson"; +#elif defined(__SWITCH__) + return "Switch"; #elif defined(__linux__) return "Linux"; #elif defined(__Bitrig__) @@ -295,10 +307,20 @@ namespace Reporting logOnceUsed.clear(); everUnsupported = false; currentSupported = IsSupported(); + pendingMessagesDone = false; } void Shutdown() { + pendingMessageLock.lock(); + pendingMessagesDone = true; + pendingMessageCond.notify_one(); + pendingMessageLock.unlock(); + if (compatThread.joinable()) + compatThread.join(); + if (messageThread.joinable()) + messageThread.join(); + // Just so it can be enabled in the menu again. Init(); } @@ -541,6 +563,28 @@ namespace Reporting return -1; } + int ProcessPending() { + setCurrentThreadName("Report"); + + std::unique_lock guard(pendingMessageLock); + while (!pendingMessagesDone) { + while (!pendingMessages.empty() && !pendingMessagesDone) { + int pos = pendingMessages.front(); + pendingMessages.pop_front(); + + guard.unlock(); + Process(pos); + guard.lock(); + } + if (pendingMessagesDone) { + break; + } + pendingMessageCond.wait(guard); + } + + return 0; + } + void ReportMessage(const char *message, ...) { if (!IsEnabled() || CheckSpamLimited()) @@ -563,8 +607,13 @@ namespace Reporting payload.string1 = message; payload.string2 = temp; - std::thread th(Process, pos); - th.detach(); + std::lock_guard guard(pendingMessageLock); + pendingMessages.push_back(pos); + pendingMessageCond.notify_one(); + + if (!messageThread.joinable()) { + messageThread = std::thread(ProcessPending); + } } void ReportMessageFormatted(const char *message, const char *formatted) @@ -580,8 +629,13 @@ namespace Reporting payload.string1 = message; payload.string2 = formatted; - std::thread th(Process, pos); - th.detach(); + std::lock_guard guard(pendingMessageLock); + pendingMessages.push_back(pos); + pendingMessageCond.notify_one(); + + if (!messageThread.joinable()) { + messageThread = std::thread(ProcessPending); + } } void ReportCompatibility(const char *compat, int graphics, int speed, int gameplay, const std::string &screenshotFilename) @@ -600,8 +654,9 @@ namespace Reporting payload.int2 = speed; payload.int3 = gameplay; - std::thread th(Process, pos); - th.detach(); + if (compatThread.joinable()) + compatThread.join(); + compatThread = std::thread(Process, pos); } std::vector CompatibilitySuggestions() { diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 806bdcbcb0..86bc96b4e8 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -153,11 +153,12 @@ namespace SaveState void ScheduleCompress(std::vector *result, const std::vector *state, const std::vector *base) { - auto th = new std::thread([=]{ + if (compressThread_.joinable()) + compressThread_.join(); + compressThread_ = std::thread([=]{ setCurrentThreadName("SaveStateCompress"); Compress(*result, *state, *base); }); - th->detach(); } void Compress(std::vector &result, const std::vector &state, const std::vector &base) @@ -208,6 +209,9 @@ namespace SaveState void Clear() { + if (compressThread_.joinable()) + compressThread_.join(); + // This lock is mainly for shutdown. std::lock_guard guard(lock_); first_ = 0; @@ -233,6 +237,7 @@ namespace SaveState StateBuffer bases_[2]; std::vector baseMapping_; std::mutex lock_; + std::thread compressThread_; int base_; int baseUsage_; diff --git a/Core/System.cpp b/Core/System.cpp index 3b32f3f56a..266eab30e3 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -268,6 +268,7 @@ PSP_LoadingLock::~PSP_LoadingLock() { void CPU_Shutdown() { // Since we load on a background thread, wait for startup to complete. PSP_LoadingLock lock; + PSPLoaders_Shutdown(); if (g_Config.bAutoSaveSymbolMap) { host->SaveSymbolMap(); diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp index f73fe3f316..7a5f08b732 100644 --- a/Core/Util/GameManager.cpp +++ b/Core/Util/GameManager.cpp @@ -44,8 +44,7 @@ GameManager g_GameManager; -GameManager::GameManager() - : installInProgress_(false), installProgress_(0.0f) { +GameManager::GameManager() { } std::string GameManager::GetTempFilename() const { @@ -66,7 +65,7 @@ bool GameManager::IsGameInstalled(std::string name) { } bool GameManager::DownloadAndInstall(std::string storeFileUrl) { - if (curDownload_.get() != 0) { + if (curDownload_.get() != nullptr) { ERROR_LOG(HLE, "Can only process one download at a time"); return false; } @@ -131,6 +130,15 @@ void GameManager::Update() { } curDownload_.reset(); } + + if (installDonePending_) { + if (installThread_.get() != nullptr) { + if (installThread_->joinable()) + installThread_->join(); + installThread_.reset(); + } + installDonePending_ = false; + } } static void countSlashes(const std::string &fileName, int *slashLocation, int *slashCount) { @@ -608,7 +616,6 @@ bool GameManager::InstallGameOnThread(std::string url, std::string fileName, boo return false; } installThread_.reset(new std::thread(std::bind(&GameManager::InstallGame, this, url, fileName, deleteAfter))); - installThread_->detach(); return true; } @@ -628,7 +635,5 @@ bool GameManager::InstallRawISO(const std::string &file, const std::string &orig } void GameManager::InstallDone() { - if (installThread_.get() != 0) { - installThread_.reset(); - } + installDonePending_ = true; } diff --git a/Core/Util/GameManager.h b/Core/Util/GameManager.h index 898d24ad4d..81d1368273 100644 --- a/Core/Util/GameManager.h +++ b/Core/Util/GameManager.h @@ -86,8 +86,9 @@ private: std::string GetISOGameID(FileLoader *loader) const; std::shared_ptr curDownload_; std::shared_ptr installThread_; - bool installInProgress_; - float installProgress_; + bool installInProgress_ = false; + bool installDonePending_ = false; + float installProgress_ = 0.0f; std::string installError_; }; diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index 3bec6d622a..8ab773fd1f 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -288,7 +288,6 @@ bool StartWebServer(WebServerFlags flags) { serverStatus = ServerStatus::STARTING; serverFlags = (int)flags; serverThread = std::thread(&ExecuteWebServer); - serverThread.detach(); return true; default: @@ -321,3 +320,9 @@ bool WebServerStopped(WebServerFlags flags) { } return serverStatus == ServerStatus::STOPPED; } + +void ShutdownWebServer() { + StopWebServer(WebServerFlags::ALL); + if (serverThread.joinable()) + serverThread.join(); +} diff --git a/Core/WebServer.h b/Core/WebServer.h index 59b2ea4d2d..d04b888761 100644 --- a/Core/WebServer.h +++ b/Core/WebServer.h @@ -26,5 +26,6 @@ bool StartWebServer(WebServerFlags flags); bool StopWebServer(WebServerFlags flags); bool WebServerStopping(WebServerFlags flags); bool WebServerStopped(WebServerFlags flags); +void ShutdownWebServer(); bool RemoteISOFileSupported(const std::string &filename); diff --git a/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp b/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp index 74243bf83a..fb9ab75006 100644 --- a/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp +++ b/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp @@ -210,7 +210,6 @@ bool GenerateVulkanGLSLVertexShader(const VShaderID &id, char *buffer) { WRITE(p, " return vec4(v.x, v.y, z * v.w, v.w);\n"); WRITE(p, "}\n\n"); } - WRITE(p, "out gl_PerVertex { vec4 gl_Position; };\n"); if (doBezier || doSpline) { WRITE(p, "struct TessData {\n"); @@ -638,6 +637,7 @@ bool GenerateVulkanGLSLVertexShader(const VShaderID &id, char *buffer) { WRITE(p, " }\n"); } WRITE(p, " gl_Position = outPos;\n"); + WRITE(p, " gl_PointSize = 1.0;\n"); if (gstate_c.Supports(GPU_NEEDS_Z_EQUAL_W_HACK)) { // See comment in GPU_Vulkan.cpp. diff --git a/README.md b/README.md index 8819aa24a7..de5ec3a992 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,12 @@ No BIOS file required to play, PPSSPP is an "HLE" emulator. Default settings ba Includes adhoc between multiply instances of PPSSPP on same PC with some additional options to make that easier. ~ LunaMoo -What's new in 1.9.0/1.9.1 +What's new in 1.9.0/1.9.3 +------------------------- +* Sizing/touch fixes on Android +* Fixes for some crashes + +What's new in 1.9.0/1.9.2 ------------------------- * Flicker fixed in God of War that appeared with newer drivers for Mali GPUs (#12224) * Improve performance of God of War on Vulkan (#12242), implement built-in GPU profiler (#12262, #12266) diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index ddf6e5cf20..28c3548201 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -443,7 +443,6 @@ int main(int argc, char *argv[]) { SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetSwapInterval(1); // Force fullscreen if the resolution is too low to run windowed. if (g_DesktopWidth < 480 * 2 && g_DesktopHeight < 272 * 2) { @@ -571,6 +570,9 @@ int main(int argc, char *argv[]) { NativeResized(); } + // Ensure that the swap interval is set after context creation (needed for kmsdrm) + SDL_GL_SetSwapInterval(1); + SDL_AudioSpec fmt, ret_fmt; memset(&fmt, 0, sizeof(fmt)); fmt.freq = 44100; diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index e2235da7d8..8b9763503b 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -21,6 +21,7 @@ #include "base/colorutil.h" #include "base/logging.h" +#include "base/display.h" #include "i18n/i18n.h" #include "input/keycodes.h" #include "input/input_state.h" @@ -570,3 +571,126 @@ void AnalogTestScreen::CreateViews() { root_->Add(new Button(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); } + +bool TouchTestScreen::touch(const TouchInput &touch) { + UIDialogScreenWithBackground::touch(touch); + if (touch.flags & TOUCH_DOWN) { + bool found = false; + for (int i = 0; i < MAX_TOUCH_POINTS; i++) { + if (touches_[i].id == touch.id) { + WLOG("Double touch"); + touches_[i].x = touch.x; + touches_[i].y = touch.y; + found = true; + } + } + if (!found) { + for (int i = 0; i < MAX_TOUCH_POINTS; i++) { + if (touches_[i].id == -1) { + touches_[i].id = touch.id; + touches_[i].x = touch.x; + touches_[i].y = touch.y; + break; + } + } + } + } + if (touch.flags & TOUCH_MOVE) { + bool found = false; + for (int i = 0; i < MAX_TOUCH_POINTS; i++) { + if (touches_[i].id == touch.id) { + touches_[i].x = touch.x; + touches_[i].y = touch.y; + found = true; + } + } + if (!found) { + WLOG("Move without touch down: %d", touch.id); + } + } + if (touch.flags & TOUCH_UP) { + bool found = false; + for (int i = 0; i < MAX_TOUCH_POINTS; i++) { + if (touches_[i].id == touch.id) { + found = true; + touches_[i].id = -1; + break; + } + } + if (!found) { + WLOG("Touch release without touch down"); + } + } + return true; +} + +void TouchTestScreen::CreateViews() { + using namespace UI; + + I18NCategory *di = GetI18NCategory("Dialog"); + I18NCategory *gr = GetI18NCategory("Graphics"); + root_ = new LinearLayout(ORIENT_VERTICAL); + LinearLayout *theTwo = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(1.0f)); + root_->Add(theTwo); + root_->Add(new CheckBox(&g_Config.bImmersiveMode, gr->T("FullScreen", "Full Screen")))->OnClick.Handle(this, &TouchTestScreen::OnImmersiveModeChange); + root_->Add(new Button(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); +} + +#if PPSSPP_PLATFORM(ANDROID) +extern int display_xres; +extern int display_yres; +#endif + +void TouchTestScreen::render() { + UIDialogScreenWithBackground::render(); + UIContext *ui_context = screenManager()->getUIContext(); + ui_context->BeginNoTex(); + for (int i = 0; i < MAX_TOUCH_POINTS; i++) { + if (touches_[i].id != -1) { + ui_context->Draw()->Circle(touches_[i].x, touches_[i].y, 100.0, 3.0, 80, 0.0f, 0xFFFFFFFF, 1.0); + } + } + ui_context->Flush(); + + ui_context->Begin(); + + char buffer[1024]; + for (int i = 0; i < MAX_TOUCH_POINTS; i++) { + if (touches_[i].id != -1) { + ui_context->Draw()->Circle(touches_[i].x, touches_[i].y, 100.0, 3.0, 80, 0.0f, 0xFFFFFFFF, 1.0); + snprintf(buffer, sizeof(buffer), "%0.1fx%0.1f", touches_[i].x, touches_[i].y); + ui_context->DrawText(buffer, touches_[i].x, touches_[i].y + (touches_[i].y > dp_yres - 100.0f ? -135.0f : 95.0f), 0xFFFFFFFF, ALIGN_HCENTER | FLAG_DYNAMIC_ASCII); + } + } + + snprintf(buffer, sizeof(buffer), +#if PPSSPP_PLATFORM(ANDROID) + "display_res: %dx%d\n" +#endif + "dp_res: %dx%d\n" + "pixel_res: %dx%d\n" + "g_dpi: %f\n" + "g_dpi_scale: %0.3fx%0.3f\n" + "g_dpi_scale_real: %0.3fx%0.3f\n", +#if PPSSPP_PLATFORM(ANDROID) + display_xres, display_yres, +#endif + dp_xres, dp_yres, + pixel_xres, pixel_yres, + g_dpi, + g_dpi_scale_x, g_dpi_scale_y, + g_dpi_scale_real_x, g_dpi_scale_real_y); + + ui_context->DrawTextShadow(buffer, 20.0f, dp_yres / 2.0f, 0xFFFFFFFF, ALIGN_VCENTER | FLAG_DYNAMIC_ASCII); + ui_context->Flush(); +} + +void RecreateActivity(); + +UI::EventReturn TouchTestScreen::OnImmersiveModeChange(UI::EventParams &e) { + System_SendMessage("immersive", ""); + if (g_Config.iAndroidHwScale != 0) { + RecreateActivity(); + } + return UI::EVENT_DONE; +} diff --git a/UI/ControlMappingScreen.h b/UI/ControlMappingScreen.h index a1292548ba..77f5c90df5 100644 --- a/UI/ControlMappingScreen.h +++ b/UI/ControlMappingScreen.h @@ -105,6 +105,32 @@ public: protected: virtual void CreateViews() override; - UI::TextView *lastKeyEvent_; - UI::TextView *lastLastKeyEvent_; + UI::TextView *lastKeyEvent_ = nullptr; + UI::TextView *lastLastKeyEvent_ = nullptr; +}; + +class TouchTestScreen : public UIDialogScreenWithBackground { +public: + TouchTestScreen() { + for (int i = 0; i < MAX_TOUCH_POINTS; i++) { + touches_[i].id = -1; + } + } + + bool touch(const TouchInput &touch) override; + void render() override; + +protected: + struct TrackedTouch { + int id; + float x; + float y; + }; + enum { + MAX_TOUCH_POINTS = 10, + }; + TrackedTouch touches_[MAX_TOUCH_POINTS]{}; + + virtual void CreateViews() override; + UI::EventReturn OnImmersiveModeChange(UI::EventParams &e); }; diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index b733028670..f83e3e2f08 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -42,6 +42,7 @@ #include "GPU/GPUState.h" #include "UI/MiscScreens.h" #include "UI/DevScreens.h" +#include "UI/ControlMappingScreen.h" #include "UI/GameSettingsScreen.h" #ifdef _WIN32 diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index c21f52998a..db9cab56b4 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -899,7 +899,7 @@ UI::EventReturn GameSettingsScreen::OnScreenRotation(UI::EventParams &e) { return UI::EVENT_DONE; } -static void RecreateActivity() { +void RecreateActivity() { const int SYSTEM_JELLYBEAN = 16; if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= SYSTEM_JELLYBEAN) { ILOG("Sending recreate"); @@ -1408,6 +1408,7 @@ void DeveloperToolsScreen::CreateViews() { if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN || g_Config.iGPUBackend == (int)GPUBackend::OPENGL) { list->Add(new Choice(dev->T("GPU Driver Test")))->OnClick.Handle(this, &DeveloperToolsScreen::OnGPUDriverTest); } + list->Add(new Choice(dev->T("Touchscreen Test")))->OnClick.Handle(this, &DeveloperToolsScreen::OnTouchscreenTest); allowDebugger_ = !WebServerStopped(WebServerFlags::DEBUGGER); canAllowDebugger_ = !WebServerStopping(WebServerFlags::DEBUGGER); @@ -1573,6 +1574,11 @@ UI::EventReturn DeveloperToolsScreen::OnGPUDriverTest(UI::EventParams &e) { return UI::EVENT_DONE; } +UI::EventReturn DeveloperToolsScreen::OnTouchscreenTest(UI::EventParams &e) { + screenManager()->push(new TouchTestScreen()); + return UI::EVENT_DONE; +} + UI::EventReturn DeveloperToolsScreen::OnJitAffectingSetting(UI::EventParams &e) { NativeMessageReceived("clear jit", ""); return UI::EVENT_DONE; diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 6113a0fa29..aec6e86428 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -174,6 +174,7 @@ private: UI::EventReturn OnJitDebugTools(UI::EventParams &e); UI::EventReturn OnRemoteDebugger(UI::EventParams &e); UI::EventReturn OnGPUDriverTest(UI::EventParams &e); + UI::EventReturn OnTouchscreenTest(UI::EventParams &e); bool allowDebugger_ = false; bool canAllowDebugger_ = true; diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 4de9b2dfe4..5bfd37c03b 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -365,11 +365,6 @@ void GameButton::Draw(UIContext &dc) { dc.RebindTexture(); } -enum GameBrowserFlags { - FLAG_HOMEBREWSTOREBUTTON = 1 -}; - - class DirButton : public UI::Button { public: DirButton(const std::string &path, UI::LayoutParams *layoutParams) @@ -440,8 +435,8 @@ void DirButton::Draw(UIContext &dc) { } } -GameBrowser::GameBrowser(std::string path, bool allowBrowsing, bool *gridStyle, std::string lastText, std::string lastLink, int flags, UI::LayoutParams *layoutParams) - : LinearLayout(UI::ORIENT_VERTICAL, layoutParams), gameList_(0), path_(path), gridStyle_(gridStyle), allowBrowsing_(allowBrowsing), lastText_(lastText), lastLink_(lastLink), flags_(flags) { +GameBrowser::GameBrowser(std::string path, BrowseFlags browseFlags, bool *gridStyle, std::string lastText, std::string lastLink, UI::LayoutParams *layoutParams) + : LinearLayout(UI::ORIENT_VERTICAL, layoutParams), path_(path), gridStyle_(gridStyle), browseFlags_(browseFlags), lastText_(lastText), lastLink_(lastLink) { using namespace UI; Refresh(); } @@ -510,10 +505,17 @@ bool GameBrowser::HasSpecialFiles(std::vector &filenames) { return false; } +void GameBrowser::Update() { + LinearLayout::Update(); + if (listingPending_ && path_.IsListingReady()) { + Refresh(); + } +} + void GameBrowser::Refresh() { using namespace UI; - homebrewStoreButton_ = 0; + homebrewStoreButton_ = nullptr; // Kill all the contents Clear(); @@ -523,14 +525,14 @@ void GameBrowser::Refresh() { // No topbar with SimpleUI if (!g_Config.bSimpleUI) { //(DisplayTopBar()) { // No topbar on recent screen LinearLayout *topBar = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); - if (allowBrowsing_) { + if (browseFlags_ & BrowseFlags::NAVIGATE) { topBar->Add(new Spacer(2.0f)); topBar->Add(new TextView(path_.GetFriendlyPath().c_str(), ALIGN_VCENTER | FLAG_WRAP_TEXT, true, new LinearLayoutParams(FILL_PARENT, 64.0f, 1.0f))); -#if defined(USING_WIN_UI) || defined(USING_QT_UI) - topBar->Add(new Choice(mm->T("Browse", "Browse..."), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick); -#else - topBar->Add(new Choice(mm->T("Home"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick); -#endif + if (System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) { + topBar->Add(new Choice(mm->T("Browse", "Browse..."), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick); + } else { + topBar->Add(new Choice(mm->T("Home"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick); + } } else { topBar->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 64.0f, 1.0f))); } @@ -556,12 +558,14 @@ void GameBrowser::Refresh() { std::vector dirButtons; std::vector gameButtons; + listingPending_ = !path_.IsListingReady(); + std::vector filenames; if (HasSpecialFiles(filenames)) { for (size_t i = 0; i < filenames.size(); i++) { gameButtons.push_back(new GameButton(filenames[i], *gridStyle_, new UI::LinearLayoutParams(*gridStyle_ == true ? UI::WRAP_CONTENT : UI::FILL_PARENT, UI::WRAP_CONTENT))); } - } else { + } else if (!listingPending_) { std::vector fileInfo; path_.GetListing(fileInfo, "iso:cso:pbp:elf:prx:ppdmp:"); for (size_t i = 0; i < fileInfo.size(); i++) { @@ -576,8 +580,8 @@ void GameBrowser::Refresh() { isSaveData = true; if (!isGame && !isSaveData) { - if (allowBrowsing_) { - dirButtons.push_back(new DirButton(fileInfo[i].name, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT))); + if (browseFlags_ & BrowseFlags::NAVIGATE) { + dirButtons.push_back(new DirButton(fileInfo[i].fullName, fileInfo[i].name, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT))); } } else { gameButtons.push_back(new GameButton(fileInfo[i].fullName, *gridStyle_, new UI::LinearLayoutParams(*gridStyle_ == true ? UI::WRAP_CONTENT : UI::FILL_PARENT, UI::WRAP_CONTENT))); @@ -586,7 +590,7 @@ void GameBrowser::Refresh() { // Put RAR/ZIP files at the end to get them out of the way. They're only shown so that people // can click them and get an explanation that they need to unpack them. This is necessary due // to a flood of support email... - if (allowBrowsing_) { + if (browseFlags_ & BrowseFlags::ARCHIVES) { fileInfo.clear(); path_.GetListing(fileInfo, "zip:rar:r01:7z:"); if (!fileInfo.empty()) { @@ -604,7 +608,7 @@ void GameBrowser::Refresh() { } } - if (allowBrowsing_) { + if (browseFlags_ & BrowseFlags::NAVIGATE) { gameList_->Add(new DirButton("..", new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)))-> OnClick.Handle(this, &GameBrowser::NavigateClick); @@ -616,6 +620,10 @@ void GameBrowser::Refresh() { } } + if (listingPending_) { + gameList_->Add(new UI::TextView(mm->T("Loading..."), ALIGN_CENTER, false, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT))); + } + for (size_t i = 0; i < dirButtons.size(); i++) { gameList_->Add(dirButtons[i])->OnClick.Handle(this, &GameBrowser::NavigateClick); } @@ -632,7 +640,7 @@ void GameBrowser::Refresh() { } // Show a button to toggle pinning at the very end. - if (allowBrowsing_) { + if (browseFlags_ & BrowseFlags::PIN) { std::string caption = IsCurrentPathPinned() ? "-" : "+"; if (!*gridStyle_) { caption = IsCurrentPathPinned() ? mm->T("UnpinPath", "Unpin") : mm->T("PinPath", "Pin"); @@ -641,11 +649,11 @@ void GameBrowser::Refresh() { OnClick.Handle(this, &GameBrowser::PinToggleClick); } - if (flags_ & FLAG_HOMEBREWSTOREBUTTON) { + if (browseFlags_ & BrowseFlags::HOMEBREW_STORE) { Add(new Spacer()); homebrewStoreButton_ = Add(new Choice(mm->T("DownloadFromStore", "Download from the PPSSPP Homebrew Store"), new UI::LinearLayoutParams(UI::WRAP_CONTENT, UI::WRAP_CONTENT))); } else { - homebrewStoreButton_ = 0; + homebrewStoreButton_ = nullptr; } if (!lastText_.empty() && gameButtons.empty()) { @@ -788,7 +796,7 @@ void MainScreen::CreateViews() { ScrollView *scrollRecentGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollRecentGames->SetTag("MainScreenRecentGames"); GameBrowser *tabRecentGames = new GameBrowser( - "!RECENT", false, &g_Config.bGridView1, "", "", 0, + "!RECENT", BrowseFlags::NONE, &g_Config.bGridView1, "", "", new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); scrollRecentGames->Add(tabRecentGames); gameBrowsers_.push_back(tabRecentGames); @@ -806,12 +814,11 @@ void MainScreen::CreateViews() { ScrollView *scrollHomebrew = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollHomebrew->SetTag("MainScreenHomebrew"); - GameBrowser *tabAllGames = new GameBrowser(g_Config.currentDirectory, true, &g_Config.bGridView2, - mm->T("How to get games"), "https://www.ppsspp.org/getgames.html", 0, + GameBrowser *tabAllGames = new GameBrowser(g_Config.currentDirectory, BrowseFlags::STANDARD, &g_Config.bGridView2, + mm->T("How to get games"), "https://www.ppsspp.org/getgames.html", new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); - GameBrowser *tabHomebrew = new GameBrowser(GetSysDirectory(DIRECTORY_GAME), false, &g_Config.bGridView3, + GameBrowser *tabHomebrew = new GameBrowser(GetSysDirectory(DIRECTORY_GAME), BrowseFlags::HOMEBREW_STORE, &g_Config.bGridView3, mm->T("How to get homebrew & demos", "How to get homebrew && demos"), "https://www.ppsspp.org/gethomebrew.html", - FLAG_HOMEBREWSTOREBUTTON, new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); Choice *hbStore = tabHomebrew->HomebrewStoreButton(); @@ -1257,7 +1264,7 @@ void UmdReplaceScreen::CreateViews() { ScrollView *scrollRecentGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollRecentGames->SetTag("UmdReplaceRecentGames"); GameBrowser *tabRecentGames = new GameBrowser( - "!RECENT", false, &g_Config.bGridView1, "", "", 0, + "!RECENT", BrowseFlags::NONE, &g_Config.bGridView1, "", "", new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); scrollRecentGames->Add(tabRecentGames); leftColumn->AddTab(mm->T("Recent"), scrollRecentGames); @@ -1267,8 +1274,8 @@ void UmdReplaceScreen::CreateViews() { ScrollView *scrollAllGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollAllGames->SetTag("UmdReplaceAllGames"); - GameBrowser *tabAllGames = new GameBrowser(g_Config.currentDirectory, true, &g_Config.bGridView2, - mm->T("How to get games"), "https://www.ppsspp.org/getgames.html", 0, + GameBrowser *tabAllGames = new GameBrowser(g_Config.currentDirectory, BrowseFlags::STANDARD, &g_Config.bGridView2, + mm->T("How to get games"), "https://www.ppsspp.org/getgames.html", new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); scrollAllGames->Add(tabAllGames); diff --git a/UI/MainScreen.h b/UI/MainScreen.h index 93d21a2984..eb15bd0f22 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -24,9 +24,30 @@ #include "ui/viewgroup.h" #include "UI/MiscScreens.h" +enum GameBrowserFlags { + FLAG_HOMEBREWSTOREBUTTON = 1 +}; + +enum class BrowseFlags { + NONE = 0, + NAVIGATE = 1, + ARCHIVES = 2, + PIN = 4, + HOMEBREW_STORE = 8, + STANDARD = 1 | 2 | 4, +}; + +static inline BrowseFlags operator |(const BrowseFlags &lhs, const BrowseFlags &rhs) { + return BrowseFlags((int)lhs | (int)rhs); +} + +static inline bool operator &(const BrowseFlags &lhs, const BrowseFlags &rhs) { + return ((int)lhs & (int)rhs) != 0; +} + class GameBrowser : public UI::LinearLayout { public: - GameBrowser(std::string path, bool allowBrowsing, bool *gridStyle_, std::string lastText, std::string lastLink, int flags = 0, UI::LayoutParams *layoutParams = 0); + GameBrowser(std::string path, BrowseFlags browseFlags, bool *gridStyle, std::string lastText, std::string lastLink, UI::LayoutParams *layoutParams = nullptr); UI::Event OnChoice; UI::Event OnHoldChoice; @@ -37,6 +58,8 @@ public: void FocusGame(const std::string &gamePath); void SetPath(const std::string &path); + void Update() override; + protected: virtual bool DisplayTopBar(); virtual bool HasSpecialFiles(std::vector &filenames); @@ -57,15 +80,15 @@ private: UI::EventReturn HomeClick(UI::EventParams &e); UI::EventReturn PinToggleClick(UI::EventParams &e); - UI::ViewGroup *gameList_; + UI::ViewGroup *gameList_ = nullptr; PathBrowser path_; bool *gridStyle_; - bool allowBrowsing_; + BrowseFlags browseFlags_; std::string lastText_; std::string lastLink_; - int flags_; - UI::Choice *homebrewStoreButton_; + UI::Choice *homebrewStoreButton_ = nullptr; std::string focusGamePath_; + bool listingPending_ = false; }; class RemoteISOBrowseScreen; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index f51c9dff2b..29882fd81c 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -318,6 +318,8 @@ static void PostLoadConfig() { g_Config.currentDirectory = g_Config.externalDirectory; #elif defined(IOS) g_Config.currentDirectory = g_Config.internalDataDirectory; +#elif PPSSPP_PLATFORM(SWITCH) + g_Config.currentDirectory = "/"; #else if (getenv("HOME") != nullptr) g_Config.currentDirectory = getenv("HOME"); @@ -446,12 +448,17 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch // Packed assets are included in app VFSRegister("", new DirectoryAssetReader(external_dir)); #endif -#if !defined(MOBILE_DEVICE) && !defined(_WIN32) +#if !defined(MOBILE_DEVICE) && !defined(_WIN32) && !PPSSPP_PLATFORM(SWITCH) VFSRegister("", new DirectoryAssetReader((File::GetExeDirectory() + "assets/").c_str())); VFSRegister("", new DirectoryAssetReader((File::GetExeDirectory()).c_str())); VFSRegister("", new DirectoryAssetReader("/usr/share/ppsspp/assets/")); #endif +#if PPSSPP_PLATFORM(SWITCH) + std::string assetPath = savegame_dir + "assets/"; + VFSRegister("", new DirectoryAssetReader(assetPath.c_str())); +#else VFSRegister("", new DirectoryAssetReader("assets/")); +#endif VFSRegister("", new DirectoryAssetReader(savegame_dir)); #if (defined(MOBILE_DEVICE) || !defined(USING_QT_UI)) && !PPSSPP_PLATFORM(UWP) @@ -481,6 +488,9 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch #elif defined(IOS) g_Config.memStickDirectory = user_data_path; g_Config.flash0Directory = std::string(external_dir) + "/flash0/"; +#elif PPSSPP_PLATFORM(SWITCH) + g_Config.memStickDirectory = g_Config.internalDataDirectory + "config/ppsspp/"; + g_Config.flash0Directory = g_Config.internalDataDirectory + "assets/flash0/"; #elif !defined(_WIN32) std::string config; if (getenv("XDG_CONFIG_HOME") != NULL) @@ -869,11 +879,12 @@ void NativeShutdownGraphics() { winAudioBackend = nullptr; #endif + ShutdownWebServer(); + UIBackgroundShutdown(); + delete g_gameInfoCache; g_gameInfoCache = nullptr; - UIBackgroundShutdown(); - delete uiContext; uiContext = nullptr; @@ -1006,6 +1017,7 @@ void NativeRender(GraphicsContext *graphicsContext) { } if (resized) { + ILOG("Resized flag set - recalculating bounds"); resized = false; if (uiContext) { @@ -1281,6 +1293,7 @@ void NativeMessageReceived(const char *message, const char *value) { void NativeResized() { // NativeResized can come from any thread so we just set a flag, then process it later. if (g_graphicsInited) { + ILOG("NativeResized - setting flag"); resized = true; } else { ILOG("NativeResized ignored, not initialized"); diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 6dd15835f6..2b34cfc440 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -20,10 +20,12 @@ #include #include "base/timeutil.h" +#include "file/path.h" #include "i18n/i18n.h" #include "json/json_reader.h" #include "net/http_client.h" #include "net/resolve.h" +#include "net/url.h" #include "Common/Common.h" #include "Core/Config.h" #include "Core/WebServer.h" @@ -39,13 +41,7 @@ static bool scanAborted = false; static std::string RemoteSubdir() { if (g_Config.bRemoteISOManual) { - std::string subdir = g_Config.sRemoteISOSubdir; - size_t offset = subdir.find_last_of("/"); - if (offset != subdir.length() - 1 && offset != subdir.npos) { - // Truncate everything after last / - subdir.erase(offset + 1); - } - return subdir; + return g_Config.sRemoteISOSubdir; } return "/"; @@ -142,9 +138,6 @@ static bool FindServer(std::string &resultHost, int &resultPort) { const char *host = entry.getString("ip", ""); int port = entry.getInt("p", 0); - char url[1024] = {}; - snprintf(url, sizeof(url), "http://%s:%d", host, port); - if (TryServer(host, port)) { return true; } @@ -154,82 +147,19 @@ static bool FindServer(std::string &resultHost, int &resultPort) { return false; } -static bool LoadGameList(const std::string &host, int port, std::vector &games) { - http::Client http; - Buffer result; - int code = 500; - std::vector responseHeaders; - // TODO: Use relative url parsing (URL::Relative) instead. - std::string subdir = RemoteSubdir(); - - // Start by requesting the list of games from the server. - if (http.Resolve(host.c_str(), port)) { - if (http.Connect(2, 20.0, &scanCancelled)) { - code = http.GET(subdir.c_str(), &result, responseHeaders); - http.Disconnect(); - } - } - - if (code != 200 || scanCancelled) { +static bool LoadGameList(const std::string &url, std::vector &games) { + PathBrowser browser(url); + std::vector files; + browser.GetListing(files, "iso:cso:pbp:elf:prx:ppdmp:", &scanCancelled); + if (scanCancelled) { return false; } - - std::string listing; - std::vector items; - result.TakeAll(&listing); - - std::string contentType; - for (const std::string &header : responseHeaders) { - if (startsWithNoCase(header, "Content-Type:")) { - contentType = header.substr(strlen("Content-Type:")); - // Strip any whitespace (TODO: maybe move this to stringutil?) - contentType.erase(0, contentType.find_first_not_of(" \t\r\n")); - contentType.erase(contentType.find_last_not_of(" \t\r\n") + 1); + for (auto &file : files) { + if (RemoteISOFileSupported(file.name)) { + games.push_back(file.fullName); } } - // TODO: Technically, "TExt/hTml ; chaRSet = Utf8" should pass, but "text/htmlese" should not. - // But unlikely that'll be an issue. - bool parseHtml = startsWithNoCase(contentType, "text/html"); - bool parseText = startsWithNoCase(contentType, "text/plain"); - - if (parseText) { - // Plain text format - easy. - SplitString(listing, '\n', items); - subdir.clear(); - } else if (parseHtml) { - // Try to extract from an automatic webserver directory listing... - GetQuotedStrings(listing, items); - } else { - ERROR_LOG(FILESYS, "Unsupported Content-Type: %s", contentType.c_str()); - return false; - } - - for (std::string item : items) { - // Apply some workarounds. - if (item.empty()) - continue; - - if (item[0] != '/') - item.insert(0, "/"); - if (item.back() == '\r') - item.pop_back(); - - if (!RemoteISOFileSupported(item)) { - continue; - } - - char temp[1024] = {}; - snprintf(temp, sizeof(temp) - 1, "http://%s:%d%s%s", host.c_str(), port, subdir.c_str(), item.c_str()); - games.push_back(temp); - } - - // Save for next time unless manual is true - if (!games.empty() && !g_Config.bRemoteISOManual) { - g_Config.sLastRemoteISOServer = host; - g_Config.iLastRemoteISOPort = port; - } - return !games.empty(); } @@ -327,7 +257,6 @@ RemoteISOConnectScreen::RemoteISOConnectScreen() : status_(ScanStatus::SCANNING) scanThread_ = new std::thread([](RemoteISOConnectScreen *thiz) { thiz->ExecuteScan(); }, this); - scanThread_->detach(); } RemoteISOConnectScreen::~RemoteISOConnectScreen() { @@ -341,6 +270,8 @@ RemoteISOConnectScreen::~RemoteISOConnectScreen() { break; } } + if (scanThread_->joinable()) + scanThread_->join(); delete scanThread_; } @@ -384,11 +315,12 @@ void RemoteISOConnectScreen::update() { status_ = ScanStatus::LOADING; // Let's reuse scanThread_. + if (scanThread_->joinable()) + scanThread_->join(); delete scanThread_; scanThread_ = new std::thread([](RemoteISOConnectScreen *thiz) { thiz->ExecuteLoad(); }, this); - scanThread_->detach(); break; case ScanStatus::FAILED: @@ -401,17 +333,18 @@ void RemoteISOConnectScreen::update() { status_ = ScanStatus::SCANNING; nextRetry_ = 0.0; + if (scanThread_->joinable()) + scanThread_->join(); delete scanThread_; scanThread_ = new std::thread([](RemoteISOConnectScreen *thiz) { thiz->ExecuteScan(); }, this); - scanThread_->detach(); } break; case ScanStatus::LOADED: TriggerFinish(DR_OK); - screenManager()->push(new RemoteISOBrowseScreen(games_)); + screenManager()->push(new RemoteISOBrowseScreen(url_, games_)); break; } } @@ -432,19 +365,26 @@ ScanStatus RemoteISOConnectScreen::GetStatus() { } void RemoteISOConnectScreen::ExecuteLoad() { - bool result = LoadGameList(host_, port_, games_); + std::string subdir = RemoteSubdir(); + url_ = StringFromFormat("http://%s:%d%s", host_.c_str(), port_, subdir.c_str()); + bool result = LoadGameList(url_, games_); if (scanAborted) { return; } + if (result && !games_.empty() && !g_Config.bRemoteISOManual) { + g_Config.sLastRemoteISOServer = host_; + g_Config.iLastRemoteISOPort = port_; + } + std::lock_guard guard(statusLock_); status_ = result ? ScanStatus::LOADED : ScanStatus::FAILED; } class RemoteGameBrowser : public GameBrowser { public: - RemoteGameBrowser(const std::vector &games, bool allowBrowsing, bool *gridStyle_, std::string lastText, std::string lastLink, int flags = 0, UI::LayoutParams *layoutParams = 0) - : GameBrowser("!REMOTE", allowBrowsing, gridStyle_, lastText, lastLink, flags, layoutParams) { + RemoteGameBrowser(const std::string &url, const std::vector &games, BrowseFlags browseFlags, bool *gridStyle_, std::string lastText, std::string lastLink, UI::LayoutParams *layoutParams = nullptr) + : GameBrowser(url, browseFlags, gridStyle_, lastText, lastLink, layoutParams) { games_ = games; Refresh(); } @@ -456,6 +396,7 @@ protected: bool HasSpecialFiles(std::vector &filenames) override; + std::string url_; std::vector games_; }; @@ -464,7 +405,8 @@ bool RemoteGameBrowser::HasSpecialFiles(std::vector &filenames) { return true; } -RemoteISOBrowseScreen::RemoteISOBrowseScreen(const std::vector &games) : games_(games) { +RemoteISOBrowseScreen::RemoteISOBrowseScreen(const std::string &url, const std::vector &games) + : url_(url), games_(games) { } void RemoteISOBrowseScreen::CreateViews() { @@ -485,7 +427,7 @@ void RemoteISOBrowseScreen::CreateViews() { ScrollView *scrollRecentGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollRecentGames->SetTag("RemoteGamesTab"); RemoteGameBrowser *tabRemoteGames = new RemoteGameBrowser( - games_, false, &g_Config.bGridView1, "", "", 0, + url_, games_, BrowseFlags::PIN, &g_Config.bGridView1, "", "", new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); scrollRecentGames->Add(tabRemoteGames); gameBrowsers_.push_back(tabRemoteGames); @@ -592,7 +534,7 @@ UI::EventReturn RemoteISOSettingsScreen::OnChangeRemoteISOSubdir(UI::EventParams ReplaceAll(g_Config.sRemoteISOSubdir, " ", "%20"); ReplaceAll(g_Config.sRemoteISOSubdir, "\\", "/"); //Make sure it begins with / - if (g_Config.sRemoteISOSubdir[0] != '/') + if (g_Config.sRemoteISOSubdir.empty() || g_Config.sRemoteISOSubdir[0] != '/') g_Config.sRemoteISOSubdir = "/" + g_Config.sRemoteISOSubdir; return UI::EVENT_DONE; diff --git a/UI/RemoteISOScreen.h b/UI/RemoteISOScreen.h index b7f7ff0f5c..06f7889a1c 100644 --- a/UI/RemoteISOScreen.h +++ b/UI/RemoteISOScreen.h @@ -72,16 +72,18 @@ protected: std::mutex statusLock_; std::string host_; int port_; + std::string url_; std::vector games_; }; class RemoteISOBrowseScreen : public MainScreen { public: - RemoteISOBrowseScreen(const std::vector &games); + RemoteISOBrowseScreen(const std::string &url, const std::vector &games); protected: void CreateViews() override; + std::string url_; std::vector games_; }; diff --git a/UI/Store.cpp b/UI/Store.cpp index f17f869200..c7b02c66fd 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -43,7 +43,7 @@ std::string ResolveUrl(std::string baseUrl, std::string url) { return baseUrl; } else if (url[0] == '/') { return baseUrl + url.substr(1); - } else if (url.substr(0, 7) == "http://") { + } else if (startsWith(url, "http://") || startsWith(url, "https://")) { return url; } else { // Huh. diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index 60f23cc46e..9eed29a578 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -849,6 +849,7 @@ + true diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index 9334fab6f3..c640c5642e 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -353,6 +353,7 @@ Windows\System + diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 29b3bd3f75..435ee9c4db 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="19030000" + android:versionName="1.9.3.0"> @@ -40,7 +40,7 @@ android:logo="@drawable/ic_banner" android:isGame="true" android:banner="@drawable/tv_banner"> - + GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight()); + ILOG("AndroidVulkanContext::Resize begin (oldsize: %dx%d)", g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight()); draw_->HandleEvent(Draw::Event::LOST_BACKBUFFER, g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight()); g_Vulkan->DestroyObjects(); @@ -220,7 +220,7 @@ void AndroidVulkanContext::Resize() { g_Vulkan->ReinitSurface(); g_Vulkan->InitObjects(); draw_->HandleEvent(Draw::Event::GOT_BACKBUFFER, g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight()); - ILOG("AndroidVulkanContext::Resize end (%d, %d)", g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight()); + ILOG("AndroidVulkanContext::Resize end (final size: %dx%d)", g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight()); } void AndroidVulkanContext::SwapInterval(int interval) { diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 1bebc69c87..1f9655b3a7 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -101,8 +101,9 @@ static int deviceType; // Should only be used for display detection during startup (for config defaults etc) // This is the ACTUAL display size, not the hardware scaled display size. -static int display_xres; -static int display_yres; +// Exposed so it can be displayed on the touchscreen test. +int display_xres; +int display_yres; static int display_dpi_x; static int display_dpi_y; static int backbuffer_format; // Android PixelFormat enum @@ -611,6 +612,31 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, NativeMessageReceived("recreateviews", ""); } +static void recalculateDpi() { + g_dpi = display_dpi_x; + g_dpi_scale_x = 240.0f / display_dpi_x; + g_dpi_scale_y = 240.0f / display_dpi_y; + g_dpi_scale_real_x = g_dpi_scale_x; + g_dpi_scale_real_y = g_dpi_scale_y; + + dp_xres = display_xres * g_dpi_scale_x; + dp_yres = display_yres * g_dpi_scale_y; + + // Touch scaling is from display pixels to dp pixels. + // Wait, doesn't even make sense... this is equal to g_dpi_scale_x. TODO: Figure out what's going on! + dp_xscale = (float)dp_xres / (float)display_xres; + dp_yscale = (float)dp_yres / (float)display_yres; + + pixel_in_dps_x = (float)pixel_xres / dp_xres; + pixel_in_dps_y = (float)pixel_yres / dp_yres; + + ILOG("RecalcDPI: display_xres=%d display_yres=%d", display_xres, display_yres); + ILOG("RecalcDPI: g_dpi=%f g_dpi_scale_x=%f g_dpi_scale_y=%f", g_dpi, g_dpi_scale_x, g_dpi_scale_y); + ILOG("RecalcDPI: dp_xscale=%f dp_yscale=%f", dp_xscale, dp_yscale); + ILOG("RecalcDPI: dp_xres=%d dp_yres=%d", dp_xres, dp_yres); + ILOG("RecalcDPI: pixel_xres=%d pixel_yres=%d", pixel_xres, pixel_yres); +} + extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv *, jclass, jint bufw, jint bufh, jint format) { ILOG("NativeApp.backbufferResize(%d x %d)", bufw, bufh); @@ -622,32 +648,13 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv pixel_yres = bufh; backbuffer_format = format; - g_dpi = display_dpi_x; - g_dpi_scale_x = 240.0f / g_dpi; - g_dpi_scale_y = 240.0f / g_dpi; - g_dpi_scale_real_x = g_dpi_scale_x; - g_dpi_scale_real_y = g_dpi_scale_y; - - dp_xres = display_xres * g_dpi_scale_x; - dp_yres = display_yres * g_dpi_scale_y; - - // Touch scaling is from display pixels to dp pixels. - dp_xscale = (float)dp_xres / (float)display_xres; - dp_yscale = (float)dp_yres / (float)display_yres; - - pixel_in_dps_x = (float)pixel_xres / dp_xres; - pixel_in_dps_y = (float)pixel_yres / dp_yres; - - ILOG("g_dpi=%f g_dpi_scale_x=%f g_dpi_scale_y=%f", g_dpi, g_dpi_scale_x, g_dpi_scale_y); - ILOG("dp_xscale=%f dp_yscale=%f", dp_xscale, dp_yscale); - ILOG("dp_xres=%d dp_yres=%d", dp_xres, dp_yres); - ILOG("pixel_xres=%d pixel_yres=%d", pixel_xres, pixel_yres); + recalculateDpi(); if (new_size) { ILOG("Size change detected (previously %d,%d) - calling NativeResized()", old_w, old_h); NativeResized(); } else { - ILOG("NativeApp::backbufferReisze: Size didn't change."); + ILOG("NativeApp::backbufferResize: Size didn't change."); } } @@ -897,6 +904,9 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setDisplayParameters(JN display_dpi_x = dpi; display_dpi_y = dpi; display_hz = refreshRate; + + recalculateDpi(); + NativeResized(); } extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_computeDesiredBackbufferDimensions() { diff --git a/android/res/values-v28/styles.xml b/android/res/values-v28/styles.xml new file mode 100644 index 0000000000..19a29a3a42 --- /dev/null +++ b/android/res/values-v28/styles.xml @@ -0,0 +1,13 @@ + + + #FF000000 + + diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 2a70b05e56..5676afee23 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -31,6 +31,7 @@ import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; +import android.view.DisplayCutout; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.InputDevice; @@ -85,6 +86,9 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C private boolean sustainedPerfSupported; + private boolean navigationHidden; + private View navigationCallbackView = null; + // audioFocusChangeListener to listen to changes in audio state private AudioFocusChangeListener audioFocusChangeListener; private AudioManager audioManager; @@ -116,6 +120,11 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C private int pixelWidth; private int pixelHeight; + private int safeInsetLeft = 0; + private int safeInsetRight = 0; + private int safeInsetTop = 0; + private int safeInsetBottom = 0; + private static final String[] permissionsForStorage = { Manifest.permission.WRITE_EXTERNAL_STORAGE, }; @@ -404,17 +413,24 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C @SuppressLint("InlinedApi") @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) private void updateSystemUiVisibility() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + setupSystemUiCallback(); + } + + // Compute our _desired_ systemUiVisibility int flags = 0; if (useLowProfileButtons()) { flags |= View.SYSTEM_UI_FLAG_LOW_PROFILE; } if (useImmersive()) { - flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN; } - if (getWindow().getDecorView() != null) { - getWindow().getDecorView().setSystemUiVisibility(flags); + + View decorView = getWindow().peekDecorView(); + if (decorView != null) { + decorView.setSystemUiVisibility(flags); } else { - Log.e(TAG, "updateSystemUiVisibility: decor view not yet created, ignoring"); + Log.e(TAG, "updateSystemUiVisibility: decor view not yet created, ignoring for now"); } updateDisplayMeasurements(); } @@ -452,21 +468,33 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C sz.y = NativeApp.getDesiredBackbufferHeight(); } + private SurfaceView getSurfaceView() { + if (mGLSurfaceView != null) { + return mGLSurfaceView; + } else { + return mSurfaceView; + } + } + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public void updateDisplayMeasurements() { Display display = getWindowManager().getDefaultDisplay(); + // Early in startup, we don't have a view to query. Do our best to get some kind of size + // that can be used by config default heuristics, and so on. DisplayMetrics metrics = new DisplayMetrics(); - if (useImmersive() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || !isInMultiWindowMode()) { - display.getRealMetrics(metrics); - } else { - // multi-window mode - display.getMetrics(metrics); - } + if (navigationHidden && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + display.getRealMetrics(metrics); } else { display.getMetrics(metrics); } + + // Later on, we have the exact pixel size so let's just use it. + SurfaceView view = getSurfaceView(); + if (view != null) { + metrics.widthPixels = view.getWidth(); + metrics.heightPixels = view.getHeight(); + } densityDpi = metrics.densityDpi; refreshRate = display.getRefreshRate(); @@ -539,9 +567,6 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { updateSystemUiVisibility(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - setupSystemUiCallback(); - } } mSurfaceView = new NativeSurfaceView(NativeActivity.this); @@ -578,6 +603,7 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C } Log.d(TAG, "Surface created. pixelWidth=" + pixelWidth + ", pixelHeight=" + pixelHeight + " holder: " + holder.toString() + " or: " + requestedOr); + NativeApp.setDisplayParameters(pixelWidth, pixelHeight, (int) densityDpi, refreshRate); getDesiredBackbufferSize(desiredSize); // Note that desiredSize might be 0,0 here - but that's fine when calling setFixedSize! It means auto. @@ -601,7 +627,9 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C return; } Log.w(TAG, "Surface changed. Resolution: " + width + "x" + height + " Format: " + format); + // The window size might have changed (immersive mode, native fullscreen on some devices) NativeApp.backbufferResize(width, height, format); + updateDisplayMeasurements(); mSurface = holder.getSurface(); if (!javaGL) { // If we got a surface, this starts the thread. If not, it doesn't. @@ -667,14 +695,26 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C @TargetApi(Build.VERSION_CODES.KITKAT) void setupSystemUiCallback() { - getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() { + final View decorView = getWindow().peekDecorView(); + if (decorView == null || decorView == navigationCallbackView) { + return; + } + + decorView.setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() { @Override public void onSystemUiVisibilityChange(int visibility) { - if (visibility == 0) { - updateSystemUiVisibility(); - } + // Called when the system UI's visibility changes, regardless of + // whether it's because of our or system actions. + // We will try to force it to follow our preference but will not stupidly + // act as if it's visible if it's not. + navigationHidden = ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0); + // TODO: Check here if it's the state we want. + Log.i(TAG, "SystemUiVisibilityChange! visibility=" + visibility + " navigationHidden: " + navigationHidden); + Log.i(TAG, "decorView: " + decorView.getWidth() + "x" + decorView.getHeight()); + updateDisplayMeasurements(); } }); + navigationCallbackView = decorView; } @Override @@ -720,6 +760,7 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C NativeApp.shutdown(); initialized = false; } + navigationCallbackView = null; } @Override @@ -795,6 +836,33 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C } } + @Override + public void onAttachedToWindow() { + Log.i(TAG, "onAttachedToWindow"); + super.onAttachedToWindow(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + DisplayCutout cutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout(); + if (cutout != null) { + safeInsetLeft = cutout.getSafeInsetLeft(); + safeInsetRight = cutout.getSafeInsetRight(); + safeInsetTop = cutout.getSafeInsetTop(); + safeInsetBottom = cutout.getSafeInsetBottom(); + Log.i(TAG, "Safe insets: left: " + safeInsetLeft + " right: " + safeInsetRight + " top: " + safeInsetTop + " bottom: " + safeInsetBottom); + } else { + Log.i(TAG, "Cutout was null"); + safeInsetLeft = 0; + safeInsetRight = 0; + safeInsetTop = 0; + safeInsetBottom = 0; + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + setupSystemUiCallback(); + } + } + @Override public void onConfigurationChanged(Configuration newConfig) { Log.i(TAG, "onConfigurationChanged"); @@ -803,7 +871,7 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C updateSystemUiVisibility(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - densityDpi = (float) newConfig.densityDpi; + densityDpi = (float)newConfig.densityDpi; } } diff --git a/android/src/org/ppsspp/ppsspp/NativeGLView.java b/android/src/org/ppsspp/ppsspp/NativeGLView.java index af5b738be6..b56380358d 100644 --- a/android/src/org/ppsspp/ppsspp/NativeGLView.java +++ b/android/src/org/ppsspp/ppsspp/NativeGLView.java @@ -116,7 +116,6 @@ public class NativeGLView extends GLSurfaceView implements SensorEventListener, @Override public void onPause() { - Log.i(TAG, "onPause"); super.onPause(); mSensorManager.unregisterListener(this); if (mController != null) { @@ -126,19 +125,16 @@ public class NativeGLView extends GLSurfaceView implements SensorEventListener, @Override public void onResume() { - Log.i(TAG, "onResume"); super.onResume(); mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); if (mController != null) { mController.onResume(); - // According to the docs, the Moga's state can be inconsistent here. // We should do a one time poll. TODO } } public void onDestroy() { - Log.i(TAG, "onDestroy"); if (mController != null) { mController.exit(); } diff --git a/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java b/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java index 1efc71fd8e..01686be52f 100644 --- a/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java +++ b/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java @@ -3,6 +3,7 @@ package org.ppsspp.ppsspp; // Touch- and sensor-enabled SurfaceView. // Supports simple multitouch and pressure. // DPI scaling is handled by the native code. +// Used by the Vulkan backend (and EGL, but that path is no longer active) import android.annotation.SuppressLint; import android.annotation.TargetApi; @@ -131,7 +132,6 @@ public class NativeSurfaceView extends SurfaceView implements SensorEventListene } public void onDestroy() { - Log.i(TAG, "onDestroy"); if (mController != null) { mController.exit(); } diff --git a/build.gradle b/build.gradle index 6fe5d2708d..043e3571c5 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:3.5.1' } } diff --git a/ext/native/base/stringutil.cpp b/ext/native/base/stringutil.cpp index 0fadb197bc..2861f844d3 100644 --- a/ext/native/base/stringutil.cpp +++ b/ext/native/base/stringutil.cpp @@ -1,8 +1,13 @@ +#include "ppsspp_config.h" #ifdef _WIN32 #include #undef min #undef max #endif +#if PPSSPP_PLATFORM(SWITCH) +#define _GNU_SOURCE +#include +#endif #include #include #include diff --git a/ext/native/base/stringutil.h b/ext/native/base/stringutil.h index 9205c009b6..6a06a739db 100644 --- a/ext/native/base/stringutil.h +++ b/ext/native/base/stringutil.h @@ -13,6 +13,8 @@ #ifdef _MSC_VER #pragma warning (disable:4996) #define strncasecmp _strnicmp +#else +#include #endif // Dumb wrapper around itoa, providing a buffer. Declare this on the stack. diff --git a/ext/native/file/fd_util.cpp b/ext/native/file/fd_util.cpp index 0490c86a3e..38a84f0f22 100644 --- a/ext/native/file/fd_util.cpp +++ b/ext/native/file/fd_util.cpp @@ -1,3 +1,4 @@ +#include "ppsspp_config.h" #include "file/fd_util.h" #include @@ -136,7 +137,9 @@ std::string GetLocalIP(int sock) { union { struct sockaddr sa; struct sockaddr_in ipv4; +#if !PPSSPP_PLATFORM(SWITCH) struct sockaddr_in6 ipv6; +#endif } server_addr; memset(&server_addr, 0, sizeof(server_addr)); socklen_t len = sizeof(server_addr); @@ -144,11 +147,14 @@ std::string GetLocalIP(int sock) { char temp[64]{}; // We clear the port below for WSAAddressToStringA. - void *addr; + void *addr = nullptr; +#if !PPSSPP_PLATFORM(SWITCH) if (server_addr.sa.sa_family == AF_INET6) { server_addr.ipv6.sin6_port = 0; addr = &server_addr.ipv6.sin6_addr; - } else { + } +#endif + if (addr == nullptr) { server_addr.ipv4.sin_port = 0; addr = &server_addr.ipv4.sin_addr; } diff --git a/ext/native/file/path.cpp b/ext/native/file/path.cpp index d50a64f237..ec62c179b5 100644 --- a/ext/native/file/path.cpp +++ b/ext/native/file/path.cpp @@ -1,9 +1,131 @@ +#include +#include +#include "base/stringutil.h" +#include "base/timeutil.h" #include "file/path.h" +#include "net/http_client.h" +#include "net/url.h" +#include "thread/threadutil.h" + +bool LoadRemoteFileList(const std::string &url, bool *cancel, std::vector &files) { + http::Client http; + Buffer result; + int code = 500; + std::vector responseHeaders; + + Url baseURL(url); + if (!baseURL.Valid()) { + return false; + } + + // Start by requesting the list of files from the server. + if (http.Resolve(baseURL.Host().c_str(), baseURL.Port())) { + if (http.Connect(2, 20.0, cancel)) { + code = http.GET(baseURL.Resource().c_str(), &result, responseHeaders); + http.Disconnect(); + } + } + + if (code != 200 || (cancel && *cancel)) { + return false; + } + + std::string listing; + std::vector items; + result.TakeAll(&listing); + + std::string contentType; + for (const std::string &header : responseHeaders) { + if (startsWithNoCase(header, "Content-Type:")) { + contentType = header.substr(strlen("Content-Type:")); + // Strip any whitespace (TODO: maybe move this to stringutil?) + contentType.erase(0, contentType.find_first_not_of(" \t\r\n")); + contentType.erase(contentType.find_last_not_of(" \t\r\n") + 1); + } + } + + // TODO: Technically, "TExt/hTml ; chaRSet = Utf8" should pass, but "text/htmlese" should not. + // But unlikely that'll be an issue. + bool parseHtml = startsWithNoCase(contentType, "text/html"); + bool parseText = startsWithNoCase(contentType, "text/plain"); + + if (parseText) { + // Plain text format - easy. + SplitString(listing, '\n', items); + } else if (parseHtml) { + // Try to extract from an automatic webserver directory listing... + GetQuotedStrings(listing, items); + } else { + ELOG("Unsupported Content-Type: %s", contentType.c_str()); + return false; + } + + for (std::string item : items) { + // Apply some workarounds. + if (item.empty()) + continue; + if (item.back() == '\r') + item.pop_back(); + if (item == baseURL.Resource()) + continue; + + FileInfo info; + info.name = item; + info.fullName = baseURL.Relative(item).ToString(); + info.isDirectory = endsWith(item, "/"); + info.exists = true; + info.size = 0; + info.isWritable = false; + + files.push_back(info); + } + + return !files.empty(); +} + +std::vector ApplyFilter(std::vector files, const char *filter) { + std::set filters; + if (filter) { + std::string tmp; + while (*filter) { + if (*filter == ':') { + filters.insert(std::move(tmp)); + } else { + tmp.push_back(*filter); + } + filter++; + } + if (!tmp.empty()) + filters.insert(std::move(tmp)); + } + + auto pred = [&](const FileInfo &info) { + if (info.isDirectory || !filter) + return false; + std::string ext = getFileExtension(info.fullName); + return filters.find(ext) == filters.end(); + }; + files.erase(std::remove_if(files.begin(), files.end(), pred), files.end()); + return files; +} + +PathBrowser::~PathBrowser() { + std::unique_lock guard(pendingLock_); + pendingCancel_ = true; + pendingStop_ = true; + pendingCond_.notify_all(); + guard.unlock(); + + if (pendingThread_.joinable()) { + pendingThread_.join(); + } +} // Normalize slashes. void PathBrowser::SetPath(const std::string &path) { if (path[0] == '!') { path_ = path; + HandlePath(); return; } path_ = path; @@ -12,9 +134,78 @@ void PathBrowser::SetPath(const std::string &path) { } if (!path_.size() || (path_[path_.size() - 1] != '/')) path_ += "/"; + HandlePath(); } -void PathBrowser::GetListing(std::vector &fileInfo, const char *filter) { +void PathBrowser::HandlePath() { + std::lock_guard guard(pendingLock_); + + if (!path_.empty() && path_[0] == '!') { + ready_ = true; + pendingCancel_ = true; + pendingPath_.clear(); + return; + } + if (!startsWith(path_, "http://") && !startsWith(path_, "https://")) { + ready_ = true; + pendingCancel_ = true; + pendingPath_.clear(); + return; + } + + ready_ = false; + pendingCancel_ = false; + pendingFiles_.clear(); + pendingPath_ = path_; + pendingCond_.notify_all(); + + if (pendingThread_.joinable()) + return; + + pendingThread_ = std::thread([&] { + setCurrentThreadName("PathBrowser"); + + std::unique_lock guard(pendingLock_); + std::vector results; + std::string lastPath; + while (!pendingStop_) { + while (lastPath == pendingPath_ && !pendingCancel_) { + pendingCond_.wait(guard); + } + lastPath = pendingPath_; + bool success = false; + if (!lastPath.empty()) { + guard.unlock(); + results.clear(); + success = LoadRemoteFileList(lastPath, &pendingCancel_, results); + guard.lock(); + } + + if (pendingPath_ == lastPath) { + if (success && !pendingCancel_) { + pendingFiles_ = results; + } + pendingPath_.clear(); + lastPath.clear(); + ready_ = true; + } + } + }); +} + +bool PathBrowser::IsListingReady() { + return ready_; +} + +bool PathBrowser::GetListing(std::vector &fileInfo, const char *filter, bool *cancel) { + std::unique_lock guard(pendingLock_); + while (!IsListingReady() && (!cancel || !*cancel)) { + // In case cancel changes, just sleep. + guard.unlock(); + sleep_ms(100); + guard.lock(); + } + #ifdef _WIN32 if (path_ == "/") { // Special path that means root of file system. @@ -34,7 +225,13 @@ void PathBrowser::GetListing(std::vector &fileInfo, const char *filter } #endif - getFilesInDir(path_.c_str(), &fileInfo, filter); + if (startsWith(path_, "http://") || startsWith(path_, "https://")) { + fileInfo = ApplyFilter(pendingFiles_, filter); + return true; + } else { + getFilesInDir(path_.c_str(), &fileInfo, filter); + return true; + } } // TODO: Support paths like "../../hello" @@ -52,11 +249,12 @@ void PathBrowser::Navigate(const std::string &path) { path_ = path_.substr(0, slash + 1); } } else { - if (path[1] == ':' && path_ == "/") + if (path.size() > 2 && path[1] == ':' && path_ == "/") path_ = path; else path_ = path_ + path; if (path_[path_.size() - 1] != '/') path_ += "/"; } + HandlePath(); } diff --git a/ext/native/file/path.h b/ext/native/file/path.h index b54e9be494..ed14962156 100644 --- a/ext/native/file/path.h +++ b/ext/native/file/path.h @@ -1,7 +1,10 @@ #pragma once +#include +#include #include #include +#include #include #include @@ -14,9 +17,11 @@ class PathBrowser { public: PathBrowser() {} PathBrowser(std::string path) { SetPath(path); } + ~PathBrowser(); void SetPath(const std::string &path); - void GetListing(std::vector &fileInfo, const char *filter = 0); + bool IsListingReady(); + bool GetListing(std::vector &fileInfo, const char *filter = nullptr, bool *cancel = nullptr); void Navigate(const std::string &path); std::string GetPath() const { @@ -39,6 +44,17 @@ public: return str; } +private: + void HandlePath(); + std::string path_; + std::string pendingPath_; + std::vector pendingFiles_; + std::condition_variable pendingCond_; + std::mutex pendingLock_; + std::thread pendingThread_; + bool pendingCancel_ = false; + bool pendingStop_ = false; + bool ready_ = false; }; diff --git a/ext/native/net/http_client.cpp b/ext/native/net/http_client.cpp index 19bff66f48..05bb656830 100644 --- a/ext/native/net/http_client.cpp +++ b/ext/native/net/http_client.cpp @@ -86,7 +86,6 @@ bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) { int maxfd = 1; FD_ZERO(&fds); for (addrinfo *possible = resolved_; possible != nullptr; possible = possible->ai_next) { - // TODO: Could support ipv6 without huge difficulty... if (possible->ai_family != AF_INET && possible->ai_family != AF_INET6) continue; @@ -428,16 +427,16 @@ int Client::ReadResponseEntity(Buffer *readbuf, const std::vector & } Download::Download(const std::string &url, const std::string &outfile) - : progress_(0.0f), url_(url), outfile_(outfile), resultCode_(0), completed_(false), failed_(false), cancelled_(false), hidden_(false) { + : url_(url), outfile_(outfile) { } Download::~Download() { - + if (thread_.joinable()) + thread_.join(); } void Download::Start(std::shared_ptr self) { - std::thread th(std::bind(&Download::Do, this, self)); - th.detach(); + thread_ = std::thread(std::bind(&Download::Do, this, self)); } void Download::SetFailed(int code) { @@ -577,6 +576,7 @@ void Downloader::CancelAll() { for (size_t i = 0; i < downloads_.size(); i++) { downloads_[i]->Cancel(); } + downloads_.clear(); } } // http diff --git a/ext/native/net/http_client.h b/ext/native/net/http_client.h index 3d2d97eb20..b671169585 100644 --- a/ext/native/net/http_client.h +++ b/ext/native/net/http_client.h @@ -142,16 +142,17 @@ private: int PerformGET(const std::string &url); std::string RedirectLocation(const std::string &baseUrl); void SetFailed(int code); - float progress_; + float progress_ = 0.0f; Buffer buffer_; std::vector responseHeaders_; std::string url_; std::string outfile_; - int resultCode_; - bool completed_; - bool failed_; - bool cancelled_; - bool hidden_; + std::thread thread_; + int resultCode_ = 0; + bool completed_ = false; + bool failed_ = false; + bool cancelled_ = false; + bool hidden_ = false; std::function callback_; }; diff --git a/ext/native/net/http_server.cpp b/ext/native/net/http_server.cpp index 099c537782..1c0b5995b3 100644 --- a/ext/native/net/http_server.cpp +++ b/ext/native/net/http_server.cpp @@ -191,6 +191,7 @@ bool Server::Listen4(int port) { } bool Server::Listen6(int port, bool ipv6_only) { +#if !PPSSPP_PLATFORM(SWITCH) listener_ = socket(AF_INET6, SOCK_STREAM, 0); if (listener_ < 0) return false; @@ -232,6 +233,9 @@ bool Server::Listen6(int port, bool ipv6_only) { port_ = port; return true; +#else + return false; +#endif } bool Server::RunSlice(double timeout) { @@ -249,7 +253,9 @@ bool Server::RunSlice(double timeout) { union { struct sockaddr sa; struct sockaddr_in ipv4; +#if !PPSSPP_PLATFORM(SWITCH) struct sockaddr_in6 ipv6; +#endif } client_addr; socklen_t client_addr_size = sizeof(client_addr); int conn_fd = accept(listener_, &client_addr.sa, &client_addr_size); diff --git a/ext/native/net/resolve.cpp b/ext/native/net/resolve.cpp index 60182e4d64..a5bd024c7a 100644 --- a/ext/native/net/resolve.cpp +++ b/ext/native/net/resolve.cpp @@ -43,8 +43,17 @@ void Shutdown() #endif } -bool DNSResolve(const std::string &host, const std::string &service, addrinfo **res, std::string &error, DNSType type) -{ +bool DNSResolve(const std::string &host, const std::string &service, addrinfo **res, std::string &error, DNSType type) { +#if PPSSPP_PLATFORM(SWITCH) + // Force IPv4 lookups. + if (type == DNSType::IPV6) { + *res = nullptr; + return false; + } else if (type == DNSType::ANY) { + type = DNSType::IPV4; + } +#endif + addrinfo hints = {0}; // TODO: Might be uses to lookup other values. hints.ai_socktype = SOCK_STREAM; @@ -61,9 +70,9 @@ bool DNSResolve(const std::string &host, const std::string &service, addrinfo ** else if (type == DNSType::IPV6) hints.ai_family = AF_INET6; - const char *servicep = service.length() == 0 ? NULL : service.c_str(); + const char *servicep = service.length() == 0 ? nullptr : service.c_str(); - *res = NULL; + *res = nullptr; int result = getaddrinfo(host.c_str(), servicep, &hints, res); if (result == EAI_AGAIN) { // Temporary failure. Since this already blocks, let's just try once more. @@ -77,9 +86,9 @@ bool DNSResolve(const std::string &host, const std::string &service, addrinfo ** #else error = gai_strerror(result); #endif - if (*res != NULL) + if (*res != nullptr) freeaddrinfo(*res); - *res = NULL; + *res = nullptr; return false; } diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index 8be1ca7120..19b9e1c6a3 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -1,4 +1,6 @@ #include + +#include "base/timeutil.h" #include "DataFormat.h" #include "VulkanQueueRunner.h" #include "VulkanRenderManager.h" @@ -375,7 +377,9 @@ VkRenderPass VulkanQueueRunner::GetRenderPass(const RPKey &key) { return pass; } -void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &steps, VkQueryPool queryPool, std::vector *timestampDescriptions) { +void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &steps, QueueProfileContext *profile) { + if (profile) + profile->cpuStartTime = real_time_now(); // Optimizes renderpasses, then sequences them. // Planned optimizations: // * Create copies of render target that are rendered to multiple times and textured from in sequence, and push those render passes @@ -465,9 +469,9 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st break; } - if (queryPool) { - vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, queryPool, (uint32_t)timestampDescriptions->size()); - timestampDescriptions->push_back(StepToString(step)); + if (profile) { + vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile->queryPool, (uint32_t)profile->timestampDescriptions.size()); + profile->timestampDescriptions.push_back(StepToString(step)); } } @@ -476,6 +480,9 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st for (size_t i = 0; i < steps.size(); i++) { delete steps[i]; } + + if (profile) + profile->cpuEndTime = real_time_now(); } void VulkanQueueRunner::ApplyMGSHack(std::vector &steps) { diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index 3c840856c6..8bb1adfbdf 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -173,6 +173,14 @@ struct TransitionRequest { VkImageLayout targetLayout; }; +struct QueueProfileContext { + VkQueryPool queryPool; + std::vector timestampDescriptions; + std::string profileSummary; + double cpuStartTime; + double cpuEndTime; +}; + struct VKRStep { VKRStep(VKRStepType _type) : stepType(_type) {} ~VKRStep() {} @@ -232,7 +240,7 @@ public: } // RunSteps can modify steps but will leave it in a valid state. - void RunSteps(VkCommandBuffer cmd, std::vector &steps, VkQueryPool queryPool, std::vector *timestampDescriptions); + void RunSteps(VkCommandBuffer cmd, std::vector &steps, QueueProfileContext *profile); void LogSteps(const std::vector &steps); std::string StepToString(const VKRStep &step) const; diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 921a2c05a9..04d423ab5d 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -138,7 +138,7 @@ VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan) : vulkan_(vulkan VkQueryPoolCreateInfo query_ci{ VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO }; query_ci.queryCount = MAX_TIMESTAMP_QUERIES; query_ci.queryType = VK_QUERY_TYPE_TIMESTAMP; - res = vkCreateQueryPool(vulkan_->GetDevice(), &query_ci, nullptr, &frameData_[i].timestampQueryPool_); + res = vkCreateQueryPool(vulkan_->GetDevice(), &query_ci, nullptr, &frameData_[i].profile.queryPool); } queueRunner_.CreateDeviceObjects(); @@ -230,7 +230,7 @@ void VulkanRenderManager::StopThread() { frameData.pull_condVar.notify_all(); } // Zero the queries so we don't try to pull them later. - frameData.timestampDescriptions.clear(); + frameData.profile.timestampDescriptions.clear(); } thread_.join(); ILOG("Vulkan submission thread joined. Frame=%d", vulkan_->GetCurFrame()); @@ -301,7 +301,7 @@ VulkanRenderManager::~VulkanRenderManager() { vkDestroyCommandPool(device, frameData_[i].cmdPoolInit, nullptr); vkDestroyCommandPool(device, frameData_[i].cmdPoolMain, nullptr); vkDestroyFence(device, frameData_[i].fence, nullptr); - vkDestroyQueryPool(device, frameData_[i].timestampQueryPool_, nullptr); + vkDestroyQueryPool(device, frameData_[i].profile.queryPool, nullptr); } queueRunner_.DestroyDeviceObjects(); } @@ -378,11 +378,11 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling) { if (frameData.profilingEnabled_) { // Pull the profiling results from last time and produce a summary! - if (!frameData.timestampDescriptions.empty()) { - int numQueries = (int)frameData.timestampDescriptions.size(); + if (!frameData.profile.timestampDescriptions.empty()) { + int numQueries = (int)frameData.profile.timestampDescriptions.size(); VkResult res = vkGetQueryPoolResults( vulkan_->GetDevice(), - frameData.timestampQueryPool_, 0, numQueries, sizeof(uint64_t) * numQueries, &queryResults[0], sizeof(uint64_t), + frameData.profile.queryPool, 0, numQueries, sizeof(uint64_t) * numQueries, &queryResults[0], sizeof(uint64_t), VK_QUERY_RESULT_64_BIT); if (res == VK_SUCCESS) { double timestampConversionFactor = (double)vulkan_->GetPhysicalDeviceProperties().properties.limits.timestampPeriod * (1.0 / 1000000.0); @@ -393,19 +393,20 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling) { char line[256]; snprintf(line, sizeof(line), "Total GPU time: %0.3f ms\n", ((double)((queryResults[numQueries - 1] - queryResults[0]) & timestampDiffMask) * timestampConversionFactor)); str << line; + snprintf(line, sizeof(line), "Render CPU time: %0.3f ms\n", (frameData.profile.cpuEndTime - frameData.profile.cpuStartTime) * 1000.0); + str << line; for (int i = 0; i < numQueries - 1; i++) { uint64_t diff = (queryResults[i + 1] - queryResults[i]) & timestampDiffMask; double milliseconds = (double)diff * timestampConversionFactor; - snprintf(line, sizeof(line), "%s: %0.3f ms\n", frameData.timestampDescriptions[i + 1].c_str(), milliseconds); + snprintf(line, sizeof(line), "%s: %0.3f ms\n", frameData.profile.timestampDescriptions[i + 1].c_str(), milliseconds); str << line; } - frameData.profileSummary = str.str(); + frameData.profile.profileSummary = str.str(); } else { - frameData.profileSummary = "(error getting GPU profile - not ready?)"; + frameData.profile.profileSummary = "(error getting GPU profile - not ready?)"; } - } - else { - frameData.profileSummary = "(no GPU profile data collected)"; + } else { + frameData.profile.profileSummary = "(no GPU profile data collected)"; } } @@ -418,16 +419,16 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling) { insideFrame_ = true; - frameData.timestampDescriptions.clear(); + frameData.profile.timestampDescriptions.clear(); if (frameData_->profilingEnabled_) { // For various reasons, we need to always use an init cmd buffer in this case to perform the vkCmdResetQueryPool, // unless we want to limit ourselves to only measure the main cmd buffer. // Reserve the first two queries for initCmd. - frameData.timestampDescriptions.push_back("initCmd Begin"); - frameData.timestampDescriptions.push_back("initCmd"); + frameData.profile.timestampDescriptions.push_back("initCmd Begin"); + frameData.profile.timestampDescriptions.push_back("initCmd"); VkCommandBuffer initCmd = GetInitCmd(); - vkCmdResetQueryPool(initCmd, frameData.timestampQueryPool_, 0, MAX_TIMESTAMP_QUERIES); - vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, frameData.timestampQueryPool_, 0); + vkCmdResetQueryPool(initCmd, frameData.profile.queryPool, 0, MAX_TIMESTAMP_QUERIES); + vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, frameData.profile.queryPool, 0); } } @@ -963,7 +964,7 @@ void VulkanRenderManager::Submit(int frame, bool triggerFence) { if (frameData.hasInitCommands) { if (frameData.profilingEnabled_ && triggerFence) { // Pre-allocated query ID 1. - vkCmdWriteTimestamp(frameData.initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, frameData.timestampQueryPool_, 1); + vkCmdWriteTimestamp(frameData.initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, frameData.profile.queryPool, 1); } VkResult res = vkEndCommandBuffer(frameData.initCmd); _assert_msg_(G3D, res == VK_SUCCESS, "vkEndCommandBuffer failed (init)! result=%s", VulkanResultToString(res)); @@ -1064,7 +1065,7 @@ void VulkanRenderManager::Run(int frame) { auto &stepsOnThread = frameData_[frame].steps; VkCommandBuffer cmd = frameData.mainCmd; // queueRunner_.LogSteps(stepsOnThread); - queueRunner_.RunSteps(cmd, stepsOnThread, frameData.profilingEnabled_ ? frameData.timestampQueryPool_ : VK_NULL_HANDLE, &frameData.timestampDescriptions); + queueRunner_.RunSteps(cmd, stepsOnThread, frameData.profilingEnabled_ ? &frameData.profile : nullptr); stepsOnThread.clear(); switch (frameData.type) { diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index 56a3fa1cf3..d0b0d74793 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -258,7 +258,7 @@ public: } std::string GetGpuProfileString() const { - return frameData_[vulkan_->GetCurFrame()].profileSummary; + return frameData_[vulkan_->GetCurFrame()].profile.profileSummary; } private: @@ -305,10 +305,8 @@ private: uint32_t curSwapchainImage = -1; // Profiling. - VkQueryPool timestampQueryPool_ = VK_NULL_HANDLE; + QueueProfileContext profile; bool profilingEnabled_; - std::vector timestampDescriptions; - std::string profileSummary; }; FrameData frameData_[VulkanContext::MAX_INFLIGHT_FRAMES]; diff --git a/ext/native/ui/screen.cpp b/ext/native/ui/screen.cpp index 8d86f10a6b..3bdfb5cb9f 100644 --- a/ext/native/ui/screen.cpp +++ b/ext/native/ui/screen.cpp @@ -125,6 +125,7 @@ void ScreenManager::deviceRestored() { } void ScreenManager::resized() { + ILOG("ScreenManager::resized(dp: %dx%d)", dp_xres, dp_yres); std::lock_guard guard(inputLock_); // Have to notify the whole stack, otherwise there will be problems when going back // to non-top screens. diff --git a/headless/SDLHeadlessHost.cpp b/headless/SDLHeadlessHost.cpp index c5702eabc4..ac04878237 100644 --- a/headless/SDLHeadlessHost.cpp +++ b/headless/SDLHeadlessHost.cpp @@ -119,11 +119,13 @@ bool GLDummyGraphicsContext::InitFromRenderThread(std::string *errorMessage) { SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetSwapInterval(1); screen_ = CreateHiddenWindow(); glContext_ = SDL_GL_CreateContext(screen_); + // Ensure that the swap interval is set after context creation (needed for kmsdrm) + SDL_GL_SetSwapInterval(1); + #ifndef USING_GLES2 // Some core profile drivers elide certain extensions from GL_EXTENSIONS/etc. // glewExperimental allows us to force GLEW to search for the pointers anyway. diff --git a/ppsspp_config.h b/ppsspp_config.h index d2072fbb56..8743e2290e 100644 --- a/ppsspp_config.h +++ b/ppsspp_config.h @@ -110,6 +110,8 @@ #else #error "Unknown Apple platform" #endif +#elif defined(__SWITCH__) + #define PPSSPP_PLATFORM_SWITCH 1 #elif defined(__ANDROID__) #define PPSSPP_PLATFORM_ANDROID 1 #define PPSSPP_PLATFORM_LINUX 1