Browse Source

cocoa: use CVDisplayLink on macOS to sync video to display(s)

Closes #487
Fixes #486
Donny Lawrence 7 years ago
parent
commit
0fa0ad673f

+ 8 - 0
panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h

@@ -20,6 +20,7 @@
 
 
 #import <AppKit/NSOpenGL.h>
 #import <AppKit/NSOpenGL.h>
 #import <OpenGL/OpenGL.h>
 #import <OpenGL/OpenGL.h>
+#import <CoreVideo/CoreVideo.h>
 
 
 /**
 /**
  * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
  * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
@@ -38,14 +39,21 @@ public:
                              CocoaGraphicsStateGuardian *share_with);
                              CocoaGraphicsStateGuardian *share_with);
 
 
   virtual ~CocoaGraphicsStateGuardian();
   virtual ~CocoaGraphicsStateGuardian();
+  bool setup_vsync();
 
 
   INLINE void lock_context();
   INLINE void lock_context();
   INLINE void unlock_context();
   INLINE void unlock_context();
 
 
   NSOpenGLContext *_share_context;
   NSOpenGLContext *_share_context;
   NSOpenGLContext *_context;
   NSOpenGLContext *_context;
+  NSOpenGLPixelFormat *_format = nullptr;
   FrameBufferProperties _fbprops;
   FrameBufferProperties _fbprops;
 
 
+  CVDisplayLinkRef _display_link = nullptr;
+  Mutex _swap_lock;
+  ConditionVar _swap_condition;
+  AtomicAdjust::Integer _last_wait_frame = 0;
+
 protected:
 protected:
   virtual void query_gl_version();
   virtual void query_gl_version();
   virtual void *do_get_extension_func(const char *name);
   virtual void *do_get_extension_func(const char *name);

+ 66 - 4
panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm

@@ -28,6 +28,20 @@
 #define NSAppKitVersionNumber10_7 1138
 #define NSAppKitVersionNumber10_7 1138
 #endif
 #endif
 
 
+/**
+ * Called whenever a display wants a frame.  The context argument contains the
+ * applicable CocoaGraphicsStateGuardian.
+ */
+static CVReturn
+display_link_cb(CVDisplayLinkRef link, const CVTimeStamp *now,
+                const CVTimeStamp* output_time, CVOptionFlags flags_in,
+                CVOptionFlags *flags_out, void *context) {
+  CocoaGraphicsStateGuardian *gsg = (CocoaGraphicsStateGuardian *)context;
+  MutexHolder swap_holder(gsg->_swap_lock);
+  gsg->_swap_condition.notify();
+  return kCVReturnSuccess;
+}
+
 TypeHandle CocoaGraphicsStateGuardian::_type_handle;
 TypeHandle CocoaGraphicsStateGuardian::_type_handle;
 
 
 /**
 /**
@@ -36,7 +50,8 @@ TypeHandle CocoaGraphicsStateGuardian::_type_handle;
 CocoaGraphicsStateGuardian::
 CocoaGraphicsStateGuardian::
 CocoaGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe,
 CocoaGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe,
                            CocoaGraphicsStateGuardian *share_with) :
                            CocoaGraphicsStateGuardian *share_with) :
-  GLGraphicsStateGuardian(engine, pipe)
+  GLGraphicsStateGuardian(engine, pipe),
+  _swap_condition(_swap_lock)
 {
 {
   _share_context = nil;
   _share_context = nil;
   _context = nil;
   _context = nil;
@@ -52,12 +67,59 @@ CocoaGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe,
  */
  */
 CocoaGraphicsStateGuardian::
 CocoaGraphicsStateGuardian::
 ~CocoaGraphicsStateGuardian() {
 ~CocoaGraphicsStateGuardian() {
+  if (_format != nil) {
+    [_format release];
+  }
+  if (_display_link != nil) {
+    CVDisplayLinkRelease(_display_link);
+    _display_link = nil;
+    MutexHolder swap_holder(_swap_lock);
+    _swap_condition.notify();
+  }
   if (_context != nil) {
   if (_context != nil) {
     [_context clearDrawable];
     [_context clearDrawable];
     [_context release];
     [_context release];
   }
   }
 }
 }
 
 
+/**
+ * Creates a CVDisplayLink, which tells us when the display the window is on
+ * will want a frame.
+ */
+bool CocoaGraphicsStateGuardian::
+setup_vsync() {
+  if (_display_link != nil) {
+    // Already set up.
+    return true;
+  }
+
+  CVReturn result = CVDisplayLinkCreateWithActiveCGDisplays(&_display_link);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to create CVDisplayLink.\n";
+    return false;
+  }
+
+  result = CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_display_link, (CGLContextObj)[_context CGLContextObj], (CGLPixelFormatObj)[_format CGLPixelFormatObj]);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to set CVDisplayLink's current display.\n";
+    return false;
+  }
+
+  result = CVDisplayLinkSetOutputCallback(_display_link, &display_link_cb, this);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to set CVDisplayLink output callback.\n";
+    return false;
+  }
+
+  result = CVDisplayLinkStart(_display_link);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to start the CVDisplayLink.\n";
+    return false;
+  }
+
+  return true;
+}
+
 /**
 /**
  * Gets the FrameBufferProperties to match the indicated config.
  * Gets the FrameBufferProperties to match the indicated config.
  */
  */
@@ -262,15 +324,15 @@ choose_pixel_format(const FrameBufferProperties &properties,
   // TODO: print out renderer
   // TODO: print out renderer
 
 
   _context = [[NSOpenGLContext alloc] initWithFormat:format shareContext:_share_context];
   _context = [[NSOpenGLContext alloc] initWithFormat:format shareContext:_share_context];
-  [format release];
+  _format = format;
   if (_context == nil) {
   if (_context == nil) {
     cocoadisplay_cat.error() <<
     cocoadisplay_cat.error() <<
       "Failed to create OpenGL context!\n";
       "Failed to create OpenGL context!\n";
     return;
     return;
   }
   }
 
 
-  // Set vsync setting on the context
-  GLint swap = sync_video ? 1 : 0;
+  // Disable vsync via the built-in mechanism, which doesn't work on Mojave
+  GLint swap = 0;
   [_context setValues:&swap forParameter:NSOpenGLCPSwapInterval];
   [_context setValues:&swap forParameter:NSOpenGLCPSwapInterval];
 
 
   cocoadisplay_cat.debug()
   cocoadisplay_cat.debug()

+ 3 - 0
panda/src/cocoadisplay/cocoaGraphicsWindow.h

@@ -23,6 +23,8 @@
 #import <AppKit/NSView.h>
 #import <AppKit/NSView.h>
 #import <AppKit/NSWindow.h>
 #import <AppKit/NSWindow.h>
 
 
+#import <CoreVideo/CoreVideo.h>
+
 /**
 /**
  * An interface to the Cocoa system for managing OpenGL windows under Mac OS
  * An interface to the Cocoa system for managing OpenGL windows under Mac OS
  * X.
  * X.
@@ -92,6 +94,7 @@ private:
   PT(GraphicsWindowInputDevice) _input;
   PT(GraphicsWindowInputDevice) _input;
   bool _mouse_hidden;
   bool _mouse_hidden;
   bool _context_needs_update;
   bool _context_needs_update;
+  bool _vsync_enabled = false;
 
 
 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
   CGDisplayModeRef _fullscreen_mode;
   CGDisplayModeRef _fullscreen_mode;

+ 13 - 0
panda/src/cocoadisplay/cocoaGraphicsWindow.mm

@@ -274,6 +274,14 @@ end_flip() {
     CocoaGraphicsStateGuardian *cocoagsg;
     CocoaGraphicsStateGuardian *cocoagsg;
     DCAST_INTO_V(cocoagsg, _gsg);
     DCAST_INTO_V(cocoagsg, _gsg);
 
 
+    if (_vsync_enabled) {
+      AtomicAdjust::Integer cur_frame = ClockObject::get_global_clock()->get_frame_count();
+      if (AtomicAdjust::set(cocoagsg->_last_wait_frame, cur_frame) != cur_frame) {
+        MutexHolder swap_holder(cocoagsg->_swap_lock);
+        cocoagsg->_swap_condition.wait();
+      }
+    }
+
     cocoagsg->lock_context();
     cocoagsg->lock_context();
 
 
     // Swap the front and back buffer.
     // Swap the front and back buffer.
@@ -669,6 +677,8 @@ open_window() {
     mouse_mode_relative();
     mouse_mode_relative();
   }
   }
 
 
+  _vsync_enabled = sync_video && cocoagsg->setup_vsync();
+
   return true;
   return true;
 }
 }
 
 
@@ -710,6 +720,8 @@ close_window() {
     _view = nil;
     _view = nil;
   }
   }
 
 
+  _vsync_enabled = false;
+
   GraphicsWindow::close_window();
   GraphicsWindow::close_window();
 }
 }
 
 
@@ -1491,6 +1503,7 @@ handle_close_event() {
       cocoagsg->unlock_context();
       cocoagsg->unlock_context();
     }
     }
     _gsg.clear();
     _gsg.clear();
+    _vsync_enabled = false;
   }
   }
 
 
   // Dump the view, too
   // Dump the view, too