From 463d703feb8fd6a8326c0433ea445cacd164eb3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Dec 2022 23:07:30 +0100 Subject: [PATCH] More assorted cleanup --- Common/GPU/OpenGL/GLFeatures.cpp | 22 ++++++--- Common/GPU/OpenGL/GLFeatures.h | 4 +- android/jni/AndroidGraphicsContext.h | 12 ++++- android/jni/AndroidJavaGLContext.cpp | 14 ++++-- android/jni/AndroidJavaGLContext.h | 11 +---- android/jni/AndroidVulkanContext.cpp | 5 ++ android/jni/AndroidVulkanContext.h | 8 +-- android/jni/app-android.cpp | 49 ++++++++++--------- .../src/org/ppsspp/ppsspp/NativeActivity.java | 19 ++++--- 9 files changed, 82 insertions(+), 62 deletions(-) diff --git a/Common/GPU/OpenGL/GLFeatures.cpp b/Common/GPU/OpenGL/GLFeatures.cpp index c7bd284feb..c474ad7b8c 100644 --- a/Common/GPU/OpenGL/GLFeatures.cpp +++ b/Common/GPU/OpenGL/GLFeatures.cpp @@ -127,14 +127,14 @@ void ProcessGPUFeatures() { // http://stackoverflow.com/questions/16147700/opengl-es-using-tegra-specific-extensions-gl-ext-texture-array -void CheckGLExtensions() { - +bool CheckGLExtensions() { #if PPSSPP_API(ANY_GL) + // Make sure to only do this once. It's okay to call CheckGLExtensions from wherever, + // as long as you're on the rendering thread (the one with the GL context). + if (extensionsDone) { + return true; + } - // Make sure to only do this once. It's okay to call CheckGLExtensions from wherever. - if (extensionsDone) - return; - extensionsDone = true; memset(&gl_extensions, 0, sizeof(gl_extensions)); gl_extensions.IsCoreContext = useCoreContext; @@ -142,6 +142,12 @@ void CheckGLExtensions() { const char *versionStr = (const char *)glGetString(GL_VERSION); const char *glslVersionStr = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION); + if (!renderer || !versionStr || !glslVersionStr) { + // Something is very wrong! Bail. + return false; + } + + extensionsDone = true; #ifdef USING_GLES2 gl_extensions.IsGLES = !useCoreContext; @@ -269,7 +275,7 @@ void CheckGLExtensions() { // If the above didn't give us a version, or gave us a crazy version, fallback. #ifdef USING_GLES2 - if (gl_extensions.ver[0] < 3 || gl_extensions.ver[0] > 5) { + if (versionStr && (gl_extensions.ver[0] < 3 || gl_extensions.ver[0] > 5)) { // Try to load GLES 3.0 only if "3.0" found in version // This simple heuristic avoids issues on older devices where you can only call eglGetProcAddress a limited // number of times. Make sure to check for 3.0 in the shader version too to avoid false positives, see #5584. @@ -569,7 +575,7 @@ void CheckGLExtensions() { ERROR_LOG(G3D, "GL error in init: %i", error); #endif - + return true; } void SetGLCoreContext(bool flag) { diff --git a/Common/GPU/OpenGL/GLFeatures.h b/Common/GPU/OpenGL/GLFeatures.h index a2d20b178f..c85c8b3e38 100644 --- a/Common/GPU/OpenGL/GLFeatures.h +++ b/Common/GPU/OpenGL/GLFeatures.h @@ -129,7 +129,9 @@ void ProcessGPUFeatures(); extern std::string g_all_gl_extensions; extern std::string g_all_egl_extensions; -void CheckGLExtensions(); +// If this returns false, we're not gonna be able to use a GL context. +bool CheckGLExtensions(); + void SetGLCoreContext(bool flag); void ResetGLExtensions(); diff --git a/android/jni/AndroidGraphicsContext.h b/android/jni/AndroidGraphicsContext.h index 434017e077..b35f0de2a5 100644 --- a/android/jni/AndroidGraphicsContext.h +++ b/android/jni/AndroidGraphicsContext.h @@ -16,13 +16,23 @@ enum { ANDROID_VERSION_NOUGAT_1 = 25, }; +enum class GraphicsContextState { + PENDING, + INITIALIZED, + FAILED_INIT, + SHUTDOWN, +}; + class AndroidGraphicsContext : public GraphicsContext { 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; virtual void BeginAndroidShutdown() {} + virtual GraphicsContextState GetState() const { return state_; } + +protected: + GraphicsContextState state_ = GraphicsContextState::PENDING; private: using GraphicsContext::InitFromRenderThread; diff --git a/android/jni/AndroidJavaGLContext.cpp b/android/jni/AndroidJavaGLContext.cpp index f7f7b70444..08e52034a5 100644 --- a/android/jni/AndroidJavaGLContext.cpp +++ b/android/jni/AndroidJavaGLContext.cpp @@ -12,19 +12,27 @@ AndroidJavaEGLGraphicsContext::AndroidJavaEGLGraphicsContext() { bool AndroidJavaEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) { INFO_LOG(G3D, "AndroidJavaEGLGraphicsContext::InitFromRenderThread"); - CheckGLExtensions(); + if (!CheckGLExtensions()) { + ERROR_LOG(G3D, "CheckGLExtensions failed - not gonna attempt starting up."); + state_ = GraphicsContextState::FAILED_INIT; + return false; + } // OpenGL handles rotated rendering in the driver. g_display_rotation = DisplayRotation::ROTATE_0; g_display_rot_matrix.setIdentity(); + draw_ = Draw::T3DCreateGLContext(); // Can't fail renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); renderManager_->SetInflightFrames(g_Config.iInflightFrames); if (!draw_->CreatePresets()) { + // This can't really happen now that compilation is async - they're only really queued for compile here. _assert_msg_(false, "Failed to compile preset shaders"); + state_ = GraphicsContextState::FAILED_INIT; return false; } + state_ = GraphicsContextState::INITIALIZED; return true; } @@ -34,7 +42,5 @@ void AndroidJavaEGLGraphicsContext::ShutdownFromRenderThread() { renderManager_ = nullptr; // owned by draw_. delete draw_; draw_ = nullptr; -} - -void AndroidJavaEGLGraphicsContext::Shutdown() { + state_ = GraphicsContextState::SHUTDOWN; } diff --git a/android/jni/AndroidJavaGLContext.h b/android/jni/AndroidJavaGLContext.h index e952de8acc..919bd8b80f 100644 --- a/android/jni/AndroidJavaGLContext.h +++ b/android/jni/AndroidJavaGLContext.h @@ -4,24 +4,17 @@ #include "Common/GPU/OpenGL/GLRenderManager.h" #include "Common/GPU/thin3d_create.h" -// Doesn't do much. Just to fit in. class AndroidJavaEGLGraphicsContext : public AndroidGraphicsContext { public: AndroidJavaEGLGraphicsContext(); - ~AndroidJavaEGLGraphicsContext() { - delete draw_; - } - - bool Initialized() override { - return draw_ != nullptr; - } + ~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 Shutdown() override {} void SwapBuffers() override {} void SwapInterval(int interval) override {} void Resize() override {} diff --git a/android/jni/AndroidVulkanContext.cpp b/android/jni/AndroidVulkanContext.cpp index 2c6d63f8b5..6d5693273e 100644 --- a/android/jni/AndroidVulkanContext.cpp +++ b/android/jni/AndroidVulkanContext.cpp @@ -47,6 +47,7 @@ bool AndroidVulkanContext::InitAPI() { if (!VulkanLoad()) { ERROR_LOG(G3D, "Failed to load Vulkan driver library"); + state_ = GraphicsContextState::FAILED_INIT; return false; } @@ -65,6 +66,7 @@ bool AndroidVulkanContext::InitAPI() { VulkanSetAvailable(false); delete g_Vulkan; g_Vulkan = nullptr; + state_ = GraphicsContextState::FAILED_INIT; return false; } @@ -74,6 +76,7 @@ bool AndroidVulkanContext::InitAPI() { g_Vulkan->DestroyInstance(); delete g_Vulkan; g_Vulkan = nullptr; + state_ = GraphicsContextState::FAILED_INIT; return false; } @@ -86,10 +89,12 @@ bool AndroidVulkanContext::InitAPI() { g_Vulkan->DestroyInstance(); delete g_Vulkan; g_Vulkan = nullptr; + state_ = GraphicsContextState::FAILED_INIT; return false; } INFO_LOG(G3D, "Vulkan device created!"); + state_ = GraphicsContextState::INITIALIZED; return true; } diff --git a/android/jni/AndroidVulkanContext.h b/android/jni/AndroidVulkanContext.h index 2e5cfb7328..31d88c1a20 100644 --- a/android/jni/AndroidVulkanContext.h +++ b/android/jni/AndroidVulkanContext.h @@ -20,13 +20,7 @@ public: void Resize() override; void *GetAPIContext() override { return g_Vulkan; } - - Draw::DrawContext *GetDrawContext() override { - return draw_; - } - bool Initialized() override { - return draw_ != nullptr; - } + Draw::DrawContext *GetDrawContext() override { return draw_; } private: VulkanContext *g_Vulkan = nullptr; diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 93ddb99cde..8f08a4233e 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -122,15 +122,15 @@ struct FrameCommand { static std::mutex frameCommandLock; static std::queue frameCommands; -std::string systemName; -std::string langRegion; -std::string mogaVersion; -std::string boardName; +static std::string systemName; +static std::string langRegion; +static std::string mogaVersion; +static std::string boardName; std::string g_externalDir; // Original external dir (root of Android storage). std::string g_extFilesDir; // App private external dir. -std::vector g_additionalStorageDirs; +static std::vector g_additionalStorageDirs; static int optimalFramesPerBuffer = 0; static int optimalSampleRate = 0; @@ -152,7 +152,7 @@ static int desiredBackbufferSizeX; static int desiredBackbufferSizeY; // Cache the class loader so we can use it from native threads. Required for TextAndroid. -JavaVM* gJvm = nullptr; +static JavaVM* gJvm = nullptr; static jobject gClassLoader; static jmethodID gFindClassMethod; @@ -167,18 +167,18 @@ static jmethodID getDebugString; static jobject nativeActivity; static std::atomic exitRenderLoop; -static bool renderLoopRunning; +static std::atomic renderLoopRunning; static bool renderer_inited = false; static std::mutex renderLock; static int inputBoxSequence = 1; -std::map> inputBoxCallbacks; +static std::map> inputBoxCallbacks; static bool sustainedPerfSupported = false; static std::map permissions; -AndroidGraphicsContext *graphicsContext; +static AndroidGraphicsContext *graphicsContext; #ifndef LOG_APP_NAME #define LOG_APP_NAME "PPSSPP" @@ -274,18 +274,23 @@ static void EmuThreadFunc() { INFO_LOG(SYSTEM, "Entering emu thread"); // Wait for render loop to get started. - if (!graphicsContext || !graphicsContext->Initialized()) { - INFO_LOG(SYSTEM, "Runloop: Waiting for displayInit..."); - while (!graphicsContext || !graphicsContext->Initialized()) { - sleep_ms(20); - } - } else { - INFO_LOG(SYSTEM, "Runloop: Graphics context available!"); + INFO_LOG(SYSTEM, "Runloop: Waiting for displayInit..."); + while (!graphicsContext || graphicsContext->GetState() == GraphicsContextState::PENDING) { + sleep_ms(20); + } + + // Check the state of the graphics context before we try to feed it into NativeInitGraphics. + if (graphicsContext->GetState() != GraphicsContextState::INITIALIZED) { + ERROR_LOG(G3D, "Failed to initialize the graphics context! %d", (int)graphicsContext->GetState()); + emuThreadState = (int)EmuThreadState::QUIT_REQUESTED; + gJvm->DetachCurrentThread(); + return; } if (!NativeInitGraphics(graphicsContext)) { - _assert_msg_(false, "Failed to initialize graphics, might as well bail"); + _assert_msg_(false, "NativeInitGraphics failed, might as well bail"); emuThreadState = (int)EmuThreadState::QUIT_REQUESTED; + gJvm->DetachCurrentThread(); return; } @@ -1186,7 +1191,7 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendMessage(JNIEnv *env NativeMessageReceived(msg.c_str(), prm.c_str()); } -extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeActivity_exitEGLRenderLoop(JNIEnv *env, jobject obj) { +extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeActivity_requestExitVulkanRenderLoop(JNIEnv *env, jobject obj) { if (!renderLoopRunning) { ERROR_LOG(SYSTEM, "Render loop already exited"); return; @@ -1347,11 +1352,11 @@ static void ProcessFrameCommands(JNIEnv *env) { } // This runs in Vulkan mode only. -extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(JNIEnv *env, jobject obj, jobject _surf) { +extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runVulkanRenderLoop(JNIEnv *env, jobject obj, jobject _surf) { _assert_(!useCPUThread); if (!graphicsContext) { - ERROR_LOG(G3D, "runEGLRenderLoop: Tried to enter without a created graphics context."); + ERROR_LOG(G3D, "runVulkanRenderLoop: Tried to enter without a created graphics context."); return false; } @@ -1361,7 +1366,7 @@ extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(J ANativeWindow *wnd = _surf ? ANativeWindow_fromSurface(env, _surf) : nullptr; - WARN_LOG(G3D, "runEGLRenderLoop. display_xres=%d display_yres=%d desiredBackbufferSizeX=%d desiredBackbufferSizeY=%d", + WARN_LOG(G3D, "runVulkanRenderLoop. display_xres=%d display_yres=%d desiredBackbufferSizeX=%d desiredBackbufferSizeY=%d", display_xres, display_yres, desiredBackbufferSizeX, desiredBackbufferSizeY); if (!wnd) { @@ -1374,7 +1379,7 @@ extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(J if (!graphicsContext->InitFromRenderThread(wnd, desiredBackbufferSizeX, desiredBackbufferSizeY, backbuffer_format, androidVersion)) { // On Android, if we get here, really no point in continuing. // The UI is supposed to render on any device both on OpenGL and Vulkan. If either of those don't work - // on a device, we blacklist it. + // on a device, we blacklist it. Hopefully we should have already failed in InitAPI anyway and reverted to GL back then. ERROR_LOG(G3D, "Failed to initialize graphics context."); System_Toast("Failed to initialize graphics context."); diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 02dc8c32a7..869403abde 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -25,7 +25,6 @@ import android.os.Bundle; import android.os.Environment; import android.os.PowerManager; import android.os.Vibrator; -import android.provider.DocumentsContract; import android.provider.MediaStore; import androidx.documentfile.provider.DocumentFile; import android.text.InputType; @@ -573,17 +572,17 @@ public abstract class NativeActivity extends Activity { public void run() { Log.i(TAG, "Starting the render loop: " + mSurface); // Start emulation using the provided Surface. - if (!runEGLRenderLoop(mSurface)) { + if (!runVulkanRenderLoop(mSurface)) { // Shouldn't happen. - Log.e(TAG, "Failed to start up OpenGL/Vulkan"); + Log.e(TAG, "Failed to start up OpenGL/Vulkan - runVulkanRenderLoop returned false"); } Log.i(TAG, "Left the render loop: " + mSurface); } }; - public native boolean runEGLRenderLoop(Surface surface); + public native boolean runVulkanRenderLoop(Surface surface); // Tells the render loop thread to exit, so we can restart it. - public native void exitEGLRenderLoop(); + public native void requestExitVulkanRenderLoop(); @Override public void onCreate(Bundle savedInstanceState) { @@ -688,17 +687,17 @@ public abstract class NativeActivity extends Activity { updateSustainedPerformanceMode(); } - // Invariants: After this, mRenderLoopThread will be set, and the thread will be running. + // Invariants: After this, mRenderLoopThread will be set, and the thread will be running, + // if in Vulkan mode. protected synchronized void ensureRenderLoop() { if (javaGL) { - Log.e(TAG, "JavaGL - should not get into ensureRenderLoop."); + Log.e(TAG, "JavaGL mode - should not get into ensureRenderLoop."); return; } if (mSurface == null) { Log.w(TAG, "ensureRenderLoop - not starting thread, needs surface"); return; } - if (mRenderLoopThread == null) { Log.w(TAG, "ensureRenderLoop: Starting thread"); mRenderLoopThread = new Thread(mEmulationRunner); @@ -715,8 +714,8 @@ public abstract class NativeActivity extends Activity { if (mRenderLoopThread != null) { // This will wait until the thread has exited. - Log.i(TAG, "exitEGLRenderLoop"); - exitEGLRenderLoop(); + Log.i(TAG, "requestExitVulkanRenderLoop"); + requestExitVulkanRenderLoop(); try { Log.i(TAG, "joining render loop thread..."); mRenderLoopThread.join();