master
ブランチ master
ShaderPlayground / deps / glfw / src / cocoa_monitor.m
18.4 KB、612行 生 Blame 履歴
1//========================================================================
2// GLFW 3.3 macOS - www.glfw.org
3//------------------------------------------------------------------------
4// Copyright (c) 2002-2006 Marcus Geelnard
5// Copyright (c) 2006-2019 Camilla Löwy <elmindreda@glfw.org>
6//
7// This software is provided 'as-is', without any express or implied
8// warranty. In no event will the authors be held liable for any damages
9// arising from the use of this software.
10//
11// Permission is granted to anyone to use this software for any purpose,
12// including commercial applications, and to alter it and redistribute it
13// freely, subject to the following restrictions:
14//
15// 1. The origin of this software must not be misrepresented; you must not
16// claim that you wrote the original software. If you use this software
17// in a product, an acknowledgment in the product documentation would
18// be appreciated but is not required.
19//
20// 2. Altered source versions must be plainly marked as such, and must not
21// be misrepresented as being the original software.
22//
23// 3. This notice may not be removed or altered from any source
24// distribution.
25//
26//========================================================================
27// It is fine to use C99 in this file because it will not be built with VS
28//========================================================================
29
30#include "internal.h"
31
32#include <stdlib.h>
33#include <limits.h>
34#include <math.h>
35
36#include <IOKit/graphics/IOGraphicsLib.h>
37#include <ApplicationServices/ApplicationServices.h>
38
39
40// Get the name of the specified display, or NULL
41//
42static char* getDisplayName(CGDirectDisplayID displayID)
43{
44 io_iterator_t it;
45 io_service_t service;
46 CFDictionaryRef info;
47
48 if (IOServiceGetMatchingServices(kIOMasterPortDefault,
49 IOServiceMatching("IODisplayConnect"),
50 &it) != 0)
51 {
52 // This may happen if a desktop Mac is running headless
53 return NULL;
54 }
55
56 while ((service = IOIteratorNext(it)) != 0)
57 {
58 info = IODisplayCreateInfoDictionary(service,
59 kIODisplayOnlyPreferredName);
60
61 CFNumberRef vendorIDRef =
62 CFDictionaryGetValue(info, CFSTR(kDisplayVendorID));
63 CFNumberRef productIDRef =
64 CFDictionaryGetValue(info, CFSTR(kDisplayProductID));
65 if (!vendorIDRef || !productIDRef)
66 {
67 CFRelease(info);
68 continue;
69 }
70
71 unsigned int vendorID, productID;
72 CFNumberGetValue(vendorIDRef, kCFNumberIntType, &vendorID);
73 CFNumberGetValue(productIDRef, kCFNumberIntType, &productID);
74
75 if (CGDisplayVendorNumber(displayID) == vendorID &&
76 CGDisplayModelNumber(displayID) == productID)
77 {
78 // Info dictionary is used and freed below
79 break;
80 }
81
82 CFRelease(info);
83 }
84
85 IOObjectRelease(it);
86
87 if (!service)
88 {
89 _glfwInputError(GLFW_PLATFORM_ERROR,
90 "Cocoa: Failed to find service port for display");
91 return NULL;
92 }
93
94 CFDictionaryRef names =
95 CFDictionaryGetValue(info, CFSTR(kDisplayProductName));
96
97 CFStringRef nameRef;
98
99 if (!names || !CFDictionaryGetValueIfPresent(names, CFSTR("en_US"),
100 (const void**) &nameRef))
101 {
102 // This may happen if a desktop Mac is running headless
103 CFRelease(info);
104 return NULL;
105 }
106
107 const CFIndex size =
108 CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
109 kCFStringEncodingUTF8);
110 char* name = calloc(size + 1, 1);
111 CFStringGetCString(nameRef, name, size, kCFStringEncodingUTF8);
112
113 CFRelease(info);
114 return name;
115}
116
117// Check whether the display mode should be included in enumeration
118//
119static GLFWbool modeIsGood(CGDisplayModeRef mode)
120{
121 uint32_t flags = CGDisplayModeGetIOFlags(mode);
122
123 if (!(flags & kDisplayModeValidFlag) || !(flags & kDisplayModeSafeFlag))
124 return GLFW_FALSE;
125 if (flags & kDisplayModeInterlacedFlag)
126 return GLFW_FALSE;
127 if (flags & kDisplayModeStretchedFlag)
128 return GLFW_FALSE;
129
130#if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
131 CFStringRef format = CGDisplayModeCopyPixelEncoding(mode);
132 if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) &&
133 CFStringCompare(format, CFSTR(IO32BitDirectPixels), 0))
134 {
135 CFRelease(format);
136 return GLFW_FALSE;
137 }
138
139 CFRelease(format);
140#endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
141 return GLFW_TRUE;
142}
143
144// Convert Core Graphics display mode to GLFW video mode
145//
146static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode,
147 double fallbackRefreshRate)
148{
149 GLFWvidmode result;
150 result.width = (int) CGDisplayModeGetWidth(mode);
151 result.height = (int) CGDisplayModeGetHeight(mode);
152 result.refreshRate = (int) round(CGDisplayModeGetRefreshRate(mode));
153
154 if (result.refreshRate == 0)
155 result.refreshRate = (int) round(fallbackRefreshRate);
156
157#if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
158 CFStringRef format = CGDisplayModeCopyPixelEncoding(mode);
159 if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) == 0)
160 {
161 result.redBits = 5;
162 result.greenBits = 5;
163 result.blueBits = 5;
164 }
165 else
166#endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
167 {
168 result.redBits = 8;
169 result.greenBits = 8;
170 result.blueBits = 8;
171 }
172
173#if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
174 CFRelease(format);
175#endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
176 return result;
177}
178
179// Starts reservation for display fading
180//
181static CGDisplayFadeReservationToken beginFadeReservation(void)
182{
183 CGDisplayFadeReservationToken token = kCGDisplayFadeReservationInvalidToken;
184
185 if (CGAcquireDisplayFadeReservation(5, &token) == kCGErrorSuccess)
186 {
187 CGDisplayFade(token, 0.3,
188 kCGDisplayBlendNormal,
189 kCGDisplayBlendSolidColor,
190 0.0, 0.0, 0.0,
191 TRUE);
192 }
193
194 return token;
195}
196
197// Ends reservation for display fading
198//
199static void endFadeReservation(CGDisplayFadeReservationToken token)
200{
201 if (token != kCGDisplayFadeReservationInvalidToken)
202 {
203 CGDisplayFade(token, 0.5,
204 kCGDisplayBlendSolidColor,
205 kCGDisplayBlendNormal,
206 0.0, 0.0, 0.0,
207 FALSE);
208 CGReleaseDisplayFadeReservation(token);
209 }
210}
211
212// Finds and caches the NSScreen corresponding to the specified monitor
213//
214static GLFWbool refreshMonitorScreen(_GLFWmonitor* monitor)
215{
216 if (monitor->ns.screen)
217 return GLFW_TRUE;
218
219 for (NSScreen* screen in [NSScreen screens])
220 {
221 NSNumber* displayID = [screen deviceDescription][@"NSScreenNumber"];
222
223 // HACK: Compare unit numbers instead of display IDs to work around
224 // display replacement on machines with automatic graphics
225 // switching
226 if (monitor->ns.unitNumber == CGDisplayUnitNumber([displayID unsignedIntValue]))
227 {
228 monitor->ns.screen = screen;
229 return GLFW_TRUE;
230 }
231 }
232
233 _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to find a screen for monitor");
234 return GLFW_FALSE;
235}
236
237// Returns the display refresh rate queried from the I/O registry
238//
239static double getFallbackRefreshRate(CGDirectDisplayID displayID)
240{
241 double refreshRate = 60.0;
242
243 io_iterator_t it;
244 io_service_t service;
245
246 if (IOServiceGetMatchingServices(kIOMasterPortDefault,
247 IOServiceMatching("IOFramebuffer"),
248 &it) != 0)
249 {
250 return refreshRate;
251 }
252
253 while ((service = IOIteratorNext(it)) != 0)
254 {
255 const CFNumberRef indexRef =
256 IORegistryEntryCreateCFProperty(service,
257 CFSTR("IOFramebufferOpenGLIndex"),
258 kCFAllocatorDefault,
259 kNilOptions);
260 if (!indexRef)
261 continue;
262
263 uint32_t index = 0;
264 CFNumberGetValue(indexRef, kCFNumberIntType, &index);
265 CFRelease(indexRef);
266
267 if (CGOpenGLDisplayMaskToDisplayID(1 << index) != displayID)
268 continue;
269
270 const CFNumberRef clockRef =
271 IORegistryEntryCreateCFProperty(service,
272 CFSTR("IOFBCurrentPixelClock"),
273 kCFAllocatorDefault,
274 kNilOptions);
275 const CFNumberRef countRef =
276 IORegistryEntryCreateCFProperty(service,
277 CFSTR("IOFBCurrentPixelCount"),
278 kCFAllocatorDefault,
279 kNilOptions);
280 if (!clockRef || !countRef)
281 break;
282
283 uint32_t clock = 0, count = 0;
284 CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
285 CFNumberGetValue(countRef, kCFNumberIntType, &count);
286 CFRelease(clockRef);
287 CFRelease(countRef);
288
289 if (clock > 0 && count > 0)
290 refreshRate = clock / (double) count;
291
292 break;
293 }
294
295 IOObjectRelease(it);
296 return refreshRate;
297}
298
299
300//////////////////////////////////////////////////////////////////////////
301////// GLFW internal API //////
302//////////////////////////////////////////////////////////////////////////
303
304// Poll for changes in the set of connected monitors
305//
306void _glfwPollMonitorsNS(void)
307{
308 uint32_t displayCount;
309 CGGetOnlineDisplayList(0, NULL, &displayCount);
310 CGDirectDisplayID* displays = calloc(displayCount, sizeof(CGDirectDisplayID));
311 CGGetOnlineDisplayList(displayCount, displays, &displayCount);
312
313 for (int i = 0; i < _glfw.monitorCount; i++)
314 _glfw.monitors[i]->ns.screen = nil;
315
316 _GLFWmonitor** disconnected = NULL;
317 uint32_t disconnectedCount = _glfw.monitorCount;
318 if (disconnectedCount)
319 {
320 disconnected = calloc(_glfw.monitorCount, sizeof(_GLFWmonitor*));
321 memcpy(disconnected,
322 _glfw.monitors,
323 _glfw.monitorCount * sizeof(_GLFWmonitor*));
324 }
325
326 for (uint32_t i = 0; i < displayCount; i++)
327 {
328 if (CGDisplayIsAsleep(displays[i]))
329 continue;
330
331 // HACK: Compare unit numbers instead of display IDs to work around
332 // display replacement on machines with automatic graphics
333 // switching
334 const uint32_t unitNumber = CGDisplayUnitNumber(displays[i]);
335 for (uint32_t j = 0; j < disconnectedCount; j++)
336 {
337 if (disconnected[j] && disconnected[j]->ns.unitNumber == unitNumber)
338 {
339 disconnected[j] = NULL;
340 break;
341 }
342 }
343
344 const CGSize size = CGDisplayScreenSize(displays[i]);
345 char* name = getDisplayName(displays[i]);
346 if (!name)
347 name = _glfw_strdup("Unknown");
348
349 _GLFWmonitor* monitor = _glfwAllocMonitor(name, size.width, size.height);
350 monitor->ns.displayID = displays[i];
351 monitor->ns.unitNumber = unitNumber;
352
353 free(name);
354
355 CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displays[i]);
356 if (CGDisplayModeGetRefreshRate(mode) == 0.0)
357 monitor->ns.fallbackRefreshRate = getFallbackRefreshRate(displays[i]);
358 CGDisplayModeRelease(mode);
359
360 _glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
361 }
362
363 for (uint32_t i = 0; i < disconnectedCount; i++)
364 {
365 if (disconnected[i])
366 _glfwInputMonitor(disconnected[i], GLFW_DISCONNECTED, 0);
367 }
368
369 free(disconnected);
370 free(displays);
371}
372
373// Change the current video mode
374//
375void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
376{
377 GLFWvidmode current;
378 _glfwPlatformGetVideoMode(monitor, &current);
379
380 const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired);
381 if (_glfwCompareVideoModes(&current, best) == 0)
382 return;
383
384 CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
385 const CFIndex count = CFArrayGetCount(modes);
386 CGDisplayModeRef native = NULL;
387
388 for (CFIndex i = 0; i < count; i++)
389 {
390 CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
391 if (!modeIsGood(dm))
392 continue;
393
394 const GLFWvidmode mode =
395 vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
396 if (_glfwCompareVideoModes(best, &mode) == 0)
397 {
398 native = dm;
399 break;
400 }
401 }
402
403 if (native)
404 {
405 if (monitor->ns.previousMode == NULL)
406 monitor->ns.previousMode = CGDisplayCopyDisplayMode(monitor->ns.displayID);
407
408 CGDisplayFadeReservationToken token = beginFadeReservation();
409 CGDisplaySetDisplayMode(monitor->ns.displayID, native, NULL);
410 endFadeReservation(token);
411 }
412
413 CFRelease(modes);
414}
415
416// Restore the previously saved (original) video mode
417//
418void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor)
419{
420 if (monitor->ns.previousMode)
421 {
422 CGDisplayFadeReservationToken token = beginFadeReservation();
423 CGDisplaySetDisplayMode(monitor->ns.displayID,
424 monitor->ns.previousMode, NULL);
425 endFadeReservation(token);
426
427 CGDisplayModeRelease(monitor->ns.previousMode);
428 monitor->ns.previousMode = NULL;
429 }
430}
431
432
433//////////////////////////////////////////////////////////////////////////
434////// GLFW platform API //////
435//////////////////////////////////////////////////////////////////////////
436
437void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
438{
439}
440
441void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
442{
443 @autoreleasepool {
444
445 const CGRect bounds = CGDisplayBounds(monitor->ns.displayID);
446
447 if (xpos)
448 *xpos = (int) bounds.origin.x;
449 if (ypos)
450 *ypos = (int) bounds.origin.y;
451
452 } // autoreleasepool
453}
454
455void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
456 float* xscale, float* yscale)
457{
458 @autoreleasepool {
459
460 if (!refreshMonitorScreen(monitor))
461 return;
462
463 const NSRect points = [monitor->ns.screen frame];
464 const NSRect pixels = [monitor->ns.screen convertRectToBacking:points];
465
466 if (xscale)
467 *xscale = (float) (pixels.size.width / points.size.width);
468 if (yscale)
469 *yscale = (float) (pixels.size.height / points.size.height);
470
471 } // autoreleasepool
472}
473
474void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor,
475 int* xpos, int* ypos,
476 int* width, int* height)
477{
478 @autoreleasepool {
479
480 if (!refreshMonitorScreen(monitor))
481 return;
482
483 const NSRect frameRect = [monitor->ns.screen visibleFrame];
484
485 if (xpos)
486 *xpos = frameRect.origin.x;
487 if (ypos)
488 *ypos = _glfwTransformYNS(frameRect.origin.y + frameRect.size.height - 1);
489 if (width)
490 *width = frameRect.size.width;
491 if (height)
492 *height = frameRect.size.height;
493
494 } // autoreleasepool
495}
496
497GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
498{
499 @autoreleasepool {
500
501 *count = 0;
502
503 CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
504 const CFIndex found = CFArrayGetCount(modes);
505 GLFWvidmode* result = calloc(found, sizeof(GLFWvidmode));
506
507 for (CFIndex i = 0; i < found; i++)
508 {
509 CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
510 if (!modeIsGood(dm))
511 continue;
512
513 const GLFWvidmode mode =
514 vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
515 CFIndex j;
516
517 for (j = 0; j < *count; j++)
518 {
519 if (_glfwCompareVideoModes(result + j, &mode) == 0)
520 break;
521 }
522
523 // Skip duplicate modes
524 if (i < *count)
525 continue;
526
527 (*count)++;
528 result[*count - 1] = mode;
529 }
530
531 CFRelease(modes);
532 return result;
533
534 } // autoreleasepool
535}
536
537void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
538{
539 @autoreleasepool {
540
541 CGDisplayModeRef native = CGDisplayCopyDisplayMode(monitor->ns.displayID);
542 *mode = vidmodeFromCGDisplayMode(native, monitor->ns.fallbackRefreshRate);
543 CGDisplayModeRelease(native);
544
545 } // autoreleasepool
546}
547
548GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
549{
550 @autoreleasepool {
551
552 uint32_t size = CGDisplayGammaTableCapacity(monitor->ns.displayID);
553 CGGammaValue* values = calloc(size * 3, sizeof(CGGammaValue));
554
555 CGGetDisplayTransferByTable(monitor->ns.displayID,
556 size,
557 values,
558 values + size,
559 values + size * 2,
560 &size);
561
562 _glfwAllocGammaArrays(ramp, size);
563
564 for (uint32_t i = 0; i < size; i++)
565 {
566 ramp->red[i] = (unsigned short) (values[i] * 65535);
567 ramp->green[i] = (unsigned short) (values[i + size] * 65535);
568 ramp->blue[i] = (unsigned short) (values[i + size * 2] * 65535);
569 }
570
571 free(values);
572 return GLFW_TRUE;
573
574 } // autoreleasepool
575}
576
577void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
578{
579 @autoreleasepool {
580
581 CGGammaValue* values = calloc(ramp->size * 3, sizeof(CGGammaValue));
582
583 for (unsigned int i = 0; i < ramp->size; i++)
584 {
585 values[i] = ramp->red[i] / 65535.f;
586 values[i + ramp->size] = ramp->green[i] / 65535.f;
587 values[i + ramp->size * 2] = ramp->blue[i] / 65535.f;
588 }
589
590 CGSetDisplayTransferByTable(monitor->ns.displayID,
591 ramp->size,
592 values,
593 values + ramp->size,
594 values + ramp->size * 2);
595
596 free(values);
597
598 } // autoreleasepool
599}
600
601
602//////////////////////////////////////////////////////////////////////////
603////// GLFW native API //////
604//////////////////////////////////////////////////////////////////////////
605
606GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle)
607{
608 _GLFWmonitor* monitor = (_GLFWmonitor*) handle;
609 _GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay);
610 return monitor->ns.displayID;
611}
612