• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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