Cycles: enable Metal on AMD GPUs, set macOS minimum versions

* Apple Silicon support enabled on macOS 12.2+
* AMD support enabled on macOS 12.3+

This patch also fixes a device enumeration crash on certain AMD configs which
was caused by over-release of MTLDevice objects.

Differential Revision: https://developer.blender.org/D14090
This commit is contained in:
Michael Jones (Apple) 2022-02-11 19:19:51 +01:00 committed by Brecht Van Lommel
parent 8fb2926a53
commit 40fce61a6a
4 changed files with 42 additions and 121 deletions

View File

@ -39,33 +39,20 @@ bool device_metal_init()
return true;
}
static int device_metal_get_num_devices_safe(uint32_t *num_devices)
{
*num_devices = MTLCopyAllDevices().count;
return 0;
}
void device_metal_info(vector<DeviceInfo> &devices)
{
uint32_t num_devices = 0;
device_metal_get_num_devices_safe(&num_devices);
if (num_devices == 0) {
return;
}
vector<MetalPlatformDevice> usable_devices;
MetalInfo::get_usable_devices(&usable_devices);
auto usable_devices = MetalInfo::get_usable_devices();
/* Devices are numbered consecutively across platforms. */
set<string> unique_ids;
int device_index = 0;
for (MetalPlatformDevice &device : usable_devices) {
for (id<MTLDevice> &device : usable_devices) {
/* Compute unique ID for persistent user preferences. */
const string &device_name = device.device_name;
string device_name = [device.name UTF8String];
string id = string("METAL_") + device_name;
/* Hardware ID might not be unique, add device number in that case. */
if (unique_ids.find(id) != unique_ids.end()) {
id += string_printf("_ID_%d", num_devices);
id += string_printf("_ID_%d", device_index);
}
unique_ids.insert(id);
@ -94,15 +81,13 @@ void device_metal_info(vector<DeviceInfo> &devices)
string device_metal_capabilities()
{
string result = "";
string error_msg = "";
uint32_t num_devices = 0;
assert(device_metal_get_num_devices_safe(&num_devices));
auto allDevices = MTLCopyAllDevices();
uint32_t num_devices = allDevices.count;
if (num_devices == 0) {
return "No Metal devices found\n";
}
result += string_printf("Number of devices: %u\n", num_devices);
NSArray<id<MTLDevice>> *allDevices = MTLCopyAllDevices();
for (id<MTLDevice> device in allDevices) {
result += string_printf("\t\tDevice: %s\n", [device.name UTF8String]);
}

View File

@ -53,16 +53,10 @@ MetalDevice::MetalDevice(const DeviceInfo &info, Stats &stats, Profiler &profile
mtlDevId = info.num;
/* select chosen device */
vector<MetalPlatformDevice> usable_devices;
MetalInfo::get_usable_devices(&usable_devices);
if (usable_devices.size() == 0) {
set_error("Metal: no devices found.");
return;
}
auto usable_devices = MetalInfo::get_usable_devices();
assert(mtlDevId < usable_devices.size());
MetalPlatformDevice &platform_device = usable_devices[mtlDevId];
mtlDevice = platform_device.device_id;
device_name = platform_device.device_name;
mtlDevice = usable_devices[mtlDevId];
device_name = [mtlDevice.name UTF8String];
device_vendor = MetalInfo::get_vendor_from_device_name(device_name);
assert(device_vendor != METAL_GPU_UNKNOWN);
metal_printf("Creating new Cycles device for Metal: %s\n", device_name.c_str());
@ -458,7 +452,8 @@ MetalDevice::MetalMem *MetalDevice::generic_alloc(device_memory &mem)
id<MTLBuffer> metal_buffer = nil;
MTLResourceOptions options = default_storage_mode;
/* Workaround for "bake" unit tests which fail if RenderBuffers is allocated with MTLResourceStorageModeShared. */
/* Workaround for "bake" unit tests which fail if RenderBuffers is allocated with
* MTLResourceStorageModeShared. */
if (strstr(mem.name, "RenderBuffers")) {
options = MTLResourceStorageModeManaged;
}
@ -769,9 +764,11 @@ void MetalDevice::tex_alloc(device_texture &mem)
/* Check that dimensions fit within maximum allowable size.
See https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
*/
if (mem.data_width > 16384 ||
mem.data_height > 16384) {
set_error(string_printf("Texture exceeds maximum allowed size of 16384 x 16384 (requested: %zu x %zu)", mem.data_width, mem.data_height));
if (mem.data_width > 16384 || mem.data_height > 16384) {
set_error(string_printf(
"Texture exceeds maximum allowed size of 16384 x 16384 (requested: %zu x %zu)",
mem.data_width,
mem.data_height));
return;
}

View File

@ -36,33 +36,10 @@ enum MetalGPUVendor {
METAL_GPU_INTEL = 3,
};
/* Retains a named MTLDevice for device enumeration. */
struct MetalPlatformDevice {
MetalPlatformDevice(id<MTLDevice> device, const string &device_name)
: device_id(device), device_name(device_name)
{
[device_id retain];
}
~MetalPlatformDevice()
{
[device_id release];
}
id<MTLDevice> device_id;
string device_name;
};
/* Contains static Metal helper functions. */
struct MetalInfo {
static bool device_version_check(id<MTLDevice> device);
static void get_usable_devices(vector<MetalPlatformDevice> *usable_devices);
static vector<id<MTLDevice>> const &get_usable_devices();
static MetalGPUVendor get_vendor_from_device_name(string const &device_name);
/* Platform information. */
static bool get_num_devices(uint32_t *num_platforms);
static uint32_t get_num_devices();
static bool get_device_name(id<MTLDevice> device_id, string *device_name);
static string get_device_name(id<MTLDevice> device_id);
};
/* Pool of MTLBuffers whose lifetime is linked to a single MTLCommandBuffer */

View File

@ -43,83 +43,45 @@ MetalGPUVendor MetalInfo::get_vendor_from_device_name(string const &device_name)
return METAL_GPU_UNKNOWN;
}
bool MetalInfo::device_version_check(id<MTLDevice> device)
vector<id<MTLDevice>> const &MetalInfo::get_usable_devices()
{
/* Metal Cycles doesn't work correctly on macOS versions older than 12.0 */
if (@available(macos 12.0, *)) {
MetalGPUVendor vendor = get_vendor_from_device_name([[device name] UTF8String]);
static vector<id<MTLDevice>> usable_devices;
static bool already_enumerated = false;
/* Metal Cycles works on Apple Silicon GPUs at present */
return (vendor == METAL_GPU_APPLE);
if (already_enumerated) {
return usable_devices;
}
return false;
}
metal_printf("Usable Metal devices:\n");
for (id<MTLDevice> device in MTLCopyAllDevices()) {
const char *device_name = [device.name UTF8String];
void MetalInfo::get_usable_devices(vector<MetalPlatformDevice> *usable_devices)
{
static bool first_time = true;
# define FIRST_VLOG(severity) \
if (first_time) \
VLOG(severity)
MetalGPUVendor vendor = get_vendor_from_device_name(device_name);
bool usable = false;
usable_devices->clear();
NSArray<id<MTLDevice>> *allDevices = MTLCopyAllDevices();
for (id<MTLDevice> device in allDevices) {
string device_name;
if (!get_device_name(device, &device_name)) {
FIRST_VLOG(2) << "Failed to get device name, ignoring.";
continue;
if (@available(macos 12.2, *)) {
usable |= (vendor == METAL_GPU_APPLE);
}
static const char *forceIntelStr = getenv("CYCLES_METAL_FORCE_INTEL");
bool forceIntel = forceIntelStr ? (atoi(forceIntelStr) != 0) : false;
if (forceIntel && device_name.find("Intel") == string::npos) {
FIRST_VLOG(2) << "CYCLES_METAL_FORCE_INTEL causing non-Intel device " << device_name
<< " to be ignored.";
continue;
if (@available(macos 12.3, *)) {
usable |= (vendor == METAL_GPU_AMD);
}
if (!device_version_check(device)) {
FIRST_VLOG(2) << "Ignoring device " << device_name << " due to too old compiler version.";
continue;
if (usable) {
metal_printf("- %s\n", device_name);
[device retain];
usable_devices.push_back(device);
}
else {
metal_printf(" (skipping \"%s\")\n", device_name);
}
FIRST_VLOG(2) << "Adding new device " << device_name << ".";
string hardware_id;
usable_devices->push_back(MetalPlatformDevice(device, device_name));
}
first_time = false;
}
bool MetalInfo::get_num_devices(uint32_t *num_devices)
{
*num_devices = MTLCopyAllDevices().count;
return true;
}
uint32_t MetalInfo::get_num_devices()
{
uint32_t num_devices;
if (!get_num_devices(&num_devices)) {
return 0;
if (usable_devices.empty()) {
metal_printf(" No usable Metal devices found\n");
}
return num_devices;
}
already_enumerated = true;
bool MetalInfo::get_device_name(id<MTLDevice> device, string *platform_name)
{
*platform_name = [device.name UTF8String];
return true;
}
string MetalInfo::get_device_name(id<MTLDevice> device)
{
string platform_name;
if (!get_device_name(device, &platform_name)) {
return "";
}
return platform_name;
return usable_devices;
}
id<MTLBuffer> MetalBufferPool::get_buffer(id<MTLDevice> device,