Browse Source

Merge pull request #25443 from BastiaanOlij/fix_vsync_osx

Using DisplayLink to emulate vsync on OSX
Rémi Verschelde 6 years ago
parent
commit
faad3a013a
3 changed files with 60 additions and 15 deletions
  1. 1 1
      platform/osx/detect.py
  2. 7 2
      platform/osx/os_osx.h
  3. 52 12
      platform/osx/os_osx.mm

+ 1 - 1
platform/osx/detect.py

@@ -126,7 +126,7 @@ def configure(env):
 
     env.Append(CPPPATH=['#platform/osx'])
     env.Append(CPPFLAGS=['-DOSX_ENABLED', '-DUNIX_ENABLED', '-DGLES_ENABLED', '-DAPPLE_STYLE_KEYS', '-DCOREAUDIO_ENABLED', '-DCOREMIDI_ENABLED'])
-    env.Append(LINKFLAGS=['-framework', 'Cocoa', '-framework', 'Carbon', '-framework', 'OpenGL', '-framework', 'AGL', '-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreMIDI', '-lz', '-framework', 'IOKit', '-framework', 'ForceFeedback'])
+    env.Append(LINKFLAGS=['-framework', 'Cocoa', '-framework', 'Carbon', '-framework', 'OpenGL', '-framework', 'AGL', '-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreMIDI', '-lz', '-framework', 'IOKit', '-framework', 'ForceFeedback', '-framework', 'CoreVideo'])
     env.Append(LIBS=['pthread'])
 
     env.Append(CPPFLAGS=['-mmacosx-version-min=10.9'])

+ 7 - 2
platform/osx/os_osx.h

@@ -43,8 +43,10 @@
 #include "servers/visual/rasterizer.h"
 #include "servers/visual/visual_server_wrap_mt.h"
 #include "servers/visual_server.h"
+#include <AppKit/AppKit.h>
 #include <AppKit/NSCursor.h>
 #include <ApplicationServices/ApplicationServices.h>
+#include <CoreVideo/CoreVideo.h>
 
 #undef CursorShape
 /**
@@ -102,10 +104,13 @@ public:
 	id window_view;
 	id autoreleasePool;
 	id cursor;
-	id pixelFormat;
-	id context;
+	NSOpenGLPixelFormat *pixelFormat;
+	NSOpenGLContext *context;
 
 	bool layered_window;
+	bool waiting_for_vsync;
+	NSCondition *vsync_condition;
+	CVDisplayLinkRef displayLink;
 
 	CursorShape cursor_shape;
 	NSCursor *cursors[CURSOR_MAX];

+ 52 - 12
platform/osx/os_osx.mm

@@ -117,6 +117,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
 
@@ -1366,6 +1381,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];
@@ -1453,6 +1477,11 @@ void OS_OSX::finalize() {
 	midi_driver.close();
 #endif
 
+	if (displayLink) {
+		CVDisplayLinkRelease(displayLink);
+	}
+	[vsync_condition release];
+
 	CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL);
 	CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, NULL);
 
@@ -1967,6 +1996,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];
 }
@@ -2633,22 +2673,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);
-	}
-}
-/*
-bool OS_OSX::is_vsync_enabled() const {
-	GLint swapInterval = 0;
-	CGLContextObj ctx = CGLGetCurrentContext();
-	if (ctx) {
-		CGLGetParameter(ctx, kCGLCPSwapInterval, &swapInterval);
-	}
-	return swapInterval ? true : false;
+	}*/
+
+	///TODO Maybe pause/unpause display link?
+	waiting_for_vsync = p_enable;
 }
-*/
+
 OS_OSX *OS_OSX::singleton = NULL;
 
 OS_OSX::OS_OSX() {