| Summary: | [Android] [Feature Request?] Support Pause/Resume | ||
|---|---|---|---|
| Product: | SDL | Reporter: | wagic.the.homebrew |
| Component: | main | Assignee: | Sam Lantinga <slouken> |
| Status: | RESOLVED FIXED | QA Contact: | Sam Lantinga <slouken> |
| Severity: | normal | ||
| Priority: | P2 | CC: | gabomdq, tim, vitto.giova |
| Version: | HG 2.0 | ||
| Hardware: | Other | ||
| OS: | Android (All) | ||
| Attachments: |
Support for Pause/Resume in Android
SDLActivity.java with Pause/Resume support Support for Pause/Resume in Android via Window Events |
||
|
Description
wagic.the.homebrew
2011-08-28 19:08:59 UTC
I dug a bit more and I might have a suggested fix, however my current file is so far from the initial SDLActivity.java that I cannot submit a patch immediately, but here are the changes:
/************************************/
SDLACtivity:
// Events
@Override
protected void onPause() {
//Log.v("SDL", "onPause()");
super.onPause();
SDLActivity.nativePause();
}
@Override
protected void onResume() {
Log.v("SDL", "onResume()");
super.onResume();
SDLActivity.nativeResume();
}
@Override
public void onStop() {
super.onStop();
}
@Override
public void onDestroy() {
Log.v("SDL", "onDestroy()");
super.onDestroy();
SDLActivity.nativeQuit();
mSurface.onDestroy();
}
@Override
public void onStart() {
super.onStart();
}
[...]
/**
SDLSurface. This is what we draw on, so we need to know when it's created
in order to do anything useful.
Because of this, that's where we set up the SDL thread
*/
class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
// This is what SDL runs in. It invokes SDL_main(), eventually
private Thread mSDLThread;
// EGL private objects
private EGLContext mEGLContext;
private EGLSurface mEGLSurface;
private EGLDisplay mEGLDisplay;
private EGLConfig mEGLConfig;
// Sensors
private static SensorManager mSensorManager;
private static VelocityTracker mVelocityTracker;
final private Object _sem_surface;
private SurfaceHolder _surface_holder;
private Boolean mSurfaceValid;
/**
Simple nativeInit() runnable
*/
void startSDLThread() {
if (mSDLThread == null) {
mSDLThread = new Thread(new SDLMain(), "SDLThread");
mSDLThread.start();
}
}
class SDLMain implements Runnable {
public void run() {
// Runs SDL_main()
try {
// wait for the surfaceChanged callback
synchronized(_sem_surface) {
while (_surface_holder == null)
_sem_surface.wait();
}
} catch (Exception e) {
throw new RuntimeException("Error preparing the ScummVM thread", e);
}
SDLActivity.nativeInit();
SDLActivity.nativeQuit();
// On exit, tear everything down for a fresh restart next time.
System.exit(0);
Log.v("SDL", "SDL thread terminated");
}
}
// Startup
public SDLSurface(Context context) {
super(context);
_sem_surface = new Object();
_surface_holder = null;
mSurfaceValid = false;
getHolder().addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnKeyListener(this);
setOnTouchListener(this);
mSensorManager = (SensorManager)context.getSystemService("sensor");
}
// Called when we have a valid drawing surface
public void surfaceCreated(SurfaceHolder holder) {
Log.v("SDL", "surfaceCreated()");
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
}
public void onDestroy() {
// Send a quit message to the application
//should that be in SDLActivity "onDestroy" instead ?
SDLActivity.nativeQuit();
// Now wait for the SDL thread to quit
if (mSDLThread != null) {
try {
mSDLThread.join();
} catch(Exception e) {
Log.v("SDL", "Problem stopping thread: " + e);
}
mSDLThread = null;
//Log.v("SDL", "Finished waiting for SDL thread");
}
}
// Called when we lose the surface
public void surfaceDestroyed(SurfaceHolder holder) {
Log.v("SDL", "surfaceDestroyed()");
synchronized(_sem_surface) {
_surface_holder = null;
mSurfaceValid = false;
_sem_surface.notifyAll();
}
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
}
// Called when the surface is resized
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
Log.v("SDL", "surfaceChanged()");
int sdlFormat = 0x85151002; // SDL_PIXELFORMAT_RGB565 by default
switch (format) {
case PixelFormat.A_8:
Log.v("SDL", "pixel format A_8");
break;
case PixelFormat.LA_88:
Log.v("SDL", "pixel format LA_88");
break;
case PixelFormat.L_8:
Log.v("SDL", "pixel format L_8");
break;
case PixelFormat.RGBA_4444:
Log.v("SDL", "pixel format RGBA_4444");
sdlFormat = 0x85421002; // SDL_PIXELFORMAT_RGBA4444
break;
case PixelFormat.RGBA_5551:
Log.v("SDL", "pixel format RGBA_5551");
sdlFormat = 0x85441002; // SDL_PIXELFORMAT_RGBA5551
break;
case PixelFormat.RGBA_8888:
Log.v("SDL", "pixel format RGBA_8888");
sdlFormat = 0x86462004; // SDL_PIXELFORMAT_RGBA8888
break;
case PixelFormat.RGBX_8888:
Log.v("SDL", "pixel format RGBX_8888");
sdlFormat = 0x86262004; // SDL_PIXELFORMAT_RGBX8888
break;
case PixelFormat.RGB_332:
Log.v("SDL", "pixel format RGB_332");
sdlFormat = 0x84110801; // SDL_PIXELFORMAT_RGB332
break;
case PixelFormat.RGB_565:
Log.v("SDL", "pixel format RGB_565");
sdlFormat = 0x85151002; // SDL_PIXELFORMAT_RGB565
break;
case PixelFormat.RGB_888:
Log.v("SDL", "pixel format RGB_888");
// Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
sdlFormat = 0x86161804; // SDL_PIXELFORMAT_RGB888
break;
default:
Log.v("SDL", "pixel format unknown " + format);
break;
}
SDLActivity.onNativeResize(width, height, sdlFormat);
// Now start up the C app thread
synchronized(_sem_surface) {
_surface_holder = holder;
_sem_surface.notifyAll();
}
}
// unused
public void onDraw(Canvas canvas) {}
// EGL functions
public boolean initEGL(int majorVersion, int minorVersion) {
Log.v("SDL", "Starting up OpenGL ES " + majorVersion + "." + minorVersion);
try {
EGL10 egl = (EGL10)EGLContext.getEGL();
EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
int[] version = new int[2];
egl.eglInitialize(dpy, version);
int EGL_OPENGL_ES_BIT = 1;
int EGL_OPENGL_ES2_BIT = 4;
int renderableType = 0;
if (majorVersion == 2) {
renderableType = EGL_OPENGL_ES2_BIT;
} else if (majorVersion == 1) {
renderableType = EGL_OPENGL_ES_BIT;
}
int[] configSpec = {
//EGL10.EGL_DEPTH_SIZE, 16,
EGL10.EGL_RENDERABLE_TYPE, renderableType,
EGL10.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] num_config = new int[1];
if (!egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config) || num_config[0] == 0) {
Log.e("SDL", "No EGL config available");
return false;
}
mEGLConfig = configs[0];
EGLContext ctx = egl.eglCreateContext(dpy, mEGLConfig, EGL10.EGL_NO_CONTEXT, null);
if (ctx == EGL10.EGL_NO_CONTEXT) {
Log.e("SDL", "Couldn't create context");
return false;
}
EGLSurface surface = egl.eglCreateWindowSurface(dpy, mEGLConfig, this, null);
if (surface == EGL10.EGL_NO_SURFACE) {
Log.e("SDL", "Couldn't create surface");
return false;
}
if (!egl.eglMakeCurrent(dpy, surface, surface, ctx)) {
Log.e("SDL", "Couldn't make context current");
return false;
}
mEGLContext = ctx;
mEGLDisplay = dpy;
mEGLSurface = surface;
mSurfaceValid = true;
} catch(Exception e) {
Log.v("SDL", e + "");
for (StackTraceElement s : e.getStackTrace()) {
Log.v("SDL", s.toString());
}
}
return true;
}
public Boolean createSurface(SurfaceHolder holder) {
/*
* The window size has changed, so we need to create a new
* surface.
*/
EGL10 egl = (EGL10)EGLContext.getEGL();
if (mEGLSurface != null) {
/*
* Unbind and destroy the old EGL surface, if
* there is one.
*/
egl.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
egl.eglDestroySurface(mEGLDisplay, mEGLSurface);
}
/*
* Create an EGL surface we can render into.
*/
mEGLSurface = egl.eglCreateWindowSurface(mEGLDisplay,
mEGLConfig, holder, null);
/*
* Before we can issue GL commands, we need to make sure
* the context is current and bound to a surface.
*/
egl.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface,
mEGLContext);
mSurfaceValid = true;
return true;
/*
GL gl = mEGLContext.getGL();
if (mGLWrapper != null) {
gl = mGLWrapper.wrap(gl);
}
return gl;*/
}
// EGL buffer flip
public void flipEGL() {
if (!mSurfaceValid)
{
createSurface(this.getHolder());
}
[...]
/**************************************/
Sorry, it's a bit "raw" (EGLInit should probably call surfaceCreated, also surfaceCreated should have more defensive checks, etc...) because I just got it to work and I don't know if I'll be good enough to submit a real patch once I clean up the code, but basically:
- do not destroy the app when the surface is destroyed, instead do it when the Activity is destroyed
- split initEGL into 2 functions: initEGL and createSurface. CreateSurface needs to be called whenever flipBuffers attempts to render on an invalidSurface. a Boolean can be used in surfaceDestroyed to keep track of when the surface becomes invalid.
For me, this solves some of the issues I had with rendering not working after opening an ad webview. I believe this also fixes pause/resume issues. Note that I added my own native pause/resume functions. Pause only sets a "mPause" bool in the C code, and the main SDL loop just checks for this.
In the fragment you've provided it looks like startSDLThread() is never called? It may be better to just attach your entire SDLActivity.java, unless there is something commercially sensitive in it... My diff can be found here: http://code.google.com/p/wagic/source/diff?spec=svn3879&r=3879&format=side&path=/trunk/projects/mtg/Android/src/org/libsdl/app/SDLActivity.java&old_path=/trunk/projects/mtg/Android/src/org/libsdl/app/SDLActivity.java&old=3867 That diff also contains things you don't need: adMob integration and things related to "JGE" which is our own engine. The rest are changes to support pause/resume. I hope this helps Created attachment 739 [details]
Support for Pause/Resume in Android
Created attachment 740 [details]
SDLActivity.java with Pause/Resume support
The attached files provide some improvement over the current handling of pause/resume in Android.
- I disabled the exit(status) instruction in SDL_main as that makes the entire app instead of the SDL thread exit (while not needed for pause/resume it is needed for Live Wallpapers, an SDLActivity for which I'll upload in a separate bug).
- Added nativePause and nativeResume which basically just mark the window as visible/hidden, something that the end user needs to take into consideration (ideally pausing the event loop).
Also, this arrangement creates a new GL context when needed, which at least in my test system is every time you go away from the app and come back to it. So, this means that the textures need to be generated again after resuming (a problem the end user didn't have before because the app exited completely when it should've been pausing). I'd like to know if there's a standard way of letting the user know that the GL context has changed and that he needs to refresh his textures, or if this is out of the scope of the library and each user handles it in their own way (I don't know how/if this same thing is handled in the iPhone backend, but it would be wise to try to imitate that).
Also, in the SDLActivity the EGL handling code is moved up to the Activity from the Surface code, as I think it is possible (in theory) that the surface is destroyed temporarily while the context remains alive (though in practice in my test system this is not the case) Created attachment 743 [details]
Support for Pause/Resume in Android via Window Events
Improved handling via window events.
Thanks Gabriel, your changes look fairly clean. There currently isn't any way to signal to the application that textures need to be reloaded, and this is something that probably needs to be added to the API. For now, we should note in the documentation that when you regain focus on Android you need to re-initialize your graphics. This bears further thought, but I'm committing your patch as a place to start. http://hg.libsdl.org/SDL/rev/e565ac981de6 Thanks! Just a thought, would it be possible to have a uniform event for ios/android? Currently on iOS when an app is suspended/resumed it receives SDL_WINDOWEVENT_MINIMIZED/SDL_WINDOWEVENT_RESTORED, it would be nice to have the same events on android. Should this be on a different bug report? Nope, that's a good point. Fixed! http://hg.libsdl.org/SDL/rev/2c0d35b1af4e |