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 <OpenGL/OpenGL.h>
#import <CoreVideo/CoreVideo.h>
/**
* 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);

View File

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

View File

@ -23,6 +23,8 @@
#import <AppKit/NSView.h>
#import <AppKit/NSWindow.h>
#import <CoreVideo/CoreVideo.h>
/**
* 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;

View File

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