Edit

kc3-lang/angle/src/gpu_info_util/SystemInfo_macos.mm

Branch :

  • Show log

    Commit

  • Author : Christopher Cameron
    Date : 2020-07-28 15:26:42
    Hash : 66665af2
    Message : macOS/ARM: Allow populating GPU based on AGXAccelerator The existing scheme for populating GPU vendor and device IDs fails on macOS/ARM. If we find no PCI registry entries, look for a AGXAccelerator entry, and (partially) populate that. This is sufficient for Chrome to initialize hardware acceleration. It is unknown how this will interact with multiple GPUs, and this will likely need to be revisited. Bug: chromium:1110421 Change-Id: I08069d7aecf45c83a1d2827cfccc4733c1835994 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2324939 Reviewed-by: Corentin Wallez <cwallez@chromium.org> Reviewed-by: Geoff Lang <geofflang@chromium.org> Reviewed-by: Lingfeng Yang <lfy@google.com> Commit-Queue: ccameron <ccameron@chromium.org>

  • src/gpu_info_util/SystemInfo_macos.mm
  • //
    // 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>
    
    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;
    }
    
    bool GetSystemInfo(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;
        }
    
        return true;
    }
    
    }  // namespace angle
    
    #endif  // defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST)