diff --git a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h index 2e83e60c34..8365834a16 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h +++ b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h @@ -20,6 +20,7 @@ #import #import +#import /** * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific @@ -38,14 +39,21 @@ public: CocoaGraphicsStateGuardian *share_with); virtual ~CocoaGraphicsStateGuardian(); + bool setup_vsync(); INLINE void lock_context(); INLINE void unlock_context(); NSOpenGLContext *_share_context; NSOpenGLContext *_context; + NSOpenGLPixelFormat *_format = nullptr; FrameBufferProperties _fbprops; + CVDisplayLinkRef _display_link = nullptr; + Mutex _swap_lock; + ConditionVar _swap_condition; + AtomicAdjust::Integer _last_wait_frame = 0; + protected: virtual void query_gl_version(); virtual void *do_get_extension_func(const char *name); diff --git a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm index d0dbd37c16..92f6226492 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm @@ -28,6 +28,20 @@ #define NSAppKitVersionNumber10_7 1138 #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; /** @@ -36,7 +50,8 @@ TypeHandle CocoaGraphicsStateGuardian::_type_handle; CocoaGraphicsStateGuardian:: CocoaGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe, CocoaGraphicsStateGuardian *share_with) : - GLGraphicsStateGuardian(engine, pipe) + GLGraphicsStateGuardian(engine, pipe), + _swap_condition(_swap_lock) { _share_context = nil; _context = nil; @@ -52,12 +67,59 @@ CocoaGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe, */ 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) { [_context clearDrawable]; [_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. */ @@ -262,15 +324,15 @@ choose_pixel_format(const FrameBufferProperties &properties, // TODO: print out renderer _context = [[NSOpenGLContext alloc] initWithFormat:format shareContext:_share_context]; - [format release]; + _format = format; if (_context == nil) { cocoadisplay_cat.error() << "Failed to create OpenGL context!\n"; 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]; cocoadisplay_cat.debug() diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.h b/panda/src/cocoadisplay/cocoaGraphicsWindow.h index a49f748020..a053cb11b9 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.h +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.h @@ -23,6 +23,8 @@ #import #import +#import + /** * An interface to the Cocoa system for managing OpenGL windows under Mac OS * X. @@ -92,6 +94,7 @@ private: PT(GraphicsWindowInputDevice) _input; bool _mouse_hidden; bool _context_needs_update; + bool _vsync_enabled = false; #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 CGDisplayModeRef _fullscreen_mode; diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index df3ecf3b12..85e01917eb 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -274,6 +274,14 @@ end_flip() { CocoaGraphicsStateGuardian *cocoagsg; 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(); // Swap the front and back buffer. @@ -669,6 +677,8 @@ open_window() { mouse_mode_relative(); } + _vsync_enabled = sync_video && cocoagsg->setup_vsync(); + return true; } @@ -710,6 +720,8 @@ close_window() { _view = nil; } + _vsync_enabled = false; + GraphicsWindow::close_window(); } @@ -1491,6 +1503,7 @@ handle_close_event() { cocoagsg->unlock_context(); } _gsg.clear(); + _vsync_enabled = false; } // Dump the view, too