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