Edit

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

Branch :

  • Show log

    Commit

  • Author : Kimmo Kinnunen
    Date : 2022-03-10 15:38:13
    Hash : 9637185c
    Message : Add ForceGPUSwitch to EGL_ANGLE_power_preference eglHandleGPUSwitch() does not work with WebKit sandbox profile. The root cause is that we do not know the primary display, and as such we do not know which GPU drives this. Add eglForceGPUSwitchANGLE(display, gpuIDHigh, gpuIDLow). This lets the caller figure out the GPU in another process. Then the caller can just set the GPU in the sandboxed process. Add tests that are disabled by default until the runner and the infrastructure supports running the tests with automatic switching enabled. Bug: angleproject:7092 Change-Id: I316ee431156596effbdb89659a5e24291719a204 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3516274 Reviewed-by: Jonah Ryan-Davis <jonahr@google.com> Reviewed-by: Kenneth Russell <kbr@chromium.org> Commit-Queue: Kenneth Russell <kbr@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>
    
    #    include "common/gl/cgl/FunctionsCGL.h"
    
    #    if !defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 120000
    #        define HAVE_MAIN_PORT_DEFAULT 1
    #    else
    #        define HAVE_MAIN_PORT_DEFAULT 0
    #    endif
    
    namespace angle
    {
    
    namespace
    {
    
    constexpr CGLRendererProperty kCGLRPRegistryIDLow  = static_cast<CGLRendererProperty>(140);
    constexpr CGLRendererProperty kCGLRPRegistryIDHigh = static_cast<CGLRendererProperty>(141);
    
    std::string GetMachineModel()
    {
    #    if HAVE_MAIN_PORT_DEFAULT
        const mach_port_t mainPort = kIOMainPortDefault;
    #    else
    #        pragma clang diagnostic push
    #        pragma clang diagnostic ignored "-Wdeprecated-declarations"
        const mach_port_t mainPort = kIOMasterPortDefault;
    #        pragma clang diagnostic pop
    #    endif
        io_service_t platformExpert =
            IOServiceGetMatchingService(mainPort, 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] = {"IOGraphicsAccelerator2", "AGXAccelerator"};
        const bool kServiceIsGraphicsAccelerator2[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 HAVE_MAIN_PORT_DEFAULT
            const mach_port_t mainPort = kIOMainPortDefault;
    #    else
    #        pragma clang diagnostic push
    #        pragma clang diagnostic ignored "-Wdeprecated-declarations"
            const mach_port_t mainPort = kIOMasterPortDefault;
    #        pragma clang diagnostic pop
    #    endif
            if (IOServiceGetMatchingServices(mainPort, 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;
                // The registry ID of an IOGraphicsAccelerator2 or AGXAccelerator matches the ID used
                // for GPU selection by ANGLE_platform_angle_device_id
                if (IORegistryEntryGetRegistryEntryID(entry, &info.systemDeviceId) != kIOReturnSuccess)
                {
                    IOObjectRelease(entry);
                    continue;
                }
    
                io_registry_entry_t queryEntry = entry;
                if (kServiceIsGraphicsAccelerator2[i])
                {
                    // If the matching entry is an IOGraphicsAccelerator2, get the parent entry that
                    // will be the IOPCIDevice which holds vendor-id and device-id
                    io_registry_entry_t deviceEntry = IO_OBJECT_NULL;
                    if (IORegistryEntryGetParentEntry(entry, kIOServicePlane, &deviceEntry) !=
                            kIOReturnSuccess ||
                        deviceEntry == IO_OBJECT_NULL)
                    {
                        IOObjectRelease(entry);
                        continue;
                    }
                    IOObjectRelease(entry);
                    queryEntry = deviceEntry;
                }
    
                if (!GetEntryProperty(queryEntry, CFSTR("vendor-id"), &info.vendorId))
                {
                    IOObjectRelease(queryEntry);
                    continue;
                }
                // AGXAccelerator entries only provide a vendor ID.
                if (kServiceIsGraphicsAccelerator2[i])
                {
                    if (!GetEntryProperty(queryEntry, CFSTR("class-code"), &classCode))
                    {
                        IOObjectRelease(queryEntry);
                        continue;
                    }
                    if (classCode != kClassCodeDisplayVGA)
                    {
                        IOObjectRelease(queryEntry);
                        continue;
                    }
                    if (!GetEntryProperty(queryEntry, CFSTR("device-id"), &info.deviceId))
                    {
                        IOObjectRelease(queryEntry);
                        continue;
                    }
                    // Populate revisionId if we can
                    GetEntryProperty(queryEntry, CFSTR("revision-id"), &info.revisionId);
                }
    
                devices->push_back(info);
                IOObjectRelease(queryEntry);
            }
            IOObjectRelease(entryIterator);
    
            // If any devices have been populated by IOGraphicsAccelerator2, do not continue to
            // AGXAccelerator.
            if (!devices->empty())
            {
                break;
            }
        }
    }
    
    void ForceGPUSwitchIndex(SystemInfo *info)
    {
        VendorID activeVendor = 0;
        DeviceID activeDevice = 0;
    
        uint64_t gpuID = GetGpuIDFromDisplayID(kCGDirectMainDisplay);
    
        if (gpuID == 0)
            return;
    
        CFMutableDictionaryRef matchDictionary = IORegistryEntryIDMatching(gpuID);
    #    if HAVE_MAIN_PORT_DEFAULT
        const mach_port_t mainPort = kIOMainPortDefault;
    #    else
    #        pragma clang diagnostic push
    #        pragma clang diagnostic ignored "-Wdeprecated-declarations"
        const mach_port_t mainPort = kIOMasterPortDefault;
    #        pragma clang diagnostic pop
    #    endif
        io_service_t gpuEntry = IOServiceGetMatchingService(mainPort, 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.
    #    if HAVE_MAIN_PORT_DEFAULT
            const mach_port_t mainPort = kIOMainPortDefault;
    #    else
    #        pragma clang diagnostic push
    #        pragma clang diagnostic ignored "-Wdeprecated-declarations"
            const mach_port_t mainPort = kIOMasterPortDefault;
    #        pragma clang diagnostic pop
    #    endif
            io_registry_entry_t acceleratorEntry = IOServiceGetMatchingService(mainPort, 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, *))
        {
            ForceGPUSwitchIndex(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)