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