blob: 10cd8d09db08cab008cd3a215f14c746112b0e29 [file] [log] [blame]
//
// 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 <Cocoa/Cocoa.h>
# import <IOKit/IOKitLib.h>
# include "common/gl/cgl/FunctionsCGL.h"
namespace angle
{
namespace
{
constexpr CGLRendererProperty kCGLRPRegistryIDLow = static_cast<CGLRendererProperty>(140);
constexpr CGLRendererProperty kCGLRPRegistryIDHigh = static_cast<CGLRendererProperty>(141);
std::string GetMachineModel()
{
io_service_t platformExpert = IOServiceGetMatchingService(
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
if (platformExpert == IO_OBJECT_NULL)
{
return "";
}
CFDataRef modelData = static_cast<CFDataRef>(
IORegistryEntryCreateCFProperty(platformExpert, CFSTR("model"), kCFAllocatorDefault, 0));
if (modelData == nullptr)
{
IOObjectRelease(platformExpert);
return "";
}
std::string result = reinterpret_cast<const char *>(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<CFDataRef>(
IORegistryEntrySearchCFProperty(entry, kIOServicePlane, name, kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents));
if (data == nullptr)
{
return false;
}
const uint32_t *valuePtr = reinterpret_cast<const uint32_t *>(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<GPUDeviceInfo> *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<int>(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<uint64_t>(static_cast<uint32_t>(gpuIDHigh)) << 32) |
static_cast<uint64_t>(static_cast<uint32_t>(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)