#include #include #include #include "base/timeutil.h" #include "base/NativeApp.h" #include "i18n/i18n.h" #include "input/input_state.h" #include "util/text/utf8.h" #include "Common/Log.h" #include "Common/StringUtils.h" #include "Common/GraphicsContext.h" #include "Windows/EmuThread.h" #include "Windows/W32Util/Misc.h" #include "Windows/MainWindow.h" #include "Windows/resource.h" #include "Windows/WindowsHost.h" #include "Core/Reporting.h" #include "Core/MemMap.h" #include "Core/Core.h" #include "Core/Host.h" #include "Core/System.h" #include "Core/Config.h" #include "thread/threadutil.h" static std::mutex emuThreadLock; static std::thread emuThread; static std::atomic emuThreadState; static std::mutex renderThreadLock; static std::thread renderThread; static std::atomic renderThreadReady; static bool useRenderThread; static bool renderThreadFailed; static bool renderThreadSucceeded; static std::string g_error_message; extern std::vector GetWideCmdLine(); class GraphicsContext; static GraphicsContext *g_graphicsContext; enum EmuThreadStatus : int { THREAD_NONE = 0, THREAD_INIT, THREAD_CORE_LOOP, THREAD_SHUTDOWN, THREAD_END, }; void EmuThreadFunc(); void RenderThreadFunc(); // On most other platforms, we let the main thread become the render thread and // start a separate emu thread from that, if needed. Should probably switch to that // to make it the same on all platforms. void EmuThread_Start(bool separateRenderThread) { std::lock_guard guard(emuThreadLock); emuThread = std::thread(&EmuThreadFunc); useRenderThread = separateRenderThread; if (useRenderThread) { renderThread = std::thread(&RenderThreadFunc); } } void EmuThread_Stop() { // Already stopped? { std::lock_guard guard(emuThreadLock); if (emuThreadState == THREAD_END) return; } UpdateUIState(UISTATE_EXIT); Core_Stop(); Core_WaitInactive(800); emuThread.join(); if (useRenderThread) { renderThread.join(); } PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_UPDATE_UI, 0, 0); } bool EmuThread_Ready() { return emuThreadState == THREAD_CORE_LOOP; } void RenderThreadFunc() { setCurrentThreadName("Render"); renderThreadFailed = false; renderThreadSucceeded = false; while (!g_graphicsContext) { sleep_ms(10); continue; } std::string error_message; if (!g_graphicsContext->InitFromRenderThread(&error_message)) { g_error_message = error_message; renderThreadFailed = true; return; } else { renderThreadSucceeded = true; } g_graphicsContext->ThreadStart(); while (g_graphicsContext->ThreadFrame()) { continue; } g_graphicsContext->ThreadEnd(); g_graphicsContext->ShutdownFromRenderThread(); } void EmuThreadFunc() { emuThreadState = THREAD_INIT; setCurrentThreadName("Emu"); host = new WindowsHost(MainWindow::GetHInstance(), MainWindow::GetHWND(), MainWindow::GetDisplayHWND()); host->SetWindowTitle(nullptr); // We convert command line arguments to UTF-8 immediately. std::vector wideArgs = GetWideCmdLine(); std::vector argsUTF8; for (auto& string : wideArgs) { argsUTF8.push_back(ConvertWStringToUTF8(string)); } std::vector args; for (auto& string : argsUTF8) { args.push_back(string.c_str()); } bool performingRestart = NativeIsRestarting(); NativeInit(static_cast(args.size()), &args[0], "1234", "1234", nullptr); host->UpdateUI(); std::string error_string; bool success = host->InitGraphics(&error_string, &g_graphicsContext); if (success) { if (!useRenderThread) { // This is also the render thread. success = g_graphicsContext->InitFromRenderThread(&error_string); } else { while (!renderThreadFailed && !renderThreadSucceeded) { sleep_ms(10); } success = renderThreadSucceeded; if (!success) { error_string = g_error_message; } } } if (!success) { // Before anything: are we restarting right now? if (performingRestart) { // Okay, switching graphics didn't work out. Probably a driver bug - fallback to restart. // This happens on NVIDIA when switching OpenGL -> Vulkan. g_Config.Save(); W32Util::ExitAndRestart(); } I18NCategory *err = GetI18NCategory("Error"); Reporting::ReportMessage("Graphics init error: %s", error_string.c_str()); const char *defaultErrorVulkan = "Failed initializing graphics. Try upgrading your graphics drivers.\n\nWould you like to try switching to OpenGL?\n\nError message:"; const char *defaultErrorOpenGL = "Failed initializing graphics. Try upgrading your graphics drivers.\n\nWould you like to try switching to DirectX 9?\n\nError message:"; const char *defaultErrorDirect3D9 = "Failed initializing graphics. Try upgrading your graphics drivers and directx 9 runtime.\n\nWould you like to try switching to OpenGL?\n\nError message:"; const char *genericError; GPUBackend nextBackend = GPUBackend::DIRECT3D9; switch (g_Config.iGPUBackend) { case (int)GPUBackend::DIRECT3D9: nextBackend = GPUBackend::OPENGL; genericError = err->T("GenericDirect3D9Error", defaultErrorDirect3D9); break; case (int)GPUBackend::VULKAN: nextBackend = GPUBackend::OPENGL; genericError = err->T("GenericVulkanError", defaultErrorVulkan); break; case (int)GPUBackend::OPENGL: default: nextBackend = GPUBackend::DIRECT3D9; genericError = err->T("GenericOpenGLError", defaultErrorOpenGL); break; } std::string full_error = StringFromFormat("%s\n\n%s", genericError, error_string.c_str()); std::wstring title = ConvertUTF8ToWString(err->T("GenericGraphicsError", "Graphics Error")); bool yes = IDYES == MessageBox(0, ConvertUTF8ToWString(full_error).c_str(), title.c_str(), MB_ICONERROR | MB_YESNO); ERROR_LOG(BOOT, full_error.c_str()); if (yes) { // Change the config to the alternative and restart. g_Config.iGPUBackend = (int)nextBackend; g_Config.Save(); W32Util::ExitAndRestart(); } // No safe way out without graphics. exit(1); } NativeInitGraphics(g_graphicsContext); NativeResized(); INFO_LOG(BOOT, "Done."); _dbg_update_(); if (coreState == CORE_POWERDOWN) { INFO_LOG(BOOT, "Exit before core loop."); goto shutdown; } emuThreadState = THREAD_CORE_LOOP; if (g_Config.bBrowse) PostMessage(MainWindow::GetHWND(), WM_COMMAND, ID_FILE_LOAD, 0); Core_EnableStepping(false); while (GetUIState() != UISTATE_EXIT) { // We're here again, so the game quit. Restart Core_Run() which controls the UI. // This way they can load a new game. if (!Core_IsActive()) UpdateUIState(UISTATE_MENU); Core_Run(g_graphicsContext); } shutdown: emuThreadState = THREAD_SHUTDOWN; NativeShutdownGraphics(); if (!useRenderThread) g_graphicsContext->ShutdownFromRenderThread(); // NativeShutdown deletes the graphics context through host->ShutdownGraphics(). NativeShutdown(); emuThreadState = THREAD_END; }