当你有一个正常的NSOpenGLView
,你可以简单地通过 OpenGL 绘制一些东西,然后调用-flushBuffer
of the NSOpenGLContext
使渲染出现在屏幕上。如果你的上下文不是双缓冲的,如果你渲染到一个窗口,这是不必要的,因为在 MacOS X 中所有窗口都已经是双缓冲的,调用glFlush()
也足够了(仅对于真正的全屏 OpenGL 渲染,您需要双缓冲以避免伪影)。然后,OpenGL 将直接渲染到视图的像素存储中(实际上是窗口的后备存储),或者在双缓冲的情况下,它将渲染到后缓冲区,然后与前缓冲区交换;因此,新内容立即在屏幕上可见(实际上不是在下一次屏幕刷新之前,但这样的刷新每秒至少发生 50-60 次)。
如果NSOpenGLView
是层支持的。你打电话时-flushBuffer
or glFlush()
,渲染实际上确实像之前一样发生了,图像直接渲染到视图的像素存储中,但是,这个像素存储不再是窗口的后备存储,它是“后备层” ”的观点。因此,您的 OpenGL 图像已更新,您只是看不到它发生,因为“绘制到图层中”和“在屏幕上显示图层”是两个完全不同的事情!要使新图层内容可见,您必须调用setNeedsDisplay:YES
在你的层支持上NSOpenGLView
.
为什么当你打电话时它对你不起作用setNeedsDisplay:YES
?首先,确保在主线程上执行此调用。您可以在您喜欢的任何线程上执行此调用,它肯定会将视图标记为脏,但只有在主线程上执行此调用时,它还会为其安排重绘调用(如果没有该调用,它会被标记为脏,但它在重绘其任何其他父/子视图之前不会重绘)。另一个问题可能是drawRect:
方法。当您将视图标记为脏并重新绘制时,将调用此方法,并且此方法“绘制”的任何内容都会覆盖图层中当前的任何内容。只要您的视图不是基于图层的,那么在何处渲染 OpenGL 内容并不重要,但对于基于图层的视图,这实际上是您应该执行所有绘图的方法。
尝试以下操作: 创建一个NSTimer
在你的主线程上,每 20 毫秒触发一次并调用一个方法,该方法调用setNeedsDisplay:YES
在你的层支持上NSOpenGLView
。将所有 OpenGL 渲染代码移至drawRect:
你的层支持的方法NSOpenGLView
。那应该效果很好。如果您需要比NSTimer
, try a CVDisplayLink
(CV = 核心视频)。 ACVDisplayLink
就像一个计时器,但每次屏幕重新绘制时它都会触发。
Update
分层 NSOpenGLView 有点过时了,从 10.6 开始,它们不再需要了。当您将其分层时,NSOpenGLView 在内部会创建一个 NSOpenGLLayer,因此您也可以自己直接使用这样的层并“构建”您自己的 NSOpenGLView:
- 创建您自己的子类
NSOpenGLLayer
,我们称之为MyOpenGLLayer
- 创建您自己的子类
NSView
,我们称之为MyGLView
- 覆盖
- (CALayer *)makeBackingLayer
返回一个自动释放的实例MyOpenGLLayer
- Set
wantsLayer:YES
for MyGLView
您现在拥有自己的图层支持视图,并且它是由 NSOpenGLLayer 子类支持的图层。因为它是层支持,向其中添加子视图(例如按钮、文本字段等)是绝对可以的。
对于您的背衬层,您基本上有两种选择。
Option 1
正确且官方支持的方法是将渲染保持在主线程上。因此,您必须执行以下操作:
- 覆盖
canDrawInContext:...
回来YES
/NO
,取决于您是否可以/想要绘制下一帧。
- 覆盖
drawInContext:...
执行实际的 OpenGL 渲染。
- 使层异步(
setAsynchronous:YES
)
- 确保图层在调整大小时“更新”(
setNeedsDisplayOnBoundsChange:YES
),否则当调整图层大小时,OpenGL 背衬表面不会调整大小(并且每次图层重绘时,渲染的 OpenGL 上下文都必须拉伸/收缩)
苹果将创建一个CVDisplayLink
对你来说,这需要canDrawInContext:...
每次触发时在主线程上并且如果此方法返回YES
,它调用drawInContext:...
。这就是你应该这样做的方式。
如果您的渲染成本太高而无法在主线程上进行,您可以执行以下技巧:覆盖openGLContextForPixelFormat:...
创建与您之前创建的另一个上下文(上下文 A)共享的上下文(上下文 B)。在上下文 A 中创建一个帧缓冲区(您可以在创建上下文 B 之前或之后执行此操作,这并不重要);如果需要的话附加深度和/或模板渲染缓冲区(您选择的位深度),但是不是颜色渲染缓冲区,而是附加“纹理”(纹理 X)作为颜色附件(glFramebufferTexture()
)。现在,在渲染到该帧缓冲区时,所有颜色渲染输出都会写入该纹理。在您选择的任何线程上使用上下文 A 执行对此帧缓冲区的所有渲染!渲染完成后,进行canDrawInContext:...
return YES
and in drawInContext:...
就画个简单的quad它填充整个活动帧缓冲区(Apple 已经为您设置了它以及视口以完全填充它)并且使用纹理 X 进行纹理化。这是可能的,因为共享上下文还共享所有对象(例如纹理、帧缓冲区、 ETC。)。所以你的drawInContext:...
方法永远不会比绘制一个简单的、简单的纹理四边形更重要,仅此而已。所有其他(可能昂贵的渲染)都发生在后台线程上的该纹理上,并且不会阻塞主线程。
Option 2
另一个选项不受 Apple 官方支持,可能适合您,也可能不适合您:
- 不要覆盖
canDrawInContext:...
,默认实现总是返回YES
这就是你想要的。
- 覆盖
drawInContext:...
执行实际的 OpenGL 渲染,全部。
- 不要使该层异步。
- 不设置
needsDisplayOnBoundsChange
.
每当你想重绘该层时,调用display
直接地 (NOT setNeedsDisplay
!确实如此,苹果说你不应该调用它,但“不应该”不是“不能”)并且调用后display
, call [CATransaction flush]
。即使从后台线程调用,这也会起作用!你的drawInContext:...
方法是从调用的同一线程调用的display
它可以是任何线程。呼唤display
直接将确保您的 OpenGL 渲染代码执行,但新渲染的内容仍然仅在图层的后备存储中可见,要将其带到屏幕上,您必须强制系统执行图层合成并[CATransaction flush]
就会这么做。 CATransaction 类只有类方法(您永远不会创建它的实例),它是隐式线程安全的,并且可以随时从任何线程使用(无论何时何地需要,它都会自行执行锁定)。
虽然不推荐这种方法,因为它可能会导致其他视图的重绘问题(因为这些视图也可能在主线程以外的线程上重绘,并且并非所有视图都支持),但它是也不禁止,它不使用私有 API,并且已在 Apple 邮件列表中建议,Apple 中没有任何人反对。