diff --git a/Core/Config.cpp b/Core/Config.cpp index 03e1043ddbe4..5b251058bd55 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "base/display.h" #include "base/NativeApp.h" @@ -545,6 +546,46 @@ static int DefaultGPUBackend() { return (int)GPUBackend::OPENGL; } +int Config::NextValidBackend() { + std::vector split; + std::set failed; + SplitString(sFailedGPUBackends, ',', split); + for (const auto &str : split) { + if (!str.empty() && str != "ALL") { + failed.insert(atoi(str.c_str())); + } + } + + if (failed.count(iGPUBackend)) { +#if (PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(ANDROID)) && !PPSSPP_PLATFORM(UWP) + if (VulkanMayBeAvailable() && !failed.count((int)GPUBackend::VULKAN)) { + return (int)GPUBackend::VULKAN; + } +#endif +#if PPSSPP_PLATFORM(WINDOWS) + if (DoesVersionMatchWindows(6, 1, 0, 0, true) && !failed.count((int)GPUBackend::DIRECT3D11)) { + return (int)GPUBackend::DIRECT3D11; + } +#endif +#if !PPSSPP_PLATFORM(UWP) + if (!failed.count((int)GPUBackend::OPENGL)) { + return (int)GPUBackend::OPENGL; + } +#endif +#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) + if (!failed.count((int)GPUBackend::DIRECT3D9)) { + return (int)GPUBackend::DIRECT3D9; + } +#endif + + // They've all failed. Let them try the default. + sFailedGPUBackends += ",ALL"; + return DefaultGPUBackend(); + } + + return iGPUBackend; +} + static bool DefaultVertexCache() { return DefaultGPUBackend() == (int)GPUBackend::OPENGL; } @@ -556,6 +597,7 @@ static ConfigSetting graphicsSettings[] = { ConfigSetting("CardboardYShift", &g_Config.iCardboardXShift, 0, true, true), ConfigSetting("ShowFPSCounter", &g_Config.iShowFPSCounter, 0, true, true), ReportedConfigSetting("GraphicsBackend", &g_Config.iGPUBackend, &DefaultGPUBackend), + ConfigSetting("FailedGraphicsBackends", &g_Config.sFailedGPUBackends, ""), ConfigSetting("VulkanDevice", &g_Config.sVulkanDevice, "", true, false), #ifdef _WIN32 ConfigSetting("D3D11Device", &g_Config.sD3D11Device, "", true, false), diff --git a/Core/Config.h b/Core/Config.h index e956c2b97c3c..24e7d98664d7 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -121,6 +121,7 @@ struct Config { // GFX int iGPUBackend; + std::string sFailedGPUBackends; // We have separate device parameters for each backend so it doesn't get erased if you switch backends. // If not set, will use the "best" device. std::string sVulkanDevice; @@ -441,6 +442,7 @@ struct Config { void GetReportingInfo(UrlEncoder &data); bool IsPortrait() const; + int NextValidBackend(); protected: void LoadStandardControllerIni(); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 690c8105227a..5beab45f1d2f 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -29,13 +29,10 @@ // in NativeShutdown. #include -// Linux doesn't like using std::find with std::vector without this :/ -#if !defined(MOBILE_DEVICE) #include -#endif #include -#include #include +#include #if defined(_WIN32) #include "Windows/DSoundStream.h" @@ -348,6 +345,53 @@ void CreateDirectoriesAndroid() { File::CreateEmptyFile(GetSysDirectory(DIRECTORY_SYSTEM) + ".nomedia"); } +static void CheckFailedGPUBackends() { + std::string cache = GetSysDirectory(DIRECTORY_APP_CACHE) + "/FailedGraphicsBackends.txt"; + + if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) { + std::string data; + if (readFileToString(true, cache.c_str(), data)) + g_Config.sFailedGPUBackends = data; + } + + // Use this if you want to debug a graphics crash... + if (g_Config.sFailedGPUBackends == "IGNORE") + return; + + // Okay, let's not try a backend in the failed list. + g_Config.iGPUBackend = g_Config.NextValidBackend(); + // And then let's - for now - add the current to the failed list. + if (g_Config.sFailedGPUBackends.empty()) { + g_Config.sFailedGPUBackends = StringFromFormat("%d", g_Config.iGPUBackend); + } else if (g_Config.sFailedGPUBackends.find("ALL") == std::string::npos) { + g_Config.sFailedGPUBackends += StringFromFormat(",%d", g_Config.iGPUBackend); + } + + if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) { + // Let's try to create, in case it doesn't exist. + if (!File::Exists(GetSysDirectory(DIRECTORY_APP_CACHE))) + File::CreateDir(GetSysDirectory(DIRECTORY_APP_CACHE)); + writeStringToFile(true, g_Config.sFailedGPUBackends, cache.c_str()); + } else { + // Just save immediately, since we have storage. + g_Config.Save(); + } +} + +static void ClearFailedGPUBackends() { + if (g_Config.sFailedGPUBackends == "IGNORE") + return; + + // We've successfully started graphics without crashing, hurray. + // In case they update drivers and have totally different problems much later, clear the failed list. + g_Config.sFailedGPUBackends.clear(); + if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) { + File::Delete(GetSysDirectory(DIRECTORY_APP_CACHE) + "/FailedGraphicsBackends.txt"); + } else { + g_Config.Save(); + } +} + void NativeInit(int argc, const char *argv[], const char *savegame_dir, const char *external_dir, const char *cache_dir) { net::Init(); // This needs to happen before we load the config. So on Windows we also run it in Main. It's fine to call multiple times. @@ -631,6 +675,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch // We do this here, instead of in NativeInitGraphics, because the display may be reset. // When it's reset we don't want to forget all our managed things. + CheckFailedGPUBackends(); SetGPUBackend((GPUBackend) g_Config.iGPUBackend); // Must be done restarting by now. @@ -952,6 +997,12 @@ void NativeRender(GraphicsContext *graphicsContext) { ui_draw2d.PopDrawMatrix(); ui_draw2d_front.PopDrawMatrix(); + + static int renderCounter = 0; + if (renderCounter < 10 && ++renderCounter == 10) { + // We're rendering fine, clear out failure info. + ClearFailedGPUBackends(); + } } void HandleGlobalMessage(const std::string &msg, const std::string &value) { diff --git a/Windows/EmuThread.cpp b/Windows/EmuThread.cpp index 987bb8b5e41d..5a81b5ceb89b 100644 --- a/Windows/EmuThread.cpp +++ b/Windows/EmuThread.cpp @@ -132,6 +132,18 @@ void MainThreadFunc() { bool performingRestart = NativeIsRestarting(); NativeInit(static_cast(args.size()), &args[0], "1234", "1234", nullptr); + if (g_Config.sFailedGPUBackends.find("ALL") != std::string::npos) { + Reporting::ReportMessage("Graphics init error: %s", "ALL"); + + I18NCategory *err = GetI18NCategory("Error"); + const char *defaultErrorAll = "Failed initializing any graphics. Try upgrading your graphics drivers."; + const char *genericError = err->T("GenericAllGraphicsError", defaultErrorAll); + std::wstring title = ConvertUTF8ToWString(err->T("GenericGraphicsError", "Graphics Error")); + MessageBox(0, ConvertUTF8ToWString(genericError).c_str(), title.c_str(), MB_OK); + + // Let's continue (and probably crash) just so they have a way to keep trying. + } + host->UpdateUI(); std::string error_string; @@ -182,6 +194,8 @@ void MainThreadFunc() { if (yes) { // Change the config to the alternative and restart. g_Config.iGPUBackend = (int)nextBackend; + // Clear this to ensure we try their selection. + g_Config.sFailedGPUBackends.clear(); g_Config.Save(); W32Util::ExitAndRestart(); diff --git a/Windows/GPU/D3D11Context.cpp b/Windows/GPU/D3D11Context.cpp index 806458b1e8fb..1689b51d6c33 100644 --- a/Windows/GPU/D3D11Context.cpp +++ b/Windows/GPU/D3D11Context.cpp @@ -145,6 +145,7 @@ bool D3D11Context::Init(HINSTANCE hInst, HWND wnd, std::string *error_message) { if (yes) { // Change the config to D3D and restart. g_Config.iGPUBackend = (int)GPUBackend::DIRECT3D9; + g_Config.sFailedGPUBackends.clear(); g_Config.Save(); W32Util::ExitAndRestart(); diff --git a/Windows/GPU/WindowsGLContext.cpp b/Windows/GPU/WindowsGLContext.cpp index f3808583b05a..527860d2eef9 100644 --- a/Windows/GPU/WindowsGLContext.cpp +++ b/Windows/GPU/WindowsGLContext.cpp @@ -267,6 +267,7 @@ bool WindowsGLContext::InitFromRenderThread(std::string *error_message) { std::wstring whichD3D9 = ConvertUTF8ToWString(err->T("D3D9or11", d3d9Or11)); bool d3d9 = IDYES == MessageBox(hWnd_, whichD3D9.c_str(), title.c_str(), MB_YESNO); g_Config.iGPUBackend = d3d9 ? (int)GPUBackend::DIRECT3D9 : (int)GPUBackend::DIRECT3D11; + g_Config.sFailedGPUBackends.clear(); g_Config.Save(); W32Util::ExitAndRestart();