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