include/mscvpdb.h: Use flexible array members for the rest of structures.
[wine.git] / dlls / winemac.drv / cocoa_display.m
blob3f6246e7e1b455a3bfc37f426831ed02b3069389
1 /*
2  * MACDRV Cocoa display settings
3  *
4  * Copyright 2011, 2012 Ken Thomases for CodeWeavers Inc.
5  *
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.
10  *
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.
15  *
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
19  */
21 #include "config.h"
23 #import <AppKit/AppKit.h>
24 #ifdef HAVE_MTLDEVICE_REGISTRYID
25 #import <Metal/Metal.h>
26 #endif
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
36  *
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.
39  */
40 static inline void convert_display_rect(CGRect* out_rect, NSRect in_rect,
41                                         NSRect primary_frame)
43     *out_rect = NSRectToCGRect(in_rect);
44     out_rect->origin.y = NSMaxY(primary_frame) - NSMaxY(in_rect);
48 /***********************************************************************
49  *              macdrv_get_displays
50  *
51  * Returns information about the displays.
52  *
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.
58  *
59  * Returns non-zero on failure and *displays and *count are unchanged.
60  */
61 int macdrv_get_displays(struct macdrv_display** displays, int* count)
63 @autoreleasepool
65     NSArray* screens = [NSScreen screens];
66     if (screens)
67     {
68         NSUInteger num_screens = [screens count];
69         struct macdrv_display* disps = malloc(num_screens * sizeof(disps[0]));
71         if (disps)
72         {
73             NSRect primary_frame;
75             NSUInteger i;
76             for (i = 0; i < num_screens; i++)
77             {
78                 NSScreen* screen = screens[i];
79                 NSRect frame = [screen frame];
80                 NSRect visible_frame = [screen visibleFrame];
82                 if (i == 0)
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,
88                                      primary_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);
91             }
93             *displays = disps;
94             *count = num_screens;
95             return 0;
96         }
97     }
99     return -1;
104 /***********************************************************************
105  *              macdrv_free_displays
107  * Deallocates an array of macdrv_display structures previously returned
108  * from macdrv_get_displays().
109  */
110 void macdrv_free_displays(struct macdrv_display* displays)
112     free(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.
121  */
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);
125     if (!data)
126         return -1;
128     if (CFGetTypeID(data) != CFDataGetTypeID() || CFDataGetLength(data) != sizeof(uint32_t))
129     {
130         CFRelease(data);
131         return -1;
132     }
134     CFDataGetBytes(data, CFRangeMake(0, sizeof(uint32_t)), (UInt8*)value);
135     CFRelease(data);
136     return 0;
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.
145  */
146 static int get_entry_property_string(io_registry_entry_t entry, CFStringRef property_name, char* buffer,
147                                      size_t buffer_size)
149     CFTypeRef type_ref;
150     CFDataRef data_ref;
151     CFStringRef string_ref;
152     size_t length;
153     int ret = -1;
155     type_ref = IORegistryEntrySearchCFProperty(entry, kIOServicePlane, property_name, kCFAllocatorDefault, 0);
156     if (!type_ref)
157         goto done;
159     if (CFGetTypeID(type_ref) == CFDataGetTypeID())
160     {
161         data_ref = type_ref;
162         length = CFDataGetLength(data_ref);
163         if (length + 1 > buffer_size)
164             goto done;
165         CFDataGetBytes(data_ref, CFRangeMake(0, length), (UInt8*)buffer);
166         buffer[length] = 0;
167     }
168     else if (CFGetTypeID(type_ref) == CFStringGetTypeID())
169     {
170         string_ref = type_ref;
171         if (!CFStringGetCString(string_ref, buffer, buffer_size, kCFStringEncodingUTF8))
172             goto done;
173     }
174     else
175         goto done;
177     ret = 0;
178 done:
179     if (type_ref)
180         CFRelease(type_ref);
181     return ret;
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.
192  */
193 static int macdrv_get_gpu_info_from_entry(struct macdrv_gpu* gpu, io_registry_entry_t entry)
195 @autoreleasepool
197     io_registry_entry_t parent_entry;
198     io_registry_entry_t gpu_entry;
199     kern_return_t result;
200     int ret = -1;
202     gpu_entry = entry;
203     while (![@"IOPCIDevice" isEqualToString:[(NSString*)IOObjectCopyClass(gpu_entry) autorelease]])
204     {
205         result = IORegistryEntryGetParentEntry(gpu_entry, kIOServicePlane, &parent_entry);
206         if (gpu_entry != entry)
207             IOObjectRelease(gpu_entry);
208         if (result == kIOReturnNoDevice)
209         {
210             /* If no IOPCIDevice node is found, get properties from the given entry. */
211             gpu_entry = entry;
212             break;
213         }
214         else if (result != kIOReturnSuccess)
215         {
216             return ret;
217         }
219         gpu_entry = parent_entry;
220     }
222     if (IORegistryEntryGetRegistryEntryID(gpu_entry, &gpu->id) != kIOReturnSuccess)
223         goto done;
225     ret = 0;
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));
233 done:
234     if (gpu_entry != entry)
235         IOObjectRelease(gpu_entry);
236     return ret;
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.
248  */
249 static int macdrv_get_gpu_info_from_registry_id(struct macdrv_gpu* gpu, uint64_t registry_id)
251     int ret;
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);
257     return ret;
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.
266  */
267 static int macdrv_get_gpu_info_from_mtldevice(struct macdrv_gpu* gpu, id<MTLDevice> device)
269     int ret;
270     if ((ret = macdrv_get_gpu_info_from_registry_id(gpu, [device registryID])))
271         return ret;
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])
276     {
277         MTLGPUFamily highest = MTLGPUFamilyApple1;
278         while (1)
279         {
280             /* Apple2, etc are all in order */
281             MTLGPUFamily next = highest + 1;
282             if ([device supportsFamily:next])
283                 highest = next;
284             else
285                 break;
286         }
287         gpu->device_id = highest;
288     }
289 #endif
290     return 0;
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.
299  */
300 static int macdrv_get_gpus_from_metal(struct macdrv_gpu** new_gpus, int* count)
302 @autoreleasepool
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;
309     int gpu_count = 0;
311     /* Test if Metal is available */
312     if (&MTLCopyAllDevices == NULL)
313         return -1;
314     NSArray<id<MTLDevice>>* devices = [MTLCopyAllDevices() autorelease];
315     if (!devices.count || ![devices[0] respondsToSelector:@selector(registryID)])
316         return -1;
318     gpus = calloc(devices.count, sizeof(*gpus));
319     if (!gpus)
320         return -1;
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))
327         goto fail;
329     /* Hide the integrated GPU if the system default device is a dedicated GPU */
330     if (!primary_device.isLowPower)
331     {
332         dedicated_gpu_id = primary_gpu.id;
333         hide_integrated = TRUE;
334     }
336     for (i = 0; i < devices.count; i++)
337     {
338         if (macdrv_get_gpu_info_from_mtldevice(&gpus[gpu_count], devices[i]))
339             goto fail;
341         if (hide_integrated && devices[i].isLowPower)
342         {
343             integrated_gpu_id = gpus[gpu_count].id;
344             continue;
345         }
347         if (gpus[gpu_count].id == primary_gpu.id)
348             primary_index = gpu_count;
350         gpu_count++;
351     }
353     /* Make sure the first GPU is primary */
354     if (primary_index)
355     {
356         struct macdrv_gpu tmp;
357         tmp = gpus[0];
358         gpus[0] = gpus[primary_index];
359         gpus[primary_index] = tmp;
360     }
362     *new_gpus = gpus;
363     *count = gpu_count;
364     return 0;
365 fail:
366     macdrv_free_gpus(gpus);
367     return -1;
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.
377  */
378 static int macdrv_get_gpu_info_from_display_id_using_metal(struct macdrv_gpu* gpu, CGDirectDisplayID display_id)
380 @autoreleasepool
382     id<MTLDevice> device;
384     /* Test if Metal is available */
385     if (&CGDirectDisplayCopyCurrentMetalDevice == NULL)
386         return -1;
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);
391     else
392         return -1;
396 #else
398 static int macdrv_get_gpus_from_metal(struct macdrv_gpu** new_gpus, int* count)
400     return -1;
403 static int macdrv_get_gpu_info_from_display_id_using_metal(struct macdrv_gpu* gpu, CGDirectDisplayID display_id)
405     return -1;
408 #endif
410 /***********************************************************************
411  *              macdrv_get_gpu_info_from_display_id
413  * Get GPU information from a display id.
415  * Returns non-zero value on failure.
416  */
417 static int macdrv_get_gpu_info_from_display_id(struct macdrv_gpu* gpu, CGDirectDisplayID display_id)
419     int ret;
420     io_registry_entry_t entry;
422     ret = macdrv_get_gpu_info_from_display_id_using_metal(gpu, display_id);
423     if (ret)
424     {
425         entry = CGDisplayIOServicePort(display_id);
426         ret = macdrv_get_gpu_info_from_entry(gpu, entry);
427     }
428     return ret;
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.
438  */
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;
448     int gpu_count = 0;
449     char buffer[64];
450     int ret = -1;
451     int i;
453     gpus = calloc(MAX_GPUS, sizeof(*gpus));
454     if (!gpus)
455         goto done;
457     if (IOServiceGetMatchingServices(0, IOServiceMatching("IOPCIDevice"), &iterator)
458         != kIOReturnSuccess)
459         goto done;
461     while ((entry = IOIteratorNext(iterator)))
462     {
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))
466         {
467             gpu_count++;
468             assert(gpu_count < MAX_GPUS);
469         }
470         IOObjectRelease(entry);
471     }
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 */
478     if (gpu_count > 1)
479     {
480         for (i = 0; i < gpu_count; i++)
481         {
482             /* FIXME:
483              * Find integrated GPU without Metal support.
484              * Assuming integrated GPU vendor is Intel for now */
485             if (gpus[i].vendor_id == 0x8086)
486             {
487                 integrated_gpu_id = gpus[i].id;
488                 integrated_index = i;
489             }
491             if (gpus[i].id == primary_gpu.id)
492             {
493                 primary_index = i;
494             }
495         }
497         if (integrated_index != -1)
498         {
499             if (integrated_index != gpu_count - 1)
500                 gpus[integrated_index] = gpus[gpu_count - 1];
502             /* FIXME:
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)
506                 primary_index = 0;
507             else if (primary_index == gpu_count - 1)
508                 primary_index = integrated_index;
510             dedicated_gpu_id = gpus[primary_index].id;
511             gpu_count--;
512         }
513     }
515     /* Make sure the first GPU is primary */
516     if (primary_index)
517     {
518         struct macdrv_gpu tmp;
519         tmp = gpus[0];
520         gpus[0] = gpus[primary_index];
521         gpus[primary_index] = tmp;
522     }
524     *new_gpus = gpus;
525     *count = gpu_count;
526     ret = 0;
527 done:
528     if (ret)
529         macdrv_free_gpus(gpus);
530     return ret;
533 /***********************************************************************
534  *              macdrv_get_gpus
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.
540  */
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))
547         return 0;
548     else
549         return macdrv_get_gpus_from_iokit(new_gpus, count);
552 /***********************************************************************
553  *              macdrv_free_gpus
555  * Frees a GPU list allocated from macdrv_get_gpus()
556  */
557 void macdrv_free_gpus(struct macdrv_gpu* gpus)
559     if (gpus)
560         free(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.
570  */
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;
579     int ret = -1;
580     uint32_t i;
582     if (CGGetOnlineDisplayList(sizeof(display_ids) / sizeof(display_ids[0]), display_ids, &display_id_count)
583         != kCGErrorSuccess)
584         return -1;
586     if (!display_id_count)
587     {
588         *new_adapters = NULL;
589         *count = 0;
590         return 0;
591     }
593     /* Actual adapter count may be less */
594     adapters = calloc(display_id_count, sizeof(*adapters));
595     if (!adapters)
596         return -1;
598     for (i = 0; i < display_id_count; i++)
599     {
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)
602             continue;
604         if (macdrv_get_gpu_info_from_display_id(&gpu, display_ids[i]))
605             goto done;
607         if (gpu.id == gpu_id || (gpu_id == dedicated_gpu_id && gpu.id == integrated_gpu_id))
608         {
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]))
613             {
614                 adapters[adapter_count].state_flags |= DISPLAY_DEVICE_PRIMARY_DEVICE;
615                 primary_index = adapter_count;
616             }
618             adapter_count++;
619         }
620     }
622     /* Make sure the first adapter is primary if the GPU is primary */
623     if (primary_index)
624     {
625         struct macdrv_adapter tmp;
626         tmp = adapters[0];
627         adapters[0] = adapters[primary_index];
628         adapters[primary_index] = tmp;
629     }
631     *new_adapters = adapters;
632     *count = adapter_count;
633     ret = 0;
634 done:
635     if (ret)
636         macdrv_free_adapters(adapters);
637     return ret;
640 /***********************************************************************
641  *              macdrv_free_adapters
643  * Frees an adapter list allocated from macdrv_get_adapters()
644  */
645 void macdrv_free_adapters(struct macdrv_adapter* adapters)
647     if (adapters)
648         free(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.
658  */
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;
668     int display_count;
669     int capacity;
670     int ret = -1;
671     int i, j;
673     /* 2 should be enough for most cases */
674     capacity = 2;
675     monitors = calloc(capacity, sizeof(*monitors));
676     if (!monitors)
677         return -1;
679     if (CGGetOnlineDisplayList(sizeof(display_ids) / sizeof(display_ids[0]), display_ids, &display_id_count)
680         != kCGErrorSuccess)
681         goto done;
683     if (macdrv_get_displays(&displays, &display_count))
684         goto done;
686     for (i = 0; i < display_id_count; i++)
687     {
688         if (display_ids[i] != adapter_id && CGDisplayMirrorsDisplay(display_ids[i]) != adapter_id)
689             continue;
691         /* Find and fill in monitor info */
692         for (j = 0; j < display_count; j++)
693         {
694             if (displays[j].displayID == display_ids[i]
695                 || CGDisplayMirrorsDisplay(display_ids[i]) == displays[j].displayID)
696             {
697                 /* Allocate more space if needed */
698                 if (monitor_count >= capacity)
699                 {
700                     capacity *= 2;
701                     realloc_monitors = realloc(monitors, sizeof(*monitors) * capacity);
702                     if (!realloc_monitors)
703                         goto done;
704                     monitors = realloc_monitors;
705                 }
707                 if (j == 0)
708                     primary_index = monitor_count;
710                 monitors[monitor_count].rc_monitor = displays[j].frame;
711                 monitors[monitor_count].rc_work = displays[j].work_frame;
712                 monitor_count++;
713                 break;
714             }
715         }
716     }
718     /* Make sure the first monitor on primary adapter is primary */
719     if (primary_index)
720     {
721         struct macdrv_monitor tmp;
722         tmp = monitors[0];
723         monitors[0] = monitors[primary_index];
724         monitors[primary_index] = tmp;
725     }
727     *new_monitors = monitors;
728     *count = monitor_count;
729     ret = 0;
730 done:
731     if (displays)
732         macdrv_free_displays(displays);
733     if (ret)
734         macdrv_free_monitors(monitors);
735     return ret;
738 /***********************************************************************
739  *              macdrv_free_monitors
741  * Frees an monitor list allocated from macdrv_get_monitors()
742  */
743 void macdrv_free_monitors(struct macdrv_monitor* monitors)
745     if (monitors)
746         free(monitors);