blob: 42d663a801e6bede13a5bba7a7303459d433467b [file] [log] [blame]
//
// Copyright 2022 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.
//
#include "common/apple_platform_utils.h"
#include "common/debug.h"
#include <Metal/Metal.h>
namespace angle
{
bool IsMetalRendererAvailable()
{
#if defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST)
static bool queriedMachineModel = false;
static bool machineModelSufficient = true;
if (!queriedMachineModel)
{
queriedMachineModel = true;
std::string fullMachineModel;
if (GetMacosMachineModel(&fullMachineModel))
{
using MachineModelVersion = std::pair<int32_t, int32_t>;
std::string name;
MachineModelVersion version;
ParseMacMachineModel(fullMachineModel, &name, &version.first, &version.second);
std::optional<MachineModelVersion> minVersion;
if (name == "MacBookAir")
{
minVersion = {8, 1}; // MacBook Air (Retina, 13-inch, 2018)
}
else if (name == "MacBookPro")
{
minVersion = {13, 1}; // MacBook Pro (13-inch, 2016)
}
else if (name == "MacBook")
{
minVersion = {9, 1}; // MacBook (Retina, 12-inch, Early 2016)
}
else if (name == "Macmini")
{
minVersion = {8, 1}; // Mac mini (2018)
}
else if (name == "iMac")
{
minVersion = {17, 1}; // iMac (Retina 5K, 27-inch, Late 2015)
}
else if (name == "iMacPro")
{
minVersion = {1, 1}; // iMac Pro
}
if (minVersion.has_value() && version < minVersion.value())
{
WARN() << "Disabling Metal because machine model \"" << fullMachineModel
<< "\" is below the minium supported version.";
machineModelSufficient = false;
}
}
}
if (!machineModelSufficient)
{
ASSERT(queriedMachineModel);
return false;
}
#endif
static bool queriedSystemDevice = false;
static bool gpuFamilySufficient = false;
// We only support macos 10.13+ and 11 for now. Since they are requirements for Metal 2.0.
#if TARGET_OS_SIMULATOR
if (ANGLE_APPLE_AVAILABLE_XCI(10.13, 13.1, 13))
#else
if (ANGLE_APPLE_AVAILABLE_XCI(10.13, 13.1, 11))
#endif
{
if (!queriedSystemDevice)
{
ANGLE_APPLE_OBJC_SCOPE
{
queriedSystemDevice = true;
auto device = [MTLCreateSystemDefaultDevice() ANGLE_APPLE_AUTORELEASE];
if (!device)
{
return false;
}
// -[MTLDevice supportsFamily] introduced in macOS 10.15, Catalyst 13.1, iOS 13.
#if defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST)
// Old Macs, such as MacBookPro11,4, cannot use ANGLE's Metal backend.
// This check can be removed once they are no longer supported.
if (ANGLE_APPLE_AVAILABLE_XCI(10.15, 13.1, 13))
{
if ([device supportsFamily:MTLGPUFamilyMac2])
gpuFamilySufficient = true;
}
else
{
// Hardcode constant to sidestep compiler errors. Call will
// return false on older macOS versions.
const NSUInteger macFamily2v1 = 10005;
ANGLE_APPLE_ALLOW_DEPRECATED_BEGIN
if ([device supportsFeatureSet:static_cast<MTLFeatureSet>(macFamily2v1)])
gpuFamilySufficient = true;
ANGLE_APPLE_ALLOW_DEPRECATED_END
}
#elif ANGLE_PLATFORM_IOS_FAMILY && !ANGLE_PLATFORM_IOS_FAMILY_SIMULATOR
# if !defined(__IPHONE_16_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_16_0
// Hardcode constant to sidestep compiler errors. Call will
// return false on older macOS versions.
const NSUInteger iosFamily3v1 = 4;
if ([device supportsFeatureSet:static_cast<MTLFeatureSet>(iosFamily3v1)])
gpuFamilySufficient = true;
# else
// iOS 16 and later target A11 Bionic.
gpuFamilySufficient = true;
# endif
#elif ANGLE_PLATFORM_IOS_FAMILY && ANGLE_PLATFORM_IOS_FAMILY_SIMULATOR
// FIXME: Currently we do not have good simulator query, as it does not support
// the whole feature set needed for iOS.
gpuFamilySufficient = true;
#endif
}
}
return gpuFamilySufficient;
}
return false;
}
bool GetMacosMachineModel(std::string *outMachineModel)
{
#if defined(ANGLE_PLATFORM_MACOS) || defined(ANGLE_PLATFORM_MACCATALYST)
# 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 false;
}
CFDataRef modelData = static_cast<CFDataRef>(
IORegistryEntryCreateCFProperty(platformExpert, CFSTR("model"), kCFAllocatorDefault, 0));
if (modelData == nullptr)
{
IOObjectRelease(platformExpert);
return false;
}
*outMachineModel = reinterpret_cast<const char *>(CFDataGetBytePtr(modelData));
IOObjectRelease(platformExpert);
CFRelease(modelData);
return true;
#else
return false;
#endif
}
bool ParseMacMachineModel(const std::string &identifier,
std::string *type,
int32_t *major,
int32_t *minor)
{
size_t numberLoc = identifier.find_first_of("0123456789");
if (numberLoc == std::string::npos)
{
return false;
}
size_t commaLoc = identifier.find(',', numberLoc);
if (commaLoc == std::string::npos || commaLoc >= identifier.size())
{
return false;
}
const char *numberPtr = &identifier[numberLoc];
const char *commaPtr = &identifier[commaLoc + 1];
char *endPtr = nullptr;
int32_t majorTmp = static_cast<int32_t>(std::strtol(numberPtr, &endPtr, 10));
if (endPtr == numberPtr)
{
return false;
}
int32_t minorTmp = static_cast<int32_t>(std::strtol(commaPtr, &endPtr, 10));
if (endPtr == commaPtr)
{
return false;
}
*major = majorTmp;
*minor = minorTmp;
*type = identifier.substr(0, numberLoc);
return true;
}
} // namespace angle