From 4629ef627f164a166f6ba7e619c407b2e6e6185c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 20 Feb 2019 15:08:07 +0530 Subject: [PATCH] GLFW: Add support for render frames on Cocoa (CVDisplayLink) Allows vsync to work again since Apple broke OpenGL swap intervals on Mojave --- glfw/cocoa_init.m | 7 +++++ glfw/cocoa_monitor.m | 56 ++++++++++++++++++++++++++++++++++ glfw/cocoa_platform.h | 24 +++++++++++++++ glfw/cocoa_window.m | 70 +++++++++++++++++++++++++++++++++++++++---- glfw/glfw.py | 2 ++ glfw/nsgl_context.m | 10 +++---- kitty/glfw-wrapper.c | 2 ++ kitty/glfw-wrapper.h | 5 ++++ 8 files changed, 164 insertions(+), 12 deletions(-) diff --git a/glfw/cocoa_init.m b/glfw/cocoa_init.m index 1ea35c031..041793b12 100644 --- a/glfw/cocoa_init.m +++ b/glfw/cocoa_init.m @@ -375,6 +375,7 @@ int _glfwPlatformInit(void) if (!initializeTIS()) return GLFW_FALSE; + _glfw.ns.displayLinks.lock = [NSLock new]; _glfwInitTimerNS(); _glfwInitJoysticksNS(); @@ -384,6 +385,12 @@ int _glfwPlatformInit(void) void _glfwPlatformTerminate(void) { + if (_glfw.ns.displayLinks.lock) { + _glfwClearDisplayLinks(); + [_glfw.ns.displayLinks.lock release]; + _glfw.ns.displayLinks.lock = nil; + } + if (_glfw.ns.inputSource) { CFRelease(_glfw.ns.inputSource); diff --git a/glfw/cocoa_monitor.m b/glfw/cocoa_monitor.m index a4ea42a9f..c0bf5f5fd 100644 --- a/glfw/cocoa_monitor.m +++ b/glfw/cocoa_monitor.m @@ -217,6 +217,60 @@ static void endFadeReservation(CGDisplayFadeReservationToken token) ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// +void _glfwClearDisplayLinks() { + [_glfw.ns.displayLinks.lock lock]; + for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) { + if (_glfw.ns.displayLinks.entries[i].displayLinkStarted) { + CVDisplayLinkStop(_glfw.ns.displayLinks.entries[i].displayLink); + _glfw.ns.displayLinks.entries[i].displayLinkStarted = GLFW_FALSE; + } + if (_glfw.ns.displayLinks.entries[i].displayLink) { + CVDisplayLinkRelease(_glfw.ns.displayLinks.entries[i].displayLink); + _glfw.ns.displayLinks.entries[i].displayLink = nil; + } + } + _glfw.ns.displayLinks.count = 0; + [_glfw.ns.displayLinks.lock unlock]; +} + +static CVReturn displayLinkCallback( + CVDisplayLinkRef displayLink, + const CVTimeStamp* now, const CVTimeStamp* outputTime, + CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* userInfo) +{ + CGDirectDisplayID displayID = (CGDirectDisplayID)userInfo; + [_glfw.ns.displayLinks.lock lock]; + GLFWbool notify = GLFW_FALSE; + for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) { + if (_glfw.ns.displayLinks.entries[i].displayID == displayID) { + if (_glfw.ns.displayLinks.entries[i].renderFrameRequested) { + notify = GLFW_TRUE; + _glfw.ns.displayLinks.entries[i].renderFrameRequested = GLFW_FALSE; + } + break; + } + } + [_glfw.ns.displayLinks.lock unlock]; + if (notify) { + _glfwCocoaPostEmptyEvent(RENDER_FRAME_REQUEST_EVENT_TYPE, displayID); + } + return kCVReturnSuccess; +} + +static inline void createDisplayLink(CGDirectDisplayID displayID) { + [_glfw.ns.displayLinks.lock lock]; + if (_glfw.ns.displayLinks.count >= sizeof(_glfw.ns.displayLinks.entries)/sizeof(_glfw.ns.displayLinks.entries[0]) - 1) return; + for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) { + if (_glfw.ns.displayLinks.entries[i].displayID == displayID) return; + } + _GLFWDisplayLinkNS *entry = &_glfw.ns.displayLinks.entries[_glfw.ns.displayLinks.count++]; + memset(entry, 0, sizeof(_GLFWDisplayLinkNS)); + entry->displayID = displayID; + CVDisplayLinkCreateWithCGDisplay(displayID, &entry->displayLink); + CVDisplayLinkSetOutputCallback(entry->displayLink, &displayLinkCallback, (void*)(uintptr_t)displayID); + [_glfw.ns.displayLinks.lock unlock]; +} + // Poll for changes in the set of connected monitors // void _glfwPollMonitorsNS(void) @@ -228,6 +282,7 @@ void _glfwPollMonitorsNS(void) CGGetOnlineDisplayList(0, NULL, &displayCount); displays = calloc(displayCount, sizeof(CGDirectDisplayID)); CGGetOnlineDisplayList(displayCount, displays, &displayCount); + _glfwClearDisplayLinks(); for (i = 0; i < _glfw.monitorCount; i++) _glfw.monitors[i]->ns.screen = nil; @@ -269,6 +324,7 @@ void _glfwPollMonitorsNS(void) monitor = _glfwAllocMonitor(name, size.width, size.height); monitor->ns.displayID = displays[i]; monitor->ns.unitNumber = unitNumber; + createDisplayLink(monitor->ns.displayID); free(name); diff --git a/glfw/cocoa_platform.h b/glfw/cocoa_platform.h index da6074a63..b34061414 100644 --- a/glfw/cocoa_platform.h +++ b/glfw/cocoa_platform.h @@ -30,14 +30,19 @@ #include #if defined(__OBJC__) #import +#import #else typedef void* id; +typedef void* CVDisplayLinkRef; #endif +#define RENDER_FRAME_REQUEST_EVENT_TYPE 1 + typedef VkFlags VkMacOSSurfaceCreateFlagsMVK; typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long); typedef int (* GLFWapplicationshouldhandlereopenfun)(int); typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); +typedef void (* GLFWcocoarenderframefun)(GLFWwindow*); typedef struct VkMacOSSurfaceCreateInfoMVK { @@ -105,8 +110,19 @@ typedef struct _GLFWwindowNS GLFWcocoatogglefullscreenfun toggleFullscreenCallback; // Dead key state UInt32 deadKeyState; + // Whether a render frame has been requested for this window + GLFWbool renderFrameRequested; + GLFWcocoarenderframefun renderFrameCallback; } _GLFWwindowNS; +typedef struct _GLFWDisplayLinkNS +{ + CVDisplayLinkRef displayLink; + CGDirectDisplayID displayID; + GLFWbool displayLinkStarted; + GLFWbool renderFrameRequested; +} _GLFWDisplayLinkNS; + // Cocoa-specific global data // typedef struct _GLFWlibraryNS @@ -141,6 +157,12 @@ typedef struct _GLFWlibraryNS CFStringRef kPropertyUnicodeKeyLayoutData; } tis; + struct { + _GLFWDisplayLinkNS entries[256]; + size_t count; + id lock; + } displayLinks; + } _GLFWlibraryNS; // Cocoa-specific per-monitor data @@ -176,3 +198,5 @@ void _glfwInitTimerNS(void); void _glfwPollMonitorsNS(void); void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired); void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor); +void _glfwClearDisplayLinks(); +void _glfwCocoaPostEmptyEvent(short subtype, long data1); diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index 2ca55f7a9..d20addc00 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -1744,6 +1744,55 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) [window->ns.object setAlphaValue:opacity]; } +static inline CGDirectDisplayID displayIDForWindow(_GLFWwindow *w) { + NSWindow *nw = w->ns.object; + NSDictionary *dict = [nw.screen deviceDescription]; + NSNumber *displayIDns = [dict objectForKey:@"NSScreenNumber"]; + if (displayIDns) return [displayIDns unsignedIntValue]; + return (CGDirectDisplayID)-1; +} + +static inline void sendEvent(NSEvent *event) { + if (event.type == NSEventTypeApplicationDefined) { + if (event.subtype == RENDER_FRAME_REQUEST_EVENT_TYPE) { + CGDirectDisplayID displayID = (CGDirectDisplayID)event.data1; + _GLFWwindow *w = _glfw.windowListHead; + while (w) { + if (w->ns.renderFrameRequested && displayID == displayIDForWindow(w)) { + w->ns.renderFrameRequested = GLFW_FALSE; + w->ns.renderFrameCallback((GLFWwindow*)w); + } + w = w->next; + } + } + } else [NSApp sendEvent:event]; +} + +static inline void +requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) { + if (!callback) { + w->ns.renderFrameRequested = GLFW_FALSE; + w->ns.renderFrameCallback = NULL; + return; + } + w->ns.renderFrameCallback = callback; + w->ns.renderFrameRequested = GLFW_TRUE; + CGDirectDisplayID displayID = displayIDForWindow(w); + [_glfw.ns.displayLinks.lock lock]; + for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) { + _GLFWDisplayLinkNS *dl = &_glfw.ns.displayLinks.entries[i]; + if (dl->displayID == displayID) { + dl->renderFrameRequested = GLFW_TRUE; + if (!dl->displayLinkStarted) { + CVDisplayLinkStart(dl->displayLink); + dl->displayLinkStarted = GLFW_TRUE; + } + break; + } + } + [_glfw.ns.displayLinks.lock unlock]; +} + void _glfwPlatformPollEvents(void) { for (;;) @@ -1755,7 +1804,7 @@ void _glfwPlatformPollEvents(void) if (event == nil) break; - if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event]; + sendEvent(event); } [_glfw.ns.autoreleasePool drain]; @@ -1771,7 +1820,7 @@ void _glfwPlatformWaitEvents(void) untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES]; - if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event]; + sendEvent(event); _glfwPlatformPollEvents(); } @@ -1783,12 +1832,12 @@ void _glfwPlatformWaitEventsTimeout(double timeout) untilDate:date inMode:NSDefaultRunLoopMode dequeue:YES]; - if (event && [event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event]; + if (event) sendEvent(event); _glfwPlatformPollEvents(); } -void _glfwPlatformPostEmptyEvent(void) +void _glfwCocoaPostEmptyEvent(short subtype, long data1) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined @@ -1797,13 +1846,18 @@ void _glfwPlatformPostEmptyEvent(void) timestamp:0 windowNumber:0 context:nil - subtype:0 - data1:0 + subtype:subtype + data1:data1 data2:0]; [NSApp postEvent:event atStart:YES]; [pool drain]; } +void _glfwPlatformPostEmptyEvent(void) +{ + _glfwCocoaPostEmptyEvent(0, 0); +} + void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) { const NSRect contentRect = [window->ns.view frame]; @@ -2101,6 +2155,10 @@ GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReope return previous; } +GLFWAPI void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback) { + requestRenderFrame((_GLFWwindow*)w, callback); +} + GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned short *cocoa_key, int *cocoa_mods) { *cocoa_key = 0; *cocoa_mods = 0; diff --git a/glfw/glfw.py b/glfw/glfw.py index f28792804..8cc480743 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -172,6 +172,7 @@ def generate_wrappers(glfw_header): GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback) GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods) + void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback) void* glfwGetX11Display(void) int32_t glfwGetX11Window(GLFWwindow* window) void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string) @@ -199,6 +200,7 @@ const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callbac typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long); typedef int (* GLFWapplicationshouldhandlereopenfun)(int); typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); +typedef void (* GLFWcocoarenderframefun)(GLFWwindow*); typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id); typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*); typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, const char*); diff --git a/glfw/nsgl_context.m b/glfw/nsgl_context.m index 2c7211f7a..b3b1b65e5 100644 --- a/glfw/nsgl_context.m +++ b/glfw/nsgl_context.m @@ -28,7 +28,6 @@ #if (MAC_OS_X_VERSION_MAX_ALLOWED < 101400) - #define NSOpenGLContextParameterSwapInterval NSOpenGLCPSwapInterval #define NSOpenGLContextParameterSurfaceOpacity NSOpenGLCPSurfaceOpacity #endif @@ -50,11 +49,10 @@ static void swapBuffersNSGL(_GLFWwindow* window) static void swapIntervalNSGL(int interval) { - _GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot); - - GLint sync = interval; - [window->context.nsgl.object setValues:&sync - forParameter:NSOpenGLContextParameterSwapInterval]; + // As of Mojave this does not work so we use CVDisplayLink instead + (void)(interval); + _glfwInputError(GLFW_API_UNAVAILABLE, + "NSGL: Swap intervals do not work on macOS"); } static int extensionSupportedNSGL(const char* extension) diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index c5e528205..6fd8f804b 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -376,6 +376,8 @@ load_glfw(const char* path) { *(void **) (&glfwGetCocoaKeyEquivalent_impl) = dlsym(handle, "glfwGetCocoaKeyEquivalent"); + *(void **) (&glfwCocoaRequestRenderFrame_impl) = dlsym(handle, "glfwCocoaRequestRenderFrame"); + *(void **) (&glfwGetX11Display_impl) = dlsym(handle, "glfwGetX11Display"); *(void **) (&glfwGetX11Window_impl) = dlsym(handle, "glfwGetX11Window"); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 666da732f..eec79140c 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1416,6 +1416,7 @@ typedef struct GLFWgamepadstate typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long); typedef int (* GLFWapplicationshouldhandlereopenfun)(int); typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); +typedef void (* GLFWcocoarenderframefun)(GLFWwindow*); typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id); typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*); typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, const char*); @@ -1911,6 +1912,10 @@ typedef void (*glfwGetCocoaKeyEquivalent_func)(int, int, void*, void*); glfwGetCocoaKeyEquivalent_func glfwGetCocoaKeyEquivalent_impl; #define glfwGetCocoaKeyEquivalent glfwGetCocoaKeyEquivalent_impl +typedef void (*glfwCocoaRequestRenderFrame_func)(GLFWwindow*, GLFWcocoarenderframefun); +glfwCocoaRequestRenderFrame_func glfwCocoaRequestRenderFrame_impl; +#define glfwCocoaRequestRenderFrame glfwCocoaRequestRenderFrame_impl + typedef void* (*glfwGetX11Display_func)(); glfwGetX11Display_func glfwGetX11Display_impl; #define glfwGetX11Display glfwGetX11Display_impl