diff --git a/src/dawn_native/metal/BackendMTL.mm b/src/dawn_native/metal/BackendMTL.mm index 7e1be79743..e09976d558 100644 --- a/src/dawn_native/metal/BackendMTL.mm +++ b/src/dawn_native/metal/BackendMTL.mm @@ -14,76 +14,53 @@ #include "dawn_native/metal/BackendMTL.h" +#include "common/Constants.h" #include "dawn_native/Instance.h" #include "dawn_native/MetalBackend.h" #include "dawn_native/metal/DeviceMTL.h" -#include +#import namespace dawn_native { namespace metal { namespace { - // Since CGDisplayIOServicePort was deprecated in macOS 10.9, we need create - // an alternative function for getting I/O service port from current display. - io_service_t GetDisplayIOServicePort() { - // The matching service port (or 0 if none can be found) - io_service_t servicePort = 0; + struct PCIIDs { + uint32_t vendorId; + uint32_t deviceId; + }; - // Create matching dictionary for display service - CFMutableDictionaryRef matchingDict = IOServiceMatching("IODisplayConnect"); - if (matchingDict == nullptr) { - return 0; - } + struct Vendor { + const char* trademark; + uint32_t vendorId; + }; - io_iterator_t iter; - // IOServiceGetMatchingServices look up the default master ports that match a - // matching dictionary, and will consume the reference on the matching dictionary, - // so we don't need to release the dictionary, but the iterator handle should - // be released when its iteration is finished. - if (IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iter) != - kIOReturnSuccess) { - return 0; - } + const Vendor kVendors[] = {{"AMD", kVendorID_AMD}, + {"Radeon", kVendorID_AMD}, + {"Intel", kVendorID_Intel}, + {"Geforce", kVendorID_Nvidia}, + {"Quadro", kVendorID_Nvidia}}; - // Vendor number and product number of current main display - const uint32_t displayVendorNumber = CGDisplayVendorNumber(kCGDirectMainDisplay); - const uint32_t displayProductNumber = CGDisplayModelNumber(kCGDirectMainDisplay); - - io_service_t serv; - while ((serv = IOIteratorNext(iter)) != IO_OBJECT_NULL) { - CFDictionaryRef displayInfo = - IODisplayCreateInfoDictionary(serv, kIODisplayOnlyPreferredName); - - CFNumberRef vendorIDRef, productIDRef; - Boolean success; - // The ownership of CF object follows the 'Get Rule', we don't need to - // release these values - success = CFDictionaryGetValueIfPresent(displayInfo, CFSTR(kDisplayVendorID), - (const void**)&vendorIDRef); - success &= CFDictionaryGetValueIfPresent(displayInfo, CFSTR(kDisplayProductID), - (const void**)&productIDRef); - if (success) { - CFIndex vendorID = 0, productID = 0; - CFNumberGetValue(vendorIDRef, kCFNumberSInt32Type, &vendorID); - CFNumberGetValue(productIDRef, kCFNumberSInt32Type, &productID); - - if (vendorID == displayVendorNumber && productID == displayProductNumber) { - // Check if vendor id and product id match with current display's - // If it does, we find the desired service port - servicePort = serv; - CFRelease(displayInfo); - break; - } + // Find vendor ID from MTLDevice name. + MaybeError GetVendorIdFromVendors(id device, PCIIDs* ids) { + uint32_t vendorId = 0; + const char* deviceName = [device.name UTF8String]; + for (const auto& it : kVendors) { + if (strstr(deviceName, it.trademark) != nullptr) { + vendorId = it.vendorId; + break; } - - CFRelease(displayInfo); - IOObjectRelease(serv); } - IOObjectRelease(iter); - return servicePort; + + if (vendorId == 0) { + return DAWN_CONTEXT_LOST_ERROR("Failed to find vendor id with the device"); + } + + // Set vendor id with 0 + *ids = PCIIDs{vendorId, 0}; + return {}; } - // Get integer property from registry entry. + // Extracts an integer property from a registry entry. uint32_t GetEntryProperty(io_registry_entry_t entry, CFStringRef name) { uint32_t value = 0; @@ -93,19 +70,82 @@ namespace dawn_native { namespace metal { entry, kIOServicePlane, name, kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents)); - if (data != nullptr) { - const uint32_t* valuePtr = - reinterpret_cast(CFDataGetBytePtr(data)); - if (valuePtr) { - value = *valuePtr; - } - - CFRelease(data); + if (data == nullptr) { + return value; } + // CFDataGetBytePtr() is guaranteed to return a read-only pointer + value = *reinterpret_cast(CFDataGetBytePtr(data)); + + CFRelease(data); return value; } + // Queries the IO Registry to find the PCI device and vendor IDs of the MTLDevice. + // The registry entry correponding to [device registryID] doesn't contain the exact PCI ids + // because it corresponds to a driver. However its parent entry corresponds to the device + // itself and has uint32_t "device-id" and "registry-id" keys. For example on a dual-GPU + // MacBook Pro 2017 the IORegistry explorer shows the following tree (simplified here): + // + // - PCI0@0 + // | - AppleACPIPCI + // | | - IGPU@2 (type IOPCIDevice) + // | | | - IntelAccelerator (type IOGraphicsAccelerator2) + // | | - PEG0@1 + // | | | - IOPP + // | | | | - GFX0@0 (type IOPCIDevice) + // | | | | | - AMDRadeonX4000_AMDBaffinGraphicsAccelerator (type IOGraphicsAccelerator2) + // + // [device registryID] is the ID for one of the IOGraphicsAccelerator2 and we can see that + // their parent always is an IOPCIDevice that has properties for the device and vendor IDs. + MaybeError GetDeviceIORegistryPCIInfo(id device, PCIIDs* ids) { + // Get a matching dictionary for the IOGraphicsAccelerator2 + CFMutableDictionaryRef matchingDict = IORegistryEntryIDMatching([device registryID]); + if (matchingDict == nullptr) { + return DAWN_CONTEXT_LOST_ERROR("Failed to create the matching dict for the device"); + } + + // IOServiceGetMatchingService will consume the reference on the matching dictionary, + // so we don't need to release the dictionary. + io_registry_entry_t acceleratorEntry = + IOServiceGetMatchingService(kIOMasterPortDefault, matchingDict); + if (acceleratorEntry == IO_OBJECT_NULL) { + return DAWN_CONTEXT_LOST_ERROR( + "Failed to get the IO registry entry for the accelerator"); + } + + // Get the parent entry that will be the IOPCIDevice + io_registry_entry_t deviceEntry = IO_OBJECT_NULL; + if (IORegistryEntryGetParentEntry(acceleratorEntry, kIOServicePlane, &deviceEntry) != + kIOReturnSuccess) { + IOObjectRelease(acceleratorEntry); + return DAWN_CONTEXT_LOST_ERROR( + "Failed to get the IO registry entry for the device"); + } + + ASSERT(deviceEntry != IO_OBJECT_NULL); + IOObjectRelease(acceleratorEntry); + + uint32_t vendorId = GetEntryProperty(deviceEntry, CFSTR("vendor-id")); + uint32_t deviceId = GetEntryProperty(deviceEntry, CFSTR("device-id")); + + *ids = PCIIDs{vendorId, deviceId}; + + IOObjectRelease(deviceEntry); + + return {}; + } + + MaybeError GetDevicePCIInfo(id device, PCIIDs* ids) { + // [device registryID] is introduced on macOS 10.13+, otherwise workaround to get vendor + // id by vendor name on old macOS + if ([NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:{10, 13, 0}]) { + return GetDeviceIORegistryPCIInfo(device, ids); + } else { + return GetVendorIdFromVendors(device, ids); + } + } + bool IsMetalSupported() { // Metal was first introduced in macOS 10.11 NSOperatingSystemVersion macOS10_11 = {10, 11, 0}; @@ -120,16 +160,12 @@ namespace dawn_native { namespace metal { Adapter(InstanceBase* instance, id device) : AdapterBase(instance, BackendType::Metal), mDevice([device retain]) { mPCIInfo.name = std::string([mDevice.name UTF8String]); - // Gather the PCI device and vendor IDs based on which device is rendering to the - // main display. This is obviously wrong for systems with multiple devices. - // TODO(cwallez@chromium.org): Once Chromium has the macOS 10.13 SDK rolled, we - // should use MTLDevice.registryID to gather the information. - io_registry_entry_t entry = GetDisplayIOServicePort(); - if (entry != IO_OBJECT_NULL) { - mPCIInfo.vendorId = GetEntryProperty(entry, CFSTR("vendor-id")); - mPCIInfo.deviceId = GetEntryProperty(entry, CFSTR("device-id")); - IOObjectRelease(entry); - } + + PCIIDs ids; + if (!instance->ConsumedError(GetDevicePCIInfo(device, &ids))) { + mPCIInfo.vendorId = ids.vendorId; + mPCIInfo.deviceId = ids.deviceId; + }; if ([device isLowPower]) { mDeviceType = DeviceType::IntegratedGPU;