I haven’t been able to completely solve this yet, but I’ve found some documentation of others reporting similar issues. There are some variability in the reports, so I’m not sure if they are all explicitly related.
Best guess currently is that this is an SDK3.0 / OS3.0 / 3GS issue. There has been some discussion https://devforums.apple.com/thread/16515?start=25&tstart=0 on Apple’s dev forums that the issue is relieved in beta versions of the SDK (the specifics of which are under NDA).
The (mostly) successful workaround has been to abandon the NSTimer method for controlling the frame rate in ofxiPhone’s iPhoneAppDelegate in favor of dropping the timerLoop into a separate thread which is throttled by a sleep command. An iPhone 2G (2.2.1) and 3GS (3.0) are both performing at 50fps. I was previously able to occasionally able to achieve 60fps, but if I overran the NSTimer my framerate dropped to 30fps.
The other contributing issue (on the 3GS/3.0) is a drop in framerate during and after the display of a UIAlertView. Specifically, I’m displaying an alert view over the EAGLview, then dismissing it (and releasing the alert view instance) but the frame rate stays at 40fps. Sporadically the framerate will return to 50fps, but I haven’t isolated the causality.
This same alert view code on the iPhone 2G will initially drop the frame rate to 30fps during the “pop” animation, but then quickly returns to 50fps, even while it’s still composited over the EAGLview.
Confusing the matter is that I initially had the application *always* display an alert view at launch (asking if the user would like to restore the game state). It’s likely that this clouded my earlier findings. The app on the 3GS performs at full speed after launch when an alert view is not displayed…and continues to perform optimally until an alert view appears.
In the meantime, worst-case 40fps using NSthread vs 30fps using NSTimer is an acceptable performance gain (for this specific application). I understand that the overhead of running the timerLoop in another thread may degrade performance for CPU intensive programs.
Below are the changes to ofxiPhone iPhoneAppDelegate.mm:
Add this method:
-(void) timerLoopThreaded:(id*)sender {
// create autorelease pool in case anything needs it
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// SET CURRENT GL CONTEXT FOR THREAD
[EAGLContext setCurrentContext:iPhoneGlobals.glView.context];
double begin_time; // RC: testing alternative loop method
while(!kill_loop)
{
iPhoneGlobals.iPhoneOFWindow->timerLoop();
// throttle the loop for desired framerate
double sleep = (1.0/framerate) - (((double)CFAbsoluteTimeGetCurrent()) - begin_time);
if(sleep>0.0) [ NSThread sleepForTimeInterval:sleep];
double end_time = (double)CFAbsoluteTimeGetCurrent();
//float fps = (1.0 / (end_time-begin_time));
begin_time = end_time;
}
// release pool
[pool release];
}
Add this line at the bottom of applicationDidFinishLaunching:
[NSThread detachNewThreadSelector:@selector(timerLoopThreaded:) toTarget:self withObject:nil];
Comment out the existing implementation of setFrameRate and add this substitute:
-(void) setFrameRate:(float)rate {
framerate = rate;
}
Update applicationWillTerminate:
-(void) applicationWillTerminate:(UIApplication *)application {
[timer invalidate];
kill_loop = true;
}
Finally, add in some instance variable declarations to iPhoneAppDelegate.h:
@interface iPhoneAppDelegate : NSObject <UIApplicationDelegate> {
NSTimer *timer;
float framerate;
bool kill_loop;
}
I’m still hunting for the cause of the slowdown on the 3GS. I certainly welcome any advice or warning on the above NSThread implementation. In the meantime I’m going to look for other ways to optimize my logic and drawing code further.
cheers!
-robert
ps. UI stuff has to happen on the main thread. I had to modify my implementation of the ofxUIAlertView wrapper to ensure this. Of course, this is my wrapper and I don’t think anyone else is using it…so this is just for posterity:
void ofxiPhoneAlertView::show(){
// UI events need to occur on the main thread.
[alertViewDelegate performSelectorOnMainThread:@selector(show) withObject:nil waitUntilDone:NO];
}