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 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) {
std::lock_guard<std::mutex> guard(emuThreadLock);
emuThread = std::thread(&EmuThreadFunc);
@ -218,7 +221,7 @@ void EmuThreadFunc() {
if (g_Config.bBrowse)
PostMessage(MainWindow::GetHWND(), WM_COMMAND, ID_FILE_LOAD, 0);
Core_EnableStepping(FALSE);
Core_EnableStepping(false);
while (GetUIState() != UISTATE_EXIT) {
// 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 {
return draw_;
}
bool Initialized() override {
return draw_ != nullptr;
}
private:
Draw::DrawContext *draw_;

View File

@ -22,4 +22,5 @@ public:
// This is different than the base class function since on
// 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 Initialized() = 0;
};

View File

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

View File

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

View File

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

View File

@ -171,6 +171,20 @@ static void EmuThreadFunc() {
gJvm->AttachCurrentThread(&env, nullptr);
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.
// We just call the update/render loop here.
@ -179,18 +193,31 @@ static void EmuThreadFunc() {
UpdateRunLoopAndroid(env);
}
emuThreadState = (int)EmuThreadState::STOPPED;
NativeShutdownGraphics();
gJvm->DetachCurrentThread();
ILOG("Leaving emu thread");
}
static void EmuThreadStart(JNIEnv *env) {
static void EmuThreadStart() {
ILOG("EmuThreadStart");
emuThreadState = (int)EmuThreadState::START_REQUESTED;
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() {
ILOG("EmuThreadStop - stopping...");
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
}
static void EmuThreadJoin() {
emuThread.join();
emuThread = std::thread();
ILOG("EmuThreadStop - joined.");
}
static void ProcessFrameCommands(JNIEnv *env);
@ -429,9 +456,8 @@ retry:
if (useCPUThread) {
ILOG("NativeApp.init() - launching emu thread");
EmuThreadStart(env);
EmuThreadStart();
}
ILOG("NativeApp.init() -- end");
}
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) {
if (useCPUThread)
if (useCPUThread && graphicsContext) {
EmuThreadStop();
while (emuThreadState != (int)EmuThreadState::STOPPED) {
graphicsContext->ThreadFrame();
}
EmuThreadJoin();
}
ILOG("NativeApp.shutdown() -- begin");
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.
std::string errorMessage;
if (renderer_inited) {
// Would be really nice if we could get something on the GL thread immediately when shutting down...
ILOG("NativeApp.displayInit() restoring");
graphicsContext->ThreadEnd();
if (useCPUThread) {
EmuThreadStop();
while (emuThreadState != (int)EmuThreadState::STOPPED) {
graphicsContext->ThreadFrame();
}
EmuThreadJoin();
} else {
NativeShutdownGraphics();
}
graphicsContext->ShutdownFromRenderThread();
ILOG("Shut down both threads. Now let's bring it up again!");
graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0);
if (useCPUThread) {
EmuThreadStart();
} else {
NativeInitGraphics(graphicsContext);
}
graphicsContext->ThreadStart();
ILOG("Restored.");
} else {
@ -564,15 +612,6 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv
}
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();
NativeRender(graphicsContext);

View File

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