使用NSOpenGLLayer从单独的线程中绘制 [英] Draw from a separate thread with NSOpenGLLayer
问题描述
我正在开发一个应用程序,需要使用OpengGL绘制,刷新率至少等于显示器的刷新率。我需要在一个单独的线程中执行绘图,使绘图永远不会被强烈的UI操作锁定。
其实我使用的是 NSOpenGLView
与 CVDisplayLink
结合使用,我可以毫无问题地启用60-80FPS。
由于我还需要在这个视图的顶部显示一些可可控件,我试图子类 NSOpenGLView
,并使其支持layer, LayerBackedOpenGLView Apple example。
因此,我使用单独的 NSWindow
NSOpenGLView
的主窗口的子窗口。 它工作正常,我能够得到与初始实现相同的FPS。
由于我认为这个解决方案很像一个脏的黑客,
几天前我遇到了 NSOpenGLLayer
最后,在所有的前言之后,这里是我的问题:
是可以使用 CVDisplayLink
回调从单独的线程中绘制 NSOpenGLLayer
?
到目前为止,我已经尝试实现这个,但是我不能从 CVDisplayLink
回调中绘制。我只能从 CVDisplayLink $ c $中的
NSOpenGLLayer
中 -setNeedsDisplay:TRUE
c>回调,然后在 -drawInOpenGLContext:pixelFormat:forLayerTime:displayTime:
中执行绘图,当它被可可自动调用时。但我想这样我从主线程,是不是?
在googling之后,我甚至发现了此帖子中,用户声称,根据狮子绘图只能在 -drawInOpenGLContext:pixelFormat:forLayerTime:displayTime:
中出现。
我错过了什么?
是的,这是可能的,虽然不推荐。在CVDisplayLink中的图层上调用 display
。这将导致 canDrawInContext:...
被调用,如果返回YES, drawInContext:...
调用和所有这些在任何线程调用 display
。要使渲染的图像在屏幕上可见,您必须调用 [CATransaction flush]
。这个方法已经建议在苹果邮件列表,虽然它不是完全没有问题(其他视图的显示方法可能会调用你的后台线程,以及并不是所有的视图都支持从后台线程呈现)。
推荐的方法是使图层异步并在主线程上渲染OpenGL上下文。如果你不能以这种方式实现一个好的帧速率,由于你的主线程在其他地方忙,建议把其他的(几乎整个应用程序逻辑)移动到其他线程(例如使用大中央调度),只保留用户输入和在主线程上绘制代码。如果你的窗口很大,你可能仍然得不到什么比30 FPS(一帧每两次屏幕刷新),但是这是从事实,CALayer组成看起来是一个相当昂贵的过程,它已经优化了或多或少静态图层(例如包含图片的图层),而不是更新自己的图层60 FPS。
如果你正在编写一个3D游戏,建议不要混合使用CALAYERS与OpenGL内容。如果你需要Cocoa UI元素,要么保持它们与你的OpenGL内容分离(例如将窗口水平分割成只显示OpenGL的部分和只显示控件的部分),或者自己绘制所有控件(这在游戏中很常见)。
最后但并非最不重要的是,两个窗口的方法并不像你想象的那么奇怪,这就是VLC(视频播放器)如何绘制其控制视频图像也由Mac上的OpenGL渲染)。
I'm working on an app which needs to draw with OpengGL at a refresh rate at least equal to the refresh rate of the monitor. And I need to perform the drawing in a separate thread so that drawing is never locked by intense UI actions.
Actually I'm using a NSOpenGLView
in combination with CVDisplayLink
and I'm able to achive 60-80FPS without any problem.
Since I need also to display some cocoa controls on top of this view I tried to subclass NSOpenGLView
and make it layer-backed, following LayerBackedOpenGLView Apple example.
The result isn't satisfactory and I get a lot of artifacts.
Therefore I've solved the problem using a separate NSWindow
to host the cocoa controls and adding this window as a child window of the main window containing the NSOpenGLView
.
It works fine and I'm able to get quite the same FPS as the initial implementation.
Since I consider this solution quite like a dirty hack, I'm looking for an alternative and more clean way of achiving what I need.
Few days ago I came across NSOpenGLLayer
and I thought that it could be used as a viable solution for my problem.
So finally, after all this preamble, here comes my question:
is it possible to draw to a NSOpenGLLayer
from a separate thread using CVDisplayLink
callback?.
So far I've tried to implement this but I'm not able to draw from the CVDisplayLink
callback. I can only -setNeedsDisplay:TRUE
on the NSOpenGLLayer
from the CVDisplayLink
callback and then perform the drawing in -drawInOpenGLContext:pixelFormat:forLayerTime:displayTime:
when it gets automatically called by cocoa. But I suppose that this way I'm drawing from the main thread, isn't it?
After googling for this I've even found this post in which the user claims that under Lion drawing can occur only inside -drawInOpenGLContext:pixelFormat:forLayerTime:displayTime:
.
I'm on Snow Leopard at the moment but the app should run flawlessly even on Lion.
Am I missing something?
Yes, it is possible, though not recommend. Call display
on the layer from within your CVDisplayLink. This will cause canDrawInContext:...
to be called and if it returns YES, drawInContext:...
will be called and all this on whatever thread called display
. To make the rendered image visible on screen, you have to call [CATransaction flush]
. This method has been suggested on the Apple mailing list, though it is not completely problem free (the display method of other view may get called on your background thread as well and not all views support rendering from a background thread).
The recommend way is to make the layer asynchronous and render the OpenGL context on main thread. If you cannot achieve a good framerate that way, since your main thread is busy elsewhere, it is recommend to rather move everything else (pretty much your whole application logic) to other threads (e.g. using Grand Central Dispatch) and only keep user input and drawing code on the main thread. If your window is very big, you may still not get anything better than 30 FPS (one frame ever two screen refreshes), yet that comes from the fact, that CALayer composition seems a rather expensive process and it has been optimized for more or less static layers (e.g. layers containing a picture) and not for layers updating themselves 60 FPS.
E.g. if you are writing a 3D game, you are advised not to mix CALayers with OpenGL content at all. If you need Cocoa UI elements, either keep them separated from your OpenGL content (e.g. split the window horizontally into a part that displays only OpenGL and a part that only displays controls) or draw all controls yourself (which is pretty common for games).
Last but not least, the two window approach is not as exotic as you may think, that's how VLC (the video player) draws its controls over the video image (which is also rendered by OpenGL on Mac).
这篇关于使用NSOpenGLLayer从单独的线程中绘制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!