osx_idle_app_high_cpu - oxygine/oxygine-framework GitHub Wiki

Dealing with high CPU usage on OS X when the app is idle

The problem

On OS X game updates are bound to display refresh rate. But when the window is hidden or entirely covered by another window, CPU usage suddenly jumps to 100%.

Fix by patching SDL (optional)

I've compared the code of SwapWindow with GLFW implementation which cocos2d-x uses internally, and it seems GLFW doesn't call updateIfNeeded, it only calls flushBuffer.

In SDL/src/video/cocoa/SDL_cocoaopengl.m comment out updateIfNeeded:


Cocoa_GL_SwapWindow(_THIS, SDL_Window * window)
{ @autoreleasepool
{
    SDLOpenGLContext* nscontext = (SDLOpenGLContext*)SDL_GL_GetCurrentContext();
    [nscontext flushBuffer];
    //[nscontext updateIfNeeded]; // <--- COMMENT OUT
}}

Commenting out updateIfNeeded brought CPU usage from 100% to ~5%. Yet this is not a complete fix. The function is called a lot when the window is inactive:

Cocoa_GL_SwapWindow: 60 fps
Cocoa_GL_SwapWindow: 60 fps
Cocoa_GL_SwapWindow: 60 fps
Cocoa_GL_SwapWindow: 60 fps
[Window hidden]
Cocoa_GL_SwapWindow: 2964 fps
Cocoa_GL_SwapWindow: 3045 fps
Cocoa_GL_SwapWindow: 3095 fps
Cocoa_GL_SwapWindow: 2999 fps

Add sleeps to game loop

This is the approeach used by Cocos2d-x on Mac OS X. Below is an example of modified game loop in main.cpp:


    //here is main game loop
    while (1)
    {
#ifdef TARGET_OS_MAC
        Uint32 start = SDL_GetTicks();
#endif
			
        int done = mainloop();
        if (done)
            break;

#ifdef TARGET_OS_MAC
        Uint32 duration = SDL_GetTicks() - start;
        if (duration < 1000 / 60)
            usleep((1000 / 60 - duration) * 1000);
#endif
	}

Done! CPU usage ~4.5%.

The downside is that usleep sometimes leads to frame drops (59-60 fps instead of steady 60). For fast-paces games you may want to lower threshold to avoid usleep as much as possible, but not too low, so high-HZ monitors are still throttled, but 75 hz plays full speed:

        if (duration < 1000 / 90) // sleep only if framerate is ~1.5x the normal or more
            usleep((1000 / 60 - duration) * 1000);

If you'll experiment further, this code for measuring FPS might come in handy:

Cocoa_GL_SwapWindow(_THIS, SDL_Window * window)
{ @autoreleasepool
{
    static int t = 0, n = 0;
    if (t != time(0)) {
        printf("Cocoa_GL_SwapWindow: %d fps\n", n);
        n = 0;
    }
    t = time(0);
    ++n;

    SDLOpenGLContext* nscontext = (SDLOpenGLContext*)SDL_GL_GetCurrentContext();
    [nscontext flushBuffer];
    //[nscontext updateIfNeeded];
}}

TODO: Another solutions to consider

  • NSTimer of 1 msec + waiting for vsync;
  • CVDisplayLink.