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#include "common/debug.h" 9 10#include <Metal/Metal.h> 11 12namespace angle 13{ 14 15bool IsMetalRendererAvailable() 16{ 17#if defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST) 18 static bool queriedMachineModel = false; 19 static bool machineModelSufficient = true; 20 21 if (!queriedMachineModel) 22 { 23 queriedMachineModel = true; 24 25 std::string fullMachineModel; 26 if (GetMacosMachineModel(&fullMachineModel)) 27 { 28 using MachineModelVersion = std::pair<int32_t, int32_t>; 29 30 std::string name; 31 MachineModelVersion version; 32 ParseMacMachineModel(fullMachineModel, &name, &version.first, &version.second); 33 34 std::optional<MachineModelVersion> minVersion; 35 if (name == "MacBookAir") 36 { 37 minVersion = {8, 1}; // MacBook Air (Retina, 13-inch, 2018) 38 } 39 else if (name == "MacBookPro") 40 { 41 minVersion = {13, 1}; // MacBook Pro (13-inch, 2016) 42 } 43 else if (name == "MacBook") 44 { 45 minVersion = {9, 1}; // MacBook (Retina, 12-inch, Early 2016) 46 } 47 else if (name == "Macmini") 48 { 49 minVersion = {8, 1}; // Mac mini (2018) 50 } 51 else if (name == "iMac") 52 { 53 minVersion = {17, 1}; // iMac (Retina 5K, 27-inch, Late 2015) 54 } 55 else if (name == "iMacPro") 56 { 57 minVersion = {1, 1}; // iMac Pro 58 } 59 60 if (minVersion.has_value() && version < minVersion.value()) 61 { 62 WARN() << "Disabling Metal because machine model \"" << fullMachineModel 63 << "\" is below the minium supported version."; 64 machineModelSufficient = false; 65 } 66 } 67 } 68 69 if (!machineModelSufficient) 70 { 71 ASSERT(queriedMachineModel); 72 return false; 73 } 74#endif 75 76 static bool queriedSystemDevice = false; 77 static bool gpuFamilySufficient = false; 78 79 // We only support macos 10.13+ and 11 for now. Since they are requirements for Metal 2.0. 80#if TARGET_OS_SIMULATOR 81 if (ANGLE_APPLE_AVAILABLE_XCI(10.13, 13.1, 13)) 82#else 83 if (ANGLE_APPLE_AVAILABLE_XCI(10.13, 13.1, 11)) 84#endif 85 { 86 if (!queriedSystemDevice) 87 { 88 ANGLE_APPLE_OBJC_SCOPE 89 { 90 queriedSystemDevice = true; 91 auto device = [MTLCreateSystemDefaultDevice() ANGLE_APPLE_AUTORELEASE]; 92 if (!device) 93 { 94 return false; 95 } 96 97 // -[MTLDevice supportsFamily] introduced in macOS 10.15, Catalyst 13.1, iOS 13. 98#if defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST) 99 // Old Macs, such as MacBookPro11,4, cannot use ANGLE's Metal backend. 100 // This check can be removed once they are no longer supported. 101 if (ANGLE_APPLE_AVAILABLE_XCI(10.15, 13.1, 13)) 102 { 103 if ([device supportsFamily:MTLGPUFamilyMac2]) 104 gpuFamilySufficient = true; 105 } 106 else 107 { 108 // Hardcode constant to sidestep compiler errors. Call will 109 // return false on older macOS versions. 110 const NSUInteger macFamily2v1 = 10005; 111 ANGLE_APPLE_ALLOW_DEPRECATED_BEGIN 112 if ([device supportsFeatureSet:static_cast<MTLFeatureSet>(macFamily2v1)]) 113 gpuFamilySufficient = true; 114 ANGLE_APPLE_ALLOW_DEPRECATED_END 115 } 116#elif ANGLE_PLATFORM_IOS_FAMILY && !ANGLE_PLATFORM_IOS_FAMILY_SIMULATOR 117# if !defined(__IPHONE_16_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_16_0 118 // Hardcode constant to sidestep compiler errors. Call will 119 // return false on older macOS versions. 120 const NSUInteger iosFamily3v1 = 4; 121 if ([device supportsFeatureSet:static_cast<MTLFeatureSet>(iosFamily3v1)]) 122 gpuFamilySufficient = true; 123# else 124 // iOS 16 and later target A11 Bionic. 125 gpuFamilySufficient = true; 126# endif 127#elif ANGLE_PLATFORM_IOS_FAMILY && ANGLE_PLATFORM_IOS_FAMILY_SIMULATOR 128 // FIXME: Currently we do not have good simulator query, as it does not support 129 // the whole feature set needed for iOS. 130 gpuFamilySufficient = true; 131#endif 132 } 133 } 134 135 return gpuFamilySufficient; 136 } 137 return false; 138} 139 140bool GetMacosMachineModel(std::string *outMachineModel) 141{ 142#if defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST) 143# if HAVE_MAIN_PORT_DEFAULT 144 const mach_port_t mainPort = kIOMainPortDefault; 145# else 146# pragma clang diagnostic push 147# pragma clang diagnostic ignored "-Wdeprecated-declarations" 148 const mach_port_t mainPort = kIOMasterPortDefault; 149# pragma clang diagnostic pop 150# endif 151 io_service_t platformExpert = 152 IOServiceGetMatchingService(mainPort, IOServiceMatching("IOPlatformExpertDevice")); 153 154 if (platformExpert == IO_OBJECT_NULL) 155 { 156 return false; 157 } 158 159 CFDataRef modelData = static_cast<CFDataRef>( 160 IORegistryEntryCreateCFProperty(platformExpert, CFSTR("model"), kCFAllocatorDefault, 0)); 161 if (modelData == nullptr) 162 { 163 IOObjectRelease(platformExpert); 164 return false; 165 } 166 167 *outMachineModel = reinterpret_cast<const char *>(CFDataGetBytePtr(modelData)); 168 169 IOObjectRelease(platformExpert); 170 CFRelease(modelData); 171 172 return true; 173#else 174 return false; 175#endif 176} 177 178bool ParseMacMachineModel(const std::string &identifier, 179 std::string *type, 180 int32_t *major, 181 int32_t *minor) 182{ 183 size_t numberLoc = identifier.find_first_of("0123456789"); 184 if (numberLoc == std::string::npos) 185 { 186 return false; 187 } 188 189 size_t commaLoc = identifier.find(',', numberLoc); 190 if (commaLoc == std::string::npos || commaLoc >= identifier.size()) 191 { 192 return false; 193 } 194 195 const char *numberPtr = &identifier[numberLoc]; 196 const char *commaPtr = &identifier[commaLoc + 1]; 197 char *endPtr = nullptr; 198 199 int32_t majorTmp = static_cast<int32_t>(std::strtol(numberPtr, &endPtr, 10)); 200 if (endPtr == numberPtr) 201 { 202 return false; 203 } 204 205 int32_t minorTmp = static_cast<int32_t>(std::strtol(commaPtr, &endPtr, 10)); 206 if (endPtr == commaPtr) 207 { 208 return false; 209 } 210 211 *major = majorTmp; 212 *minor = minorTmp; 213 *type = identifier.substr(0, numberLoc); 214 215 return true; 216} 217 218} // namespace angle 219