From: Zhiyi Zhang Subject: [PATCH 2/6] winemac.drv: Add GPU initialization functions. Message-Id: <5791666d-7234-71c7-91c8-b9ffb94f2468@codeweavers.com> Date: Thu, 4 Jul 2019 21:22:22 +0800 Signed-off-by: Zhiyi Zhang --- dlls/winemac.drv/Makefile.in | 2 +- dlls/winemac.drv/cocoa_display.m | 378 +++++++++++++++++++++++++++++++++++++++ dlls/winemac.drv/display.c | 200 +++++++++++++++++++++ dlls/winemac.drv/macdrv.h | 1 + dlls/winemac.drv/macdrv_cocoa.h | 17 ++ 5 files changed, 597 insertions(+), 1 deletion(-) diff --git a/dlls/winemac.drv/Makefile.in b/dlls/winemac.drv/Makefile.in index 3ffb7d666c..c6ae9733bd 100644 --- a/dlls/winemac.drv/Makefile.in +++ b/dlls/winemac.drv/Makefile.in @@ -1,5 +1,5 @@ MODULE = winemac.drv -IMPORTS = uuid user32 gdi32 advapi32 +IMPORTS = uuid setupapi rpcrt4 user32 gdi32 advapi32 DELAYIMPORTS = ole32 shell32 imm32 EXTRALIBS = -framework AppKit -framework Carbon -framework Security -framework OpenGL -framework IOKit -framework CoreVideo $(METAL_LIBS) diff --git a/dlls/winemac.drv/cocoa_display.m b/dlls/winemac.drv/cocoa_display.m index 93a0fbca35..82c9e3fcc3 100644 --- a/dlls/winemac.drv/cocoa_display.m +++ b/dlls/winemac.drv/cocoa_display.m @@ -18,7 +18,12 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +#include "config.h" + #import +#ifdef HAVE_METAL_METAL_H +#import +#endif #include "macdrv_cocoa.h" @@ -103,3 +108,376 @@ void macdrv_free_displays(struct macdrv_display* displays) { free(displays); } + +/*********************************************************************** + * get_entry_property_uint32 + * + * Get an io registry entry property of type uint32 and store it in value parameter. + * + * Returns non-zero value on failure. + */ +static int get_entry_property_uint32(io_registry_entry_t entry, CFStringRef property_name, uint32_t* value) +{ + CFDataRef data = IORegistryEntrySearchCFProperty(entry, kIOServicePlane, property_name, kCFAllocatorDefault, 0); + if (!data) + return -1; + + if (CFGetTypeID(data) != CFDataGetTypeID() || CFDataGetLength(data) != sizeof(uint32_t)) + { + CFRelease(data); + return -1; + } + + CFDataGetBytes(data, CFRangeMake(0, sizeof(uint32_t)), (UInt8*)value); + CFRelease(data); + return 0; +} + +/*********************************************************************** + * get_entry_property_string + * + * Get an io registry entry property of type string and write it in buffer parameter. + * + * Returns non-zero value on failure. + */ +static int get_entry_property_string(io_registry_entry_t entry, CFStringRef property_name, char* buffer, + size_t buffer_size) +{ + size_t length; + + CFDataRef data = IORegistryEntrySearchCFProperty(entry, kIOServicePlane, property_name, kCFAllocatorDefault, 0); + if (!data) + return -1; + + length = CFDataGetLength(data); + if (length + 1 > buffer_size || CFGetTypeID(data) != CFDataGetTypeID()) + { + CFRelease(data); + return -1; + } + + CFDataGetBytes(data, CFRangeMake(0, length), (UInt8*)buffer); + buffer[length] = 0; + CFRelease(data); + return 0; +} + +/*********************************************************************** + * macdrv_get_gpu_info_from_entry + * + * Starting from entry, search upwards to find the PCI GPU. And get GPU information from the PCI GPU entry. + * + * Returns non-zero value on failure. + */ +static int macdrv_get_gpu_info_from_entry(struct macdrv_gpu* gpu, io_registry_entry_t entry) +{ + io_registry_entry_t parent_entry; + io_registry_entry_t gpu_entry; + kern_return_t result; + int ret = -1; + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + gpu_entry = entry; + while (![@"IOPCIDevice" isEqualToString:[(__bridge NSString*)IOObjectCopyClass(gpu_entry) autorelease]]) + { + result = IORegistryEntryGetParentEntry(gpu_entry, kIOServicePlane, &parent_entry); + if (gpu_entry != entry) + IOObjectRelease(gpu_entry); + if (result == kIOReturnSuccess) + { + gpu_entry = parent_entry; + } + else + { + [pool release]; + return ret; + } + } + + if (IORegistryEntryGetRegistryEntryID(gpu_entry, &gpu->id) != kIOReturnSuccess) + goto done; + if (get_entry_property_uint32(gpu_entry, CFSTR("vendor-id"), &gpu->vendor_id)) + goto done; + if (get_entry_property_uint32(gpu_entry, CFSTR("device-id"), &gpu->device_id)) + goto done; + if (get_entry_property_uint32(gpu_entry, CFSTR("subsystem-id"), &gpu->subsys_id)) + goto done; + if (get_entry_property_uint32(gpu_entry, CFSTR("revision-id"), &gpu->revision_id)) + goto done; + if (get_entry_property_string(gpu_entry, CFSTR("model"), gpu->name, sizeof(gpu->name))) + goto done; + + ret = 0; +done: + if (gpu_entry != entry) + IOObjectRelease(gpu_entry); + [pool release]; + return ret; +} + +#ifdef HAVE_METAL_METAL_H + +/*********************************************************************** + * is_metal_available + * + * Returns non-zero value if Metal is available and zero if it's unavailable + */ +static int is_metal_available(void) +{ + int ret = 0; + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + NSArray>* devices = [MTLCopyAllDevices() autorelease]; + if (devices.count && [devices[0] respondsToSelector:@selector(registryID)]) + ret = 1; + + [pool release]; + return ret; +} + +/*********************************************************************** + * macdrv_get_gpu_info_from_registry_id + * + * Get GPU information from a Metal device registry id. + * + * Returns non-zero value on failure. + */ +static int macdrv_get_gpu_info_from_registry_id(struct macdrv_gpu* gpu, uint64_t registry_id) +{ + int ret; + io_registry_entry_t entry; + + entry = IOServiceGetMatchingService(kIOMasterPortDefault, IORegistryEntryIDMatching(registry_id)); + ret = macdrv_get_gpu_info_from_entry(gpu, entry); + IOObjectRelease(entry); + return ret; +} + +/*********************************************************************** + * macdrv_get_gpus_from_metal_devices + * + * Get a list of GPUs from Metal devices. + * + * Returns non-zero value on failure with parameters unchanged and zero on success. + */ +static int macdrv_get_gpus_from_metal_devices(struct macdrv_gpu** new_gpus, int* count) +{ + struct macdrv_gpu* gpus = NULL; + struct macdrv_gpu primary_gpu; + id primary_device; + BOOL hide_integrated = FALSE; + int primary_index = 0, i; + int gpu_count = 0; + int ret = -1; + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + NSArray>* devices = [MTLCopyAllDevices() autorelease]; + gpus = calloc(devices.count, sizeof(*gpus)); + if (!gpus) + goto done; + + /* Use MTLCreateSystemDefaultDevice instead of CGDirectDisplayCopyCurrentMetalDevice(CGMainDisplayID()) to get + * the primary GPU because we need to hide the integrated GPU for an automatic graphic switching pair to avoid apps + * using the integrated GPU. This is the behavior of Windows on a Mac. */ + primary_device = [MTLCreateSystemDefaultDevice() autorelease]; + if (macdrv_get_gpu_info_from_registry_id(&primary_gpu, primary_device.registryID)) + goto done; + + /* Hide the integrated GPU if the system default device is a dedicated GPU */ + if (!primary_device.isLowPower) + { + hide_integrated = TRUE; + } + + for (i = 0; i < devices.count; i++) + { + if (macdrv_get_gpu_info_from_registry_id(&gpus[gpu_count], devices[i].registryID)) + goto done; + + if (hide_integrated && devices[i].isLowPower) + { + continue; + } + + if (gpus[gpu_count].id == primary_gpu.id) + primary_index = gpu_count; + + gpu_count++; + } + + /* Make sure the first GPU is primary */ + if (primary_index) + { + struct macdrv_gpu tmp; + tmp = gpus[0]; + gpus[0] = gpus[primary_index]; + gpus[primary_index] = tmp; + } + + *new_gpus = gpus; + *count = gpu_count; + ret = 0; +done: + if (ret) + macdrv_free_gpus(gpus); + [pool release]; + return ret; +} + +#else + +static int is_metal_available(void) +{ + return 0; +} + +static int macdrv_get_gpus_from_metal_devices(struct macdrv_gpu** new_gpus, int* count) +{ + return -1; +} + +#endif + +/*********************************************************************** + * macdrv_get_gpu_info_from_display_id + * + * Get GPU information from a display id. + * This is a fallback for 32bit build or older Mac OS version where Metal is unavailable. + * + * Returns non-zero value on failure. + */ +static int macdrv_get_gpu_info_from_display_id(struct macdrv_gpu* gpu, CGDirectDisplayID display_id) +{ + io_registry_entry_t entry = CGDisplayIOServicePort(display_id); + return macdrv_get_gpu_info_from_entry(gpu, entry); +} + +/*********************************************************************** + * macdrv_get_gpus_from_displays + * + * Get a list of GPUs from displays. + * This is a fallback for 32bit build or older Mac OS version where Metal is unavailable. + * + * Returns non-zero value on failure with parameters unchanged and zero on success. + */ +static int macdrv_get_gpus_from_displays(struct macdrv_gpu** new_gpus, int* count) +{ + CGDirectDisplayID display_ids[16]; + uint32_t display_id_count; + struct macdrv_gpu* gpus; + BOOL duplicated; + int integrated_index = -1; + int primary_index = 0; + int gpu_count = 0; + int ret = -1; + int i, j; + + if (CGGetOnlineDisplayList(sizeof(display_ids) / sizeof(display_ids[0]), display_ids, &display_id_count) + != kCGErrorSuccess) + return ret; + + if (!display_id_count) + { + *new_gpus = NULL; + *count = 0; + return 0; + } + + /* Actual GPU count may be less */ + gpus = calloc(display_id_count, sizeof(*gpus)); + if (!gpus) + return ret; + + for (i = 0; i < display_id_count; i++) + { + if (macdrv_get_gpu_info_from_display_id(&gpus[gpu_count], display_ids[i])) + goto done; + + /* Ignore duplicated GPUs */ + duplicated = FALSE; + for (j = 0; j < gpu_count; j++) + { + if (gpus[gpu_count].id == gpus[j].id) + { + duplicated = TRUE; + break; + } + } + + if (duplicated) + continue; + + if (display_ids[i] == CGMainDisplayID()) + primary_index = gpu_count; + + /* FIXME: + * Find integrated GPU without Metal support. + * Assuming integrated GPU vendor is Intel for now */ + if (gpus[gpu_count].vendor_id == 0x8086) + { + integrated_index = gpu_count; + } + + gpu_count++; + } + + /* If there are more than two GPUs and an Intel card exists, + * assume an automatic graphics pair exists and hide the integrated GPU */ + if (gpu_count > 1 && integrated_index != -1) + { + if (integrated_index != gpu_count - 1) + gpus[integrated_index] = gpus[gpu_count - 1]; + + /* FIXME: + * Find the dedicated GPU in an automatic graphics switching pair and use that as primary GPU. + * Choose the first dedicated GPU as primary */ + if (primary_index == integrated_index) + primary_index = 0; + + gpu_count--; + } + + /* Make sure the first GPU is primary */ + if (primary_index) + { + struct macdrv_gpu tmp; + tmp = gpus[0]; + gpus[0] = gpus[primary_index]; + gpus[primary_index] = tmp; + } + + *new_gpus = gpus; + *count = gpu_count; + ret = 0; +done: + if (ret) + macdrv_free_gpus(gpus); + return ret; +} + +/*********************************************************************** + * macdrv_get_gpus + * + * Get a list of GPUs currently in the system. The first GPU is primary. + * Call macdrv_free_gpus() when you are done using the data. + * + * Returns non-zero value on failure with parameters unchanged and zero on success. + */ +int macdrv_get_gpus(struct macdrv_gpu** new_gpus, int* count) +{ + if (is_metal_available()) + return macdrv_get_gpus_from_metal_devices(new_gpus, count); + else + return macdrv_get_gpus_from_displays(new_gpus, count); +} + +/*********************************************************************** + * macdrv_free_gpus + * + * Frees a GPU list allocated from macdrv_get_gpus() + */ +void macdrv_free_gpus(struct macdrv_gpu* gpus) +{ + if (gpus) + free(gpus); +} diff --git a/dlls/winemac.drv/display.c b/dlls/winemac.drv/display.c index 00ad7738bb..3e7335f5aa 100644 --- a/dlls/winemac.drv/display.c +++ b/dlls/winemac.drv/display.c @@ -25,6 +25,13 @@ #include "winuser.h" #include "winreg.h" #include "ddrawi.h" +#include "rpc.h" +#include "initguid.h" +#include "devguid.h" +#include "devpkey.h" +#include "setupapi.h" +#define WIN32_NO_STATUS +#include "winternl.h" #include "wine/unicode.h" WINE_DEFAULT_DEBUG_CHANNEL(display); @@ -47,6 +54,29 @@ BOOL CDECL macdrv_EnumDisplaySettingsEx(LPCWSTR devname, DWORD mode, LPDEVMODEW static const char initial_mode_key[] = "Initial Display Mode"; static const WCHAR pixelencodingW[] = {'P','i','x','e','l','E','n','c','o','d','i','n','g',0}; +static const WCHAR driver_descW[] = {'D','r','i','v','e','r','D','e','s','c',0}; +static const WCHAR pciW[] = {'P','C','I',0}; +static const WCHAR video_idW[] = {'V','i','d','e','o','I','D',0}; +static const WCHAR guid_fmtW[] = { + '{','%','0','8','x','-','%','0','4','x','-','%','0','4','x','-','%','0','2','x','%','0','2','x','-', + '%','0','2','x','%','0','2','x','%','0','2','x','%','0','2','x','%','0','2','x','%','0','2','x','}',0}; +static const WCHAR gpu_instance_fmtW[] = { + 'P','C','I','\\', + 'V','E','N','_','%','0','4','X','&', + 'D','E','V','_','%','0','4','X','&', + 'S','U','B','S','Y','S','_','%','0','8','X','&', + 'R','E','V','_','%','0','2','X','\\', + '%','0','8','X',0}; +static const WCHAR gpu_hardware_id_fmtW[] = { + 'P','C','I','\\', + 'V','E','N','_','%','0','4','X','&', + 'D','E','V','_','%','0','4','X','&', + 'S','U','B','S','Y','S','_','0','0','0','0','0','0','0','0','&', + 'R','E','V','_','0','0',0}; +static const WCHAR video_keyW[] = { + 'H','A','R','D','W','A','R','E','\\', + 'D','E','V','I','C','E','M','A','P','\\', + 'V','I','D','E','O',0}; static CFArrayRef modes; @@ -1397,3 +1427,173 @@ void macdrv_displays_changed(const macdrv_event *event) MAKELPARAM(width, height)); } } + +/*********************************************************************** + * macdrv_init_gpu + * + * Initialize a GPU instance. + * + * Return FALSE on failure and TRUE on success. + */ +static BOOL macdrv_init_gpu(HDEVINFO devinfo, const struct macdrv_gpu *gpu, int gpu_index) +{ + static const BOOL present = TRUE; + SP_DEVINFO_DATA device_data = {sizeof(device_data)}; + WCHAR instanceW[MAX_PATH]; + WCHAR nameW[MAX_PATH]; + WCHAR bufferW[1024]; + HKEY hkey = NULL; + GUID guid; + INT written; + DWORD size; + BOOL ret = FALSE; + + sprintfW(instanceW, gpu_instance_fmtW, gpu->vendor_id, gpu->device_id, gpu->subsys_id, gpu->revision_id, gpu_index); + MultiByteToWideChar(CP_UTF8, 0, gpu->name, -1, nameW, ARRAY_SIZE(nameW)); + if (!SetupDiOpenDeviceInfoW(devinfo, instanceW, NULL, 0, &device_data)) + { + SetupDiCreateDeviceInfoW(devinfo, instanceW, &GUID_DEVCLASS_DISPLAY, nameW, NULL, 0, &device_data); + if (!SetupDiRegisterDeviceInfo(devinfo, &device_data, 0, NULL, NULL, NULL)) + goto done; + } + + /* Write HardwareID registry property, REG_MULTI_SZ */ + written = sprintfW(bufferW, gpu_hardware_id_fmtW, gpu->vendor_id, gpu->device_id); + bufferW[written + 1] = 0; + if (!SetupDiSetDeviceRegistryPropertyW(devinfo, &device_data, SPDRP_HARDWAREID, (const BYTE *)bufferW, + (written + 2) * sizeof(WCHAR))) + goto done; + + /* Write DEVPKEY_Device_IsPresent property */ + if (!SetupDiSetDevicePropertyW(devinfo, &device_data, &DEVPKEY_Device_IsPresent, DEVPROP_TYPE_BOOLEAN, + (const BYTE *)&present, sizeof(present), 0)) + goto done; + + /* Open driver key. + * This is where HKLM\System\CurrentControlSet\Control\Video\{GPU GUID}\{Adapter Index} links to */ + hkey = SetupDiCreateDevRegKeyW(devinfo, &device_data, DICS_FLAG_GLOBAL, 0, DIREG_DRV, NULL, NULL); + + /* Write DriverDesc value */ + if (RegSetValueExW(hkey, driver_descW, 0, REG_SZ, (const BYTE *)nameW, (lstrlenW(nameW) + 1) * sizeof(WCHAR))) + goto done; + RegCloseKey(hkey); + + /* Write GUID in VideoID in .../instance/Device Parameters, reuse the GUID if it's existent */ + hkey = SetupDiCreateDevRegKeyW(devinfo, &device_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, NULL, NULL); + + size = sizeof(bufferW); + if (RegQueryValueExW(hkey, video_idW, 0, NULL, (BYTE *)bufferW, &size)) + { + UuidCreate(&guid); + sprintfW(bufferW, guid_fmtW, guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], + guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); + if (RegSetValueExW(hkey, video_idW, 0, REG_SZ, (const BYTE *)bufferW, (lstrlenW(bufferW) + 1) * sizeof(WCHAR))) + goto done; + } + + ret = TRUE; +done: + RegCloseKey(hkey); + if (!ret) + ERR("Failed to initialize GPU\n"); + return ret; +} + +static void prepare_devices(void) +{ + static const BOOL not_present = FALSE; + SP_DEVINFO_DATA device_data = {sizeof(device_data)}; + HDEVINFO devinfo; + DWORD i = 0; + + /* FIXME: + * Currently SetupDiGetClassDevsW with DIGCF_PRESENT is unsupported, So we need to clean up not present devices in + * case application uses SetupDiGetClassDevsW to enumerate devices. Wrong devices could exist in registry as a result + * of prefix copying or having devices unplugged. But then we couldn't simply delete GPUs because we need to retain + * the same GUID for the same GPU. */ + devinfo = SetupDiGetClassDevsW(&GUID_DEVCLASS_DISPLAY, pciW, NULL, 0); + while (SetupDiEnumDeviceInfo(devinfo, i++, &device_data)) + { + if (!SetupDiSetDevicePropertyW(devinfo, &device_data, &DEVPKEY_Device_IsPresent, DEVPROP_TYPE_BOOLEAN, + (const BYTE *)¬_present, sizeof(not_present), 0)) + ERR("Failed to set GPU present property\n"); + } + SetupDiDestroyDeviceInfoList(devinfo); +} + +static void cleanup_devices(void) +{ + SP_DEVINFO_DATA device_data = {sizeof(device_data)}; + HDEVINFO devinfo; + DWORD type; + DWORD i = 0; + BOOL present; + + devinfo = SetupDiGetClassDevsW(&GUID_DEVCLASS_DISPLAY, pciW, NULL, 0); + while (SetupDiEnumDeviceInfo(devinfo, i++, &device_data)) + { + present = FALSE; + SetupDiGetDevicePropertyW(devinfo, &device_data, &DEVPKEY_Device_IsPresent, &type, (BYTE *)&present, + sizeof(present), NULL, 0); + if (!present && !SetupDiRemoveDevice(devinfo, &device_data)) + ERR("Failed to remove GPU\n"); + } + SetupDiDestroyDeviceInfoList(devinfo); +} + +/*********************************************************************** + * macdrv_init_display_devices + * + * Initialize display device registry data. + */ +void macdrv_init_display_devices(void) +{ + static const WCHAR init_mutexW[] = {'d','i','s','p','l','a','y','_','d','e','v','i','c','e','_','i','n','i','t',0}; + HANDLE mutex; + struct macdrv_gpu *gpus = NULL; + INT gpu_count; + INT gpu; + HDEVINFO gpu_devinfo = NULL; + HKEY video_hkey = NULL; + DWORD disposition = 0; + + mutex = CreateMutexW(NULL, FALSE, init_mutexW); + WaitForSingleObject(mutex, INFINITE); + + if (RegCreateKeyExW(HKEY_LOCAL_MACHINE, video_keyW, 0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &video_hkey, + &disposition)) + { + ERR("Failed to create video device key\n"); + goto done; + } + + /* Avoid unnecessary reinit */ + if (disposition != REG_CREATED_NEW_KEY) + goto done; + + TRACE("\n"); + + prepare_devices(); + + gpu_devinfo = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_DISPLAY, NULL); + + /* Initialize GPUs */ + if (macdrv_get_gpus(&gpus, &gpu_count)) + goto done; + + for (gpu = 0; gpu < gpu_count; gpu++) + { + if (!macdrv_init_gpu(gpu_devinfo, &gpus[gpu], gpu)) + goto done; + } + +done: + cleanup_devices(); + SetupDiDestroyDeviceInfoList(gpu_devinfo); + RegCloseKey(video_hkey); + + ReleaseMutex(mutex); + CloseHandle(mutex); + + macdrv_free_gpus(gpus); +} diff --git a/dlls/winemac.drv/macdrv.h b/dlls/winemac.drv/macdrv.h index f948da42c7..064b93a1df 100644 --- a/dlls/winemac.drv/macdrv.h +++ b/dlls/winemac.drv/macdrv.h @@ -223,6 +223,7 @@ extern CGImageRef create_cgimage_from_icon_bitmaps(HDC hdc, HANDLE icon, HBITMAP extern void macdrv_status_item_mouse_move(const macdrv_event *event) DECLSPEC_HIDDEN; extern void check_retina_status(void) DECLSPEC_HIDDEN; +extern void macdrv_init_display_devices(void) DECLSPEC_HIDDEN; /************************************************************************** * Mac IME driver diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h index 8ca9b9afa0..513746fa0e 100644 --- a/dlls/winemac.drv/macdrv_cocoa.h +++ b/dlls/winemac.drv/macdrv_cocoa.h @@ -257,10 +257,27 @@ static inline CGPoint cgpoint_win_from_mac(CGPoint point) /* display */ + +/* Represent a physical GPU in the PCI slots */ +struct macdrv_gpu +{ + /* PCI GPU io registry entry id */ + uint64_t id; + /* Name, in UTF-8 encoding */ + char name[128]; + /* PCI ID */ + uint32_t vendor_id; + uint32_t device_id; + uint32_t subsys_id; + uint32_t revision_id; +}; + extern int macdrv_get_displays(struct macdrv_display** displays, int* count) DECLSPEC_HIDDEN; extern void macdrv_free_displays(struct macdrv_display* displays) DECLSPEC_HIDDEN; extern int macdrv_set_display_mode(const struct macdrv_display* display, CGDisplayModeRef display_mode) DECLSPEC_HIDDEN; +extern int macdrv_get_gpus(struct macdrv_gpu** gpus, int* count) DECLSPEC_HIDDEN; +extern void macdrv_free_gpus(struct macdrv_gpu* gpus) DECLSPEC_HIDDEN; /* event */ -- 2.15.2 (Apple Git-101.1)