2017-02-27 20:57:46 +00:00
# include <mutex>
2017-12-19 14:20:24 +00:00
# include <atomic>
# include <thread>
2017-02-27 20:57:46 +00:00
2020-10-04 08:30:18 +00:00
# include "Common/System/NativeApp.h"
# include "Common/System/System.h"
2020-10-01 11:05:04 +00:00
# include "Common/Data/Text/I18n.h"
2020-10-01 07:36:43 +00:00
# include "Common/Input/InputState.h"
2020-10-01 11:05:04 +00:00
# include "Common/Data/Encoding/Utf8.h"
2013-08-13 15:09:36 +00:00
# include "Common/Log.h"
# include "Common/StringUtils.h"
2017-12-15 11:40:38 +00:00
# include "Common/GraphicsContext.h"
2020-08-15 18:53:08 +00:00
# include "Common/TimeUtil.h"
2020-10-04 08:30:18 +00:00
# include "Common/Thread/ThreadUtil.h"
2013-08-13 15:09:36 +00:00
# include "Windows/EmuThread.h"
2014-09-27 04:16:56 +00:00
# include "Windows/W32Util/Misc.h"
2015-09-19 11:14:05 +00:00
# include "Windows/MainWindow.h"
2013-08-13 15:09:36 +00:00
# include "Windows/resource.h"
2017-05-05 13:53:48 +00:00
# include "Windows/WindowsHost.h"
2013-08-13 15:09:36 +00:00
# include "Core/Reporting.h"
# include "Core/MemMap.h"
# include "Core/Core.h"
# include "Core/Host.h"
# include "Core/System.h"
# include "Core/Config.h"
2018-06-17 01:42:31 +00:00
# include "Core/ConfigValues.h"
2012-11-01 15:19:01 +00:00
2018-02-07 14:52:19 +00:00
enum class EmuThreadState {
DISABLED ,
START_REQUESTED ,
RUNNING ,
QUIT_REQUESTED ,
STOPPED ,
} ;
2013-06-08 00:32:07 +00:00
2018-02-07 14:52:19 +00:00
static std : : thread emuThread ;
static std : : atomic < int > emuThreadState ( ( int ) EmuThreadState : : DISABLED ) ;
2017-12-15 11:40:38 +00:00
2018-02-07 14:52:19 +00:00
static std : : thread mainThread ;
static bool useEmuThread ;
2018-01-16 13:16:56 +00:00
static std : : string g_error_message ;
2018-02-07 14:52:19 +00:00
static bool g_inLoop ;
2017-12-15 11:40:38 +00:00
2014-08-31 07:05:59 +00:00
extern std : : vector < std : : wstring > GetWideCmdLine ( ) ;
2017-12-15 11:40:38 +00:00
class GraphicsContext ;
static GraphicsContext * g_graphicsContext ;
2018-02-07 14:52:19 +00:00
void MainThreadFunc ( ) ;
2012-11-01 15:19:01 +00:00
2018-02-07 14:52:19 +00:00
// On most other platforms, we let the "main" thread become the render thread and
2018-02-07 12:11:43 +00:00
// start a separate emu thread from that, if needed. Should probably switch to that
// to make it the same on all platforms.
2018-02-07 14:52:19 +00:00
void MainThread_Start ( bool separateEmuThread ) {
useEmuThread = separateEmuThread ;
mainThread = std : : thread ( & MainThreadFunc ) ;
2012-11-01 15:19:01 +00:00
}
2018-02-07 14:52:19 +00:00
void MainThread_Stop ( ) {
2013-08-13 15:03:13 +00:00
// Already stopped?
2014-06-22 07:38:46 +00:00
UpdateUIState ( UISTATE_EXIT ) ;
2018-02-11 23:19:16 +00:00
Core_Stop ( ) ;
2018-02-07 14:52:19 +00:00
mainThread . join ( ) ;
2012-11-01 15:19:01 +00:00
}
2018-02-07 14:52:19 +00:00
bool MainThread_Ready ( ) {
return g_inLoop ;
2013-06-08 00:32:07 +00:00
}
2018-02-07 14:52:19 +00:00
static void EmuThreadFunc ( GraphicsContext * graphicsContext ) {
2020-11-30 23:46:26 +00:00
SetCurrentThreadName ( " Emu " ) ;
2018-01-16 13:16:56 +00:00
2018-02-07 14:52:19 +00:00
// There's no real requirement that NativeInit happen on this thread.
// We just call the update/render loop here.
emuThreadState = ( int ) EmuThreadState : : RUNNING ;
NativeInitGraphics ( graphicsContext ) ;
2018-01-16 13:16:56 +00:00
2018-02-07 14:52:19 +00:00
while ( emuThreadState ! = ( int ) EmuThreadState : : QUIT_REQUESTED ) {
// 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 ) ;
2018-01-16 17:13:31 +00:00
}
2018-02-07 14:52:19 +00:00
emuThreadState = ( int ) EmuThreadState : : STOPPED ;
NativeShutdownGraphics ( ) ;
2018-02-11 00:12:33 +00:00
// Ask the main thread to stop. This prevents a hang on a race condition.
graphicsContext - > StopThread ( ) ;
2017-12-15 11:40:38 +00:00
}
2018-02-07 14:52:19 +00:00
static void EmuThreadStart ( GraphicsContext * graphicsContext ) {
emuThreadState = ( int ) EmuThreadState : : START_REQUESTED ;
emuThread = std : : thread ( & EmuThreadFunc , graphicsContext ) ;
}
2013-06-08 00:32:07 +00:00
2018-02-07 14:52:19 +00:00
static void EmuThreadStop ( ) {
emuThreadState = ( int ) EmuThreadState : : QUIT_REQUESTED ;
}
static void EmuThreadJoin ( ) {
emuThread . join ( ) ;
emuThread = std : : thread ( ) ;
2020-08-15 14:13:24 +00:00
INFO_LOG ( SYSTEM , " EmuThreadJoin - joined " ) ;
2018-02-07 14:52:19 +00:00
}
void MainThreadFunc ( ) {
if ( useEmuThread ) {
// We'll start up a separate thread we'll call Emu
2020-11-30 23:46:26 +00:00
SetCurrentThreadName ( " Render " ) ;
2018-02-07 14:52:19 +00:00
} else {
// This is both Emu and Render.
2020-11-30 23:46:26 +00:00
SetCurrentThreadName ( " Emu " ) ;
2018-02-07 14:52:19 +00:00
}
2012-11-01 15:19:01 +00:00
2017-05-05 13:53:48 +00:00
host = new WindowsHost ( MainWindow : : GetHInstance ( ) , MainWindow : : GetHWND ( ) , MainWindow : : GetDisplayHWND ( ) ) ;
host - > SetWindowTitle ( nullptr ) ;
2017-12-19 14:20:24 +00:00
// We convert command line arguments to UTF-8 immediately.
2014-08-31 05:17:25 +00:00
std : : vector < std : : wstring > wideArgs = GetWideCmdLine ( ) ;
std : : vector < std : : string > argsUTF8 ;
2014-08-31 08:16:22 +00:00
for ( auto & string : wideArgs ) {
2014-08-31 07:05:59 +00:00
argsUTF8 . push_back ( ConvertWStringToUTF8 ( string ) ) ;
2014-08-31 05:17:25 +00:00
}
std : : vector < const char * > args ;
2014-08-31 08:16:22 +00:00
for ( auto & string : argsUTF8 ) {
2014-08-31 05:17:25 +00:00
args . push_back ( string . c_str ( ) ) ;
}
2017-04-15 23:33:30 +00:00
bool performingRestart = NativeIsRestarting ( ) ;
2021-02-28 23:09:36 +00:00
NativeInit ( static_cast < int > ( args . size ( ) ) , & args [ 0 ] , " " , " " , nullptr ) ;
2014-08-31 05:17:25 +00:00
2020-03-28 13:19:11 +00:00
if ( g_Config . iGPUBackend = = ( int ) GPUBackend : : OPENGL ) {
if ( ! useEmuThread ) {
// Okay, we must've switched to OpenGL. Let's flip the emu thread on.
useEmuThread = true ;
2020-11-30 23:46:26 +00:00
SetCurrentThreadName ( " Render " ) ;
2020-03-28 13:19:11 +00:00
}
} else if ( useEmuThread ) {
// We must've failed over from OpenGL, flip the emu thread off.
useEmuThread = false ;
2020-11-30 23:46:26 +00:00
SetCurrentThreadName ( " Emu " ) ;
2020-03-28 13:19:11 +00:00
}
2018-09-01 20:57:20 +00:00
if ( g_Config . sFailedGPUBackends . find ( " ALL " ) ! = std : : string : : npos ) {
Reporting : : ReportMessage ( " Graphics init error: %s " , " ALL " ) ;
2020-01-26 18:43:18 +00:00
auto err = GetI18NCategory ( " Error " ) ;
2020-05-09 06:20:37 +00:00
const char * defaultErrorAll = " PPSSPP failed to startup with any graphics backend. Try upgrading your graphics and other drivers. " ;
const char * genericError = err - > T ( " GenericAllStartupError " , defaultErrorAll ) ;
2018-09-01 20:57:20 +00:00
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.
}
2012-11-01 15:19:01 +00:00
host - > UpdateUI ( ) ;
2014-01-26 23:36:39 +00:00
2013-03-10 22:08:57 +00:00
std : : string error_string ;
2018-01-16 13:16:56 +00:00
bool success = host - > InitGraphics ( & error_string , & g_graphicsContext ) ;
if ( success ) {
2018-02-07 14:52:19 +00:00
// Main thread is the render thread.
success = g_graphicsContext - > InitFromRenderThread ( & error_string ) ;
2018-01-16 13:16:56 +00:00
}
if ( ! success ) {
2017-04-15 23:33:30 +00:00
// 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.
2019-02-23 09:49:49 +00:00
g_Config . Save ( " switch_graphics_failed " ) ;
2017-04-15 23:33:30 +00:00
W32Util : : ExitAndRestart ( ) ;
}
2020-01-26 18:43:18 +00:00
auto err = GetI18NCategory ( " Error " ) ;
2014-08-17 14:07:14 +00:00
Reporting : : ReportMessage ( " Graphics init error: %s " , error_string . c_str ( ) ) ;
2014-09-27 04:16:56 +00:00
2016-03-13 16:33:39 +00:00
const char * defaultErrorVulkan = " Failed initializing graphics. Try upgrading your graphics drivers. \n \n Would you like to try switching to OpenGL? \n \n Error message: " ;
2014-09-27 04:16:56 +00:00
const char * defaultErrorOpenGL = " Failed initializing graphics. Try upgrading your graphics drivers. \n \n Would you like to try switching to DirectX 9? \n \n Error message: " ;
2014-09-28 07:55:16 +00:00
const char * defaultErrorDirect3D9 = " Failed initializing graphics. Try upgrading your graphics drivers and directx 9 runtime. \n \n Would you like to try switching to OpenGL? \n \n Error message: " ;
2014-09-27 04:16:56 +00:00
const char * genericError ;
2017-12-26 23:55:24 +00:00
GPUBackend nextBackend = GPUBackend : : DIRECT3D9 ;
2014-09-27 04:16:56 +00:00
switch ( g_Config . iGPUBackend ) {
2017-12-26 23:55:24 +00:00
case ( int ) GPUBackend : : DIRECT3D9 :
nextBackend = GPUBackend : : OPENGL ;
2014-09-27 04:16:56 +00:00
genericError = err - > T ( " GenericDirect3D9Error " , defaultErrorDirect3D9 ) ;
break ;
2017-12-26 23:55:24 +00:00
case ( int ) GPUBackend : : VULKAN :
nextBackend = GPUBackend : : OPENGL ;
2016-03-25 00:03:04 +00:00
genericError = err - > T ( " GenericVulkanError " , defaultErrorVulkan ) ;
2016-03-13 16:33:39 +00:00
break ;
2017-12-26 23:55:24 +00:00
case ( int ) GPUBackend : : OPENGL :
2014-09-27 04:16:56 +00:00
default :
2017-12-26 23:55:24 +00:00
nextBackend = GPUBackend : : DIRECT3D9 ;
2014-09-27 04:16:56 +00:00
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 ) ;
2021-02-14 17:59:04 +00:00
ERROR_LOG ( BOOT , " %s " , full_error . c_str ( ) ) ;
2013-09-11 19:35:04 +00:00
2014-09-27 04:16:56 +00:00
if ( yes ) {
// Change the config to the alternative and restart.
2017-12-26 23:55:24 +00:00
g_Config . iGPUBackend = ( int ) nextBackend ;
2018-09-01 20:57:20 +00:00
// Clear this to ensure we try their selection.
g_Config . sFailedGPUBackends . clear ( ) ;
2019-02-23 09:49:49 +00:00
g_Config . Save ( " save_graphics_fallback " ) ;
2014-09-27 04:16:56 +00:00
W32Util : : ExitAndRestart ( ) ;
2018-03-24 23:46:11 +00:00
} else {
if ( g_Config . iGPUBackend = = ( int ) GPUBackend : : DIRECT3D9 ) {
// Allow the user to download the DX9 runtime.
LaunchBrowser ( " https://www.microsoft.com/en-us/download/details.aspx?id=34429 " ) ;
}
2014-09-27 04:16:56 +00:00
}
// No safe way out without graphics.
2018-03-24 23:46:11 +00:00
ExitProcess ( 1 ) ;
2013-03-10 22:08:57 +00:00
}
2012-11-01 15:19:01 +00:00
2018-02-07 14:52:19 +00:00
GraphicsContext * graphicsContext = g_graphicsContext ;
if ( ! useEmuThread ) {
NativeInitGraphics ( graphicsContext ) ;
NativeResized ( ) ;
}
2012-11-01 15:19:01 +00:00
2021-08-28 16:14:53 +00:00
DEBUG_LOG ( BOOT , " Done. " ) ;
2012-11-01 15:19:01 +00:00
2013-06-08 00:32:07 +00:00
if ( coreState = = CORE_POWERDOWN ) {
INFO_LOG ( BOOT , " Exit before core loop. " ) ;
goto shutdown ;
}
2018-02-07 14:52:19 +00:00
g_inLoop = true ;
if ( useEmuThread ) {
EmuThreadStart ( graphicsContext ) ;
}
graphicsContext - > ThreadStart ( ) ;
2013-06-08 00:32:07 +00:00
if ( g_Config . bBrowse )
PostMessage ( MainWindow : : GetHWND ( ) , WM_COMMAND , ID_FILE_LOAD , 0 ) ;
2018-02-07 12:11:43 +00:00
Core_EnableStepping ( false ) ;
2013-06-08 00:32:07 +00:00
2018-02-07 14:52:19 +00:00
if ( useEmuThread ) {
while ( emuThreadState ! = ( int ) EmuThreadState : : DISABLED ) {
graphicsContext - > ThreadFrame ( ) ;
if ( GetUIState ( ) = = UISTATE_EXIT ) {
break ;
}
}
} else {
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 ) ;
2021-03-29 02:44:17 +00:00
if ( coreState = = CORE_BOOT_ERROR ) {
break ;
}
2018-02-07 14:52:19 +00:00
}
}
Core_Stop ( ) ;
2018-02-11 00:23:34 +00:00
if ( ! useEmuThread ) {
// Process the shutdown. Without this, non-GL delays 800ms on shutdown.
Core_Run ( g_graphicsContext ) ;
}
2018-02-07 14:52:19 +00:00
Core_WaitInactive ( 800 ) ;
g_inLoop = false ;
if ( useEmuThread ) {
EmuThreadStop ( ) ;
2018-02-11 19:40:11 +00:00
while ( graphicsContext - > ThreadFrame ( ) ) {
2018-02-07 14:52:19 +00:00
// Need to keep eating frames to allow the EmuThread to exit correctly.
2018-02-11 19:40:11 +00:00
continue ;
2018-02-07 14:52:19 +00:00
}
EmuThreadJoin ( ) ;
2013-06-18 07:31:15 +00:00
}
2012-11-01 15:19:01 +00:00
2012-11-05 12:42:33 +00:00
shutdown :
2013-08-15 22:57:04 +00:00
2018-02-07 14:52:19 +00:00
if ( ! useEmuThread ) {
NativeShutdownGraphics ( ) ;
}
g_graphicsContext - > ThreadEnd ( ) ;
g_graphicsContext - > ShutdownFromRenderThread ( ) ;
2017-05-07 08:40:11 +00:00
// NativeShutdown deletes the graphics context through host->ShutdownGraphics().
2013-03-29 17:50:08 +00:00
NativeShutdown ( ) ;
2012-11-01 15:19:01 +00:00
2018-02-07 14:52:19 +00:00
PostMessage ( MainWindow : : GetHWND ( ) , MainWindow : : WM_USER_UPDATE_UI , 0 , 0 ) ;
}