Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] [Feature Request?] Support Pause/Resume #474

Closed
SDLBugzilla opened this issue Feb 10, 2021 · 0 comments
Closed

[Android] [Feature Request?] Support Pause/Resume #474

SDLBugzilla opened this issue Feb 10, 2021 · 0 comments

Comments

@SDLBugzilla
Copy link
Collaborator

This bug report was migrated from our old Bugzilla tracker.

These attachments are available in the static archive:

Reported in version: HG 2.0
Reported for operating system, platform: Android (All), Other

Comments on the original bug report:

On 2011-08-28 19:08:59 +0000, wrote:

The current SDLActivity.java samples that can be found in the repository assume that when the SDLSurface object is destroyed, it means the (native) app needs to quit.
This leads to problems, basically there are cases when the SDLSurface is destroyed while the game is still supposed to be "alive" in the background. Simple use cases include : getting a phone call, showing a webview (for people who want to include ads in their app), or pressing the home button.

I am not an Android expert but it seems the SDLActivity java file should have code to "deinit" EGL. In parallel, the native code needs to have "pause" and "resume" functions that can be called from Java. SDLActivity should basically handle onPause/onStop/onStart/onResume, and SDLSurface should not assume that surfaceDestroyed == quit the game.

I believe the ScummVM port on android had some code that does things more gracefully, it might be a good source of inspiration.

I marked this as a feature request, but it is such a "normal" use case on an android phone (multitasking/getting phone calls/showing ads) that I think it should be handled as a bug

On 2011-09-01 06:53:43 +0000, wrote:

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.

On 2011-09-16 05:53:30 +0000, Tim Angus wrote:

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...

On 2011-09-16 18:53:30 +0000, wrote:

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

On 2011-12-23 12:48:55 +0000, Gabriel Jacobo wrote:

Created attachment 739
Support for Pause/Resume in Android

On 2011-12-23 12:55:11 +0000, Gabriel Jacobo wrote:

Created attachment 740
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).

On 2011-12-23 12:57:10 +0000, Gabriel Jacobo wrote:

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)

On 2011-12-28 10:23:49 +0000, Gabriel Jacobo wrote:

Created attachment 743
Support for Pause/Resume in Android via Window Events

Improved handling via window events.

On 2012-01-07 22:09:01 +0000, Sam Lantinga wrote:

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!

On 2012-01-08 05:18:05 +0000, Vittorio Giovara wrote:

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?

On 2012-01-08 10:42:46 +0000, Sam Lantinga wrote:

Nope, that's a good point. Fixed!
http://hg.libsdl.org/SDL/rev/2c0d35b1af4e

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant