NSTimer seems better than CVDisplayLink (Mac)

I have a entry level Mac Mini 2014 and I have tested CVDisplayLink vs using NSTimer (with a desired interval of 0.001), as explained here: https://developer.apple.com/library/mac/qa/qa1385/_index.html

I started using CVDisplayLink but I noticed that if my frame takes more than ~10-13 ms to complete then it will skip a frame.

I use OpenGL (single buffering) and test to call glFinish instead of glFlush so that the thread wait for vsync. I then measure the time it takes from the CVDisplayLink callback to end of glFinish. Lo and behold, the time is about ~10-13 ms.

This makes me think think that the scheduler don't call my CVDisplayLink callback as soon as vertical sync is over, but waits 3-6 ms (on my machine), depending on whatever else my computer wants to do.

Of course I want my 16 ms per frame and next I test using an NSTimer instead, with interval of 0.001 so that it fires as soon as possible. When I measure time from my timer callback to glFinish and I get reliable 15-16ms. W

This leeds me to conclude that NSTimer is the better option of the two.

Just wanted to share.
glFinish and glFlush are completely different things from vsync. Calling or not calling these two functions in no way enables or disables vsync.

If you are doing single-threaded rendering then there is little point to call glFinish or glFlush functions.

Edited by Mārtiņš Možeiko on
glFinish and glFlush is completely different things from vsync. Calling or not calling these two function in no way enables or disables vsync.

Hah, yes that's true. Thanks for point that out! That means my data is bad. :-/

I changed to measure the time it takes between each callback from either NSTimer or CVDisplayLink. NSTimer takes in average (3 times with 2000 samples) 16.83 ms between each call, and CVDisplayLink takes average 17.35 ms between each call. Which means I get fewer frames dropped, but not as much as previous data showed.

If you are doing single-threaded rendering then there is little point to call glFinish or glFlush functions.

Do you mind to elaborate? Without calling glFinish or glFlush I'm not getting any my OpenGL calls processed, resulting in white screen.

Edited by Björn Berggren on
My understanding is that glFinish/Flush just signals to OpenGL driver that it needs to finish submitting all the work to GPU and only then return to application. But it doesn't instruct OS/GPU to actually display something. For that you typically use OS functionality. On Windows it is SwapBuffers, with GLX it is glXSwapBuffers, with EGL it is eglSwapBuffers.

I don't have any experience with OSX programming. So I don't really how OpenGL behaves there. Does it have something like SwapBuffers/glXSwapBuffers/eglSwapBuffers? If yes, then you should call when you want to display what you rendered, not glFlush/Finish.

From something I just quickly googled now, it looks like you need to call flushBuffer on NSOpenGLContext object, if you are using NSOpenGLContext. Here's example that uses CVDisplayLinkRef: https://gist.github.com/astarasikov/7794123

Also make sure you have double buffering enabled when creating OpenGL context. It will give better performance, nobody really uses single-buffering unless you really-really have a very good reason to do so.

Edited by Mārtiņš Možeiko on
Ah, I see here this:
Buffer swapping routines (the flushBuffer method of the NSOpenGLContext class or the CGLFlushDrawable function) implicitly call glFlush. Note that when using the NSOpenGLContext class or the CGL API, the term flush actually refers to a buffer-swapping operation. For single-buffered contexts, glFlush and glFinish are equivalent to a swap operation, since all rendering is taking place directly in the front buffer.

This probably means that you are creating single-buffered context, that's why you need glFlush to see some output. As the docs say - for single-buffer glFlush is equivalent to swap operation.

But don't do that. Create double-buffered contex and call flushBuffer method.
Because on single-buffered context (quote from link above):
On a single-buffered context, flushing may also cause visual anomalies, such as flickering or tearing.

Edited by Mārtiņš Možeiko on
Yeah, I use CGLFlushDrawable when I use double buffering. I've tried it some, but atm I'm vexed that I cannot get reliable frame rate on a single buffer. I suspect it is my computer that is too weak to run custom app at 60Hz as well as all the other background processes which comes in a "modern" OS.

:sick:

Edited by Björn Berggren on
I played with NSTimer and CVDisplayLink a long time ago and I did not like them. A better way, in my opinion, would be to make a "traditional" game loop with a simple while - no timers and no callbacks.

You use Vsync to avoid burning cpu cycles.

Basically this in pseudo-code:

loop {
UpdateGame();
CallOpenGLStuff();
NSOpenGLContext#flushBuffer();
}

The flushBuffer call would then block until Vsync - and immediately after start over the loop. As you can see no need for callbacks. I think it is much simpler.

If you like the sound of this and want to investigate further, I can recommend you read the source code for GLFW https://github.com/glfw/glfw - check out the OSX adapter.

By the way, this advice only applies to OSX and not necessarily iOS.

Edited by Rasmus Rønn Nielsen on
Cool!

Are there any negatives to this approach? Are you able to respond to messages like:
- window will minimise
- window will enter fullscreen
- window will hide
other stuff to do with first responder, key window, menubar or easily resize window.
dompadidodadollidado
Cool!

Are there any negatives to this approach?
Well, in the end it is subjective whether you like the design or not. But personally I don't see any negatives as long as you make sure to use vsync properly (and don't burn needless cycles).

dompadidodadollidado
Are you able to respond to messages like:
- window will minimise
- window will enter fullscreen
- window will hide
other stuff to do with first responder, key window, menubar or easily resize window.
Yes. There are examples of this in GLFW I referred.

Here is another simpler Handmade Hero example:
https://github.com/tarouszars/han...b/master/code/handmadehero.mac.mm