4

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?

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
pinerd314159
  • 534
  • 4
  • 10

2 Answers2

3

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.

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • 1
    I am indeed rendering on the main thread. To shift that rendering to another thread, do I make a new thread and add the display link to that other thread's run loop? I think I read that it is inadvisable to update the UI from a thread other than main, but I'll give it a try. Thanks! – pinerd314159 Aug 17 '11 at 20:28
  • 1
    Also, I know that calling glFlush() is redundant, but it was a test to see what exactly is causing the huge slowdown. I found out that the glFlush() is the slow part. If it is called right before presentRenderBuffer, then presentRenderBuffer takes almost no time. – pinerd314159 Aug 17 '11 at 20:29
  • 1
    @pinerd314159 - I went with a GCD queue for this, as I describe here: http://stackoverflow.com/questions/5944050/cadisplaylink-opengl-rendering-breaks-uiscrollview-behaviour/5956119#5956119 . This let me contain all accesses to the OpenGL ES context (resizing frame buffers, rendering, etc.) within a single-wide queue to guarantee no simultaneous access without using expensive locks. You just have to remember to set the current context within each block, because what thread they run on may change and you need to register the context with a given thread before using it. – Brad Larson Aug 17 '11 at 21:07
  • 1
    @pinerd314159 - The reason you see `glFlush()` being slow and then `-presentRenderBuffer:` fast, if you run them in that order, is that `glFlush()` will force the rendering of all deferred items. There won't be anything left to be done then when you hit `-presentRenderBuffer:` a little while later. You will see similar behavior with something like `glReadPixels()`, which halts the rendering pipeline to execute all pending tasks before continuing. – Brad Larson Aug 17 '11 at 21:10
  • You mentioned that when I presentRenderBuffer it forces all the rendering to be done. Why would there still be a problem if the only rendering is glClear()? There is no way that simply clearing the pixels takes up enough time to throw of the sync with the screen refresh… – pinerd314159 Aug 19 '11 at 06:53
  • Also, I'd love to avoid GCDs if possible, because they don't make a lot of sense to me. Alas, I've tried adding the CADisplayLink to the run loop of another thread, and I haven't had any luck. Do you have any other suggestions, or should I just bite the bullet and look at GCD? – pinerd314159 Aug 19 '11 at 06:54
  • 1
    @pinerd314159 - I'd give GCD a second look, because I feel it is an elegant framework for performing multithreaded tasks. It's a different paradigm than you see in standard multithreading, so it takes a little time to get used to, but trust me when I say it is worth learning. I taught a class on the topic that you can see on iTunes U (go to the multithreading session): http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewPodcast?id=407243028 , and the WWDC 2010 session "Introducing Blocks and Grand Central Dispatch on iPhone" is a great tutorial on the topic. – Brad Larson Aug 19 '11 at 15:21
  • @pinerd314159 - I can only speculate on the exact timings for presenting the frame to the screen (you might need to ask Apple's OpenGL engineers in the developer forums for the real story), but I'm guessing that something (like a process temporarily slowing the main thread) is causing your presentation of the frame to the screen to be slightly out of time with the display refresh, leading to it pausing until it lines up again. All I have to base this on is my own experimentation. – Brad Larson Aug 19 '11 at 15:25
  • I will certainly take a look at your class. Thank you so much for your help! In fact I did post a couple weeks ago about this topic on the dev forum and got no response. I'll keep pursuing it, though, and I'll certainly give GCD a try. Maybe it's the answer to my prayers! – pinerd314159 Aug 19 '11 at 20:25
  • I must say, that class is very informative :) In regards to using CADisplayLink with GCD, do I simply call a selector from the display link that adds a block with rendering code to the queue to be run in a background thread? Should I instead add the display link to the run loop of a different thread altogether? – pinerd314159 Aug 20 '11 at 08:18
  • @pinerd314159 - The way I've done it (which you can see in the source code for this application: http://sunsetlakesoftware.com/molecules ) is to place a block on my OpenGL ES rendering queue in response to the CADisplayLink firing, unless there's already a block being processed. The only case I can think of where you might want to have the CADisplayLink on a background thread would be if you wanted precise timings and were afraid of tasks running on the main thread (like touch interaction) preventing the CADisplayLink from firing at the right time. – Brad Larson Aug 22 '11 at 14:41
1

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!

pinerd314159
  • 534
  • 4
  • 10