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