Android: Fix emuthread management to exit cleanly without hanging. Helps with task switching on Android.

This commit is contained in:
Henrik Rydgård 2018-02-07 13:11:43 +01:00
parent b3a09791b1
commit 7f30037e45
8 changed files with 72 additions and 17 deletions

View file

@ -54,6 +54,9 @@ enum EmuThreadStatus : int {
void EmuThreadFunc(); void EmuThreadFunc();
void RenderThreadFunc(); 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) { void EmuThread_Start(bool separateRenderThread) {
std::lock_guard<std::mutex> guard(emuThreadLock); std::lock_guard<std::mutex> guard(emuThreadLock);
emuThread = std::thread(&EmuThreadFunc); emuThread = std::thread(&EmuThreadFunc);
@ -218,7 +221,7 @@ void EmuThreadFunc() {
if (g_Config.bBrowse) if (g_Config.bBrowse)
PostMessage(MainWindow::GetHWND(), WM_COMMAND, ID_FILE_LOAD, 0); PostMessage(MainWindow::GetHWND(), WM_COMMAND, ID_FILE_LOAD, 0);
Core_EnableStepping(FALSE); Core_EnableStepping(false);
while (GetUIState() != UISTATE_EXIT) { while (GetUIState() != UISTATE_EXIT) {
// We're here again, so the game quit. Restart Core_Run() which controls the UI. // We're here again, so the game quit. Restart Core_Run() which controls the UI.

View file

@ -14,6 +14,9 @@ public:
Draw::DrawContext *GetDrawContext() override { Draw::DrawContext *GetDrawContext() override {
return draw_; return draw_;
} }
bool Initialized() override {
return draw_ != nullptr;
}
private: private:
Draw::DrawContext *draw_; Draw::DrawContext *draw_;

View file

@ -22,4 +22,5 @@ public:
// This is different than the base class function since on // This is different than the base class function since on
// Android (EGL, Vulkan) we do have all this info on the render thread. // Android (EGL, Vulkan) we do have all this info on the render thread.
virtual bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) = 0; virtual bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) = 0;
virtual bool Initialized() = 0;
}; };

View file

@ -20,6 +20,7 @@ bool AndroidJavaEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int
void AndroidJavaEGLGraphicsContext::ShutdownFromRenderThread() { void AndroidJavaEGLGraphicsContext::ShutdownFromRenderThread() {
ILOG("AndroidJavaEGLGraphicsContext::Shutdown"); ILOG("AndroidJavaEGLGraphicsContext::Shutdown");
renderManager_->WaitUntilQueueIdle();
renderManager_ = nullptr; // owned by draw_. renderManager_ = nullptr; // owned by draw_.
delete draw_; delete draw_;
draw_ = nullptr; draw_ = nullptr;

View file

@ -15,6 +15,10 @@ public:
delete draw_; delete draw_;
} }
bool Initialized() override {
return draw_ != nullptr;
}
// This performs the actual initialization, // This performs the actual initialization,
bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override; bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override;

View file

@ -22,6 +22,10 @@ public:
Draw::DrawContext *GetDrawContext() override { Draw::DrawContext *GetDrawContext() override {
return draw_; return draw_;
} }
bool Initialized() override {
return draw_ != nullptr;
}
private: private:
VulkanContext *g_Vulkan = nullptr; VulkanContext *g_Vulkan = nullptr;
Draw::DrawContext *draw_ = nullptr; Draw::DrawContext *draw_ = nullptr;

View file

@ -171,6 +171,20 @@ static void EmuThreadFunc() {
gJvm->AttachCurrentThread(&env, nullptr); gJvm->AttachCurrentThread(&env, nullptr);
setCurrentThreadName("Emu"); setCurrentThreadName("Emu");
ILOG("Entering emu thread");
// Wait for render loop to get started.
if (!graphicsContext || !graphicsContext->Initialized()) {
ILOG("Runloop: Waiting for displayInit...");
while (!graphicsContext || !graphicsContext->Initialized()) {
sleep_ms(20);
}
} else {
ILOG("Runloop: Graphics context available! %p", graphicsContext);
}
NativeInitGraphics(graphicsContext);
ILOG("Graphics initialized. Entering loop.");
// There's no real requirement that NativeInit happen on this thread. // There's no real requirement that NativeInit happen on this thread.
// We just call the update/render loop here. // We just call the update/render loop here.
@ -179,18 +193,31 @@ static void EmuThreadFunc() {
UpdateRunLoopAndroid(env); UpdateRunLoopAndroid(env);
} }
emuThreadState = (int)EmuThreadState::STOPPED; emuThreadState = (int)EmuThreadState::STOPPED;
NativeShutdownGraphics();
gJvm->DetachCurrentThread(); gJvm->DetachCurrentThread();
ILOG("Leaving emu thread");
} }
static void EmuThreadStart(JNIEnv *env) { static void EmuThreadStart() {
ILOG("EmuThreadStart");
emuThreadState = (int)EmuThreadState::START_REQUESTED; emuThreadState = (int)EmuThreadState::START_REQUESTED;
emuThread = std::thread(&EmuThreadFunc); emuThread = std::thread(&EmuThreadFunc);
} }
// Call EmuThreadStop first, then keep running the GPU (or eat commands)
// as long as emuThreadState isn't STOPPED and/or there are still things queued up.
// Only after that, call EmuThreadJoin.
static void EmuThreadStop() { static void EmuThreadStop() {
ILOG("EmuThreadStop - stopping...");
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED; emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
}
static void EmuThreadJoin() {
emuThread.join(); emuThread.join();
emuThread = std::thread(); emuThread = std::thread();
ILOG("EmuThreadStop - joined.");
} }
static void ProcessFrameCommands(JNIEnv *env); static void ProcessFrameCommands(JNIEnv *env);
@ -429,9 +456,8 @@ retry:
if (useCPUThread) { if (useCPUThread) {
ILOG("NativeApp.init() - launching emu thread"); ILOG("NativeApp.init() - launching emu thread");
EmuThreadStart(env); EmuThreadStart();
} }
ILOG("NativeApp.init() -- end");
} }
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioInit(JNIEnv *, jclass) { extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioInit(JNIEnv *, jclass) {
@ -480,8 +506,13 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_pause(JNIEnv *, jclass) {
} }
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) { extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) {
if (useCPUThread) if (useCPUThread && graphicsContext) {
EmuThreadStop(); EmuThreadStop();
while (emuThreadState != (int)EmuThreadState::STOPPED) {
graphicsContext->ThreadFrame();
}
EmuThreadJoin();
}
ILOG("NativeApp.shutdown() -- begin"); ILOG("NativeApp.shutdown() -- begin");
if (renderer_inited) { if (renderer_inited) {
@ -507,11 +538,28 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env,
// We should be running on the render thread here. // We should be running on the render thread here.
std::string errorMessage; std::string errorMessage;
if (renderer_inited) { if (renderer_inited) {
// Would be really nice if we could get something on the GL thread immediately when shutting down...
ILOG("NativeApp.displayInit() restoring"); ILOG("NativeApp.displayInit() restoring");
graphicsContext->ThreadEnd(); graphicsContext->ThreadEnd();
if (useCPUThread) {
EmuThreadStop();
while (emuThreadState != (int)EmuThreadState::STOPPED) {
graphicsContext->ThreadFrame();
}
EmuThreadJoin();
} else {
NativeShutdownGraphics();
}
graphicsContext->ShutdownFromRenderThread(); graphicsContext->ShutdownFromRenderThread();
ILOG("Shut down both threads. Now let's bring it up again!");
graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0); graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0);
if (useCPUThread) {
EmuThreadStart();
} else {
NativeInitGraphics(graphicsContext);
}
graphicsContext->ThreadStart(); graphicsContext->ThreadStart();
ILOG("Restored."); ILOG("Restored.");
} else { } else {
@ -564,15 +612,6 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv
} }
void UpdateRunLoopAndroid(JNIEnv *env) { void UpdateRunLoopAndroid(JNIEnv *env) {
// Wait for render loop to get started.
if (!renderer_inited) {
ILOG("Runloop: Waiting for displayInit");
while (!renderer_inited) {
sleep_ms(20);
}
NativeInitGraphics(graphicsContext);
}
NativeUpdate(); NativeUpdate();
NativeRender(graphicsContext); NativeRender(graphicsContext);

View file

@ -112,7 +112,7 @@ PrioritizedWorkQueueItem *PrioritizedWorkQueue::Pop() {
static std::thread *workThread; static std::thread *workThread;
static void threadfunc(PrioritizedWorkQueue *wq) { static void threadfunc(PrioritizedWorkQueue *wq) {
setCurrentThreadName("PrioritizedWorkQueue"); setCurrentThreadName("PrioQueue");
while (true) { while (true) {
PrioritizedWorkQueueItem *item = wq->Pop(); PrioritizedWorkQueueItem *item = wq->Pop();
if (!item) { if (!item) {
@ -126,7 +126,7 @@ static void threadfunc(PrioritizedWorkQueue *wq) {
} }
void ProcessWorkQueueOnThreadWhile(PrioritizedWorkQueue *wq) { void ProcessWorkQueueOnThreadWhile(PrioritizedWorkQueue *wq) {
workThread = new std::thread(std::bind(&threadfunc, wq)); workThread = new std::thread([=](){threadfunc(wq);});
} }
void StopProcessingWorkQueue(PrioritizedWorkQueue *wq) { void StopProcessingWorkQueue(PrioritizedWorkQueue *wq) {
@ -135,5 +135,5 @@ void StopProcessingWorkQueue(PrioritizedWorkQueue *wq) {
workThread->join(); workThread->join();
delete workThread; delete workThread;
} }
workThread = 0; workThread = nullptr;
} }