From 20419c5b3bbbbfdbe4e96a26d5312b53f444a81c Mon Sep 17 00:00:00 2001 From: Gabriel Jacobo Date: Tue, 19 Jun 2012 13:57:42 -0300 Subject: [PATCH] Fixes #1422, restores GL context automatically under Android --- README.android | 18 +++++++++++ .../src/org/libsdl/app/SDLActivity.java | 22 +++++++------- src/core/android/SDL_android.cpp | 7 +++++ src/video/android/SDL_androidevents.c | 30 +++++++++++++++++++ src/video/android/SDL_androidvideo.c | 1 + src/video/android/SDL_androidvideo.h | 3 ++ src/video/android/SDL_androidwindow.c | 6 ++++ 7 files changed, 77 insertions(+), 10 deletions(-) diff --git a/README.android b/README.android index 70638ca77..c7ffe3505 100644 --- a/README.android +++ b/README.android @@ -73,6 +73,24 @@ android-project/ src/org/libsdl/app/SDLActivity.java - the Java class handling the initialization and binding to SDL. Be very careful changing this, as the SDL library relies on this implementation. +================================================================================ + Pause / Resume behaviour +================================================================================ + +If SDL is compiled with SDL_ANDROID_BLOCK_ON_PAUSE defined, the event loop will +block itself when the app is paused (ie, when the user returns to the main +Android dashboard). Blocking is better in terms of battery use, and it allows your +app to spring back to life instantaneously after resume (versus polling for +a resume message). +Upon resume, SDL will attempt to restore the GL context automatically. +In modern devices (Android 3.0 and up) this will most likely succeed and your +app can continue to operate as it was. +However, there's a chance (on older hardware, or on systems under heavy load), +where the GL context can not be restored. In that case you have to listen for +a specific message, (which is not yet implemented!) and restore your textures +manually or quit the app (which is actually the kind of behaviour you'll see +under iOS, if the OS can not restore your GL context it will just kill your app) + ================================================================================ Additional documentation ================================================================================ diff --git a/android-project/src/org/libsdl/app/SDLActivity.java b/android-project/src/org/libsdl/app/SDLActivity.java index 1ea8b1fa6..0c7573c42 100644 --- a/android-project/src/org/libsdl/app/SDLActivity.java +++ b/android-project/src/org/libsdl/app/SDLActivity.java @@ -68,17 +68,17 @@ protected void onCreate(Bundle savedInstanceState) { } // Events - protected void onPause() { + /*protected void onPause() { Log.v("SDL", "onPause()"); super.onPause(); - SDLActivity.nativePause(); + // Don't call SDLActivity.nativePause(); here, it will be called by SDLSurface::surfaceDestroyed } protected void onResume() { Log.v("SDL", "onResume()"); super.onResume(); - SDLActivity.nativeResume(); - } + // Don't call SDLActivity.nativeResume(); here, it will be called via SDLSurface::surfaceChanged->SDLActivity::startApp + }*/ protected void onDestroy() { super.onDestroy(); @@ -249,12 +249,15 @@ public static boolean createEGLSurface() { return false; } - if (!egl.eglMakeCurrent(SDLActivity.mEGLDisplay, surface, surface, SDLActivity.mEGLContext)) { - Log.e("SDL", "Old EGL Context doesnt work, trying with a new one"); - createEGLContext(); + if (egl.eglGetCurrentContext() != SDLActivity.mEGLContext) { if (!egl.eglMakeCurrent(SDLActivity.mEGLDisplay, surface, surface, SDLActivity.mEGLContext)) { - Log.e("SDL", "Failed making EGL Context current"); - return false; + Log.e("SDL", "Old EGL Context doesnt work, trying with a new one"); + // TODO: Notify the user via a message that the old context could not be restored, and that textures need to be manually restored. + createEGLContext(); + if (!egl.eglMakeCurrent(SDLActivity.mEGLDisplay, surface, surface, SDLActivity.mEGLContext)) { + Log.e("SDL", "Failed making EGL Context current"); + return false; + } } } SDLActivity.mEGLSurface = surface; @@ -426,7 +429,6 @@ public SDLSurface(Context context) { public void surfaceCreated(SurfaceHolder holder) { Log.v("SDL", "surfaceCreated()"); holder.setType(SurfaceHolder.SURFACE_TYPE_GPU); - SDLActivity.createEGLSurface(); enableSensor(Sensor.TYPE_ACCELEROMETER, true); } diff --git a/src/core/android/SDL_android.cpp b/src/core/android/SDL_android.cpp index f1b5ead07..a2c27fb7c 100755 --- a/src/core/android/SDL_android.cpp +++ b/src/core/android/SDL_android.cpp @@ -176,6 +176,8 @@ extern "C" void Java_org_libsdl_app_SDLActivity_nativePause( JNIEnv* env, jclass cls) { if (Android_Window) { + /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */ + if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem); SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0); SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0); } @@ -186,6 +188,11 @@ extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume( JNIEnv* env, jclass cls) { if (Android_Window) { + /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context + * We can't restore the GL Context here because it needs to be done on the SDL main thread + * and this function will be called from the Java thread instead. + */ + if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem); SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0); SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0); } diff --git a/src/video/android/SDL_androidevents.c b/src/video/android/SDL_androidevents.c index b82f9c1b9..059b36ce9 100755 --- a/src/video/android/SDL_androidevents.c +++ b/src/video/android/SDL_androidevents.c @@ -27,7 +27,37 @@ void Android_PumpEvents(_THIS) { + static int isPaused = 0; /* No polling necessary */ + + /* + * Android_ResumeSem and Android_PauseSem are signaled from Java_org_libsdl_app_SDLActivity_nativePause and Java_org_libsdl_app_SDLActivity_nativeResume + * When the pause semaphoe is signaled, if SDL_ANDROID_BLOCK_ON_PAUSE is defined the event loop will block until the resume signal is emitted. + * When the resume semaphore is signaled, SDL_GL_CreateContext is called which in turn calls Java code + * SDLActivity::createGLContext -> SDLActivity:: initEGL -> SDLActivity::createEGLSurface -> SDLActivity::createEGLContext + */ + if (isPaused) { +#if SDL_ANDROID_BLOCK_ON_PAUSE + if(SDL_SemWait(Android_ResumeSem) == 0) { +#else + if(SDL_SemTryWait(Android_ResumeSem) == 0) { +#endif + isPaused = 0; + /* TODO: Should we double check if we are on the same thread as the one that made the original GL context? + * This call will go through the following chain of calls in Java: + * SDLActivity::createGLContext -> SDLActivity:: initEGL -> SDLActivity::createEGLSurface -> SDLActivity::createEGLContext + * SDLActivity::createEGLContext will attempt to restore the GL context first, and if that fails it will create a new one + * If a new GL context is created, the user needs to restore the textures manually (TODO: notify the user that this happened with a message) + */ + SDL_GL_CreateContext(Android_Window); + } + } + else { + if(SDL_SemTryWait(Android_PauseSem) == 0) { + /* If we fall in here, the system is/was paused */ + isPaused = 1; + } + } } #endif /* SDL_VIDEO_DRIVER_ANDROID */ diff --git a/src/video/android/SDL_androidvideo.c b/src/video/android/SDL_androidvideo.c index c97e0d7f0..047488c55 100755 --- a/src/video/android/SDL_androidvideo.c +++ b/src/video/android/SDL_androidvideo.c @@ -64,6 +64,7 @@ extern void Android_GL_DeleteContext(_THIS, SDL_GLContext context); int Android_ScreenWidth = 0; int Android_ScreenHeight = 0; Uint32 Android_ScreenFormat = SDL_PIXELFORMAT_UNKNOWN; +SDL_sem *Android_PauseSem = NULL, *Android_ResumeSem = NULL; /* Currently only one window */ SDL_Window *Android_Window = NULL; diff --git a/src/video/android/SDL_androidvideo.h b/src/video/android/SDL_androidvideo.h index 8ffe8ccc0..90905d36d 100755 --- a/src/video/android/SDL_androidvideo.h +++ b/src/video/android/SDL_androidvideo.h @@ -23,6 +23,7 @@ #ifndef _SDL_androidvideo_h #define _SDL_androidvideo_h +#include "SDL_mutex.h" #include "../SDL_sysvideo.h" /* Called by the JNI layer when the screen changes size or format */ @@ -33,8 +34,10 @@ extern void Android_SetScreenResolution(int width, int height, Uint32 format); extern int Android_ScreenWidth; extern int Android_ScreenHeight; extern Uint32 Android_ScreenFormat; +extern SDL_sem *Android_PauseSem, *Android_ResumeSem; extern SDL_Window *Android_Window; + #endif /* _SDL_androidvideo_h */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/android/SDL_androidwindow.c b/src/video/android/SDL_androidwindow.c index 43213e759..fec1e17d9 100755 --- a/src/video/android/SDL_androidwindow.c +++ b/src/video/android/SDL_androidwindow.c @@ -35,6 +35,8 @@ Android_CreateWindow(_THIS, SDL_Window * window) return -1; } Android_Window = window; + Android_PauseSem = SDL_CreateSemaphore(0); + Android_ResumeSem = SDL_CreateSemaphore(0); /* Adjust the window data to match the screen */ window->x = 0; @@ -62,6 +64,10 @@ Android_DestroyWindow(_THIS, SDL_Window * window) { if (window == Android_Window) { Android_Window = NULL; + if (Android_PauseSem) SDL_DestroySemaphore(Android_PauseSem); + if (Android_ResumeSem) SDL_DestroySemaphore(Android_ResumeSem); + Android_PauseSem = NULL; + Android_ResumeSem = NULL; } }