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 18namespace angle 19{ 20 21namespace 22{ 23 24using PlatformDisplayID = uint32_t; 25 26constexpr CGLRendererProperty kCGLRPRegistryIDLow = static_cast<CGLRendererProperty>(140); 27constexpr CGLRendererProperty kCGLRPRegistryIDHigh = static_cast<CGLRendererProperty>(141); 28 29// Code from WebKit to get the active GPU's ID given a display ID. 30uint64_t GetGpuIDFromDisplayID(PlatformDisplayID displayID) 31{ 32 GLuint displayMask = CGDisplayIDToOpenGLDisplayMask(displayID); 33 GLint numRenderers = 0; 34 CGLRendererInfoObj rendererInfo = nullptr; 35 CGLError error = CGLQueryRendererInfo(displayMask, &rendererInfo, &numRenderers); 36 if (!numRenderers || !rendererInfo || error != kCGLNoError) 37 return 0; 38 39 // The 0th renderer should not be the software renderer. 40 GLint isAccelerated; 41 error = CGLDescribeRenderer(rendererInfo, 0, kCGLRPAccelerated, &isAccelerated); 42 if (!isAccelerated || error != kCGLNoError) 43 { 44 CGLDestroyRendererInfo(rendererInfo); 45 return 0; 46 } 47 48 GLint gpuIDLow = 0; 49 GLint gpuIDHigh = 0; 50 51 error = CGLDescribeRenderer(rendererInfo, 0, kCGLRPRegistryIDLow, &gpuIDLow); 52 53 if (error != kCGLNoError || gpuIDLow < 0) 54 { 55 CGLDestroyRendererInfo(rendererInfo); 56 return 0; 57 } 58 59 error = CGLDescribeRenderer(rendererInfo, 0, kCGLRPRegistryIDHigh, &gpuIDHigh); 60 if (error != kCGLNoError || gpuIDHigh < 0) 61 { 62 CGLDestroyRendererInfo(rendererInfo); 63 return 0; 64 } 65 66 CGLDestroyRendererInfo(rendererInfo); 67 return static_cast<uint64_t>(gpuIDHigh) << 32 | gpuIDLow; 68} 69 70std::string GetMachineModel() 71{ 72 io_service_t platformExpert = IOServiceGetMatchingService( 73 kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); 74 75 if (platformExpert == IO_OBJECT_NULL) 76 { 77 return ""; 78 } 79 80 CFDataRef modelData = static_cast<CFDataRef>( 81 IORegistryEntryCreateCFProperty(platformExpert, CFSTR("model"), kCFAllocatorDefault, 0)); 82 if (modelData == nullptr) 83 { 84 IOObjectRelease(platformExpert); 85 return ""; 86 } 87 88 std::string result = reinterpret_cast<const char *>(CFDataGetBytePtr(modelData)); 89 90 IOObjectRelease(platformExpert); 91 CFRelease(modelData); 92 93 return result; 94} 95 96// Extracts one integer property from a registry entry. 97bool GetEntryProperty(io_registry_entry_t entry, CFStringRef name, uint32_t *value) 98{ 99 *value = 0; 100 101 CFDataRef data = static_cast<CFDataRef>( 102 IORegistryEntrySearchCFProperty(entry, kIOServicePlane, name, kCFAllocatorDefault, 103 kIORegistryIterateRecursively | kIORegistryIterateParents)); 104 105 if (data == nullptr) 106 { 107 return false; 108 } 109 110 const uint32_t *valuePtr = reinterpret_cast<const uint32_t *>(CFDataGetBytePtr(data)); 111 112 if (valuePtr == nullptr) 113 { 114 CFRelease(data); 115 return false; 116 } 117 118 *value = *valuePtr; 119 CFRelease(data); 120 return true; 121} 122 123// Gathers the vendor and device IDs for the PCI GPUs 124bool GetPCIDevices(std::vector<GPUDeviceInfo> *devices) 125{ 126 // matchDictionary will be consumed by IOServiceGetMatchingServices, no need to release it. 127 CFMutableDictionaryRef matchDictionary = IOServiceMatching("IOPCIDevice"); 128 129 io_iterator_t entryIterator; 130 if (IOServiceGetMatchingServices(kIOMasterPortDefault, matchDictionary, &entryIterator) != 131 kIOReturnSuccess) 132 { 133 return false; 134 } 135 136 io_registry_entry_t entry = IO_OBJECT_NULL; 137 138 while ((entry = IOIteratorNext(entryIterator)) != IO_OBJECT_NULL) 139 { 140 constexpr uint32_t kClassCodeDisplayVGA = 0x30000; 141 uint32_t classCode; 142 GPUDeviceInfo info; 143 144 if (GetEntryProperty(entry, CFSTR("class-code"), &classCode) && 145 classCode == kClassCodeDisplayVGA && 146 GetEntryProperty(entry, CFSTR("vendor-id"), &info.vendorId) && 147 GetEntryProperty(entry, CFSTR("device-id"), &info.deviceId)) 148 { 149 devices->push_back(info); 150 } 151 152 IOObjectRelease(entry); 153 } 154 IOObjectRelease(entryIterator); 155 156 return true; 157} 158 159void SetActiveGPUIndex(SystemInfo *info) 160{ 161 VendorID activeVendor = 0; 162 DeviceID activeDevice = 0; 163 164 uint64_t gpuID = GetGpuIDFromDisplayID(kCGDirectMainDisplay); 165 166 if (gpuID == 0) 167 return; 168 169 CFMutableDictionaryRef matchDictionary = IORegistryEntryIDMatching(gpuID); 170 io_service_t gpuEntry = IOServiceGetMatchingService(kIOMasterPortDefault, matchDictionary); 171 172 if (gpuEntry == IO_OBJECT_NULL) 173 { 174 IOObjectRelease(gpuEntry); 175 return; 176 } 177 178 if (!(GetEntryProperty(gpuEntry, CFSTR("vendor-id"), &activeVendor) && 179 GetEntryProperty(gpuEntry, CFSTR("device-id"), &activeDevice))) 180 { 181 IOObjectRelease(gpuEntry); 182 return; 183 } 184 185 IOObjectRelease(gpuEntry); 186 187 for (size_t i = 0; i < info->gpus.size(); ++i) 188 { 189 if (info->gpus[i].vendorId == activeVendor && info->gpus[i].deviceId == activeDevice) 190 { 191 info->activeGPUIndex = static_cast<int>(i); 192 break; 193 } 194 } 195} 196 197} // anonymous namespace 198 199bool GetSystemInfo(SystemInfo *info) 200{ 201 { 202 int32_t major = 0; 203 int32_t minor = 0; 204 ParseMacMachineModel(GetMachineModel(), &info->machineModelName, &major, &minor); 205 info->machineModelVersion = std::to_string(major) + "." + std::to_string(minor); 206 } 207 208 if (!GetPCIDevices(&(info->gpus))) 209 { 210 return false; 211 } 212 213 if (info->gpus.empty()) 214 { 215 return false; 216 } 217 218 // Call the generic GetDualGPUInfo function to initialize info fields 219 // such as isOptimus, isAMDSwitchable, and the activeGPUIndex 220 GetDualGPUInfo(info); 221 222 // Then override the activeGPUIndex field of info to reflect the current 223 // GPU instead of the non-intel GPU 224 if (@available(macOS 10.13, *)) 225 { 226 SetActiveGPUIndex(info); 227 } 228 229 // Figure out whether this is a dual-GPU system. 230 // 231 // TODO(kbr): this code was ported over from Chromium, and its correctness 232 // could be improved - need to use Mac-specific APIs to determine whether 233 // offline renderers are allowed, and whether these two GPUs are really the 234 // integrated/discrete GPUs in a laptop. 235 if (info->gpus.size() == 2 && 236 ((IsIntel(info->gpus[0].vendorId) && !IsIntel(info->gpus[1].vendorId)) || 237 (!IsIntel(info->gpus[0].vendorId) && IsIntel(info->gpus[1].vendorId)))) 238 { 239 info->isMacSwitchable = true; 240 } 241 242 return true; 243} 244 245} // namespace angle 246 247#endif // defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST) 248