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