Skip to content

Commit

Permalink
Cleanup the Windows thread stuff to work like the other platforms. No…
Browse files Browse the repository at this point in the history
…t quite perfect yet.
  • Loading branch information
hrydgard committed Feb 7, 2018
1 parent 7f30037 commit ae19c48
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 118 deletions.
6 changes: 2 additions & 4 deletions Core/Core.cpp
Expand Up @@ -189,10 +189,7 @@ void UpdateRunLoop() {
return;
}
NativeUpdate();

if (GetUIState() != UISTATE_EXIT) {
NativeRender(graphicsContext);
}
NativeRender(graphicsContext);
}

void KeepScreenAwake() {
Expand Down Expand Up @@ -268,6 +265,7 @@ void Core_Run(GraphicsContext *ctx) {
if (GetUIState() != UISTATE_INGAME) {
CoreStateProcessed();
if (GetUIState() == UISTATE_EXIT) {
UpdateRunLoop();
return;
}
Core_RunLoop(ctx);
Expand Down
4 changes: 2 additions & 2 deletions SDL/SDLMain.cpp
Expand Up @@ -641,13 +641,13 @@ int main(int argc, char *argv[]) {
useEmuThread = false;
}

SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str());

// Since we render from the main thread, there's nothing done here, but we call it to avoid confusion.
if (!graphicsContext->InitFromRenderThread(&error_message)) {
printf("Init from thread error: '%s'\n", error_message.c_str());
}

SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str());

#ifdef MOBILE_DEVICE
SDL_ShowCursor(SDL_DISABLE);
#endif
Expand Down
207 changes: 111 additions & 96 deletions Windows/EmuThread.cpp
Expand Up @@ -24,101 +24,93 @@
#include "Core/Config.h"
#include "thread/threadutil.h"

static std::mutex emuThreadLock;
static std::thread emuThread;
static std::atomic<int> emuThreadState;
enum class EmuThreadState {
DISABLED,
START_REQUESTED,
RUNNING,
QUIT_REQUESTED,
STOPPED,
};

static std::mutex renderThreadLock;
static std::thread renderThread;
static std::atomic<int> renderThreadReady;
static std::thread emuThread;
static std::atomic<int> emuThreadState((int)EmuThreadState::DISABLED);

static bool useRenderThread;
static bool renderThreadFailed;
static bool renderThreadSucceeded;
static std::thread mainThread;
static bool useEmuThread;
static std::string g_error_message;
static bool g_inLoop;

extern std::vector<std::wstring> GetWideCmdLine();

class GraphicsContext;

static GraphicsContext *g_graphicsContext;

enum EmuThreadStatus : int {
THREAD_NONE = 0,
THREAD_INIT,
THREAD_CORE_LOOP,
THREAD_SHUTDOWN,
THREAD_END,
};
void EmuThreadFunc(GraphicsContext *graphicsContext);
void MainThreadFunc();

void EmuThreadFunc();
void RenderThreadFunc();

// On most other platforms, we let the main thread become the render thread and
// 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<std::mutex> guard(emuThreadLock);
emuThread = std::thread(&EmuThreadFunc);
useRenderThread = separateRenderThread;
if (useRenderThread) {
renderThread = std::thread(&RenderThreadFunc);
}
void MainThread_Start(bool separateEmuThread) {
useEmuThread = separateEmuThread;
mainThread = std::thread(&MainThreadFunc);
}

void EmuThread_Stop() {
void MainThread_Stop() {
// Already stopped?
{
std::lock_guard<std::mutex> 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);
mainThread.join();
}

bool EmuThread_Ready() {
return emuThreadState == THREAD_CORE_LOOP;
bool MainThread_Ready() {
return g_inLoop;
}

void RenderThreadFunc() {
setCurrentThreadName("Render");
renderThreadFailed = false;
renderThreadSucceeded = false;
while (!g_graphicsContext) {
sleep_ms(10);
continue;
}
static void EmuThreadFunc(GraphicsContext *graphicsContext) {
setCurrentThreadName("Emu");

std::string error_message;
if (!g_graphicsContext->InitFromRenderThread(&error_message)) {
g_error_message = error_message;
renderThreadFailed = true;
return;
} else {
renderThreadSucceeded = true;
}
// 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);

g_graphicsContext->ThreadStart();
while (g_graphicsContext->ThreadFrame()) {
continue;
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);
}
g_graphicsContext->ThreadEnd();
g_graphicsContext->ShutdownFromRenderThread();

emuThreadState = (int)EmuThreadState::STOPPED;

NativeShutdownGraphics();
}

void EmuThreadFunc() {
emuThreadState = THREAD_INIT;
static void EmuThreadStart(GraphicsContext *graphicsContext) {
emuThreadState = (int)EmuThreadState::START_REQUESTED;
emuThread = std::thread(&EmuThreadFunc, graphicsContext);
}

setCurrentThreadName("Emu");
static void EmuThreadStop() {
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
}

static void EmuThreadJoin() {
emuThread.join();
emuThread = std::thread();
ILOG("EmuThreadJoin - joined");
}

void MainThreadFunc() {
if (useEmuThread) {
// We'll start up a separate thread we'll call Emu
setCurrentThreadName("Render");
} else {
// This is both Emu and Render.
setCurrentThreadName("Emu");
}

host = new WindowsHost(MainWindow::GetHInstance(), MainWindow::GetHWND(), MainWindow::GetDisplayHWND());
host->SetWindowTitle(nullptr);
Expand All @@ -142,18 +134,8 @@ void EmuThreadFunc() {
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;
}
}
// Main thread is the render thread.
success = g_graphicsContext->InitFromRenderThread(&error_string);
}

if (!success) {
Expand Down Expand Up @@ -205,8 +187,12 @@ void EmuThreadFunc() {
exit(1);
}

NativeInitGraphics(g_graphicsContext);
NativeResized();
GraphicsContext *graphicsContext = g_graphicsContext;

if (!useEmuThread) {
NativeInitGraphics(graphicsContext);
NativeResized();
}

INFO_LOG(BOOT, "Done.");
_dbg_update_();
Expand All @@ -216,30 +202,59 @@ void EmuThreadFunc() {
goto shutdown;
}

emuThreadState = THREAD_CORE_LOOP;
g_inLoop = true;

if (useEmuThread) {
EmuThreadStart(graphicsContext);
}
graphicsContext->ThreadStart();

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);
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);
}
}
Core_Stop();
Core_WaitInactive(800);

g_inLoop = false;

if (useEmuThread) {
EmuThreadStop();
while (emuThreadState != (int)EmuThreadState::STOPPED) {
// Need to keep eating frames to allow the EmuThread to exit correctly.
graphicsContext->ThreadFrame();
}
EmuThreadJoin();
}

shutdown:
emuThreadState = THREAD_SHUTDOWN;

NativeShutdownGraphics();
if (!useRenderThread)
g_graphicsContext->ShutdownFromRenderThread();
if (!useEmuThread) {
NativeShutdownGraphics();
}

g_graphicsContext->ThreadEnd();
g_graphicsContext->ShutdownFromRenderThread();

// NativeShutdown deletes the graphics context through host->ShutdownGraphics().
NativeShutdown();
emuThreadState = THREAD_END;
}

PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_UPDATE_UI, 0, 0);
}
6 changes: 3 additions & 3 deletions Windows/EmuThread.h
Expand Up @@ -17,6 +17,6 @@

#pragma once

void EmuThread_Start(bool separateRenderThread);
void EmuThread_Stop();
bool EmuThread_Ready();
void MainThread_Start(bool separateRenderThread);
void MainThread_Stop();
bool MainThread_Ready();
2 changes: 0 additions & 2 deletions Windows/GPU/WindowsGLContext.cpp
Expand Up @@ -386,8 +386,6 @@ void WindowsGLContext::SwapInterval(int interval) {
}

void WindowsGLContext::Shutdown() {
if (renderManager_)
renderManager_->StopThread();
glslang::FinalizeProcess();
}

Expand Down
11 changes: 5 additions & 6 deletions Windows/MainWindow.cpp
Expand Up @@ -795,7 +795,7 @@ namespace MainWindow

case WM_COMMAND:
{
if (!EmuThread_Ready())
if (!MainThread_Ready())
return DefWindowProc(hWnd, message, wParam, lParam);

MainWindowMenu_Process(hWnd, wParam);
Expand Down Expand Up @@ -844,7 +844,7 @@ namespace MainWindow

case WM_DROPFILES:
{
if (!EmuThread_Ready())
if (!MainThread_Ready())
return DefWindowProc(hWnd, message, wParam, lParam);

HDROP hdrop = (HDROP)wParam;
Expand All @@ -866,9 +866,8 @@ namespace MainWindow

case WM_CLOSE:
InputDevice::StopPolling();
EmuThread_Stop();
MainThread_Stop();
WindowsRawInput::Shutdown();

return DefWindowProc(hWnd,message,wParam,lParam);

case WM_DESTROY:
Expand Down Expand Up @@ -914,10 +913,10 @@ namespace MainWindow
case WM_USER_RESTART_EMUTHREAD:
NativeSetRestarting();
InputDevice::StopPolling();
EmuThread_Stop();
MainThread_Stop();
coreState = CORE_POWERUP;
ResetUIState();
EmuThread_Start(false);
MainThread_Start(false);
InputDevice::BeginPolling();
break;

Expand Down
5 changes: 1 addition & 4 deletions Windows/main.cpp
Expand Up @@ -532,7 +532,7 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
// Emu thread (and render thread, if any) is always running!
// Only OpenGL uses an externally managed render thread (due to GL's single-threaded context design). Vulkan
// manages its own render thread.
EmuThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL);
MainThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL);
InputDevice::BeginPolling();

HACCEL hAccelTable = LoadAccelerators(_hInstance, (LPCTSTR)IDR_ACCELS);
Expand Down Expand Up @@ -586,9 +586,6 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin

InputDevice::StopPolling();

// The emuthread calls NativeShutdown when shutting down.
EmuThread_Stop();

This comment has been minimized.

Copy link
@unknownbrackets

unknownbrackets Feb 10, 2018

Collaborator

Why don't we want to stop the thread when we get a WM_QUIT?

-[Unknown]

This comment has been minimized.

Copy link
@hrydgard

hrydgard Feb 10, 2018

Author Owner

The idea was to stop the thread from main after exiting the message loop instead. This avoids calling EmuThread_Stop twice in some cases. Unfortunately I forgot to fully implement that :( Fixing.


MainWindow::DestroyDebugWindows();
DialogManager::DestroyAll();
timeEndPeriod(1);
Expand Down
2 changes: 1 addition & 1 deletion android/jni/app-android.cpp
Expand Up @@ -217,7 +217,7 @@ static void EmuThreadStop() {
static void EmuThreadJoin() {
emuThread.join();
emuThread = std::thread();
ILOG("EmuThreadStop - joined.");
ILOG("EmuThreadJoin - joined");
}

static void ProcessFrameCommands(JNIEnv *env);
Expand Down

0 comments on commit ae19c48

Please sign in to comment.