Skip to content

Options for handling EGL Context loss on Android

RH edited this page Sep 19, 2024 · 1 revision

Under certain conditions, Android may free the current EGL context, meaning all graphical data is no longer valid. This can occur due to certain events, for example:

  • Application is in the background, and OS needs to free up resources
  • Android OS setting changes that affect current EGL context, and require them to be recreated (such as changing system font in Android settings)

Recovery from an EGL context loss means reloading all image data from disk and recreating all textures. This part is fine, but where we run into problems is with textures created at runtime, such as via RenderTexture, which cannot simply be reloaded from any media, since they only exist in memory.

In Axmol, an event is dispatched, named EVENT_RENDERER_RECREATED, which triggers the recovery process, to reload all textures from disk. Unfortunately, there is no simple way to recover RenderTexture texture data, since it does not exist on disk. If your application makes use of RenderTexture, then you have a number of options available to you on how to handle this:

  1. Add an event listener for the EVENT_RENDERER_RECREATED, and on that event, re-create all RenderTexture textures based on the data that you would have kept track of in your app.
  2. Force your entire app to restart via the AX_ENABLE_RESTART_APPLICATION_ON_CONTEXT_LOST config option in PlatformMacros.h. You can set this in your CMakeLists.txt via:
if(ANDROID)
    add_definitions(-DAX_ENABLE_CACHE_TEXTURE_DATA=0 -DAX_ENABLE_RESTART_APPLICATION_ON_CONTEXT_LOST=1)
endif()
  1. To avoid a full app restart, add code to restart from the first scene. This is a little bit more involved, since your application would need to have dummy scene in the scene stack in order to use Director::getInstance()->popToRootScene() and then clear all references to texture data, since those references point to invalid data due to the EGL context loss. An example of this is below:
void AppDelegate::applicationDidFinishLoading()
{
...
...
    auto* director = Director::getInstance();

    // Add a dummy root scene. This should be your own scene inheriting from ax::Scene, but for 
    // the purposes of this example, we will just use "ax::Scene"
    auto* rootScene = utils::createInstance<Scene>();
    director->runWithScene(rootScene);  // Start up with this scene

    // Now, push your real initial scene on top of the root scene that is already in the stack
    // which causes this new scene to be the current running scene
    auto* initialScene = utils::createInstance<YourInitialScene>();
    director->pushScene(initialScene);

#if AX_ENABLE_RESTART_APPLICATION_ON_CONTEXT_LOST == 0
    // add a listener for EVENT_RENDERER_RECREATED
    director->getEventDispatcher()->addCustomEventListener(EVENT_RENDERER_RECREATED, [director](EventCustom*) {
        AXLOGI("GL Context Lost.");
        
        // Pop all scenes back to the root scene
        director->popToRootScene();
         
        // Create a dummy scene that will be overriden after the first update of that scene
        // and the sole purpose of this is to ensure all references to textures are released
        // before we attempt to clear the texture cache
        auto dummyScene = utils::createInstance<Scene>();
        director->pushScene(dummyScene);
        
        // Schedule a single callback on the first update, which is used
        // to clear all references to the invalid texture data, and 
        // also to load our initial scene
        dummyScene->scheduleOnce([director](float){
            FontAtlasCache::purgeCachedData();
            FontFNT::purgeCachedData();
            director->getTextureCache()->removeAllTextures();

            auto* scene = utils::createInstance<YourInitialScene>();
            director->replaceScene(scene); // replace the current dummy scene with the actual scene we want to start with
        }, 0.0f, "RESET_VIEW");
    });
#endif
}