我一直在基于 OpenGL 的项目中使用 CVDisplayLink,使用 NSOpenGL 上下文和常规双缓冲像素格式。
这很好,但我对使用三重缓冲像素格式感兴趣 - 使用 NSOpenGLPFATripleBuffer 标志
Apple 的文档很少提及 NSOpenGLPFATripleBuffer,所以我试图了解它在什么情况下可能有用。 这篇文章对我想要实现的目标进行了合理的描述。
我不清楚它如何与 DisplayLink 驱动的方法交互。
似乎DisplayLink背后的想法是绘图例程将同步到显示刷新,这似乎与三重缓冲上下文的工作方式不兼容。
这里的正确方法是取消 DisplayLink 并将全速渲染到三重缓冲上下文中吗?
NSOpenGLPFATripleBuffer
标志提供的三重缓冲是提前渲染队列,而不是本文中描述的页面翻转三重缓冲。因此,您仍然需要将交换间隔设置为 1(垂直同步)以避免撕裂或掉落(在较新的 macOS 上,系统将选择丢弃帧而不是撕裂)。与翻页方法不同,它还会导致额外的帧延迟。
另一方面,Metal 支持真正的翻页三重缓冲。可以通过渲染到 FBO 来模拟 OpenGL 中的翻页方法,然后在 displaylink 回调中您可以只显示最后一个完全渲染的帧。
另请注意,在现代 macOS(macOS 11+?)上,NSOpenGLPFATripleBuffer 实际上只为您提供双缓冲上下文。
您可以使用以下代码片段来验证行为:
#import <Cocoa/Cocoa.h>
#import <OpenGL/gl.h>
#import <OpenGL/glu.h>
#import <QuartzCore/QuartzCore.h>
#import <OpenGL/gl3.h>
#include <mach/mach_time.h>
@interface MyOpenGLView : NSOpenGLView
@end
@implementation MyOpenGLView
+ (BOOL)canDrawOpenGL {
return YES;
}
- (void)prepareOpenGL {
// Set up OpenGL context and other properties
[super prepareOpenGL];
[[self openGLContext] makeCurrentContext];
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glFinish();
}
- (void)drawRect:(NSRect)dirtyRect {
static float i = 0;
i += 0.01;
glClearColor(0.0f, i, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Swap the buffers to display the rendered image
[[self openGLContext] flushBuffer];
// glFlush();
}
@end
int x = 0;
MyOpenGLView *glView;
CVReturn MyDisplayCallback(CVDisplayLinkRef displayLink,
const CVTimeStamp *inTime,
const CVTimeStamp *outTime,
CVOptionFlags flagsIn,
CVOptionFlags *flagsOut,
void *displayLinkContext) {
[glView prepareOpenGL];
for (int i = 0; i < 20; i++ ){
printf("-------\n");
CVTimeStamp out;
CVDisplayLinkGetCurrentTime(displayLink, &out);
printf("Bef: %llu\n", out.videoTime);
[glView drawRect:NSZeroRect];
CVDisplayLinkGetCurrentTime(displayLink, &out);
printf("Aft: %llu\n", out.videoTime);
}
exit(0);
return kCVReturnSuccess;
}
int main(int argc, const char * argv[]) {
// Set up the application and window
@autoreleasepool {
NSApplication *app = [NSApplication sharedApplication];
NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 400, 400)
styleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[window setBackingType:NSBackingStoreBuffered];
// Create an NSOpenGLPixelFormat object with double buffering enabled
NSOpenGLPixelFormatAttribute attribs[] = {
NSOpenGLPFATripleBuffer,
NSOpenGLPFAAccelerated,
0
};
NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
// Create an NSOpenGLContext object with the pixel format
NSOpenGLContext *glContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
// Set the pixel format and context for the view
glView = [[MyOpenGLView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400) pixelFormat:pixelFormat];
[glView setOpenGLContext:glContext];
// Optionally see what happens when using a CAOpenGLLayer instance
// [glView setWantsLayer: YES];
// [glView.layer setBackgroundColor:[NSColor blueColor].CGColor];
// Set up the window and run the application
[window setContentView:glView];
[window makeFirstResponder:glView];
[window makeKeyAndOrderFront:nil];
GLint a = 1;
[glContext setValues:&a forParameter:NSOpenGLCPSwapInterval];
CVDisplayLinkRef _link;
CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(),&_link);
CVDisplayLinkSetOutputCallback(_link, MyDisplayCallback, NULL);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
printf("Starting\n");
CVDisplayLinkStart(_link);
});
[app run];
}
return 0;
}
请注意,在双缓冲上下文中,第一个交换会立即完成,但第二个交换会阻塞(表明我们只有 1 个后台缓冲区)。在真正支持三重缓冲上下文的 OSX 版本上,您将看到前 2 个交换立即完成(表示 2 个后台缓冲区)。