GLFW: Add support for render frames on Cocoa (CVDisplayLink)

Allows vsync to work again since Apple broke OpenGL swap intervals on
Mojave
This commit is contained in:
Kovid Goyal
2019-02-20 15:08:07 +05:30
parent b1c2e90a05
commit 4629ef627f
8 changed files with 164 additions and 12 deletions

View File

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

View File

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

24
glfw/cocoa_platform.h vendored
View File

@@ -30,14 +30,19 @@
#include <Carbon/Carbon.h>
#if defined(__OBJC__)
#import <Cocoa/Cocoa.h>
#import <CoreVideo/CoreVideo.h>
#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);

View File

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

View File

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

View File

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

2
kitty/glfw-wrapper.c generated
View File

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

5
kitty/glfw-wrapper.h generated
View File

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