From 633ac4f753a5de4de34672e5a4c4f8b6bbe1507e Mon Sep 17 00:00:00 2001 From: Bastiaan Olij Date: Sun, 27 Jan 2019 22:39:32 +1100 Subject: [PATCH] Using DisplayLink to emulate vsync on OSX (cherry picked from commit b53f2d1d598193ab1d1c02345fc321be254364ca) --- platform/osx/detect.py | 2 +- platform/osx/os_osx.h | 10 +++++-- platform/osx/os_osx.mm | 64 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/platform/osx/detect.py b/platform/osx/detect.py index 52f04b7048f..08d172bdb23 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -123,7 +123,7 @@ def configure(env): env.Append(CPPPATH=['#platform/osx']) env.Append(CPPFLAGS=['-DOSX_ENABLED', '-DUNIX_ENABLED', '-DGLES_ENABLED', '-DAPPLE_STYLE_KEYS', '-DCOREAUDIO_ENABLED']) - env.Append(LINKFLAGS=['-framework', 'Cocoa', '-framework', 'Carbon', '-framework', 'OpenGL', '-framework', 'AGL', '-framework', 'AudioUnit', '-framework', 'CoreAudio', '-lz', '-framework', 'IOKit', '-framework', 'ForceFeedback']) + env.Append(LINKFLAGS=['-framework', 'Cocoa', '-framework', 'Carbon', '-framework', 'OpenGL', '-framework', 'AGL', '-framework', 'AudioUnit', '-framework', 'CoreAudio', '-lz', '-framework', 'IOKit', '-framework', 'ForceFeedback', '-framework', 'CoreVideo']) env.Append(LIBS=['pthread']) env.Append(CPPFLAGS=['-mmacosx-version-min=10.9']) diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 1b3ed260311..c82e76a9b6e 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -42,8 +42,10 @@ #include "servers/visual/rasterizer.h" #include "servers/visual/visual_server_wrap_mt.h" #include "servers/visual_server.h" +#include #include #include +#include #undef CursorShape /** @@ -96,8 +98,12 @@ public: id window_view; id autoreleasePool; id cursor; - id pixelFormat; - id context; + NSOpenGLPixelFormat *pixelFormat; + NSOpenGLContext *context; + + bool waiting_for_vsync; + NSCondition *vsync_condition; + CVDisplayLinkRef displayLink; CursorShape cursor_shape; NSCursor *cursors[CURSOR_MAX] = { NULL }; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 798fe98aeef..1c30e7dfb0f 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -114,6 +114,21 @@ static Vector2 get_mouse_pos(NSPoint locationInWindow, CGFloat backingScaleFacto return Vector2(mouse_x, mouse_y); } +// DisplayLinkCallback is called from our DisplayLink OS thread informing us right before +// a screen update is required. We can use it to work around the broken vsync. +static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) { + OS_OSX *os = (OS_OSX *)displayLinkContext; + + // Set flag so we know we can output our next frame and signal our conditional lock + // if we're not doing vsync this will be ignored + [os->vsync_condition lock]; + os->waiting_for_vsync = false; + [os->vsync_condition signal]; + [os->vsync_condition unlock]; + + return kCVReturnSuccess; +} + @interface GodotApplication : NSApplication @end @@ -1195,6 +1210,15 @@ Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_a [context makeCurrentContext]; + // setup our display link, this will inform us when a refresh is needed + CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); + CVDisplayLinkSetOutputCallback(displayLink, &DisplayLinkCallback, this); + CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, context.CGLContextObj, pixelFormat.CGLPixelFormatObj); + CVDisplayLinkStart(displayLink); + + // initialise a conditional lock object + vsync_condition = [[NSCondition alloc] init]; + set_use_vsync(p_desired.use_vsync); [NSApp activateIgnoringOtherApps:YES]; @@ -1239,6 +1263,11 @@ Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_a void OS_OSX::finalize() { + if (displayLink) { + CVDisplayLinkRelease(displayLink); + } + [vsync_condition release]; + CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL); CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, NULL); @@ -1692,6 +1721,17 @@ String OS_OSX::get_locale() const { } void OS_OSX::swap_buffers() { + if (is_vsync_enabled()) { + // Wait until our DisplayLink callback unsets our flag... + [vsync_condition lock]; + while (waiting_for_vsync) + [vsync_condition wait]; + + // Make sure we wait again next frame around + waiting_for_vsync = true; + + [vsync_condition unlock]; + } [context flushBuffer]; } @@ -2322,22 +2362,22 @@ Error OS_OSX::move_to_trash(const String &p_path) { } void OS_OSX::_set_use_vsync(bool p_enable) { - CGLContextObj ctx = CGLGetCurrentContext(); + // CGLCPSwapInterval broke in OSX 10.14 and it seems Apple is not interested in fixing + // it as OpenGL is now deprecated and Metal solves this differently. + // Following SDLs example we're working around this using DisplayLink + // When vsync is enabled we set a flag "waiting_for_vsync" to true. + // This flag is set to false when DisplayLink informs us our display is about to refresh. + + /* CGLContextObj ctx = CGLGetCurrentContext(); if (ctx) { GLint swapInterval = p_enable ? 1 : 0; CGLSetParameter(ctx, kCGLCPSwapInterval, &swapInterval); - } + }*/ + + ///TODO Maybe pause/unpause display link? + waiting_for_vsync = p_enable; } -/* -bool OS_OSX::is_vsync_enabled() const { - GLint swapInterval = 0; - CGLContextObj ctx = CGLGetCurrentContext(); - if (ctx) { - CGLGetParameter(ctx, kCGLCPSwapInterval, &swapInterval); - } - return swapInterval ? true : false; -} -*/ + OS_OSX *OS_OSX::singleton = NULL; OS_OSX::OS_OSX() {