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

Closes #487
Fixes #486
This commit is contained in:
Donny Lawrence 2018-12-24 13:36:13 +01:00 committed by rdb
parent 8a38337e6b
commit 0fa0ad673f
4 changed files with 90 additions and 4 deletions

View File

@ -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);

View File

@ -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 // Disable vsync via the built-in mechanism, which doesn't work on Mojave
GLint swap = sync_video ? 1 : 0; GLint swap = 0;
[_context setValues:&swap forParameter:NSOpenGLCPSwapInterval]; [_context setValues:&swap forParameter:NSOpenGLCPSwapInterval];
cocoadisplay_cat.debug() cocoadisplay_cat.debug()

View File

@ -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;

View File

@ -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