diff --git a/glfw/cocoa_displaylink.m b/glfw/cocoa_displaylink.m index 0d3ea2f39..70dffa983 100644 --- a/glfw/cocoa_displaylink.m +++ b/glfw/cocoa_displaylink.m @@ -10,6 +10,7 @@ #include "internal.h" #include +#include #define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL s_to_monotonic_t(30ll) @@ -18,6 +19,7 @@ typedef struct _GLFWDisplayLinkNS CVDisplayLinkRef displayLink; CGDirectDisplayID displayID; monotonic_t lastRenderFrameRequestedAt, first_unserviced_render_frame_request_at; + bool pending_dispatch; } _GLFWDisplayLinkNS; static struct { @@ -25,6 +27,18 @@ static struct { size_t count; } displayLinks = {0}; +static os_unfair_lock displayLinkLocks[256]; + +static inline size_t +index_for_entry(_GLFWDisplayLinkNS *entry) { + return (size_t)(entry - displayLinks.entries); +} + +static inline os_unfair_lock* +lock_for_entry(_GLFWDisplayLinkNS *entry) { + return &displayLinkLocks[index_for_entry(entry)]; +} + static CGDirectDisplayID displayIDForWindow(_GLFWwindow *w) { NSWindow *nw = w->ns.object; @@ -37,12 +51,21 @@ displayIDForWindow(_GLFWwindow *w) { void _glfwClearDisplayLinks(void) { for (size_t i = 0; i < displayLinks.count; i++) { - if (displayLinks.entries[i].displayLink) { - CVDisplayLinkStop(displayLinks.entries[i].displayLink); - CVDisplayLinkRelease(displayLinks.entries[i].displayLink); + _GLFWDisplayLinkNS *entry = &displayLinks.entries[i]; + os_unfair_lock *lock = &displayLinkLocks[i]; + os_unfair_lock_lock(lock); + CVDisplayLinkRef link = entry->displayLink; + entry->displayLink = NULL; + entry->displayID = (CGDirectDisplayID)0; + entry->lastRenderFrameRequestedAt = 0; + entry->first_unserviced_render_frame_request_at = 0; + entry->pending_dispatch = false; + os_unfair_lock_unlock(lock); + if (link) { + CVDisplayLinkStop(link); + CVDisplayLinkRelease(link); } } - memset(displayLinks.entries, 0, sizeof(_GLFWDisplayLinkNS) * displayLinks.count); displayLinks.count = 0; } @@ -51,7 +74,16 @@ displayLinkCallback( CVDisplayLinkRef displayLink UNUSED, const CVTimeStamp* now UNUSED, const CVTimeStamp* outputTime UNUSED, CVOptionFlags flagsIn UNUSED, CVOptionFlags* flagsOut UNUSED, void* userInfo) { - CGDirectDisplayID displayID = (uintptr_t)userInfo; + _GLFWDisplayLinkNS *entry = (_GLFWDisplayLinkNS *)userInfo; + if (!entry) return kCVReturnSuccess; + os_unfair_lock *lock = lock_for_entry(entry); + os_unfair_lock_lock(lock); + bool should_dispatch = entry->first_unserviced_render_frame_request_at && + !entry->pending_dispatch; + CGDirectDisplayID displayID = entry->displayID; + if (should_dispatch) entry->pending_dispatch = true; + os_unfair_lock_unlock(lock); + if (!should_dispatch) return kCVReturnSuccess; NSNumber *arg = [NSNumber numberWithUnsignedInt:displayID]; [NSApp performSelectorOnMainThread:@selector(render_frame_received:) withObject:arg waitUntilDone:NO]; [arg release]; @@ -61,7 +93,7 @@ displayLinkCallback( static void _glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry) { CVDisplayLinkCreateWithCGDisplay(entry->displayID, &entry->displayLink); - CVDisplayLinkSetOutputCallback(entry->displayLink, &displayLinkCallback, (void*)(uintptr_t)entry->displayID); + if (entry->displayLink) CVDisplayLinkSetOutputCallback(entry->displayLink, &displayLinkCallback, entry); } unsigned @@ -71,14 +103,25 @@ _glfwCreateDisplayLink(CGDirectDisplayID displayID) { return displayLinks.count; } for (unsigned i = 0; i < displayLinks.count; i++) { - // already created in this run - if (displayLinks.entries[i].displayID == displayID) return i; + os_unfair_lock *existing_lock = &displayLinkLocks[i]; + os_unfair_lock_lock(existing_lock); + bool already_created = displayLinks.entries[i].displayID == displayID; + os_unfair_lock_unlock(existing_lock); + if (already_created) return i; } - _GLFWDisplayLinkNS *entry = &displayLinks.entries[displayLinks.count++]; - memset(entry, 0, sizeof(_GLFWDisplayLinkNS)); + unsigned idx = displayLinks.count; + _GLFWDisplayLinkNS *entry = &displayLinks.entries[idx]; + os_unfair_lock *lock = &displayLinkLocks[idx]; + os_unfair_lock_lock(lock); + entry->displayLink = NULL; entry->displayID = displayID; + entry->lastRenderFrameRequestedAt = 0; + entry->first_unserviced_render_frame_request_at = 0; + entry->pending_dispatch = false; + displayLinks.count++; _glfw_create_cv_display_link(entry); - return displayLinks.count - 1; + os_unfair_lock_unlock(lock); + return idx; } static unsigned long long display_link_shutdown_timer = 0; @@ -88,9 +131,18 @@ _glfwShutdownCVDisplayLink(unsigned long long timer_id UNUSED, void *user_data U display_link_shutdown_timer = 0; for (size_t i = 0; i < displayLinks.count; i++) { _GLFWDisplayLinkNS *dl = &displayLinks.entries[i]; - if (dl->displayLink) CVDisplayLinkStop(dl->displayLink); + os_unfair_lock *lock = &displayLinkLocks[i]; + os_unfair_lock_lock(lock); + CVDisplayLinkRef link = dl->displayLink; + if (link) CVDisplayLinkRetain(link); dl->lastRenderFrameRequestedAt = 0; dl->first_unserviced_render_frame_request_at = 0; + dl->pending_dispatch = false; + os_unfair_lock_unlock(lock); + if (link) { + CVDisplayLinkStop(link); + CVDisplayLinkRelease(link); + } } } @@ -107,34 +159,82 @@ _glfwRequestRenderFrame(_GLFWwindow *w) { _GLFWDisplayLinkNS *dl = NULL; for (size_t i = 0; i < displayLinks.count; i++) { dl = &displayLinks.entries[i]; + bool need_start = false, need_stop = false, need_recreate = false; + bool retain_link = false; + CVDisplayLinkRef link = NULL; + CVDisplayLinkRef new_link = NULL; + bool new_link_retained = false; + os_unfair_lock *lock = &displayLinkLocks[i]; + os_unfair_lock_lock(lock); + link = dl->displayLink; if (dl->displayID == displayID) { found_display_link = true; + monotonic_t first_unserviced = dl->first_unserviced_render_frame_request_at; dl->lastRenderFrameRequestedAt = now; - if (!dl->first_unserviced_render_frame_request_at) dl->first_unserviced_render_frame_request_at = now; - if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink); - else if (now - dl->first_unserviced_render_frame_request_at > s_to_monotonic_t(1ll)) { - // display link is stuck need to recreate it because Apple can't even - // get a simple timer right - CVDisplayLinkRelease(dl->displayLink); dl->displayLink = nil; + if (!first_unserviced) { dl->first_unserviced_render_frame_request_at = now; - _glfw_create_cv_display_link(dl); - _glfwInputError(GLFW_PLATFORM_ERROR, - "CVDisplayLink stuck possibly because of sleep/screensaver + Apple's incompetence, recreating."); - if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink); + first_unserviced = now; } - } else if (dl->displayLink && dl->lastRenderFrameRequestedAt && now - dl->lastRenderFrameRequestedAt >= DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL) { - CVDisplayLinkStop(dl->displayLink); + dl->pending_dispatch = false; + if (link) { + if (!CVDisplayLinkIsRunning(link)) { + need_start = true; + retain_link = true; + } else if (now - first_unserviced > s_to_monotonic_t(1ll)) { + need_recreate = true; + dl->first_unserviced_render_frame_request_at = now; + dl->displayLink = NULL; + } + } + } else if (link && dl->lastRenderFrameRequestedAt && now - dl->lastRenderFrameRequestedAt >= DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL) { + need_stop = true; + retain_link = true; dl->lastRenderFrameRequestedAt = 0; dl->first_unserviced_render_frame_request_at = 0; + dl->pending_dispatch = false; + } + if (retain_link && link) CVDisplayLinkRetain(link); + os_unfair_lock_unlock(lock); + if (need_recreate && link) { + CVDisplayLinkStop(link); + CVDisplayLinkRelease(link); + os_unfair_lock_lock(lock); + _glfw_create_cv_display_link(dl); + new_link = dl->displayLink; + if (new_link) { + CVDisplayLinkRetain(new_link); + new_link_retained = true; + } + dl->pending_dispatch = false; + os_unfair_lock_unlock(lock); + if (new_link) { + if (!CVDisplayLinkIsRunning(new_link)) CVDisplayLinkStart(new_link); + if (new_link_retained) CVDisplayLinkRelease(new_link); + } + _glfwInputError(GLFW_PLATFORM_ERROR, + "CVDisplayLink stuck possibly because of sleep/screensaver + Apple's incompetence, recreating."); + } else { + if (need_start && link) CVDisplayLinkStart(link); + else if (need_stop && link) CVDisplayLinkStop(link); + if (retain_link && link) CVDisplayLinkRelease(link); } } if (!found_display_link) { unsigned idx = _glfwCreateDisplayLink(displayID); if (idx < displayLinks.count) { dl = &displayLinks.entries[idx]; + os_unfair_lock *lock = &displayLinkLocks[idx]; + os_unfair_lock_lock(lock); dl->lastRenderFrameRequestedAt = now; dl->first_unserviced_render_frame_request_at = now; - if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink); + dl->pending_dispatch = false; + CVDisplayLinkRef link = dl->displayLink; + if (link) CVDisplayLinkRetain(link); + os_unfair_lock_unlock(lock); + if (link) { + if (!CVDisplayLinkIsRunning(link)) CVDisplayLinkStart(link); + CVDisplayLinkRelease(link); + } } } } @@ -151,10 +251,33 @@ _glfwDispatchRenderFrame(CGDirectDisplayID displayID) { } for (size_t i = 0; i < displayLinks.count; i++) { _GLFWDisplayLinkNS *dl = &displayLinks.entries[i]; + bool need_stop = false; + CVDisplayLinkRef link = NULL; + os_unfair_lock *lock = &displayLinkLocks[i]; + os_unfair_lock_lock(lock); if (dl->displayID == displayID) { dl->first_unserviced_render_frame_request_at = 0; + dl->pending_dispatch = false; + bool any_pending_request = false; + _GLFWwindow *window = _glfw.windowListHead; + while (window) { + if (window->ns.renderFrameRequested && displayID == displayIDForWindow(window)) { + any_pending_request = true; + break; + } + window = window->next; + } + link = dl->displayLink; + if (!any_pending_request && link && CVDisplayLinkIsRunning(link)) { + need_stop = true; + CVDisplayLinkRetain(link); + dl->lastRenderFrameRequestedAt = 0; + } + } + os_unfair_lock_unlock(lock); + if (need_stop && link) { + CVDisplayLinkStop(link); + CVDisplayLinkRelease(link); } } } - -