Our company develops several games for mobile platforms, including Android. We use OpenGL for all visual items, including UI (more technical details are below).
We have received some weird warnings from Google Play Console in Pre-launch report, like “Your app took 20764 ms to launch”. On the video provided with this report, the game took about a second to start.
After some investigation we found that Android Systrace cannot detect OpenGL draws made from another thread. So Pre-launch tests think (wrongly) that our game is super-slow.
Is there some method to notify the system that a frame is drawn? It’s seems that eglSwapBuffers() is not enough.
There’s a link to the same problem with Cocos2d: https://discuss.cocos2d-x.org/t/frozen-frames-warnings-by-google-play-pre-launch-report-for-3-17-cocos-demo-app/42894
Some details
When a new build is published to Google Play Console, some automated tests are performed on different devices. Results of these tests are available in Pre-launch report section of the Google Play Console.
Starting from beginning of April we receive strange performance warnings on some of devices (always the same ones). Two examples:
- Startup time: Your app took 20764 ms to launch…
- Frozen frames: 33.33% of the frames took longer than 700ms to render
Both problems sound dreadful--would have they be true. But when we examined videos of testing, we could not see any problems. All games started pretty fast and ran without visual stuttering.
Systrace report
This is the picture of systrace showing 5 seconds of our game being started (rectangles were drawn by me).
As you can see, the systrace have found only 4 frames rendered (the pink rect), which were drawn from the RenderThread. But by some reason Android cannot detect our GL draw calls which are performed in another thread (blue rects).
Pre-launch reports also displays only 3 to 4 frames, each 300-400 ms long.
Initialization code
Our game engine runs all game logic and render code in a separate thread. This is simplified initialization code.
The worker thread is created from our Activity’s onStart() overriden method.
public class MyActivity extends Activity
{
protected Thread worker = null;
private native void Run();
@Override
protected void onStart()
{
super.onStart();
if(worker == null)
{
worker = new Thread()
{
public void run()
{
Run();
}
};
worker.start();
}
}
}
The only thing the thread does is the Run() native function. This function may be resolved into something like this:
void MyActivity::Run()
{
initApp();
while(!destroyRequested())
{
// Process the game logic.
if (activated && window != NULL)
{
time->process();
input->process();
sound->process();
logic->process();
graphics->draw();
}
}
clearApp();
}
As you can see, the worker thread constantly spins the update-and-draw loop. Vsync protects the loop from overperforming. Heavy operations like resource loading are done asynchronously to avoid freezes.
From the user side this approach works just fine. Games are loading fast and go smoothly.