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