2 * MACDRV Cocoa display settings
4 * Copyright 2011, 2012 Ken Thomases for CodeWeavers Inc.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 #import <AppKit/AppKit.h>
24 #ifdef HAVE_MTLDEVICE_REGISTRYID
25 #import <Metal/Metal.h>
27 #include "macdrv_cocoa.h"
29 #pragma GCC diagnostic ignored "-Wdeclaration-after-statement"
31 static uint64_t dedicated_gpu_id;
32 static uint64_t integrated_gpu_id;
34 /***********************************************************************
35 * convert_display_rect
37 * Converts an NSRect in Cocoa's y-goes-up-from-bottom coordinate system
38 * to a CGRect in y-goes-down-from-top coordinates.
40 static inline void convert_display_rect(CGRect* out_rect, NSRect in_rect,
43 *out_rect = NSRectToCGRect(in_rect);
44 out_rect->origin.y = NSMaxY(primary_frame) - NSMaxY(in_rect);
48 /***********************************************************************
51 * Returns information about the displays.
53 * Returns 0 on success and *displays contains a newly-allocated array
54 * of macdrv_display structures and *count contains the number of
55 * elements in that array. The first element of the array is the
56 * primary display. When the caller is done with the array, it should
57 * use macdrv_free_displays() to deallocate it.
59 * Returns non-zero on failure and *displays and *count are unchanged.
61 int macdrv_get_displays(struct macdrv_display** displays, int* count)
65 NSArray* screens = [NSScreen screens];
68 NSUInteger num_screens = [screens count];
69 struct macdrv_display* disps = malloc(num_screens * sizeof(disps[0]));
76 for (i = 0; i < num_screens; i++)
78 NSScreen* screen = screens[i];
79 NSRect frame = [screen frame];
80 NSRect visible_frame = [screen visibleFrame];
83 primary_frame = frame;
85 disps[i].displayID = [[screen deviceDescription][@"NSScreenNumber"] unsignedIntValue];
86 convert_display_rect(&disps[i].frame, frame, primary_frame);
87 convert_display_rect(&disps[i].work_frame, visible_frame,
89 disps[i].frame = cgrect_win_from_mac(disps[i].frame);
90 disps[i].work_frame = cgrect_win_from_mac(disps[i].work_frame);
104 /***********************************************************************
105 * macdrv_free_displays
107 * Deallocates an array of macdrv_display structures previously returned
108 * from macdrv_get_displays().
110 void macdrv_free_displays(struct macdrv_display* displays)
115 /***********************************************************************
116 * get_entry_property_uint32
118 * Get an io registry entry property of type uint32 and store it in value parameter.
120 * Returns non-zero value on failure.
122 static int get_entry_property_uint32(io_registry_entry_t entry, CFStringRef property_name, uint32_t* value)
124 CFDataRef data = IORegistryEntrySearchCFProperty(entry, kIOServicePlane, property_name, kCFAllocatorDefault, 0);
128 if (CFGetTypeID(data) != CFDataGetTypeID() || CFDataGetLength(data) != sizeof(uint32_t))
134 CFDataGetBytes(data, CFRangeMake(0, sizeof(uint32_t)), (UInt8*)value);
139 /***********************************************************************
140 * get_entry_property_string
142 * Get an io registry entry property of type string and write it in buffer parameter.
144 * Returns non-zero value on failure.
146 static int get_entry_property_string(io_registry_entry_t entry, CFStringRef property_name, char* buffer,
151 CFStringRef string_ref;
155 type_ref = IORegistryEntrySearchCFProperty(entry, kIOServicePlane, property_name, kCFAllocatorDefault, 0);
159 if (CFGetTypeID(type_ref) == CFDataGetTypeID())
162 length = CFDataGetLength(data_ref);
163 if (length + 1 > buffer_size)
165 CFDataGetBytes(data_ref, CFRangeMake(0, length), (UInt8*)buffer);
168 else if (CFGetTypeID(type_ref) == CFStringGetTypeID())
170 string_ref = type_ref;
171 if (!CFStringGetCString(string_ref, buffer, buffer_size, kCFStringEncodingUTF8))
184 /***********************************************************************
185 * macdrv_get_gpu_info_from_entry
187 * Starting from entry (which must be the GPU or a child below the GPU),
188 * search upwards to find the IOPCIDevice and get information from it.
189 * In case the GPU is not a PCI device, get properties from 'entry'.
191 * Returns non-zero value on failure.
193 static int macdrv_get_gpu_info_from_entry(struct macdrv_gpu* gpu, io_registry_entry_t entry)
197 io_registry_entry_t parent_entry;
198 io_registry_entry_t gpu_entry;
199 kern_return_t result;
203 while (![@"IOPCIDevice" isEqualToString:[(NSString*)IOObjectCopyClass(gpu_entry) autorelease]])
205 result = IORegistryEntryGetParentEntry(gpu_entry, kIOServicePlane, &parent_entry);
206 if (gpu_entry != entry)
207 IOObjectRelease(gpu_entry);
208 if (result == kIOReturnNoDevice)
210 /* If no IOPCIDevice node is found, get properties from the given entry. */
214 else if (result != kIOReturnSuccess)
219 gpu_entry = parent_entry;
222 if (IORegistryEntryGetRegistryEntryID(gpu_entry, &gpu->id) != kIOReturnSuccess)
227 get_entry_property_uint32(gpu_entry, CFSTR("vendor-id"), &gpu->vendor_id);
228 get_entry_property_uint32(gpu_entry, CFSTR("device-id"), &gpu->device_id);
229 get_entry_property_uint32(gpu_entry, CFSTR("subsystem-id"), &gpu->subsys_id);
230 get_entry_property_uint32(gpu_entry, CFSTR("revision-id"), &gpu->revision_id);
231 get_entry_property_string(gpu_entry, CFSTR("model"), gpu->name, sizeof(gpu->name));
234 if (gpu_entry != entry)
235 IOObjectRelease(gpu_entry);
240 #ifdef HAVE_MTLDEVICE_REGISTRYID
242 /***********************************************************************
243 * macdrv_get_gpu_info_from_registry_id
245 * Get GPU information from a Metal device registry id.
247 * Returns non-zero value on failure.
249 static int macdrv_get_gpu_info_from_registry_id(struct macdrv_gpu* gpu, uint64_t registry_id)
252 io_registry_entry_t entry;
254 entry = IOServiceGetMatchingService(0, IORegistryEntryIDMatching(registry_id));
255 ret = macdrv_get_gpu_info_from_entry(gpu, entry);
256 IOObjectRelease(entry);
260 /***********************************************************************
261 * macdrv_get_gpu_info_from_mtldevice
263 * Get GPU information from a Metal device that responds to the registryID selector.
265 * Returns non-zero value on failure.
267 static int macdrv_get_gpu_info_from_mtldevice(struct macdrv_gpu* gpu, id<MTLDevice> device)
270 if ((ret = macdrv_get_gpu_info_from_registry_id(gpu, [device registryID])))
272 #if defined(MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15
273 /* Apple GPUs aren't PCI devices and therefore have no device ID
274 * Use the Metal GPUFamily as the device ID */
275 if (!gpu->device_id && [device respondsToSelector:@selector(supportsFamily:)] && [device supportsFamily:MTLGPUFamilyApple1])
277 MTLGPUFamily highest = MTLGPUFamilyApple1;
280 /* Apple2, etc are all in order */
281 MTLGPUFamily next = highest + 1;
282 if ([device supportsFamily:next])
287 gpu->device_id = highest;
293 /***********************************************************************
294 * macdrv_get_gpus_from_metal
296 * Get a list of GPUs from Metal.
298 * Returns non-zero value on failure with parameters unchanged and zero on success.
300 static int macdrv_get_gpus_from_metal(struct macdrv_gpu** new_gpus, int* count)
304 struct macdrv_gpu* gpus = NULL;
305 struct macdrv_gpu primary_gpu;
306 id<MTLDevice> primary_device;
307 BOOL hide_integrated = FALSE;
308 int primary_index = 0, i;
311 /* Test if Metal is available */
312 if (&MTLCopyAllDevices == NULL)
314 NSArray<id<MTLDevice>>* devices = [MTLCopyAllDevices() autorelease];
315 if (!devices.count || ![devices[0] respondsToSelector:@selector(registryID)])
318 gpus = calloc(devices.count, sizeof(*gpus));
322 /* Use MTLCreateSystemDefaultDevice instead of CGDirectDisplayCopyCurrentMetalDevice(CGMainDisplayID()) to get
323 * the primary GPU because we need to hide the integrated GPU for an automatic graphic switching pair to avoid apps
324 * using the integrated GPU. This is the behavior of Windows on a Mac. */
325 primary_device = [MTLCreateSystemDefaultDevice() autorelease];
326 if (macdrv_get_gpu_info_from_mtldevice(&primary_gpu, primary_device))
329 /* Hide the integrated GPU if the system default device is a dedicated GPU */
330 if (!primary_device.isLowPower)
332 dedicated_gpu_id = primary_gpu.id;
333 hide_integrated = TRUE;
336 for (i = 0; i < devices.count; i++)
338 if (macdrv_get_gpu_info_from_mtldevice(&gpus[gpu_count], devices[i]))
341 if (hide_integrated && devices[i].isLowPower)
343 integrated_gpu_id = gpus[gpu_count].id;
347 if (gpus[gpu_count].id == primary_gpu.id)
348 primary_index = gpu_count;
353 /* Make sure the first GPU is primary */
356 struct macdrv_gpu tmp;
358 gpus[0] = gpus[primary_index];
359 gpus[primary_index] = tmp;
366 macdrv_free_gpus(gpus);
371 /***********************************************************************
372 * macdrv_get_gpu_info_from_display_id_using_metal
374 * Get GPU information for a CG display id using Metal.
376 * Returns non-zero value on failure.
378 static int macdrv_get_gpu_info_from_display_id_using_metal(struct macdrv_gpu* gpu, CGDirectDisplayID display_id)
382 id<MTLDevice> device;
384 /* Test if Metal is available */
385 if (&CGDirectDisplayCopyCurrentMetalDevice == NULL)
388 device = [CGDirectDisplayCopyCurrentMetalDevice(display_id) autorelease];
389 if (device && [device respondsToSelector:@selector(registryID)])
390 return macdrv_get_gpu_info_from_registry_id(gpu, device.registryID);
398 static int macdrv_get_gpus_from_metal(struct macdrv_gpu** new_gpus, int* count)
403 static int macdrv_get_gpu_info_from_display_id_using_metal(struct macdrv_gpu* gpu, CGDirectDisplayID display_id)
410 /***********************************************************************
411 * macdrv_get_gpu_info_from_display_id
413 * Get GPU information from a display id.
415 * Returns non-zero value on failure.
417 static int macdrv_get_gpu_info_from_display_id(struct macdrv_gpu* gpu, CGDirectDisplayID display_id)
420 io_registry_entry_t entry;
422 ret = macdrv_get_gpu_info_from_display_id_using_metal(gpu, display_id);
425 entry = CGDisplayIOServicePort(display_id);
426 ret = macdrv_get_gpu_info_from_entry(gpu, entry);
431 /***********************************************************************
432 * macdrv_get_gpus_from_iokit
434 * Get a list of GPUs from IOKit.
435 * This is a fallback for 32bit build or older Mac OS version where Metal is unavailable.
437 * Returns non-zero value on failure with parameters unchanged and zero on success.
439 static int macdrv_get_gpus_from_iokit(struct macdrv_gpu** new_gpus, int* count)
441 static const int MAX_GPUS = 4;
442 struct macdrv_gpu primary_gpu = {0};
443 io_registry_entry_t entry;
444 io_iterator_t iterator;
445 struct macdrv_gpu* gpus;
446 int integrated_index = -1;
447 int primary_index = 0;
453 gpus = calloc(MAX_GPUS, sizeof(*gpus));
457 if (IOServiceGetMatchingServices(0, IOServiceMatching("IOPCIDevice"), &iterator)
461 while ((entry = IOIteratorNext(iterator)))
463 if (!get_entry_property_string(entry, CFSTR("IOName"), buffer, sizeof(buffer)) &&
464 !strcmp(buffer, "display") &&
465 !macdrv_get_gpu_info_from_entry(&gpus[gpu_count], entry))
468 assert(gpu_count < MAX_GPUS);
470 IOObjectRelease(entry);
472 IOObjectRelease(iterator);
474 macdrv_get_gpu_info_from_display_id(&primary_gpu, CGMainDisplayID());
476 /* If there are more than two GPUs and an Intel card exists,
477 * assume an automatic graphics pair exists and hide the integrated GPU */
480 for (i = 0; i < gpu_count; i++)
483 * Find integrated GPU without Metal support.
484 * Assuming integrated GPU vendor is Intel for now */
485 if (gpus[i].vendor_id == 0x8086)
487 integrated_gpu_id = gpus[i].id;
488 integrated_index = i;
491 if (gpus[i].id == primary_gpu.id)
497 if (integrated_index != -1)
499 if (integrated_index != gpu_count - 1)
500 gpus[integrated_index] = gpus[gpu_count - 1];
503 * Find the dedicated GPU in an automatic graphics switching pair and use that as primary GPU.
504 * Choose the first dedicated GPU as primary */
505 if (primary_index == integrated_index)
507 else if (primary_index == gpu_count - 1)
508 primary_index = integrated_index;
510 dedicated_gpu_id = gpus[primary_index].id;
515 /* Make sure the first GPU is primary */
518 struct macdrv_gpu tmp;
520 gpus[0] = gpus[primary_index];
521 gpus[primary_index] = tmp;
529 macdrv_free_gpus(gpus);
533 /***********************************************************************
536 * Get a list of GPUs currently in the system. The first GPU is primary.
537 * Call macdrv_free_gpus() when you are done using the data.
539 * Returns non-zero value on failure with parameters unchanged and zero on success.
541 int macdrv_get_gpus(struct macdrv_gpu** new_gpus, int* count)
543 integrated_gpu_id = 0;
544 dedicated_gpu_id = 0;
546 if (!macdrv_get_gpus_from_metal(new_gpus, count))
549 return macdrv_get_gpus_from_iokit(new_gpus, count);
552 /***********************************************************************
555 * Frees a GPU list allocated from macdrv_get_gpus()
557 void macdrv_free_gpus(struct macdrv_gpu* gpus)
563 /***********************************************************************
564 * macdrv_get_adapters
566 * Get a list of adapters under gpu_id. The first adapter is primary if GPU is primary.
567 * Call macdrv_free_adapters() when you are done using the data.
569 * Returns non-zero value on failure with parameters unchanged and zero on success.
571 int macdrv_get_adapters(uint64_t gpu_id, struct macdrv_adapter** new_adapters, int* count)
573 CGDirectDisplayID display_ids[16];
574 uint32_t display_id_count;
575 struct macdrv_adapter* adapters;
576 struct macdrv_gpu gpu;
577 int primary_index = 0;
578 int adapter_count = 0;
582 if (CGGetOnlineDisplayList(sizeof(display_ids) / sizeof(display_ids[0]), display_ids, &display_id_count)
586 if (!display_id_count)
588 *new_adapters = NULL;
593 /* Actual adapter count may be less */
594 adapters = calloc(display_id_count, sizeof(*adapters));
598 for (i = 0; i < display_id_count; i++)
600 /* Mirrored displays are under the same adapter with primary display, so they doesn't increase adapter count */
601 if (CGDisplayMirrorsDisplay(display_ids[i]) != kCGNullDirectDisplay)
604 if (macdrv_get_gpu_info_from_display_id(&gpu, display_ids[i]))
607 if (gpu.id == gpu_id || (gpu_id == dedicated_gpu_id && gpu.id == integrated_gpu_id))
609 adapters[adapter_count].id = display_ids[i];
610 adapters[adapter_count].state_flags = DISPLAY_DEVICE_ATTACHED_TO_DESKTOP;
612 if (CGDisplayIsMain(display_ids[i]))
614 adapters[adapter_count].state_flags |= DISPLAY_DEVICE_PRIMARY_DEVICE;
615 primary_index = adapter_count;
622 /* Make sure the first adapter is primary if the GPU is primary */
625 struct macdrv_adapter tmp;
627 adapters[0] = adapters[primary_index];
628 adapters[primary_index] = tmp;
631 *new_adapters = adapters;
632 *count = adapter_count;
636 macdrv_free_adapters(adapters);
640 /***********************************************************************
641 * macdrv_free_adapters
643 * Frees an adapter list allocated from macdrv_get_adapters()
645 void macdrv_free_adapters(struct macdrv_adapter* adapters)
651 /***********************************************************************
652 * macdrv_get_monitors
654 * Get a list of monitors under adapter_id. The first monitor is primary if adapter is primary.
655 * Call macdrv_free_monitors() when you are done using the data.
657 * Returns non-zero value on failure with parameters unchanged and zero on success.
659 int macdrv_get_monitors(uint32_t adapter_id, struct macdrv_monitor** new_monitors, int* count)
661 struct macdrv_monitor* monitors = NULL;
662 struct macdrv_monitor* realloc_monitors;
663 struct macdrv_display* displays = NULL;
664 CGDirectDisplayID display_ids[16];
665 uint32_t display_id_count;
666 int primary_index = 0;
667 int monitor_count = 0;
673 /* 2 should be enough for most cases */
675 monitors = calloc(capacity, sizeof(*monitors));
679 if (CGGetOnlineDisplayList(sizeof(display_ids) / sizeof(display_ids[0]), display_ids, &display_id_count)
683 if (macdrv_get_displays(&displays, &display_count))
686 for (i = 0; i < display_id_count; i++)
688 if (display_ids[i] != adapter_id && CGDisplayMirrorsDisplay(display_ids[i]) != adapter_id)
691 /* Find and fill in monitor info */
692 for (j = 0; j < display_count; j++)
694 if (displays[j].displayID == display_ids[i]
695 || CGDisplayMirrorsDisplay(display_ids[i]) == displays[j].displayID)
697 /* Allocate more space if needed */
698 if (monitor_count >= capacity)
701 realloc_monitors = realloc(monitors, sizeof(*monitors) * capacity);
702 if (!realloc_monitors)
704 monitors = realloc_monitors;
708 primary_index = monitor_count;
710 monitors[monitor_count].rc_monitor = displays[j].frame;
711 monitors[monitor_count].rc_work = displays[j].work_frame;
718 /* Make sure the first monitor on primary adapter is primary */
721 struct macdrv_monitor tmp;
723 monitors[0] = monitors[primary_index];
724 monitors[primary_index] = tmp;
727 *new_monitors = monitors;
728 *count = monitor_count;
732 macdrv_free_displays(displays);
734 macdrv_free_monitors(monitors);
738 /***********************************************************************
739 * macdrv_free_monitors
741 * Frees an monitor list allocated from macdrv_get_monitors()
743 void macdrv_free_monitors(struct macdrv_monitor* monitors)