// // Copyright 2017 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // SystemInfo_macos.mm: implementation of the macOS-specific parts of SystemInfo.h #include "common/platform.h" #if defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST) # include "gpu_info_util/SystemInfo_internal.h" # import # import # include "common/gl/cgl/FunctionsCGL.h" namespace angle { namespace { constexpr CGLRendererProperty kCGLRPRegistryIDLow = static_cast(140); constexpr CGLRendererProperty kCGLRPRegistryIDHigh = static_cast(141); std::string GetMachineModel() { io_service_t platformExpert = IOServiceGetMatchingService( kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); if (platformExpert == IO_OBJECT_NULL) { return ""; } CFDataRef modelData = static_cast( IORegistryEntryCreateCFProperty(platformExpert, CFSTR("model"), kCFAllocatorDefault, 0)); if (modelData == nullptr) { IOObjectRelease(platformExpert); return ""; } std::string result = reinterpret_cast(CFDataGetBytePtr(modelData)); IOObjectRelease(platformExpert); CFRelease(modelData); return result; } // Extracts one integer property from a registry entry. bool GetEntryProperty(io_registry_entry_t entry, CFStringRef name, uint32_t *value) { *value = 0; CFDataRef data = static_cast( IORegistryEntrySearchCFProperty(entry, kIOServicePlane, name, kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents)); if (data == nullptr) { return false; } const uint32_t *valuePtr = reinterpret_cast(CFDataGetBytePtr(data)); if (valuePtr == nullptr) { CFRelease(data); return false; } *value = *valuePtr; CFRelease(data); return true; } // Gathers the vendor and device IDs for GPUs listed in the IORegistry. void GetIORegistryDevices(std::vector *devices) { constexpr uint32_t kNumServices = 2; const char *kServiceNames[kNumServices] = {"IOPCIDevice", "AGXAccelerator"}; const bool kServiceIsVGA[kNumServices] = {true, false}; for (uint32_t i = 0; i < kNumServices; ++i) { // matchDictionary will be consumed by IOServiceGetMatchingServices, no need to release it. CFMutableDictionaryRef matchDictionary = IOServiceMatching(kServiceNames[i]); io_iterator_t entryIterator; if (IOServiceGetMatchingServices(kIOMasterPortDefault, matchDictionary, &entryIterator) != kIOReturnSuccess) { continue; } io_registry_entry_t entry = IO_OBJECT_NULL; while ((entry = IOIteratorNext(entryIterator)) != IO_OBJECT_NULL) { constexpr uint32_t kClassCodeDisplayVGA = 0x30000; uint32_t classCode; GPUDeviceInfo info; // AGXAccelerator entries only provide a vendor ID. if (!GetEntryProperty(entry, CFSTR("vendor-id"), &info.vendorId)) { continue; } if (kServiceIsVGA[i]) { if (!GetEntryProperty(entry, CFSTR("class-code"), &classCode)) { continue; } if (classCode != kClassCodeDisplayVGA) { continue; } if (!GetEntryProperty(entry, CFSTR("device-id"), &info.deviceId)) { continue; } } devices->push_back(info); IOObjectRelease(entry); } IOObjectRelease(entryIterator); // If any devices have been populated by IOPCIDevice, do not continue to AGXAccelerator. if (!devices->empty()) { break; } } } void SetActiveGPUIndex(SystemInfo *info) { VendorID activeVendor = 0; DeviceID activeDevice = 0; uint64_t gpuID = GetGpuIDFromDisplayID(kCGDirectMainDisplay); if (gpuID == 0) return; CFMutableDictionaryRef matchDictionary = IORegistryEntryIDMatching(gpuID); io_service_t gpuEntry = IOServiceGetMatchingService(kIOMasterPortDefault, matchDictionary); if (gpuEntry == IO_OBJECT_NULL) { IOObjectRelease(gpuEntry); return; } if (!(GetEntryProperty(gpuEntry, CFSTR("vendor-id"), &activeVendor) && GetEntryProperty(gpuEntry, CFSTR("device-id"), &activeDevice))) { IOObjectRelease(gpuEntry); return; } IOObjectRelease(gpuEntry); for (size_t i = 0; i < info->gpus.size(); ++i) { if (info->gpus[i].vendorId == activeVendor && info->gpus[i].deviceId == activeDevice) { info->activeGPUIndex = static_cast(i); break; } } } } // anonymous namespace // Code from WebKit to get the active GPU's ID given a Core Graphics display ID. // https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/platform/mac/PlatformScreenMac.mm // Used with permission. uint64_t GetGpuIDFromDisplayID(uint32_t displayID) { return GetGpuIDFromOpenGLDisplayMask(CGDisplayIDToOpenGLDisplayMask(displayID)); } // Code from WebKit to query the GPU ID given an OpenGL display mask. // https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/platform/mac/PlatformScreenMac.mm // Used with permission. uint64_t GetGpuIDFromOpenGLDisplayMask(uint32_t displayMask) { if (@available(macOS 10.13, *)) { GLint numRenderers = 0; CGLRendererInfoObj rendererInfo = nullptr; CGLError error = CGLQueryRendererInfo(displayMask, &rendererInfo, &numRenderers); if (!numRenderers || !rendererInfo || error != kCGLNoError) return 0; // The 0th renderer should not be the software renderer. GLint isAccelerated; error = CGLDescribeRenderer(rendererInfo, 0, kCGLRPAccelerated, &isAccelerated); if (!isAccelerated || error != kCGLNoError) { CGLDestroyRendererInfo(rendererInfo); return 0; } GLint gpuIDLow = 0; GLint gpuIDHigh = 0; error = CGLDescribeRenderer(rendererInfo, 0, kCGLRPRegistryIDLow, &gpuIDLow); if (error != kCGLNoError) { CGLDestroyRendererInfo(rendererInfo); return 0; } error = CGLDescribeRenderer(rendererInfo, 0, kCGLRPRegistryIDHigh, &gpuIDHigh); if (error != kCGLNoError) { CGLDestroyRendererInfo(rendererInfo); return 0; } CGLDestroyRendererInfo(rendererInfo); return (static_cast(static_cast(gpuIDHigh)) << 32) | static_cast(static_cast(gpuIDLow)); } return 0; } // Get VendorID from metal device's registry ID VendorID GetVendorIDFromMetalDeviceRegistryID(uint64_t registryID) { # if defined(ANGLE_PLATFORM_MACOS) // On macOS, the following code is only supported since 10.13. if (@available(macOS 10.13, *)) # endif { // Get a matching dictionary for the IOGraphicsAccelerator2 CFMutableDictionaryRef matchingDict = IORegistryEntryIDMatching(registryID); if (matchingDict == nullptr) { return 0; } // IOServiceGetMatchingService will consume the reference on the matching dictionary, // so we don't need to release the dictionary. io_registry_entry_t acceleratorEntry = IOServiceGetMatchingService(kIOMasterPortDefault, matchingDict); if (acceleratorEntry == IO_OBJECT_NULL) { return 0; } // Get the parent entry that will be the IOPCIDevice io_registry_entry_t deviceEntry = IO_OBJECT_NULL; if (IORegistryEntryGetParentEntry(acceleratorEntry, kIOServicePlane, &deviceEntry) != kIOReturnSuccess || deviceEntry == IO_OBJECT_NULL) { IOObjectRelease(acceleratorEntry); return 0; } IOObjectRelease(acceleratorEntry); uint32_t vendorId; if (!GetEntryProperty(deviceEntry, CFSTR("vendor-id"), &vendorId)) { vendorId = 0; } IOObjectRelease(deviceEntry); return vendorId; } return 0; } bool GetSystemInfo_mac(SystemInfo *info) { { int32_t major = 0; int32_t minor = 0; ParseMacMachineModel(GetMachineModel(), &info->machineModelName, &major, &minor); info->machineModelVersion = std::to_string(major) + "." + std::to_string(minor); } GetIORegistryDevices(&info->gpus); if (info->gpus.empty()) { return false; } // Call the generic GetDualGPUInfo function to initialize info fields // such as isOptimus, isAMDSwitchable, and the activeGPUIndex GetDualGPUInfo(info); // Then override the activeGPUIndex field of info to reflect the current // GPU instead of the non-intel GPU if (@available(macOS 10.13, *)) { SetActiveGPUIndex(info); } // Figure out whether this is a dual-GPU system. // // TODO(kbr): this code was ported over from Chromium, and its correctness // could be improved - need to use Mac-specific APIs to determine whether // offline renderers are allowed, and whether these two GPUs are really the // integrated/discrete GPUs in a laptop. if (info->gpus.size() == 2 && ((IsIntel(info->gpus[0].vendorId) && !IsIntel(info->gpus[1].vendorId)) || (!IsIntel(info->gpus[0].vendorId) && IsIntel(info->gpus[1].vendorId)))) { info->isMacSwitchable = true; } # if defined(ANGLE_PLATFORM_MACCATALYST) && defined(ANGLE_CPU_ARM64) info->needsEAGLOnMac = true; # endif return true; } } // namespace angle #endif // defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST)