1// 2// Copyright 2022 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#include "common/apple_platform_utils.h" 8 9#include "common/debug.h" 10#include "common/system_utils.h" 11 12#include <Foundation/Foundation.h> 13#include <Metal/Metal.h> 14 15namespace angle 16{ 17 18bool IsMetalRendererAvailable() 19{ 20#if defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST) 21 static bool queriedMachineModel = false; 22 static bool machineModelSufficient = true; 23 24 if (!queriedMachineModel) 25 { 26 queriedMachineModel = true; 27 28 std::string fullMachineModel; 29 if (GetMacosMachineModel(&fullMachineModel)) 30 { 31 using MachineModelVersion = std::pair<int32_t, int32_t>; 32 33 std::string name; 34 MachineModelVersion version; 35 ParseMacMachineModel(fullMachineModel, &name, &version.first, &version.second); 36 37 std::optional<MachineModelVersion> minVersion; 38 if (name == "MacBookAir") 39 { 40 minVersion = {8, 1}; // MacBook Air (Retina, 13-inch, 2018) 41 } 42 else if (name == "MacBookPro") 43 { 44 minVersion = {13, 1}; // MacBook Pro (13-inch, 2016) 45 } 46 else if (name == "MacBook") 47 { 48 minVersion = {9, 1}; // MacBook (Retina, 12-inch, Early 2016) 49 } 50 else if (name == "Macmini") 51 { 52 minVersion = {8, 1}; // Mac mini (2018) 53 } 54 else if (name == "iMac") 55 { 56 minVersion = {17, 1}; // iMac (Retina 5K, 27-inch, Late 2015) 57 } 58 else if (name == "iMacPro") 59 { 60 minVersion = {1, 1}; // iMac Pro 61 } 62 63 if (minVersion.has_value() && version < minVersion.value()) 64 { 65 WARN() << "Disabling Metal because machine model \"" << fullMachineModel 66 << "\" is below the minium supported version."; 67 machineModelSufficient = false; 68 } 69 } 70 } 71 72 if (!machineModelSufficient) 73 { 74 ASSERT(queriedMachineModel); 75 return false; 76 } 77#endif 78 79#if defined(ANGLE_PLATFORM_MACOS) && defined(__aarch64__) 80 NSOperatingSystemVersion systemVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; 81 if (systemVersion.majorVersion == 15 && systemVersion.minorVersion == 0) 82 { 83 // On ARM64 MacOS 15.0.x, Metal Shader with newLibraryWithSource didn't work, 84 // if the executable path contains non-ASCII characters. 85 // Bug: https://issues.chromium.org/issues/389559087 86 std::string executableName = GetExecutablePath(); 87 for (char c : executableName) 88 { 89 if (static_cast<unsigned char>(c) > 127) 90 { 91 return false; 92 } 93 } 94 } 95#endif 96 97 static bool gpuFamilySufficient = []() -> bool { 98 ANGLE_APPLE_OBJC_SCOPE 99 { 100 auto device = [MTLCreateSystemDefaultDevice() ANGLE_APPLE_AUTORELEASE]; 101 if (!device) 102 { 103 return false; 104 } 105#if TARGET_OS_MACCATALYST && __IPHONE_OS_VERSION_MIN_REQUIRED < 160000 106 // Devices in family 1, such as MacBookPro11,4, cannot use ANGLE's Metal backend. 107 return [device supportsFamily:MTLGPUFamilyMacCatalyst2]; 108#elif TARGET_OS_MACCATALYST || TARGET_OS_OSX 109 // Devices in family 1, such as MacBookPro11,4, cannot use ANGLE's Metal backend. 110 return [device supportsFamily:MTLGPUFamilyMac2]; 111#else 112 // Devices starting with A9 onwards are supported. Simulator is supported as per 113 // definition that running simulator on Mac Family 1 devices is not supported. 114 return true; 115#endif 116 } 117 }(); 118 return gpuFamilySufficient; 119} 120 121#if defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST) 122bool GetMacosMachineModel(std::string *outMachineModel) 123{ 124# if TARGET_OS_OSX && __MAC_OS_X_VERSION_MIN_REQUIRED < 120000 125 const mach_port_t mainPort = kIOMasterPortDefault; 126# else 127 const mach_port_t mainPort = kIOMainPortDefault; 128# endif 129 io_service_t platformExpert = 130 IOServiceGetMatchingService(mainPort, IOServiceMatching("IOPlatformExpertDevice")); 131 132 if (platformExpert == IO_OBJECT_NULL) 133 { 134 return false; 135 } 136 137 CFDataRef modelData = static_cast<CFDataRef>( 138 IORegistryEntryCreateCFProperty(platformExpert, CFSTR("model"), kCFAllocatorDefault, 0)); 139 if (modelData == nullptr) 140 { 141 IOObjectRelease(platformExpert); 142 return false; 143 } 144 145 *outMachineModel = reinterpret_cast<const char *>(CFDataGetBytePtr(modelData)); 146 147 IOObjectRelease(platformExpert); 148 CFRelease(modelData); 149 150 return true; 151} 152 153bool ParseMacMachineModel(const std::string &identifier, 154 std::string *type, 155 int32_t *major, 156 int32_t *minor) 157{ 158 size_t numberLoc = identifier.find_first_of("0123456789"); 159 if (numberLoc == std::string::npos) 160 { 161 return false; 162 } 163 164 size_t commaLoc = identifier.find(',', numberLoc); 165 if (commaLoc == std::string::npos || commaLoc >= identifier.size()) 166 { 167 return false; 168 } 169 170 const char *numberPtr = &identifier[numberLoc]; 171 const char *commaPtr = &identifier[commaLoc + 1]; 172 char *endPtr = nullptr; 173 174 int32_t majorTmp = static_cast<int32_t>(std::strtol(numberPtr, &endPtr, 10)); 175 if (endPtr == numberPtr) 176 { 177 return false; 178 } 179 180 int32_t minorTmp = static_cast<int32_t>(std::strtol(commaPtr, &endPtr, 10)); 181 if (endPtr == commaPtr) 182 { 183 return false; 184 } 185 186 *major = majorTmp; 187 *minor = minorTmp; 188 *type = identifier.substr(0, numberLoc); 189 190 return true; 191} 192#endif 193 194} // namespace angle 195