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