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
18namespace angle
19{
20
21namespace
22{
23
24using PlatformDisplayID = uint32_t;
25
26constexpr CGLRendererProperty kCGLRPRegistryIDLow  = static_cast<CGLRendererProperty>(140);
27constexpr CGLRendererProperty kCGLRPRegistryIDHigh = static_cast<CGLRendererProperty>(141);
28
29// Code from WebKit to get the active GPU's ID given a display ID.
30uint64_t GetGpuIDFromDisplayID(PlatformDisplayID displayID)
31{
32    GLuint displayMask              = CGDisplayIDToOpenGLDisplayMask(displayID);
33    GLint numRenderers              = 0;
34    CGLRendererInfoObj rendererInfo = nullptr;
35    CGLError error = CGLQueryRendererInfo(displayMask, &rendererInfo, &numRenderers);
36    if (!numRenderers || !rendererInfo || error != kCGLNoError)
37        return 0;
38
39    // The 0th renderer should not be the software renderer.
40    GLint isAccelerated;
41    error = CGLDescribeRenderer(rendererInfo, 0, kCGLRPAccelerated, &isAccelerated);
42    if (!isAccelerated || error != kCGLNoError)
43    {
44        CGLDestroyRendererInfo(rendererInfo);
45        return 0;
46    }
47
48    GLint gpuIDLow  = 0;
49    GLint gpuIDHigh = 0;
50
51    error = CGLDescribeRenderer(rendererInfo, 0, kCGLRPRegistryIDLow, &gpuIDLow);
52
53    if (error != kCGLNoError || gpuIDLow < 0)
54    {
55        CGLDestroyRendererInfo(rendererInfo);
56        return 0;
57    }
58
59    error = CGLDescribeRenderer(rendererInfo, 0, kCGLRPRegistryIDHigh, &gpuIDHigh);
60    if (error != kCGLNoError || gpuIDHigh < 0)
61    {
62        CGLDestroyRendererInfo(rendererInfo);
63        return 0;
64    }
65
66    CGLDestroyRendererInfo(rendererInfo);
67    return static_cast<uint64_t>(gpuIDHigh) << 32 | gpuIDLow;
68}
69
70std::string GetMachineModel()
71{
72    io_service_t platformExpert = IOServiceGetMatchingService(
73        kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
74
75    if (platformExpert == IO_OBJECT_NULL)
76    {
77        return "";
78    }
79
80    CFDataRef modelData = static_cast<CFDataRef>(
81        IORegistryEntryCreateCFProperty(platformExpert, CFSTR("model"), kCFAllocatorDefault, 0));
82    if (modelData == nullptr)
83    {
84        IOObjectRelease(platformExpert);
85        return "";
86    }
87
88    std::string result = reinterpret_cast<const char *>(CFDataGetBytePtr(modelData));
89
90    IOObjectRelease(platformExpert);
91    CFRelease(modelData);
92
93    return result;
94}
95
96// Extracts one integer property from a registry entry.
97bool GetEntryProperty(io_registry_entry_t entry, CFStringRef name, uint32_t *value)
98{
99    *value = 0;
100
101    CFDataRef data = static_cast<CFDataRef>(
102        IORegistryEntrySearchCFProperty(entry, kIOServicePlane, name, kCFAllocatorDefault,
103                                        kIORegistryIterateRecursively | kIORegistryIterateParents));
104
105    if (data == nullptr)
106    {
107        return false;
108    }
109
110    const uint32_t *valuePtr = reinterpret_cast<const uint32_t *>(CFDataGetBytePtr(data));
111
112    if (valuePtr == nullptr)
113    {
114        CFRelease(data);
115        return false;
116    }
117
118    *value = *valuePtr;
119    CFRelease(data);
120    return true;
121}
122
123// Gathers the vendor and device IDs for the PCI GPUs
124bool GetPCIDevices(std::vector<GPUDeviceInfo> *devices)
125{
126    // matchDictionary will be consumed by IOServiceGetMatchingServices, no need to release it.
127    CFMutableDictionaryRef matchDictionary = IOServiceMatching("IOPCIDevice");
128
129    io_iterator_t entryIterator;
130    if (IOServiceGetMatchingServices(kIOMasterPortDefault, matchDictionary, &entryIterator) !=
131        kIOReturnSuccess)
132    {
133        return false;
134    }
135
136    io_registry_entry_t entry = IO_OBJECT_NULL;
137
138    while ((entry = IOIteratorNext(entryIterator)) != IO_OBJECT_NULL)
139    {
140        constexpr uint32_t kClassCodeDisplayVGA = 0x30000;
141        uint32_t classCode;
142        GPUDeviceInfo info;
143
144        if (GetEntryProperty(entry, CFSTR("class-code"), &classCode) &&
145            classCode == kClassCodeDisplayVGA &&
146            GetEntryProperty(entry, CFSTR("vendor-id"), &info.vendorId) &&
147            GetEntryProperty(entry, CFSTR("device-id"), &info.deviceId))
148        {
149            devices->push_back(info);
150        }
151
152        IOObjectRelease(entry);
153    }
154    IOObjectRelease(entryIterator);
155
156    return true;
157}
158
159void SetActiveGPUIndex(SystemInfo *info)
160{
161    VendorID activeVendor = 0;
162    DeviceID activeDevice = 0;
163
164    uint64_t gpuID = GetGpuIDFromDisplayID(kCGDirectMainDisplay);
165
166    if (gpuID == 0)
167        return;
168
169    CFMutableDictionaryRef matchDictionary = IORegistryEntryIDMatching(gpuID);
170    io_service_t gpuEntry = IOServiceGetMatchingService(kIOMasterPortDefault, matchDictionary);
171
172    if (gpuEntry == IO_OBJECT_NULL)
173    {
174        IOObjectRelease(gpuEntry);
175        return;
176    }
177
178    if (!(GetEntryProperty(gpuEntry, CFSTR("vendor-id"), &activeVendor) &&
179          GetEntryProperty(gpuEntry, CFSTR("device-id"), &activeDevice)))
180    {
181        IOObjectRelease(gpuEntry);
182        return;
183    }
184
185    IOObjectRelease(gpuEntry);
186
187    for (size_t i = 0; i < info->gpus.size(); ++i)
188    {
189        if (info->gpus[i].vendorId == activeVendor && info->gpus[i].deviceId == activeDevice)
190        {
191            info->activeGPUIndex = static_cast<int>(i);
192            break;
193        }
194    }
195}
196
197}  // anonymous namespace
198
199bool GetSystemInfo(SystemInfo *info)
200{
201    {
202        int32_t major = 0;
203        int32_t minor = 0;
204        ParseMacMachineModel(GetMachineModel(), &info->machineModelName, &major, &minor);
205        info->machineModelVersion = std::to_string(major) + "." + std::to_string(minor);
206    }
207
208    if (!GetPCIDevices(&(info->gpus)))
209    {
210        return false;
211    }
212
213    if (info->gpus.empty())
214    {
215        return false;
216    }
217
218    // Call the generic GetDualGPUInfo function to initialize info fields
219    // such as isOptimus, isAMDSwitchable, and the activeGPUIndex
220    GetDualGPUInfo(info);
221
222    // Then override the activeGPUIndex field of info to reflect the current
223    // GPU instead of the non-intel GPU
224    if (@available(macOS 10.13, *))
225    {
226        SetActiveGPUIndex(info);
227    }
228
229    // Figure out whether this is a dual-GPU system.
230    //
231    // TODO(kbr): this code was ported over from Chromium, and its correctness
232    // could be improved - need to use Mac-specific APIs to determine whether
233    // offline renderers are allowed, and whether these two GPUs are really the
234    // integrated/discrete GPUs in a laptop.
235    if (info->gpus.size() == 2 &&
236        ((IsIntel(info->gpus[0].vendorId) && !IsIntel(info->gpus[1].vendorId)) ||
237         (!IsIntel(info->gpus[0].vendorId) && IsIntel(info->gpus[1].vendorId))))
238    {
239        info->isMacSwitchable = true;
240    }
241
242    return true;
243}
244
245}  // namespace angle
246
247#endif  // defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST)
248