My iOS game is currently using CADisplayLink for timing of OpenGL rendering operations. I've got a GCD dispatch queue running on a second thread that issues all the OpenGL rendering and state calls to the GPU. Everything works fine, except the timing isn't perfect. I'm seeing frame skips and glitches on occasion, even when the animation interval is changed from 1 (60Hz) to 2 (30Hz).
CADisplayLink calls your selector from the RunLoop on the main thread, which means it can only fire in between other dispatched jobs and input events that the main thread is processing. (I've confirmed this is the case by logging these jobs/events). If those operations take several milliseconds, then CADisplayLink will never be perfectly accurate because it can't interrupt whatever is currently running on the main thread. As I imagine most games do, I'm running game simulation, physics, and scene culling on the main thread.
So what I'm thinking is that I should move all the game simulation and physics stuff off the main thread so that touch events and CADisplayLink can fire as close to when it should be as possible. But I'm not sure if this will solve anything and it's not a trivial amount of work.
I'm wondering, since the presentRenderBuffer call is really the thing that synchronizes your frame with the actual hardware display, maybe all I need is just a really accurate timer that can run in it's own thread (perhaps at higher priority) and trigger the rendering that way. Then I can keep everything on the main thread. It seems all CADisplayLink provides is a way to wait in the case when my code is running faster than 60Hz, so it seems to me that a similar kind of delay can be coded just as easily using a separate thread. What am I missing here?
Related
I'm making an iPhone game which has a quite intense use of pixel shaders. Some effects make my fps rate sometimes drop down to ~22 FPS in the 3GS, but it is around ~27 most of the time.
When the FPS rate is down there, the touch gesture response becomes extremely choppy. In other words, the gesture update time reaches nearly 5hz, which is much slower than the game itself.
Has anyone experienced similar problems? Is there any way around it?
Note1: I'm already using CADisplayLink
EDIT: I had a significant improvement by manually skipping even frames. I'm not sure if that is a good thing to do but the game remained quite playable and I'm sure it is using much less CPU now.
I have a similar situation in one of my applications, where I have very heavy shaders that can lead to slower rendering on older devices, but I still want to have the framerate be as fast as it can on more powerful hardware.
What I do is use a single GCD serial queue for all OpenGL ES rendering-related actions, combined with a dispatch semaphore. I use CADisplayLink to fire at 60 FPS, then within the callback I dispatch a block for the actual rendering action. I use a dispatch semaphore so that if the CADisplayLink tries to add another block to the rendering queue while one is running, that new block is dropped and never added.
I describe this approach in detail in this answer, and you can download the source code for my application which uses this here.
The GCD queue lets you move this rendering to a background thread, which leaves your interface responsive, while scaling the FPS so that your rendering runs as fast as your hardware supports. This has particular advantages on the new dual core iOS devices, because I noticed significant rendering speed increases just by performing my OpenGL ES updates on this background queue.
However, as I describe in that answer, you'll need to funnel all of your OpenGL ES updates through this queue to avoid the potential for more than one thread from simultaneously accessing an OpenGL ES context (which causes a crash).
If your app's game loop run at 22 fps, but is requesting 30 fps, that means that the app is oversubscribing the total number of CPU cycles available per second in the UI run loop. Either try putting more stuff in background threads, or turn your requested frame rate down to below what you can actually get (e.g. set it to 20 fps), so that there is more time left for UI stuff, such as touch event delivery.
Seemingly at random (but typically consistent during any given program run), my presentRenderBuffer call is very slow. I tracked it down to a call to glFlush() which presentRenderBuffer makes, so now I call glFlush() right before presentRenderBuffer. I put a timer on glFlush(), and it does one of two things, seemingly at random.
glFlush() either
1) consistently takes 0.0003 seconds
OR
2) alternates between about 0.019 and 0.030 seconds
The weirdest thing is, this is independent of drawing code. Even when I comment out ALL drawing code so that all it does is call glClear(), I still just randomly get one of the two results.
The drawing method is called by an CADisplayLink with the following setup:
dLink = [[UIScreen mainScreen] displayLinkWithTarget:viewController selector:#selector(drawFrame)];
dLink.frameInterval = 1;
[dLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
I'm finding it impossible to pin down what causes one of the results to occur. Can anyone offer ideas?
Performing exact timings on iOS OpenGL ES calls in general is a little tricky, due to the tile-based deferred renderers used for the devices. State changes, drawing, and other actions can be deferred until right before the scene is presented.
This can often make something like glFlush() or a context's -presentRenderBuffer: look to be very slow, when really it's just causing all of the deferred rendering to be performed at that point.
Your case where you comment out all drawing code but a glClear() wouldn't be affected by this. The varying timings you present in your alternating example correspond roughly to 1/53 or 1/33 of a second, which seems to indicate to me that it might simply be blocking for long enough to match up to the screen refresh rate. CADisplayLink should keep you in sync with the screen refresh, but I could see your drawing sometimes being slightly off that.
Are you running this test on the main thread? There may be something causing a slight blocking of the main thread, throwing you slightly off the screen refresh timing. I've seen a reduction in this kind of oscillation when I moved my rendering to a background thread, but still had it be triggered by a CADisplayLink. Rendering speed also increased as I did this, particularly on the multicore iPad 2.
Finally, I don't believe you need to explicitly use glFlush() when using OpenGL ES on iOS. Your EAGLContext's presentRenderbuffer: method should be all that is required to render your frame to the screen. I don't see a single instance of glFlush() in my OpenGL ES application here. It may be redundant in your case.
I found what I think was the problem. The view controller that was attached to the EAGLView was NOT set as the root view controller of the window as it should have been. Instead, the view was manually added as a subview to the window. When this was remedied (along with a couple other related fixes), the drawFrame method now seems to sync up perfectly with the screen refresh. Success!
I made a game that uses many timers throughout the code. However the timer has to deal with many tasks in such a small amount of time which leads to the problem where there is lag in my game. For example, my timer runs at an interval of (0.05) and it needs to draw and update many of the images on the screen. Is there any way I can distribute the work flow so that the program runs much smoother?
Thanks
Kevin
I would use an NSThread instead of an NSTimer. I have had more success in this area using NSThread because it runs on an independant thread and is not fired off your main ui thread. In the loop for the thread sleep it for 1/20 (your 0.05) of a second. Because the thread is not running on the UI thread all of its tasks should not slow your UI down. However beacsue it is not running on the UI you will have to call performSelectorOnMainThread to get the UI to update from this background thread. I put a lock on my update method (a simple boolean) that says if the last ui update has not happened, just skip this one. then if im running out of processing time i just drop a frame or two here and there. I also do a lot of checking to see if anything has actually changed before i redraw.
Simple solution: Ditch NSTimer.
Move your redrawing code to a single method, then use CADisplayLink. The issue with using your NSTimer approach is that everything is being redrawn too fast or too slow for the screen. By using CADisplayLink, you can synchronize your redraw code to the screen refresh rate. All you need to do then is touch up your code so that it can deal with not being called at a specific time.
And yes, check to make sure you don't need to redraw as Aran Mulholland said above. Just make sure the checks don't take as long as a redraw.
And remember to optimize your code. A lot. Use ivars to access objects, but the whole property (self.myObject =) to set your objects.
Core Animation uses a background thread to do it's job. Now the problem is this: I have a heavy calculation going on in the main thread. Core Animation immediately freezes until that calculation is done. And then it continues to finish it's animations. I remember reading in a document that CA has a low priority in processing time, meaning that whatever the main thread wants to do is high-prio and will be done more likely than any fancy animation at the same time.
I want to force Core Animation to schedule it's background thread nicely with the main thread under any circumstances. Or alternatively a separate thread that will run the heavy calculation outside the main thread. I tried that already, but CA still freezes until that's done. I expect the scheduler to switch processing time quickly between CA and that calculation.
How could CA be forced to keep on working? If things go just a bit slower than, that's fine. But most important thing is that all things keep on going from the users point of view.
You can use [CATransaction flush] to flush core animation if you don't let the runloop take its course
Don't run heavy calculations on the main thread, because they will block the UI and lead to a bad user experience. Run them in a background thread.
That said, the iPhone is a single-core system, so if a calculation is pegging the CPU in one thread, the performance of every other thread may grind to a near halt. If you can, try breaking your calculation into smaller elements and running them as NSOperations in an NSOperationQueue. If you make sure that the calculation segments aren't so small that the overhead of creating an NSOperation for them becomes too large, this might provide a means of throttling the calculation a bit so that your animations aren't being slowed down.
Core Animation tends to perform a number of calculations upfront, before an animation may run, so those may be getting slowed down by your heavy calculation thread. You might also be able to start your heavy calculation within the -animationDidStart: delegate method for your CAAnimation, making the calculation only kick off when the animation is in progress. I believe the progress of an animation uses fewer calculations than its start, so it may be better able to coexist with your heavy calculation.
I'm not sure, but you can try to call [NSThread setThreadPriority:1.0] if you running your CA in another thread.
I have an scroll view with some sophisticated animations happening during scrolling. Although after 2 weeks of finetuning the performance is acceptable now, the scrolling is not 100% smooth when the animations happen.
I know that core animation does animations in a background thread. But I wonder if it would help to split those animation blocks (10 of them at pretty much the same time) into threads.
There are a few methods that look interesting:
– performSelector:onThread:withObject:waitUntilDone:
– performSelectorInBackground:withObject:
or is that nonsense to do?
No, it won't help. As you correctly stated yourself, Core Animation already runs in a seperate thread. Core Animation is smart enough to handle animation blocks as efficiently as possible. I wouldn't advise interfering with it.
The Core Animation Programming Guide says:
An abstract animation interface that
allows animations to run on a separate
thread, independent of your
application's run loop. Once an
animation is configured and starts,
Core Animation assumes full
responsibility for running it at frame
rate.
Are you sure the choppy behavior is really from CA? Do you have anything else going on?
If you have any background network access, consider moving that into a separate thread - the time taken to service those calls takes away from time the UI spends updating the screen as you scroll.