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