Search code examples
cocoaopenglcore-animation

Layer backed NSOpenGLView + animation timer = strange drawing behavior?


I am creating an application which subclasses NSOpenGLView to do some OpenGL drawings. Now i wanted to add an overlay control, similar to QuickTime X. First thing i tried was setting NSOpenGLCPSurfaceOrder to -1 and making my window none-opaque. It did work, but i lost the windows shadow, so not a viable solution.

I then made my NSOpenGLView subclass layer-backed by calling [setWantsLayer:YES] and adding my control box as a subview. It worked, but i noticed a big drop in performance. I did some research and found this:

I am using an NSTimer object to call a method [timerFired:] 60 times per second. Works perfectly. The only thing i do within this method is calling [self setNeedsDisplay:YES], because when using a layer-backed OpenGLView one needs to overload the [drawRect:rect] method and do all the OpenGL drawing there. The problem: [drawRect:rect] often gets called although the timer didn't fire.

At first i thought "of course it does, it draws the NSOpenGLView, so it might be called by the window manager or something". So deleted my timer object to determine how it was called between [timerFired:] calls. The result: It wasn't called at all, at least not when not resizing or dragging the window.

So next i experimented with my timers time interval. Turns out, up until 55 times per second the [drawRect:rect] is called between 60 and 70 times per second, and from a timer interval of 59 times a second onward the [drawRect:rect] is called between 100 and 120 times a second.

I suspect that this highly unpredictable manner in which my drawing is called leads to the performance loss, either by being uneven or by clogging the thread with a lot of OpenGL drawings and not enough time to be executed or something. I also read that layer-backed OpenGLViews don't work with vsync, although i can't confirm this.

Does anyone have an explanation? Does anyone have an idea on how to only draw my OpenGL on timer fires?

I already tried the naive approach and added a boolean variable, set it true in my [timerFired:] and made my [drawRect:rect] only calling the OpenGL drawing method if the variable is true. The result was an extremely flickery and stuttery animation, so no luck.

What about using an CAOpenGLLayer with asynchronous animation oder CVDisplayLink? Would either help?

edit another thing which might be helpful information: I already used [setNeedsDisplay:YES] without my view being layer-backed, so i could easily switch between layer-backed and not-layer-backed, and i didn't have any of the problems described above, so it definitely has to do with my view being layer-backed. Everything gets a mess by just calling [setWantsLayer:YES].


Solution

  • So i replaced my NSTimer object with a CVDisplayLink and everything is smooth again. Although i don't actually understand why. It seems like requesting a backing layer broke the vertical sync, but it does not explain the random calls to drawRect that appeared out of nowhere.

    Also i would really like to if vsync works with a layer backed NSOpenGLView and / or with a CAOpenGLLayer. I can't find any official information.