使用NSOpenGLLayer从单独的线程中绘制 [英] Draw from a separate thread with NSOpenGLLayer

查看:556
本文介绍了使用NSOpenGLLayer从单独的线程中绘制的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个应用程序,需要使用OpengGL绘制,刷新率至少等于显示器的刷新率。我需要在一个单独的线程中执行绘图,使绘图永远不会被强烈的UI操作锁定。



其实我使用的是 NSOpenGLView CVDisplayLink 结合使用,我可以毫无问题地启用60-80FPS。



由于我还需要在这个视图的顶部显示一些可可控件,我试图子类 NSOpenGLView ,并使其支持layer, LayerBackedOpenGLView Apple example。



因此,我使用单独的 NSWindow

解决了这个问题。 code>托管可可控件,并将此窗口添加为包含 NSOpenGLView 的主窗口的子窗口。
它工作正常,我能够得到与初始实现相同的FPS。



由于我认为这个解决方案很像一个脏的黑客,



几天前我遇到了 NSOpenGLLayer



最后,在所有的前言之后,这里是我的问题:
是可以使用 CVDisplayLink 回调从单独的线程中绘制 NSOpenGLLayer



到目前为止,我已经尝试实现这个,但是我不能从 CVDisplayLink 回调中绘制。我只能从 CVDisplayLink 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆