cocoa_displaylink.m 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. /*
  2. * cocoa_displaylink.m
  3. * Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
  4. *
  5. * Distributed under terms of the GPL3 license.
  6. */
  7. // CVDisplayLink is deprecated replace with CADisplayLink via [NSScreen displayLink] once base macOS version is 14
  8. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  9. #include "internal.h"
  10. #include <CoreVideo/CVDisplayLink.h>
  11. #define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL s_to_monotonic_t(30ll)
  12. typedef struct _GLFWDisplayLinkNS
  13. {
  14. CVDisplayLinkRef displayLink;
  15. CGDirectDisplayID displayID;
  16. monotonic_t lastRenderFrameRequestedAt, first_unserviced_render_frame_request_at;
  17. } _GLFWDisplayLinkNS;
  18. static struct {
  19. _GLFWDisplayLinkNS entries[256];
  20. size_t count;
  21. } displayLinks = {0};
  22. static CGDirectDisplayID
  23. displayIDForWindow(_GLFWwindow *w) {
  24. NSWindow *nw = w->ns.object;
  25. NSDictionary *dict = [nw.screen deviceDescription];
  26. NSNumber *displayIDns = dict[@"NSScreenNumber"];
  27. if (displayIDns) return [displayIDns unsignedIntValue];
  28. return (CGDirectDisplayID)-1;
  29. }
  30. void
  31. _glfwClearDisplayLinks(void) {
  32. for (size_t i = 0; i < displayLinks.count; i++) {
  33. if (displayLinks.entries[i].displayLink) {
  34. CVDisplayLinkStop(displayLinks.entries[i].displayLink);
  35. CVDisplayLinkRelease(displayLinks.entries[i].displayLink);
  36. }
  37. }
  38. memset(displayLinks.entries, 0, sizeof(_GLFWDisplayLinkNS) * displayLinks.count);
  39. displayLinks.count = 0;
  40. }
  41. static CVReturn
  42. displayLinkCallback(
  43. CVDisplayLinkRef displayLink UNUSED,
  44. const CVTimeStamp* now UNUSED, const CVTimeStamp* outputTime UNUSED,
  45. CVOptionFlags flagsIn UNUSED, CVOptionFlags* flagsOut UNUSED, void* userInfo) {
  46. CGDirectDisplayID displayID = (uintptr_t)userInfo;
  47. NSNumber *arg = [NSNumber numberWithUnsignedInt:displayID];
  48. [NSApp performSelectorOnMainThread:@selector(render_frame_received:) withObject:arg waitUntilDone:NO];
  49. [arg release];
  50. return kCVReturnSuccess;
  51. }
  52. static void
  53. _glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry) {
  54. CVDisplayLinkCreateWithCGDisplay(entry->displayID, &entry->displayLink);
  55. CVDisplayLinkSetOutputCallback(entry->displayLink, &displayLinkCallback, (void*)(uintptr_t)entry->displayID);
  56. }
  57. unsigned
  58. _glfwCreateDisplayLink(CGDirectDisplayID displayID) {
  59. if (displayLinks.count >= arraysz(displayLinks.entries) - 1) {
  60. _glfwInputError(GLFW_PLATFORM_ERROR, "Too many monitors cannot create display link");
  61. return displayLinks.count;
  62. }
  63. for (unsigned i = 0; i < displayLinks.count; i++) {
  64. // already created in this run
  65. if (displayLinks.entries[i].displayID == displayID) return i;
  66. }
  67. _GLFWDisplayLinkNS *entry = &displayLinks.entries[displayLinks.count++];
  68. memset(entry, 0, sizeof(_GLFWDisplayLinkNS));
  69. entry->displayID = displayID;
  70. _glfw_create_cv_display_link(entry);
  71. return displayLinks.count - 1;
  72. }
  73. static unsigned long long display_link_shutdown_timer = 0;
  74. static void
  75. _glfwShutdownCVDisplayLink(unsigned long long timer_id UNUSED, void *user_data UNUSED) {
  76. display_link_shutdown_timer = 0;
  77. for (size_t i = 0; i < displayLinks.count; i++) {
  78. _GLFWDisplayLinkNS *dl = &displayLinks.entries[i];
  79. if (dl->displayLink) CVDisplayLinkStop(dl->displayLink);
  80. dl->lastRenderFrameRequestedAt = 0;
  81. dl->first_unserviced_render_frame_request_at = 0;
  82. }
  83. }
  84. void
  85. _glfwRequestRenderFrame(_GLFWwindow *w) {
  86. CGDirectDisplayID displayID = displayIDForWindow(w);
  87. if (display_link_shutdown_timer) {
  88. _glfwPlatformUpdateTimer(display_link_shutdown_timer, DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, true);
  89. } else {
  90. display_link_shutdown_timer = _glfwPlatformAddTimer(DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, false, _glfwShutdownCVDisplayLink, NULL, NULL);
  91. }
  92. monotonic_t now = glfwGetTime();
  93. bool found_display_link = false;
  94. _GLFWDisplayLinkNS *dl = NULL;
  95. for (size_t i = 0; i < displayLinks.count; i++) {
  96. dl = &displayLinks.entries[i];
  97. if (dl->displayID == displayID) {
  98. found_display_link = true;
  99. dl->lastRenderFrameRequestedAt = now;
  100. if (!dl->first_unserviced_render_frame_request_at) dl->first_unserviced_render_frame_request_at = now;
  101. if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
  102. else if (now - dl->first_unserviced_render_frame_request_at > s_to_monotonic_t(1ll)) {
  103. // display link is stuck need to recreate it because Apple can't even
  104. // get a simple timer right
  105. CVDisplayLinkRelease(dl->displayLink); dl->displayLink = nil;
  106. dl->first_unserviced_render_frame_request_at = now;
  107. _glfw_create_cv_display_link(dl);
  108. _glfwInputError(GLFW_PLATFORM_ERROR,
  109. "CVDisplayLink stuck possibly because of sleep/screensaver + Apple's incompetence, recreating.");
  110. if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
  111. }
  112. } else if (dl->displayLink && dl->lastRenderFrameRequestedAt && now - dl->lastRenderFrameRequestedAt >= DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL) {
  113. CVDisplayLinkStop(dl->displayLink);
  114. dl->lastRenderFrameRequestedAt = 0;
  115. dl->first_unserviced_render_frame_request_at = 0;
  116. }
  117. }
  118. if (!found_display_link) {
  119. unsigned idx = _glfwCreateDisplayLink(displayID);
  120. if (idx < displayLinks.count) {
  121. dl = &displayLinks.entries[idx];
  122. dl->lastRenderFrameRequestedAt = now;
  123. dl->first_unserviced_render_frame_request_at = now;
  124. if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
  125. }
  126. }
  127. }
  128. void
  129. _glfwDispatchRenderFrame(CGDirectDisplayID displayID) {
  130. _GLFWwindow *w = _glfw.windowListHead;
  131. while (w) {
  132. if (w->ns.renderFrameRequested && displayID == displayIDForWindow(w)) {
  133. w->ns.renderFrameRequested = false;
  134. w->ns.renderFrameCallback((GLFWwindow*)w);
  135. }
  136. w = w->next;
  137. }
  138. for (size_t i = 0; i < displayLinks.count; i++) {
  139. _GLFWDisplayLinkNS *dl = &displayLinks.entries[i];
  140. if (dl->displayID == displayID) {
  141. dl->first_unserviced_render_frame_request_at = 0;
  142. }
  143. }
  144. }