• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1//
2// Copyright 2017 The ANGLE Project Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6
7// SystemInfo_macos.mm: implementation of the macOS-specific parts of SystemInfo.h
8
9#include "gpu_info_util/SystemInfo_internal.h"
10
11#import <Cocoa/Cocoa.h>
12#import <IOKit/IOKitLib.h>
13#import <Metal/Metal.h>
14
15#include "common/gl/cgl/FunctionsCGL.h"
16
17#if !defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 120000
18#    define HAVE_MAIN_PORT_DEFAULT 1
19#else
20#    define HAVE_MAIN_PORT_DEFAULT 0
21#endif
22
23namespace angle
24{
25
26namespace
27{
28
29constexpr CGLRendererProperty kCGLRPRegistryIDLow  = static_cast<CGLRendererProperty>(140);
30constexpr CGLRendererProperty kCGLRPRegistryIDHigh = static_cast<CGLRendererProperty>(141);
31
32std::string GetMachineModel()
33{
34#if HAVE_MAIN_PORT_DEFAULT
35    const mach_port_t mainPort = kIOMainPortDefault;
36#else
37#    pragma clang diagnostic push
38#    pragma clang diagnostic ignored "-Wdeprecated-declarations"
39    const mach_port_t mainPort = kIOMasterPortDefault;
40#    pragma clang diagnostic pop
41#endif
42    io_service_t platformExpert =
43        IOServiceGetMatchingService(mainPort, IOServiceMatching("IOPlatformExpertDevice"));
44
45    if (platformExpert == IO_OBJECT_NULL)
46    {
47        return {};
48    }
49
50    CFDataRef modelData = static_cast<CFDataRef>(
51        IORegistryEntryCreateCFProperty(platformExpert, CFSTR("model"), kCFAllocatorDefault, 0));
52    if (modelData == nullptr)
53    {
54        IOObjectRelease(platformExpert);
55        return {};
56    }
57
58    std::string result(reinterpret_cast<const char *>(CFDataGetBytePtr(modelData)));
59
60    IOObjectRelease(platformExpert);
61    CFRelease(modelData);
62
63    return result;
64}
65
66// Extracts one integer property from a registry entry.
67bool GetEntryProperty(io_registry_entry_t entry, CFStringRef name, uint32_t *value)
68{
69    *value = 0;
70
71    CFDataRef data = static_cast<CFDataRef>(
72        IORegistryEntrySearchCFProperty(entry, kIOServicePlane, name, kCFAllocatorDefault,
73                                        kIORegistryIterateRecursively | kIORegistryIterateParents));
74
75    if (data == nullptr)
76    {
77        return false;
78    }
79
80    const uint32_t *valuePtr = reinterpret_cast<const uint32_t *>(CFDataGetBytePtr(data));
81
82    if (valuePtr == nullptr)
83    {
84        CFRelease(data);
85        return false;
86    }
87
88    *value = *valuePtr;
89    CFRelease(data);
90    return true;
91}
92
93// Gathers the vendor and device IDs for GPUs listed in the IORegistry.
94void GetIORegistryDevices(std::vector<GPUDeviceInfo> *devices)
95{
96    constexpr uint32_t kNumServices         = 2;
97    const char *kServiceNames[kNumServices] = {"IOGraphicsAccelerator2", "AGXAccelerator"};
98    const bool kServiceIsGraphicsAccelerator2[kNumServices] = {true, false};
99    for (uint32_t i = 0; i < kNumServices; ++i)
100    {
101        // matchDictionary will be consumed by IOServiceGetMatchingServices, no need to release it.
102        CFMutableDictionaryRef matchDictionary = IOServiceMatching(kServiceNames[i]);
103
104        io_iterator_t entryIterator;
105#if HAVE_MAIN_PORT_DEFAULT
106        const mach_port_t mainPort = kIOMainPortDefault;
107#else
108#    pragma clang diagnostic push
109#    pragma clang diagnostic ignored "-Wdeprecated-declarations"
110        const mach_port_t mainPort = kIOMasterPortDefault;
111#    pragma clang diagnostic pop
112#endif
113        if (IOServiceGetMatchingServices(mainPort, matchDictionary, &entryIterator) !=
114            kIOReturnSuccess)
115        {
116            continue;
117        }
118
119        io_registry_entry_t entry = IO_OBJECT_NULL;
120        while ((entry = IOIteratorNext(entryIterator)) != IO_OBJECT_NULL)
121        {
122            constexpr uint32_t kClassCodeDisplayVGA = 0x30000;
123            uint32_t classCode;
124            GPUDeviceInfo info;
125            // The registry ID of an IOGraphicsAccelerator2 or AGXAccelerator matches the ID used
126            // for GPU selection by ANGLE_platform_angle_device_id
127            if (IORegistryEntryGetRegistryEntryID(entry, &info.systemDeviceId) != kIOReturnSuccess)
128            {
129                IOObjectRelease(entry);
130                continue;
131            }
132
133            io_registry_entry_t queryEntry = entry;
134            if (kServiceIsGraphicsAccelerator2[i])
135            {
136                // If the matching entry is an IOGraphicsAccelerator2, get the parent entry that
137                // will be the IOPCIDevice which holds vendor-id and device-id
138                io_registry_entry_t deviceEntry = IO_OBJECT_NULL;
139                if (IORegistryEntryGetParentEntry(entry, kIOServicePlane, &deviceEntry) !=
140                        kIOReturnSuccess ||
141                    deviceEntry == IO_OBJECT_NULL)
142                {
143                    IOObjectRelease(entry);
144                    continue;
145                }
146                IOObjectRelease(entry);
147                queryEntry = deviceEntry;
148            }
149
150            if (!GetEntryProperty(queryEntry, CFSTR("vendor-id"), &info.vendorId))
151            {
152                IOObjectRelease(queryEntry);
153                continue;
154            }
155            // AGXAccelerator entries only provide a vendor ID.
156            if (kServiceIsGraphicsAccelerator2[i])
157            {
158                if (!GetEntryProperty(queryEntry, CFSTR("class-code"), &classCode))
159                {
160                    IOObjectRelease(queryEntry);
161                    continue;
162                }
163                if (classCode != kClassCodeDisplayVGA)
164                {
165                    IOObjectRelease(queryEntry);
166                    continue;
167                }
168                if (!GetEntryProperty(queryEntry, CFSTR("device-id"), &info.deviceId))
169                {
170                    IOObjectRelease(queryEntry);
171                    continue;
172                }
173                // Populate revisionId if we can
174                GetEntryProperty(queryEntry, CFSTR("revision-id"), &info.revisionId);
175            }
176
177            devices->push_back(info);
178            IOObjectRelease(queryEntry);
179        }
180        IOObjectRelease(entryIterator);
181
182        // If any devices have been populated by IOGraphicsAccelerator2, do not continue to
183        // AGXAccelerator.
184        if (!devices->empty())
185        {
186            break;
187        }
188    }
189}
190
191void ForceGPUSwitchIndex(SystemInfo *info)
192{
193    // Early-out if on a single-GPU system
194    if (info->gpus.size() < 2)
195    {
196        return;
197    }
198
199    VendorID activeVendor = 0;
200    DeviceID activeDevice = 0;
201
202    uint64_t gpuID = GetGpuIDFromDisplayID(kCGDirectMainDisplay);
203
204    if (gpuID == 0)
205        return;
206
207    CFMutableDictionaryRef matchDictionary = IORegistryEntryIDMatching(gpuID);
208#if HAVE_MAIN_PORT_DEFAULT
209    const mach_port_t mainPort = kIOMainPortDefault;
210#else
211#    pragma clang diagnostic push
212#    pragma clang diagnostic ignored "-Wdeprecated-declarations"
213    const mach_port_t mainPort = kIOMasterPortDefault;
214#    pragma clang diagnostic pop
215#endif
216    io_service_t gpuEntry = IOServiceGetMatchingService(mainPort, matchDictionary);
217
218    if (gpuEntry == IO_OBJECT_NULL)
219    {
220        IOObjectRelease(gpuEntry);
221        return;
222    }
223
224    if (!(GetEntryProperty(gpuEntry, CFSTR("vendor-id"), &activeVendor) &&
225          GetEntryProperty(gpuEntry, CFSTR("device-id"), &activeDevice)))
226    {
227        IOObjectRelease(gpuEntry);
228        return;
229    }
230
231    IOObjectRelease(gpuEntry);
232
233    for (size_t i = 0; i < info->gpus.size(); ++i)
234    {
235        if (info->gpus[i].vendorId == activeVendor && info->gpus[i].deviceId == activeDevice)
236        {
237            info->activeGPUIndex = static_cast<int>(i);
238            break;
239        }
240    }
241}
242
243}  // anonymous namespace
244
245// Modified code from WebKit to get the active GPU's ID given a Core Graphics display ID.
246// https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/platform/mac/PlatformScreenMac.mm
247// Used with permission.
248uint64_t GetGpuIDFromDisplayID(uint32_t displayID)
249{
250    // First attempt to use query the registryID from a Metal device before falling back to CGL.
251    // This avoids loading the OpenGL framework when possible.
252    if (@available(macOS 10.13, *))
253    {
254        id<MTLDevice> device = CGDirectDisplayCopyCurrentMetalDevice(displayID);
255        if (device)
256        {
257            uint64_t registryId = [device registryID];
258            [device release];
259            return registryId;
260        }
261    }
262
263    return GetGpuIDFromOpenGLDisplayMask(CGDisplayIDToOpenGLDisplayMask(displayID));
264}
265
266// Code from WebKit to query the GPU ID given an OpenGL display mask.
267// https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/platform/mac/PlatformScreenMac.mm
268// Used with permission.
269uint64_t GetGpuIDFromOpenGLDisplayMask(uint32_t displayMask)
270{
271    if (@available(macOS 10.13, *))
272    {
273        GLint numRenderers              = 0;
274        CGLRendererInfoObj rendererInfo = nullptr;
275        CGLError error = CGLQueryRendererInfo(displayMask, &rendererInfo, &numRenderers);
276        if (!numRenderers || !rendererInfo || error != kCGLNoError)
277            return 0;
278
279        // The 0th renderer should not be the software renderer.
280        GLint isAccelerated;
281        error = CGLDescribeRenderer(rendererInfo, 0, kCGLRPAccelerated, &isAccelerated);
282        if (!isAccelerated || error != kCGLNoError)
283        {
284            CGLDestroyRendererInfo(rendererInfo);
285            return 0;
286        }
287
288        GLint gpuIDLow  = 0;
289        GLint gpuIDHigh = 0;
290
291        error = CGLDescribeRenderer(rendererInfo, 0, kCGLRPRegistryIDLow, &gpuIDLow);
292        if (error != kCGLNoError)
293        {
294            CGLDestroyRendererInfo(rendererInfo);
295            return 0;
296        }
297
298        error = CGLDescribeRenderer(rendererInfo, 0, kCGLRPRegistryIDHigh, &gpuIDHigh);
299        if (error != kCGLNoError)
300        {
301            CGLDestroyRendererInfo(rendererInfo);
302            return 0;
303        }
304
305        CGLDestroyRendererInfo(rendererInfo);
306        return (static_cast<uint64_t>(static_cast<uint32_t>(gpuIDHigh)) << 32) |
307               static_cast<uint64_t>(static_cast<uint32_t>(gpuIDLow));
308    }
309
310    return 0;
311}
312
313// Get VendorID from metal device's registry ID
314VendorID GetVendorIDFromMetalDeviceRegistryID(uint64_t registryID)
315{
316#if defined(ANGLE_PLATFORM_MACOS)
317    // On macOS, the following code is only supported since 10.13.
318    if (@available(macOS 10.13, *))
319#endif
320    {
321        // Get a matching dictionary for the IOGraphicsAccelerator2
322        CFMutableDictionaryRef matchingDict = IORegistryEntryIDMatching(registryID);
323        if (matchingDict == nullptr)
324        {
325            return 0;
326        }
327
328        // IOServiceGetMatchingService will consume the reference on the matching dictionary,
329        // so we don't need to release the dictionary.
330#if HAVE_MAIN_PORT_DEFAULT
331        const mach_port_t mainPort = kIOMainPortDefault;
332#else
333#    pragma clang diagnostic push
334#    pragma clang diagnostic ignored "-Wdeprecated-declarations"
335        const mach_port_t mainPort = kIOMasterPortDefault;
336#    pragma clang diagnostic pop
337#endif
338        io_registry_entry_t acceleratorEntry = IOServiceGetMatchingService(mainPort, matchingDict);
339        if (acceleratorEntry == IO_OBJECT_NULL)
340        {
341            return 0;
342        }
343
344        // Get the parent entry that will be the IOPCIDevice
345        io_registry_entry_t deviceEntry = IO_OBJECT_NULL;
346        if (IORegistryEntryGetParentEntry(acceleratorEntry, kIOServicePlane, &deviceEntry) !=
347                kIOReturnSuccess ||
348            deviceEntry == IO_OBJECT_NULL)
349        {
350            IOObjectRelease(acceleratorEntry);
351            return 0;
352        }
353
354        IOObjectRelease(acceleratorEntry);
355
356        uint32_t vendorId;
357        if (!GetEntryProperty(deviceEntry, CFSTR("vendor-id"), &vendorId))
358        {
359            vendorId = 0;
360        }
361
362        IOObjectRelease(deviceEntry);
363
364        return vendorId;
365    }
366    return 0;
367}
368
369bool GetSystemInfo_mac(SystemInfo *info)
370{
371    {
372        int32_t major = 0;
373        int32_t minor = 0;
374        ParseMacMachineModel(GetMachineModel(), &info->machineModelName, &major, &minor);
375        info->machineModelVersion = std::to_string(major) + "." + std::to_string(minor);
376    }
377
378    GetIORegistryDevices(&info->gpus);
379    if (info->gpus.empty())
380    {
381        return false;
382    }
383
384    // Call the generic GetDualGPUInfo function to initialize info fields
385    // such as isOptimus, isAMDSwitchable, and the activeGPUIndex
386    GetDualGPUInfo(info);
387
388    // Then override the activeGPUIndex field of info to reflect the current
389    // GPU instead of the non-intel GPU
390    if (@available(macOS 10.13, *))
391    {
392        ForceGPUSwitchIndex(info);
393    }
394
395    // Figure out whether this is a dual-GPU system.
396    //
397    // TODO(kbr): this code was ported over from Chromium, and its correctness
398    // could be improved - need to use Mac-specific APIs to determine whether
399    // offline renderers are allowed, and whether these two GPUs are really the
400    // integrated/discrete GPUs in a laptop.
401    if (info->gpus.size() == 2 &&
402        ((IsIntel(info->gpus[0].vendorId) && !IsIntel(info->gpus[1].vendorId)) ||
403         (!IsIntel(info->gpus[0].vendorId) && IsIntel(info->gpus[1].vendorId))))
404    {
405        info->isMacSwitchable = true;
406    }
407
408    return true;
409}
410
411}  // namespace angle
412