OpenGL render loop - NSTimer vs rendering thread

Member
Posts: 32
Joined: 2008.10
Post: #1
The game I'm developing for the iPhone has 2 play modes (the second mode has more on screen objects). In the first game mode, I get a solid 29-30 frames per second (acceptable), however the second mode with more objects gives me 25 fps (slightly jerky). BTW, on a PC with modern hardware I get >1300 fps @1680x1050.

At first I thought that the NSTimer resolution might be causing problems. The Apple demos all use a resolution of 1/60. Setting a value of 0.0 actually bumps the frame rate up by few frames, but I lose the ability to process touch input (crap). Adding usleep(a_delta) hoping to invode a context switch does nothing for the touch input.

The next thing I tried was creating a dedicated rendering thread which basically does a while (1) render(); The thread will loop as fast as it can, and I regain touch input. On the plus side, I gained 3-4 fps for both game modes, so now I can hit 30 fps for the second mode (sweet).

To make this work, I obviously needed to add locking primitives between the render thread and the iphone input (touch, sleep, phone call etc). The render loop is now basically:

while (alive)
{
aLock->Lock();
render();
aLock->Unlock();
}

Anyway, I thought people would appretiate my experience when looking to squeeze a few more frames out of the iPhone. You just need to understand multiprocessing to work out the synchronisation issues.

PS. Ideally, it would be simpler to use NSTimer resolution of 0.0 if someone can figure out how to regain touch input. Maybe future iPhone firmware updates might address this issue.
Luminary
Posts: 5,143
Joined: 2002.04
Post: #2
0.0 doesn't work for some reason, but people have had success with 0.001 or 0.002...

Since the iPhone's a uniprocessor, using multiple threads shouldn't be a performance win.
Moderator
Posts: 3,571
Joined: 2003.06
Post: #3
I do the .001 thing on the Mac, but the smoothest/best performance I've seen on iPhone so far is with a render timer of 1/60 of a second, just like the Apple stuff, and it's pretty smooth, actually. Multi-threads do nothing but complicate things on iPhone from what I've seen -- as OSC said, it isn't multi-processing.

The only problems I've seen from touch input is an intermittent loss of touches up, but that *seems* like an OS-wide issue.
Member
Posts: 32
Joined: 2008.10
Post: #4
Setting a NSTimer interval of 0.001 doesn't work for me. Dont forget that with a timer, you are limited to the OS schedulers resolution.

Even on a uniprocessor system, you are still running multiple processes. I've just added another to the mix (besides, I'm also running 2 threads for networking, but they're usually blocked). My physics routine for the phone is tied to the render task, while on a real PC it is in it's own thread.

But I really dont care what anyway on this forum claims, my benchmarks are showing me that I'm getting 32.7fps with a dedicated thread, while when using NSTimer (regardless of value) I'm getting 24-25fps. I'm just illustrating my empirical bencmark results, hoping that it will help others.

So yes, there is a gain to be made.
Member
Posts: 32
Joined: 2008.10
Post: #5
Here are some more results (3 sections of my game):

NSTimer(1/60)
Benchmark 1 - 29-30 fps
Benchmark 2 - 24-25 fps
Benchmark 3 - 42 fps

Render Thread
Benchmark 1 - 41-42 fps
Benchmark 2 - 31-32 fps
Benchmark 3 - 50-51 fps

I rest my case.
Moderator
Posts: 3,571
Joined: 2003.06
Post: #6
Nobody is claiming your benchmarks are bull.

Multi-threading on a single processor system won't get you any "gain", but you might get some better "efficiency". For instance, if X is waiting on Y, which is waiting on system input Z, then yes, X can be processing in its own thread while Y is waiting for Z.

It depends on your program structure within the host environment. The way you do it isn't the way I do it, so multi-threading doesn't necessarily mean I will see the same benefits as you. ... and I don't, actually, I get roughly the same performance whether I'm threaded or not on iPhone, whereas on a multi-processor desktop I can get substantial gains being multi-threaded.
Nibbie
Posts: 4
Joined: 2008.10
Post: #7
I've done a lot of testing regarding this, and I also have some clarifications Smile

NSTimer is bad because it very inaccurate. In fact, the documentation says its resolution is about 50ms, but thankfully it's actually much better. The way it works is that it runs somewhere on the side (presumably in its own thread), and at specific time intervals, which you have set, puts a message in the queue of the run loop to call the function you've set as its action.
Of course, if things go wrong somewhere, i.e. something lasts longer than expected, or the run loop doesn't get a chance to run for some time, you message will be late. So if you need precise timing, don't rely on timer's intervals. Always measure it yourself, and calculate everything with regard with the delta.

But there is another problem.
Mac OSX / iPhone OS has a very weird way of processing events. You can find this info in the threading programming guide under run loops, but here it is briefly.

The run loop goes through a series of steps every pass. I'll leave out most of them here, refer to the documentation for more details. But the important part is this:
It will check if there are any events waiting on the queue. If there are, it will skip the rest of the steps, and go to the part where it actually extracts these events from the queue and processes them. But the problem is that it will always first check if there are any user-generated events. Your timer is one of these. Now, everything runs fine while your rendering takes less time than the timer's interval. For example, if you've set timer's interval to 1/30, then things will work until your rendering lasts shorter than that.
But once it starts lasting longer, the following happens: while you are rendering your frame, the timer realizes that it's time for another call to your function, and puts the message in the queue. Now, when your rendering function returns and the run loop gets another pass, it will immediately call your action function again. As I've written above, your own events have the precedence. So no matter how many touch events there are in the queue, they will not be processed because your rendering function will always have priority. That's why I say the run loops act weird Smile

When you have a separate thread, things are a bit different because your thread will be paused from time to time, and then the main thread's run loop will have the chance to process the touch events. You should also get a smoother framerate (i.e. without sudden drops) because your rendering function won't be skipped for too long just because the run loop's message queue got filled with something irrelevant.

I have one question for smallstepforman:
what exactly did you lock in your approach? In OSX, you'd have to take care of OpenGL context switching besides locking your own data, but I'm not sure how it works on the iPhone. Once you've inserted your locks, did you experience any unexpected behavior?

I hope I've helped Smile

Ivan
Member
Posts: 32
Joined: 2008.10
Post: #8
Code:
/*    FUNCTION:        render_thread
    ARGUMENTS:        data
    RETURN:            n/a
    DESCRIPTION:    Update screen
*/
static int render_thread(void *data)
{
    float sPreviousTime = YPlatform_GetTime() - 1.0f/60.0f, sTotalTime = 0.0f;
    int sNumFrames = 0;
    
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    while (gRenderThreadKeepAlive)
    {
        gRenderLock->Lock();
        
        [EAGLContext setCurrentContext:context];
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
        
        float current_time = YPlatform_GetTime();
        float delta = (current_time - sPreviousTime);
        sPreviousTime = current_time;
        sTotalTime += delta;
        if (sTotalTime > 1.0f)
        {
#if Y_DEBUG_MODE
            if (yarra_ShowFPS)
            YPlatform_Debug("fps = %f\n", sNumFrames/sTotalTime);
#endif        
            sTotalTime = 0.0f;
            sNumFrames = 0;
        }
        
        sNumFrames++;
        theGame->Render(delta);
        
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
        [context presentRenderbuffer:GL_RENDERBUFFER_OES];
        
        theGame->PostRender(delta);
        gRenderLock->Unlock();
    }
    //    Signal stopAnimation that we're done
    gRenderThreadKeepAlive = true;
    
    [pool drain];
    YPlatform_ExitThread();
    return 0;
}

I also kill the thread when the game is suspended, and restart it afterwads (hence the weird flags).
Luminary
Posts: 5,143
Joined: 2002.04
Post: #9
You've got a [potential] memory leak.
Member
Posts: 32
Joined: 2008.10
Post: #10
OneSadCookie Wrote:You've got a [potential] memory leak.

Uh? The only possible leak is killing the app unexpectadly, so that noone can actually kill the render thread, hence not release the pool. But in the very next cycle, theGame is no longer valid, hence the thread will trigger an invalid misaddress and be terminated anyway. Dont you love systems with memory protection.

The only purpose of the pool is to handle Obj-C code which determines resource folder path name (which uses NSString), and to append that to requested texture file name. The NSString object lives on the stack and is released the moment I exit that function. Hence each acquisition has a corresponding release.

So what exactly are you concerned with? I'd really like to know. Smile
Member
Posts: 269
Joined: 2005.04
Post: #11
igalic Wrote:But there is another problem.
Mac OSX / iPhone OS has a very weird way of processing events. You can find this info in the threading programming guide under run loops, but here it is briefly.

The run loop goes through a series of steps every pass. I'll leave out most of them here, refer to the documentation for more details. But the important part is this:
It will check if there are any events waiting on the queue. If there are, it will skip the rest of the steps, and go to the part where it actually extracts these events from the queue and processes them. But the problem is that it will always first check if there are any user-generated events. Your timer is one of these. Now, everything runs fine while your rendering takes less time than the timer's interval. For example, if you've set timer's interval to 1/30, then things will work until your rendering lasts shorter than that.
But once it starts lasting longer, the following happens: while you are rendering your frame, the timer realizes that it's time for another call to your function, and puts the message in the queue. Now, when your rendering function returns and the run loop gets another pass, it will immediately call your action function again. As I've written above, your own events have the precedence. So no matter how many touch events there are in the queue, they will not be processed because your rendering function will always have priority. That's why I say the run loops act weird Smile

Wow, that totally solved a problem I was having without even having to ask.

I was having serious issues with touch events getting totally ignored for several seconds, but my framerate wasn't being seriously impacted. Looks like this is the culprit.
Luminary
Posts: 5,143
Joined: 2002.04
Post: #12
smallstepforman Wrote:So what exactly are you concerned with? I'd really like to know. Smile

You don't periodically release your autorelease pool, and you do send ObjC messages to objects whose implementation you don't control within the loop. If (now or in the future) one of those calls allocates and autoreleases an ObjC object, you will leak.
Member
Posts: 32
Joined: 2008.10
Post: #13
OneSadCookie Wrote:You don't periodically release your autorelease pool, and you do send ObjC messages to objects whose implementation you don't control within the loop. If (now or in the future) one of those calls allocates and autoreleases an ObjC object, you will leak.

Periodic release? I'm completely new to Obj-C, but I do have 12 years embedded C++ experience. According to Cocoa Programming book, "in a Cocoa application, an autorelease pool is created before every event is handled and is drained after the event has been handled". I've bypassed the run loop, hence the event/autorelease cycle. Is there a way to periodically release resources?

Originally I didn't need an autorelease pool, but later added it due to warnings. The cultrit ended up being the code which extracts resources from a bundle (NSString), which I autorelease then and there (an NSString). Instruments shows no leaks after an hour of loading/unloading bundle resources.

I'm actually quite annoyed with Objective-C memory management. Bjarne's language is just much more thoughtful than Brad's language.
Moderator
Posts: 133
Joined: 2008.05
Post: #14
There is an autorelease pool that is created and maintained by the run loop. When you create another autorelease pool(on another thread, for example), you are in charge of draining it.

The dislike of Objective-C memory management is nearly always because of a misunderstanding of it. I've never met a C++ programmer come to Objective-C, become fluent, and appreciate C++ memory management more.
Luminary
Posts: 5,143
Joined: 2002.04
Post: #15
smallstepforman Wrote:Instruments shows no leaks after an hour of loading/unloading bundle resources.

Objects in NSAutoreleasePools are still reachable (by the autorelease pool), so won't show as leaks. That doesn't mean they're not leaked Wink

Quote:I'm actually quite annoyed with Objective-C memory management. Bjarne's language is just much more thoughtful than Brad's language.

You *are* kidding, right?
Thread Closed 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  NSTimer fine, CADisplayLink having some issues monteboyd 5 9,615 Aug 31, 2010 07:05 PM
Last Post: Skorche
  emulator slow when rendering from a second thread captainfreedom 1 2,834 Jan 30, 2010 05:05 PM
Last Post: ChrisD
  Alternative to NSTimer demonpants 78 43,237 Jan 11, 2010 01:52 PM
Last Post: riruilo
  NSTimer hiccups/choppy/jerky jeonghyunhan 2 2,877 Sep 24, 2009 07:35 PM
Last Post: jeonghyunhan
  Modfied OpenGL Sprite Rendering tonyb 3 2,925 Jul 26, 2009 07:30 AM
Last Post: tonyb