macoscocoaopenglnsviewcadisplaylink

Correct way to initialize / sync OpenGL for NSView


I created a custom OpenGLView instead of using NSOpenGLView by following Apple's docs. Drawing seems fine except it seems I have thread sync issues here. Since NSView should be in main thread then how to sync threads, I mean CADisplayLink seems work on different thread so after rotating or zooming to 3D scene some nodes keep their old transform (transformed slowly) until rotation/zooming finished. But if I use dispatch_sync/dispatch_async in display link drawing seems correct. But I'm not sure how it can hurt performance.

Q1: Is it right to use dispatch_sync in displaylink cb? Or what is the better alternative?

Q2: Also I'm not drawing to full screen view, there will be cocoa controls or maybe multiple openglviews. Since main thread is not dedicated to opengl , I don't know how it can affect FPS or cocoa controls. So Is it possible to run opengl drawing operations in separate thread?

For zoom/rotation case I have a solution, I'm adding a flag (when zoom/rotation changed) to scene or node then in rendering func I'm checking that flag then applying transforms. And rendering/drawing seems also correct. But this is one case; there would be some other issues in the future. So I need to sync threads I think

CVReturn
displaylink_cb(CVDisplayLinkRef    CV_NONNULL  displayLink,
               const CVTimeStamp * CV_NONNULL  inNow,
               const CVTimeStamp * CV_NONNULL  inOutputTime,
               CVOptionFlags                   flagsIn,
               CVOptionFlags     * CV_NONNULL  flagsOut,
               void              * CV_NULLABLE displayLinkContext) {
  dispatch_sync(dispatch_get_main_queue(), ^{
    [(__bridge GLView *)displayLinkContext renderOnce];
  });

  return kCVReturnSuccess;
}

- (void)syncWithCurrentDisplay {
  NSOpenGLContext  *openGLContext;
  CGLContextObj     cglContext;
  CGLPixelFormatObj cglPixelFormat;
  GLint             swapInt;

  openGLContext = [self openGLContext];
  swapInt       = 1;

  /* Synchronize buffer swaps with vertical refresh rate */
  [openGLContext setValues: &swapInt
              forParameter: NSOpenGLCPSwapInterval];

  /* Create a display link capable of being used with all active displays */
  CVDisplayLinkCreateWithActiveCGDisplays(&m_displayLink);

  /* Set the renderer output callback function */
  CVDisplayLinkSetOutputCallback(m_displayLink,
                                 display_link_cb,
                                 (__bridge void *)self);

  /* Set the display link for the current renderer */
  cglContext     = [openGLContext CGLContextObj];
  cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj];
  CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(m_displayLink,
                                                    cglContext,
                                                    cglPixelFormat);
}

- (void) renderOnce {
  NSOpenGLContext *context;
  context = [self openGLContext];

  [context makeCurrentContext];

  /* because display link is threaded */
  CGLLockContext([context CGLContextObj]);
  [[self delegate] render];
  [context flushBuffer];
  CGLUnlockContext([context CGLContextObj]);
}

Solution

  • OpenGL rendering can happen on any thread, as long as it only happens on one thread at a time. Just because you have an NSView doesn't mean your context must render on the main thread.

    See the example below. The rendering is done on the display link's thread, except during the view frame change notification which happens on the main thread, so the code uses a lock to render that one frame on the main thread (which is not actually necessary, but if you do, the lock shows how to do it).

    https://developer.apple.com/library/content/samplecode/GLFullScreen/Introduction/Intro.html