CVDisplayLink 和三重缓冲

问题描述 投票:0回答:1

我一直在基于 OpenGL 的项目中使用 CVDisplayLink,使用 NSOpenGL 上下文和常规双缓冲像素格式。

这很好,但我对使用三重缓冲像素格式感兴趣 - 使用 NSOpenGLPFATripleBuffer 标志

Apple 的文档很少提及 NSOpenGLPFATripleBuffer,所以我试图了解它在什么情况下可能有用。 这篇文章对我想要实现的目标进行了合理的描述。

我不清楚它如何与 DisplayLink 驱动的方法交互。

似乎DisplayLink背后的想法是绘图例程将同步到显示刷新,这似乎与三重缓冲上下文的工作方式不兼容。

这里的正确方法是取消 DisplayLink 并将全速渲染到三重缓冲上下文中吗?

objective-c macos cocoa opengl
1个回答
0
投票

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 个后台缓冲区)。

© www.soinside.com 2019 - 2024. All rights reserved.