CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Windows/EmuThread.cpp
Views: 1401
1
#include <mutex>
2
#include <atomic>
3
#include <thread>
4
5
#include "Common/System/NativeApp.h"
6
#include "Common/System/System.h"
7
#include "Common/System/Request.h"
8
#include "Common/Data/Text/I18n.h"
9
#include "Common/Input/InputState.h"
10
#include "Common/Data/Encoding/Utf8.h"
11
#include "Common/Log.h"
12
#include "Common/StringUtils.h"
13
#include "Common/GraphicsContext.h"
14
#include "Common/TimeUtil.h"
15
#include "Common/Thread/ThreadUtil.h"
16
17
#include "Windows/EmuThread.h"
18
#include "Windows/W32Util/Misc.h"
19
#include "Windows/MainWindow.h"
20
#include "Windows/resource.h"
21
#include "Windows/WindowsHost.h"
22
#include "Core/Reporting.h"
23
#include "Core/MemMap.h"
24
#include "Core/Core.h"
25
#include "Core/System.h"
26
#include "Core/Config.h"
27
#include "Core/ConfigValues.h"
28
29
#if PPSSPP_API(ANY_GL)
30
#include "Windows/GPU/WindowsGLContext.h"
31
#endif
32
#include "Windows/GPU/WindowsVulkanContext.h"
33
#include "Windows/GPU/D3D9Context.h"
34
#include "Windows/GPU/D3D11Context.h"
35
36
enum class EmuThreadState {
37
DISABLED,
38
START_REQUESTED,
39
RUNNING,
40
QUIT_REQUESTED,
41
STOPPED,
42
};
43
44
static std::thread emuThread;
45
static std::atomic<int> emuThreadState((int)EmuThreadState::DISABLED);
46
47
static std::thread mainThread;
48
static bool useEmuThread;
49
static std::string g_error_message;
50
static bool g_inLoop;
51
52
extern std::vector<std::wstring> GetWideCmdLine();
53
54
class GraphicsContext;
55
static GraphicsContext *g_graphicsContext;
56
57
void MainThreadFunc();
58
59
// On most other platforms, we let the "main" thread become the render thread and
60
// start a separate emu thread from that, if needed. Should probably switch to that
61
// to make it the same on all platforms.
62
void MainThread_Start(bool separateEmuThread) {
63
useEmuThread = separateEmuThread;
64
mainThread = std::thread(&MainThreadFunc);
65
}
66
67
void MainThread_Stop() {
68
// Already stopped?
69
UpdateUIState(UISTATE_EXIT);
70
Core_Stop();
71
mainThread.join();
72
}
73
74
bool MainThread_Ready() {
75
return g_inLoop;
76
}
77
78
static void EmuThreadFunc(GraphicsContext *graphicsContext) {
79
SetCurrentThreadName("Emu");
80
81
// There's no real requirement that NativeInit happen on this thread.
82
// We just call the update/render loop here.
83
emuThreadState = (int)EmuThreadState::RUNNING;
84
85
NativeInitGraphics(graphicsContext);
86
87
while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
88
// We're here again, so the game quit. Restart Core_Run() which controls the UI.
89
// This way they can load a new game.
90
if (!Core_IsActive())
91
UpdateUIState(UISTATE_MENU);
92
if (!Core_Run(g_graphicsContext)) {
93
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
94
}
95
}
96
97
emuThreadState = (int)EmuThreadState::STOPPED;
98
99
NativeShutdownGraphics();
100
101
// Ask the main thread to stop. This prevents a hang on a race condition.
102
graphicsContext->StopThread();
103
}
104
105
static void EmuThreadStart(GraphicsContext *graphicsContext) {
106
emuThreadState = (int)EmuThreadState::START_REQUESTED;
107
emuThread = std::thread(&EmuThreadFunc, graphicsContext);
108
}
109
110
static void EmuThreadStop() {
111
if (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED &&
112
emuThreadState != (int)EmuThreadState::STOPPED) {
113
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
114
}
115
}
116
117
static void EmuThreadJoin() {
118
emuThread.join();
119
INFO_LOG(Log::System, "EmuThreadJoin - joined");
120
}
121
122
bool CreateGraphicsBackend(std::string *error_message, GraphicsContext **ctx) {
123
WindowsGraphicsContext *graphicsContext = nullptr;
124
switch (g_Config.iGPUBackend) {
125
#if PPSSPP_API(ANY_GL)
126
case (int)GPUBackend::OPENGL:
127
graphicsContext = new WindowsGLContext();
128
break;
129
#endif
130
case (int)GPUBackend::DIRECT3D9:
131
graphicsContext = new D3D9Context();
132
break;
133
case (int)GPUBackend::DIRECT3D11:
134
graphicsContext = new D3D11Context();
135
break;
136
case (int)GPUBackend::VULKAN:
137
graphicsContext = new WindowsVulkanContext();
138
break;
139
default:
140
return false;
141
}
142
143
if (graphicsContext->Init(MainWindow::GetHInstance(), MainWindow::GetDisplayHWND(), error_message)) {
144
*ctx = graphicsContext;
145
return true;
146
} else {
147
delete graphicsContext;
148
*ctx = nullptr;
149
return false;
150
}
151
}
152
153
void MainThreadFunc() {
154
// We'll start up a separate thread we'll call Emu
155
SetCurrentThreadName(useEmuThread ? "Render" : "Emu");
156
157
SetConsolePosition();
158
159
System_SetWindowTitle("");
160
161
// We convert command line arguments to UTF-8 immediately.
162
std::vector<std::wstring> wideArgs = GetWideCmdLine();
163
std::vector<std::string> argsUTF8;
164
for (auto& string : wideArgs) {
165
argsUTF8.push_back(ConvertWStringToUTF8(string));
166
}
167
std::vector<const char *> args;
168
for (auto& string : argsUTF8) {
169
args.push_back(string.c_str());
170
}
171
bool performingRestart = NativeIsRestarting();
172
NativeInit(static_cast<int>(args.size()), &args[0], "", "", nullptr);
173
174
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
175
if (!useEmuThread) {
176
// Okay, we must've switched to OpenGL. Let's flip the emu thread on.
177
useEmuThread = true;
178
SetCurrentThreadName("Render");
179
}
180
} else if (useEmuThread) {
181
// We must've failed over from OpenGL, flip the emu thread off.
182
useEmuThread = false;
183
SetCurrentThreadName("Emu");
184
}
185
186
if (g_Config.sFailedGPUBackends.find("ALL") != std::string::npos) {
187
Reporting::ReportMessage("Graphics init error: %s", "ALL");
188
189
auto err = GetI18NCategory(I18NCat::ERRORS);
190
const char *defaultErrorAll = "PPSSPP failed to startup with any graphics backend. Try upgrading your graphics and other drivers.";
191
std::string_view genericError = err->T("GenericAllStartupError", defaultErrorAll);
192
std::wstring title = ConvertUTF8ToWString(err->T("GenericGraphicsError", "Graphics Error"));
193
MessageBox(0, ConvertUTF8ToWString(genericError).c_str(), title.c_str(), MB_OK);
194
195
// Let's continue (and probably crash) just so they have a way to keep trying.
196
}
197
198
System_Notify(SystemNotification::UI);
199
200
std::string error_string;
201
bool success = CreateGraphicsBackend(&error_string, &g_graphicsContext);
202
203
if (success) {
204
// Main thread is the render thread.
205
success = g_graphicsContext->InitFromRenderThread(&error_string);
206
}
207
208
if (!success) {
209
// Before anything: are we restarting right now?
210
if (performingRestart) {
211
// Okay, switching graphics didn't work out. Probably a driver bug - fallback to restart.
212
// This happens on NVIDIA when switching OpenGL -> Vulkan.
213
g_Config.Save("switch_graphics_failed");
214
W32Util::ExitAndRestart();
215
}
216
217
auto err = GetI18NCategory(I18NCat::ERRORS);
218
Reporting::ReportMessage("Graphics init error: %s", error_string.c_str());
219
220
const char *defaultErrorVulkan = "Failed initializing graphics. Try upgrading your graphics drivers.\n\nWould you like to try switching to OpenGL?\n\nError message:";
221
const char *defaultErrorOpenGL = "Failed initializing graphics. Try upgrading your graphics drivers.\n\nWould you like to try switching to DirectX 9?\n\nError message:";
222
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:";
223
std::string_view genericError;
224
GPUBackend nextBackend = GPUBackend::DIRECT3D9;
225
switch (g_Config.iGPUBackend) {
226
case (int)GPUBackend::DIRECT3D9:
227
nextBackend = GPUBackend::OPENGL;
228
genericError = err->T("GenericDirect3D9Error", defaultErrorDirect3D9);
229
break;
230
case (int)GPUBackend::VULKAN:
231
nextBackend = GPUBackend::OPENGL;
232
genericError = err->T("GenericVulkanError", defaultErrorVulkan);
233
break;
234
case (int)GPUBackend::OPENGL:
235
default:
236
nextBackend = GPUBackend::DIRECT3D9;
237
genericError = err->T("GenericOpenGLError", defaultErrorOpenGL);
238
break;
239
}
240
std::string full_error = StringFromFormat("%.*s\n\n%s", (int)genericError.size(), genericError.data(), error_string.c_str());
241
std::wstring title = ConvertUTF8ToWString(err->T("GenericGraphicsError", "Graphics Error"));
242
bool yes = IDYES == MessageBox(0, ConvertUTF8ToWString(full_error).c_str(), title.c_str(), MB_ICONERROR | MB_YESNO);
243
ERROR_LOG(Log::Boot, "%s", full_error.c_str());
244
245
if (yes) {
246
// Change the config to the alternative and restart.
247
g_Config.iGPUBackend = (int)nextBackend;
248
// Clear this to ensure we try their selection.
249
g_Config.sFailedGPUBackends.clear();
250
g_Config.Save("save_graphics_fallback");
251
252
W32Util::ExitAndRestart();
253
} else {
254
if (g_Config.iGPUBackend == (int)GPUBackend::DIRECT3D9) {
255
// Allow the user to download the DX9 runtime.
256
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.microsoft.com/en-us/download/details.aspx?id=34429");
257
}
258
}
259
260
// No safe way out without graphics.
261
ExitProcess(1);
262
}
263
264
GraphicsContext *graphicsContext = g_graphicsContext;
265
266
if (!useEmuThread) {
267
NativeInitGraphics(graphicsContext);
268
NativeResized();
269
}
270
271
DEBUG_LOG(Log::Boot, "Done.");
272
273
if (coreState == CORE_POWERDOWN) {
274
INFO_LOG(Log::Boot, "Exit before core loop.");
275
goto shutdown;
276
}
277
278
g_inLoop = true;
279
280
if (useEmuThread) {
281
EmuThreadStart(graphicsContext);
282
}
283
graphicsContext->ThreadStart();
284
285
if (g_Config.bBrowse)
286
PostMessage(MainWindow::GetHWND(), WM_COMMAND, ID_FILE_LOAD, 0);
287
288
Core_EnableStepping(false);
289
290
if (useEmuThread) {
291
while (emuThreadState != (int)EmuThreadState::DISABLED) {
292
graphicsContext->ThreadFrame();
293
if (GetUIState() == UISTATE_EXIT) {
294
break;
295
}
296
}
297
} else {
298
while (GetUIState() != UISTATE_EXIT) {
299
// We're here again, so the game quit. Restart Core_Run() which controls the UI.
300
// This way they can load a new game.
301
if (!Core_IsActive())
302
UpdateUIState(UISTATE_MENU);
303
Core_Run(g_graphicsContext);
304
if (coreState == CORE_BOOT_ERROR) {
305
break;
306
}
307
}
308
}
309
Core_Stop();
310
if (!useEmuThread) {
311
// Process the shutdown. Without this, non-GL delays 800ms on shutdown.
312
Core_Run(g_graphicsContext);
313
}
314
Core_WaitInactive(800);
315
316
g_inLoop = false;
317
318
if (useEmuThread) {
319
EmuThreadStop();
320
while (graphicsContext->ThreadFrame()) {
321
// Need to keep eating frames to allow the EmuThread to exit correctly.
322
continue;
323
}
324
EmuThreadJoin();
325
}
326
327
shutdown:
328
329
if (!useEmuThread) {
330
NativeShutdownGraphics();
331
}
332
333
g_graphicsContext->ThreadEnd();
334
g_graphicsContext->ShutdownFromRenderThread();
335
336
g_graphicsContext->Shutdown();
337
338
delete g_graphicsContext;
339
340
UpdateConsolePosition();
341
NativeShutdown();
342
343
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_UPDATE_UI, 0, 0);
344
}
345
346