diff --git a/android/jni/AndroidEGLContext.cpp b/android/jni/AndroidEGLContext.cpp index 8742085535..c9b5d81251 100644 --- a/android/jni/AndroidEGLContext.cpp +++ b/android/jni/AndroidEGLContext.cpp @@ -6,13 +6,18 @@ #include "GL/GLInterface/EGLAndroid.h" #include "Core/System.h" -bool AndroidEGLGraphicsContext::Init(ANativeWindow *wnd, int backbufferWidth, int backbufferHeight, int backbufferFormat, int androidVersion) { +bool AndroidEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, + int desiredBackbufferSizeY, + int backbufferFormat, int androidVersion) { + ILOG("AndroidEGLGraphicsContext::Init()"); wnd_ = wnd; gl = HostGL_CreateGLInterface(); if (!gl) { ELOG("ERROR: Failed to create GL interface"); return false; } + int backbufferWidth = desiredBackbufferSizeX; + int backbufferHeight = desiredBackbufferSizeY; ILOG("EGL interface created. Desired backbuffer size: %dx%d", backbufferWidth, backbufferHeight); // Apparently we still have to set this through Java through setFixedSize on the bufferHolder for it to take effect... diff --git a/android/jni/AndroidEGLContext.h b/android/jni/AndroidEGLContext.h index efeafecf80..fd86bc6cfe 100644 --- a/android/jni/AndroidEGLContext.h +++ b/android/jni/AndroidEGLContext.h @@ -6,7 +6,7 @@ class AndroidEGLGraphicsContext : public AndroidGraphicsContext { public: AndroidEGLGraphicsContext() : draw_(nullptr), wnd_(nullptr), gl(nullptr) {} - bool Init(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override; + bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override; void Shutdown() override; void SwapBuffers() override; void SwapInterval(int interval) override {} diff --git a/android/jni/AndroidGraphicsContext.h b/android/jni/AndroidGraphicsContext.h index 027be92505..72279b7d6d 100644 --- a/android/jni/AndroidGraphicsContext.h +++ b/android/jni/AndroidGraphicsContext.h @@ -19,5 +19,7 @@ enum { class AndroidGraphicsContext : public GraphicsContext { public: - virtual bool Init(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) = 0; + // 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) {} }; diff --git a/android/jni/AndroidJavaGLContext.cpp b/android/jni/AndroidJavaGLContext.cpp index 8a97a62187..70dc543a4c 100644 --- a/android/jni/AndroidJavaGLContext.cpp +++ b/android/jni/AndroidJavaGLContext.cpp @@ -4,16 +4,25 @@ #include "Core/System.h" AndroidJavaEGLGraphicsContext::AndroidJavaEGLGraphicsContext() { + SetGPUBackend(GPUBackend::OPENGL); +} + +bool AndroidJavaEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) { + ILOG("AndroidJavaEGLGraphicsContext::InitFromRenderThread"); CheckGLExtensions(); draw_ = Draw::T3DCreateGLContext(); - SetGPUBackend(GPUBackend::OPENGL); + renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); bool success = draw_->CreatePresets(); - assert(success); + return success; +} + +void AndroidJavaEGLGraphicsContext::ShutdownFromRenderThread() { + ILOG("AndroidJavaEGLGraphicsContext::Shutdown"); + renderManager_ = nullptr; // owned by draw_. + delete draw_; + draw_ = nullptr; } void AndroidJavaEGLGraphicsContext::Shutdown() { - ILOG("AndroidJavaEGLGraphicsContext::Shutdown"); - delete draw_; - draw_ = nullptr; - NativeShutdownGraphics(); + // TODO } diff --git a/android/jni/AndroidJavaGLContext.h b/android/jni/AndroidJavaGLContext.h index 36e443e51d..c6e11910e7 100644 --- a/android/jni/AndroidJavaGLContext.h +++ b/android/jni/AndroidJavaGLContext.h @@ -1,14 +1,25 @@ #pragma once +#include +#include +#include + #include "AndroidGraphicsContext.h" +#include "thin3d/GLRenderManager.h" // Doesn't do much. Just to fit in. -class AndroidJavaEGLGraphicsContext : public GraphicsContext { +class AndroidJavaEGLGraphicsContext : public AndroidGraphicsContext { public: AndroidJavaEGLGraphicsContext(); ~AndroidJavaEGLGraphicsContext() { delete draw_; } + + // This performs the actual initialization, + bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override; + + void ShutdownFromRenderThread() override; + void Shutdown() override; void SwapBuffers() override {} void SwapInterval(int interval) override {} @@ -16,7 +27,21 @@ public: Draw::DrawContext *GetDrawContext() override { return draw_; } + + void ThreadStart() override { + renderManager_->ThreadStart(); + } + + bool ThreadFrame() override { + return renderManager_->ThreadFrame(); + } + + void ThreadEnd() override { + renderManager_->ThreadEnd(); + } + private: - Draw::DrawContext *draw_; + Draw::DrawContext *draw_ = nullptr; + GLRenderManager *renderManager_ = nullptr; }; diff --git a/android/jni/AndroidVulkanContext.cpp b/android/jni/AndroidVulkanContext.cpp index 99b21469be..29ad3f3103 100644 --- a/android/jni/AndroidVulkanContext.cpp +++ b/android/jni/AndroidVulkanContext.cpp @@ -58,7 +58,8 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL Vulkan_Dbg(VkDebugReportFlagsEXT msgFlags, loglevel = ANDROID_LOG_WARN; } - __android_log_print(loglevel, APP_NAME, "[%s] %s Code %d : %s", pLayerPrefix, ObjTypeToString(objType), msgCode, pMsg); + __android_log_print(loglevel, APP_NAME, "[%s] %s Code %d : %s", + pLayerPrefix, ObjTypeToString(objType), msgCode, pMsg); // false indicates that layer should not bail-out of an // API call that had validation failures. This may mean that the @@ -73,7 +74,9 @@ AndroidVulkanContext::~AndroidVulkanContext() { g_Vulkan = nullptr; } -bool AndroidVulkanContext::Init(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) { +bool AndroidVulkanContext::InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, + int desiredBackbufferSizeY, int backbufferFormat, + int androidVersion) { ILOG("AndroidVulkanContext::Init"); init_glslang(); diff --git a/android/jni/AndroidVulkanContext.h b/android/jni/AndroidVulkanContext.h index 8c48e09c92..e43041d3dc 100644 --- a/android/jni/AndroidVulkanContext.h +++ b/android/jni/AndroidVulkanContext.h @@ -11,7 +11,7 @@ public: AndroidVulkanContext() : draw_(nullptr) {} ~AndroidVulkanContext(); - bool Init(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override; + bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override; void Shutdown() override; void SwapInterval(int interval) override; void SwapBuffers() override; diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 97ce69edc4..819d88468c 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,21 @@ JNIEnv *jniEnvMain; JNIEnv *jniEnvGraphics; JavaVM *javaVM; +bool useCPUThread = true; + +enum class EmuThreadState { + DISABLED, + START_REQUESTED, + RUNNING, + QUIT_REQUESTED, + STOPPED, +}; + +static std::thread emuThread; +static std::atomic emuThreadState((int)EmuThreadState::DISABLED); + +void UpdateRunLoopAndroid(JNIEnv *env); + static AndroidAudioState *g_audioState; struct FrameCommand { @@ -114,7 +130,33 @@ static bool javaGL = true; static std::string library_path; static std::map permissions; -GraphicsContext *graphicsContext; +AndroidGraphicsContext *graphicsContext; + +static void EmuThreadFunc(JavaVM *vm) { + JNIEnv *env; + vm->AttachCurrentThread(&env, nullptr); + // There's no real requirement that NativeInit happen on this thread. + // We just call the update/render loop here. + emuThreadState = (int)EmuThreadState::RUNNING; + while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) { + UpdateRunLoopAndroid(env); + } + emuThreadState = (int)EmuThreadState::STOPPED; + vm->DetachCurrentThread(); +} + +static void EmuThreadStart(JNIEnv *env) { + emuThreadState = (int)EmuThreadState::START_REQUESTED; + JavaVM *vm; + env->GetJavaVM(&vm); + emuThread = std::thread(&EmuThreadFunc, vm); +} + +static void EmuThreadStop() { + emuThreadState = (int)EmuThreadState::QUIT_REQUESTED; + emuThread.join(); + emuThread = std::thread(); +} static void ProcessFrameCommands(JNIEnv *env); @@ -225,7 +267,6 @@ std::string GetJavaString(JNIEnv *env, jstring jstr) { extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_registerCallbacks(JNIEnv *env, jobject obj) { nativeActivity = env->NewGlobalRef(obj); postCommand = env->GetMethodID(env->GetObjectClass(obj), "postCommand", "(Ljava/lang/String;Ljava/lang/String;)V"); - ILOG("Got method ID to postCommand: %p", postCommand); } extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_unregisterCallbacks(JNIEnv *env, jobject obj) { @@ -323,8 +364,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init if (shortcut_param.empty()) { const char *argv[2] = {app_name.c_str(), 0}; NativeInit(1, argv, user_data_path.c_str(), externalDir.c_str(), cacheDir.c_str()); - } - else { + } else { const char *argv[3] = {app_name.c_str(), shortcut_param.c_str(), 0}; NativeInit(2, argv, user_data_path.c_str(), externalDir.c_str(), cacheDir.c_str()); } @@ -332,6 +372,33 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init // Now that we've loaded config, set javaGL. javaGL = NativeQueryConfig("androidJavaGL") == "true"; +retry: + switch (g_Config.iGPUBackend) { + case (int)GPUBackend::OPENGL: + ILOG("NativeApp.init() -- creating OpenGL context"); + useCPUThread = true; + if (javaGL) { + graphicsContext = new AndroidJavaEGLGraphicsContext(); + } else { + graphicsContext = new AndroidEGLGraphicsContext(); + } + break; + case (int)GPUBackend::VULKAN: + ILOG("NativeApp.init() -- creating Vulkan context"); + useCPUThread = false; // The Vulkan render manager manages its own thread. + graphicsContext = new AndroidVulkanContext(); + break; + default: + ELOG("NativeApp.init(): iGPUBackend %d not supported. Switching to OpenGL.", (int)g_Config.iGPUBackend); + g_Config.iGPUBackend = (int)GPUBackend::OPENGL; + goto retry; + // Crash(); + } + + if (useCPUThread) { + ILOG("NativeApp.init() - launching emu thread"); + EmuThreadStart(env); + } ILOG("NativeApp.init() -- end"); } @@ -381,9 +448,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) + EmuThreadStop(); + ILOG("NativeApp.shutdown() -- begin"); if (renderer_inited) { ILOG("Shutting down renderer"); + // This will be from the wrong thread? :/ graphicsContext->Shutdown(); delete graphicsContext; graphicsContext = nullptr; @@ -407,26 +478,26 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, ELOG("GetEnv failed: %d", res); } - if (javaGL && !graphicsContext) { - graphicsContext = new AndroidJavaEGLGraphicsContext(); - } else if (!graphicsContext) { - _assert_msg_(G3D, false, "No graphics context in displayInit?"); - } + if (!graphicsContext) + Crash(); + // We should be running on the render thread here. + std::string errorMessage; if (renderer_inited) { ILOG("NativeApp.displayInit() restoring"); - NativeShutdownGraphics(); - delete graphicsContext; + graphicsContext->ThreadEnd(); + graphicsContext->ShutdownFromRenderThread(); - graphicsContext = new AndroidJavaEGLGraphicsContext(); - NativeInitGraphics(graphicsContext); + graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0); + graphicsContext->ThreadStart(); ILOG("Restored."); } else { ILOG("NativeApp.displayInit() first time"); + graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0); + graphicsContext->ThreadStart(); NativeInitGraphics(graphicsContext); renderer_inited = true; } - NativeMessageReceived("recreateviews", ""); } @@ -470,23 +541,19 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv } } -// JavaEGL -extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, jobject obj) { - static bool hasSetThreadName = false; - if (!hasSetThreadName) { - hasSetThreadName = true; - setCurrentThreadName("AndroidRender"); +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); + } } - if (renderer_inited) { - NativeUpdate(); + NativeUpdate(); - NativeRender(graphicsContext); - time_update(); - } else { - ELOG("BAD: Ended up in nativeRender even though app has quit.%s", ""); - // Shouldn't really get here. - } + NativeRender(graphicsContext); + time_update(); std::lock_guard guard(frameCommandLock); if (!nativeActivity) { @@ -498,6 +565,21 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, ProcessFrameCommands(env); } +extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, jobject obj) { + static bool hasSetThreadName = false; + if (!hasSetThreadName) { + hasSetThreadName = true; + setCurrentThreadName("AndroidRender"); + } + + if (useCPUThread) { + // This is the "GPU thread". + graphicsContext->ThreadFrame(); + } else { + UpdateRunLoopAndroid(env); + } +} + void System_AskForPermission(SystemPermission permission) { switch (permission) { case SYSTEM_PERMISSION_STORAGE: @@ -762,6 +844,8 @@ static void ProcessFrameCommands(JNIEnv *env) { } extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(JNIEnv *env, jobject obj, jobject _surf) { + // Needed for Vulkan, even if we're not using the old EGL path. + exitRenderLoop = false; // This is up here to prevent race conditions, in case we pause during init. renderLoopRunning = true; @@ -787,14 +871,8 @@ retry: bool vulkan = g_Config.iGPUBackend == (int)GPUBackend::VULKAN; int tries = 0; - AndroidGraphicsContext *graphicsContext; - if (vulkan) { - graphicsContext = new AndroidVulkanContext(); - } else { - graphicsContext = new AndroidEGLGraphicsContext(); - } - if (!graphicsContext->Init(wnd, desiredBackbufferSizeX, desiredBackbufferSizeY, backbuffer_format, androidVersion)) { + if (!graphicsContext->InitFromRenderThread(wnd, desiredBackbufferSizeX, desiredBackbufferSizeY, backbuffer_format, androidVersion)) { ELOG("Failed to initialize graphics context."); if (!exitRenderLoop && (vulkan && tries < 2)) { diff --git a/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java b/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java index 2d53328573..1e13dba115 100644 --- a/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java +++ b/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java @@ -47,7 +47,7 @@ public class NativeSurfaceView extends SurfaceView implements SensorEventListene Log.i(TAG, "MOGA initialized"); mController.setListener(this, new Handler()); } catch (Exception e) { - Log.i(TAG, "Moga failed to initialize"); + // Ignore. } }