| // | |
| // Copyright 2019 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. | |
| // | |
| // VulkanExternalHelper.cpp : Helper for allocating & managing vulkan external objects. | |
| #include "test_utils/VulkanExternalHelper.h" | |
| #include <vector> | |
| #include "common/bitset_utils.h" | |
| #include "common/debug.h" | |
| namespace angle | |
| { | |
| namespace | |
| { | |
| constexpr VkImageUsageFlags kRequiredImageUsageFlags = | |
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | | |
| VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | | |
| VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; | |
| std::vector<VkExtensionProperties> EnumerateInstanceExtensionProperties(const char *layerName) | |
| { | |
| uint32_t instanceExtensionCount; | |
| VkResult result = | |
| vkEnumerateInstanceExtensionProperties(layerName, &instanceExtensionCount, nullptr); | |
| ASSERT(result == VK_SUCCESS); | |
| std::vector<VkExtensionProperties> instanceExtensionProperties(instanceExtensionCount); | |
| result = vkEnumerateInstanceExtensionProperties(layerName, &instanceExtensionCount, | |
| instanceExtensionProperties.data()); | |
| ASSERT(result == VK_SUCCESS); | |
| return instanceExtensionProperties; | |
| } | |
| std::vector<VkPhysicalDevice> EnumeratePhysicalDevices(VkInstance instance) | |
| { | |
| uint32_t physicalDeviceCount; | |
| VkResult result = vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, nullptr); | |
| ASSERT(result == VK_SUCCESS); | |
| std::vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount); | |
| result = vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, physicalDevices.data()); | |
| return physicalDevices; | |
| } | |
| std::vector<VkExtensionProperties> EnumerateDeviceExtensionProperties( | |
| VkPhysicalDevice physicalDevice, | |
| const char *layerName) | |
| { | |
| uint32_t deviceExtensionCount; | |
| VkResult result = vkEnumerateDeviceExtensionProperties(physicalDevice, layerName, | |
| &deviceExtensionCount, nullptr); | |
| ASSERT(result == VK_SUCCESS); | |
| std::vector<VkExtensionProperties> deviceExtensionProperties(deviceExtensionCount); | |
| result = vkEnumerateDeviceExtensionProperties(physicalDevice, layerName, &deviceExtensionCount, | |
| deviceExtensionProperties.data()); | |
| ASSERT(result == VK_SUCCESS); | |
| return deviceExtensionProperties; | |
| } | |
| std::vector<VkQueueFamilyProperties> GetPhysicalDeviceQueueFamilyProperties( | |
| VkPhysicalDevice physicalDevice) | |
| { | |
| uint32_t queueFamilyPropertyCount; | |
| vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyPropertyCount, nullptr); | |
| std::vector<VkQueueFamilyProperties> physicalDeviceQueueFamilyProperties( | |
| queueFamilyPropertyCount); | |
| vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyPropertyCount, | |
| physicalDeviceQueueFamilyProperties.data()); | |
| return physicalDeviceQueueFamilyProperties; | |
| } | |
| bool HasExtension(const std::vector<VkExtensionProperties> instanceExtensions, | |
| const char *extensionName) | |
| { | |
| for (const auto &extensionProperties : instanceExtensions) | |
| { | |
| if (!strcmp(extensionProperties.extensionName, extensionName)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool HasExtension(const std::vector<const char *> enabledExtensions, const char *extensionName) | |
| { | |
| for (const char *enabledExtension : enabledExtensions) | |
| { | |
| if (!strcmp(enabledExtension, extensionName)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| uint32_t FindMemoryType(const VkPhysicalDeviceMemoryProperties &memoryProperties, | |
| uint32_t memoryTypeBits, | |
| VkMemoryPropertyFlags requiredMemoryPropertyFlags) | |
| { | |
| for (size_t memoryIndex : angle::BitSet32<32>(memoryTypeBits)) | |
| { | |
| ASSERT(memoryIndex < memoryProperties.memoryTypeCount); | |
| if ((memoryProperties.memoryTypes[memoryIndex].propertyFlags & | |
| requiredMemoryPropertyFlags) == requiredMemoryPropertyFlags) | |
| { | |
| return static_cast<uint32_t>(memoryIndex); | |
| } | |
| } | |
| return UINT32_MAX; | |
| } | |
| } // namespace | |
| VulkanExternalHelper::VulkanExternalHelper() {} | |
| VulkanExternalHelper::~VulkanExternalHelper() | |
| { | |
| if (mDevice != VK_NULL_HANDLE) | |
| { | |
| vkDeviceWaitIdle(mDevice); | |
| vkDestroyDevice(mDevice, nullptr); | |
| mDevice = VK_NULL_HANDLE; | |
| mGraphicsQueue = VK_NULL_HANDLE; | |
| } | |
| if (mInstance != VK_NULL_HANDLE) | |
| { | |
| vkDestroyInstance(mInstance, nullptr); | |
| mInstance = VK_NULL_HANDLE; | |
| } | |
| } | |
| void VulkanExternalHelper::initialize() | |
| { | |
| ASSERT(mInstance == VK_NULL_HANDLE); | |
| VkResult result = volkInitialize(); | |
| ASSERT(result == VK_SUCCESS); | |
| std::vector<VkExtensionProperties> instanceExtensionProperties = | |
| EnumerateInstanceExtensionProperties(nullptr); | |
| std::vector<const char *> requestedInstanceExtensions = { | |
| VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, | |
| VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, | |
| VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME}; | |
| std::vector<const char *> enabledInstanceExtensions; | |
| for (const char *extensionName : requestedInstanceExtensions) | |
| { | |
| if (HasExtension(instanceExtensionProperties, extensionName)) | |
| { | |
| enabledInstanceExtensions.push_back(extensionName); | |
| } | |
| } | |
| VkApplicationInfo applicationInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_APPLICATION_INFO, | |
| /* .pNext = */ nullptr, | |
| /* .pApplicationName = */ "ANGLE Tests", | |
| /* .applicationVersion = */ 1, | |
| /* .pEngineName = */ nullptr, | |
| /* .engineVersion = */ 0, | |
| /* .apiVersion = */ VK_API_VERSION_1_0, | |
| }; | |
| uint32_t enabledInstanceExtensionCount = | |
| static_cast<uint32_t>(enabledInstanceExtensions.size()); | |
| VkInstanceCreateInfo instanceCreateInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, | |
| /* .pNext = */ nullptr, | |
| /* .flags = */ 0, | |
| /* .pApplicationInfo = */ &applicationInfo, | |
| /* .enabledLayerCount = */ 0, | |
| /* .ppEnabledLayerNames = */ nullptr, | |
| /* .enabledExtensionCount = */ enabledInstanceExtensionCount, | |
| /* .ppEnabledExtensionName = */ enabledInstanceExtensions.data(), | |
| }; | |
| result = vkCreateInstance(&instanceCreateInfo, nullptr, &mInstance); | |
| ASSERT(result == VK_SUCCESS); | |
| ASSERT(mInstance != VK_NULL_HANDLE); | |
| volkLoadInstance(mInstance); | |
| std::vector<VkPhysicalDevice> physicalDevices = EnumeratePhysicalDevices(mInstance); | |
| ASSERT(physicalDevices.size() > 0); | |
| mPhysicalDevice = physicalDevices[0]; | |
| vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &mMemoryProperties); | |
| std::vector<VkExtensionProperties> deviceExtensionProperties = | |
| EnumerateDeviceExtensionProperties(mPhysicalDevice, nullptr); | |
| std::vector<const char *> requestedDeviceExtensions = { | |
| VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, | |
| VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, | |
| VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, | |
| VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, | |
| }; | |
| std::vector<const char *> enabledDeviceExtensions; | |
| for (const char *extensionName : requestedDeviceExtensions) | |
| { | |
| if (HasExtension(deviceExtensionProperties, extensionName)) | |
| { | |
| enabledDeviceExtensions.push_back(extensionName); | |
| } | |
| } | |
| std::vector<VkQueueFamilyProperties> queueFamilyProperties = | |
| GetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice); | |
| for (uint32_t i = 0; i < queueFamilyProperties.size(); ++i) | |
| { | |
| if (queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) | |
| { | |
| mGraphicsQueueFamilyIndex = i; | |
| } | |
| } | |
| ASSERT(mGraphicsQueueFamilyIndex != UINT32_MAX); | |
| constexpr uint32_t kQueueCreateInfoCount = 1; | |
| constexpr uint32_t kGraphicsQueueCount = 1; | |
| float graphicsQueuePriorities[kGraphicsQueueCount] = {0.f}; | |
| VkDeviceQueueCreateInfo queueCreateInfos[kQueueCreateInfoCount] = { | |
| /* [0] = */ { | |
| /* .sType = */ VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, | |
| /* .pNext = */ nullptr, | |
| /* .flags = */ 0, | |
| /* .queueFamilyIndex = */ mGraphicsQueueFamilyIndex, | |
| /* .queueCount = */ 1, | |
| /* .pQueuePriorities = */ graphicsQueuePriorities, | |
| }, | |
| }; | |
| uint32_t enabledDeviceExtensionCount = static_cast<uint32_t>(enabledDeviceExtensions.size()); | |
| VkDeviceCreateInfo deviceCreateInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, | |
| /* .pNext = */ nullptr, | |
| /* .flags = */ 0, | |
| /* .queueCreateInfoCount = */ kQueueCreateInfoCount, | |
| /* .pQueueCreateInfos = */ queueCreateInfos, | |
| /* .enabledLayerCount = */ 0, | |
| /* .ppEnabledLayerNames = */ nullptr, | |
| /* .enabledExtensionCount = */ enabledDeviceExtensionCount, | |
| /* .ppEnabledExtensionName = */ enabledDeviceExtensions.data(), | |
| /* .pEnabledFeatures = */ nullptr, | |
| }; | |
| result = vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice); | |
| ASSERT(result == VK_SUCCESS); | |
| ASSERT(mDevice != VK_NULL_HANDLE); | |
| volkLoadDevice(mDevice); | |
| constexpr uint32_t kGraphicsQueueIndex = 0; | |
| static_assert(kGraphicsQueueIndex < kGraphicsQueueCount, "must be in range"); | |
| vkGetDeviceQueue(mDevice, mGraphicsQueueFamilyIndex, kGraphicsQueueIndex, &mGraphicsQueue); | |
| ASSERT(mGraphicsQueue != VK_NULL_HANDLE); | |
| mHasExternalMemoryFd = | |
| HasExtension(enabledDeviceExtensions, VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME); | |
| mHasExternalSemaphoreFd = | |
| HasExtension(enabledDeviceExtensions, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME); | |
| vkGetPhysicalDeviceImageFormatProperties2 = | |
| reinterpret_cast<PFN_vkGetPhysicalDeviceImageFormatProperties2>( | |
| vkGetInstanceProcAddr(mInstance, "vkGetPhysicalDeviceImageFormatProperties2")); | |
| vkGetMemoryFdKHR = reinterpret_cast<PFN_vkGetMemoryFdKHR>( | |
| vkGetInstanceProcAddr(mInstance, "vkGetMemoryFdKHR")); | |
| vkGetSemaphoreFdKHR = reinterpret_cast<PFN_vkGetSemaphoreFdKHR>( | |
| vkGetInstanceProcAddr(mInstance, "vkGetSemaphoreFdKHR")); | |
| vkGetPhysicalDeviceExternalSemaphorePropertiesKHR = | |
| reinterpret_cast<PFN_vkGetPhysicalDeviceExternalSemaphorePropertiesKHR>( | |
| vkGetInstanceProcAddr(mInstance, "vkGetPhysicalDeviceExternalSemaphorePropertiesKHR")); | |
| } | |
| bool VulkanExternalHelper::canCreateImageOpaqueFd(VkFormat format, | |
| VkImageType type, | |
| VkImageTiling tiling) const | |
| { | |
| if (!mHasExternalMemoryFd) | |
| { | |
| return false; | |
| } | |
| VkPhysicalDeviceExternalImageFormatInfo externalImageFormatInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO, | |
| /* .pNext = */ nullptr, | |
| /* .handleType = */ VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT, | |
| }; | |
| VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, | |
| /* .pNext = */ &externalImageFormatInfo, | |
| /* .format = */ format, | |
| /* .type = */ type, | |
| /* .tiling = */ tiling, | |
| /* .usage = */ kRequiredImageUsageFlags, | |
| /* .flags = */ 0, | |
| }; | |
| VkExternalImageFormatProperties externalImageFormatProperties = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES, | |
| /* .pNext = */ nullptr, | |
| }; | |
| VkImageFormatProperties2 imageFormatProperties = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, | |
| /* .pNext = */ &externalImageFormatProperties}; | |
| VkResult result = vkGetPhysicalDeviceImageFormatProperties2(mPhysicalDevice, &imageFormatInfo, | |
| &imageFormatProperties); | |
| if (result == VK_ERROR_FORMAT_NOT_SUPPORTED) | |
| { | |
| return false; | |
| } | |
| ASSERT(result == VK_SUCCESS); | |
| constexpr VkExternalMemoryFeatureFlags kUnsupportedFeatures = | |
| VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT; | |
| constexpr VkExternalMemoryFeatureFlags kRequiredFeatures = | |
| VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT | VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT; | |
| if (externalImageFormatProperties.externalMemoryProperties.externalMemoryFeatures & | |
| kUnsupportedFeatures) | |
| { | |
| return false; | |
| } | |
| if ((externalImageFormatProperties.externalMemoryProperties.externalMemoryFeatures & | |
| kRequiredFeatures) != kRequiredFeatures) | |
| { | |
| return false; | |
| } | |
| return true; | |
| } | |
| VkResult VulkanExternalHelper::createImage2DOpaqueFd(VkFormat format, | |
| VkExtent3D extent, | |
| VkImage *imageOut, | |
| VkDeviceMemory *deviceMemoryOut, | |
| VkDeviceSize *deviceMemorySizeOut) | |
| { | |
| VkExternalMemoryImageCreateInfoKHR externalMemoryImageCreateInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, | |
| /* .pNext = */ nullptr, | |
| /* .handleTypes = */ VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT, | |
| }; | |
| VkImageCreateInfo imageCreateInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, | |
| /* .pNext = */ &externalMemoryImageCreateInfo, | |
| /* .flags = */ 0, | |
| /* .imageType = */ VK_IMAGE_TYPE_2D, | |
| /* .format = */ format, | |
| /* .extent = */ extent, | |
| /* .mipLevels = */ 1, | |
| /* .arrayLayers = */ 1, | |
| /* .samples = */ VK_SAMPLE_COUNT_1_BIT, | |
| /* .tiling = */ VK_IMAGE_TILING_OPTIMAL, | |
| /* .usage = */ kRequiredImageUsageFlags, | |
| /* .sharingMode = */ VK_SHARING_MODE_EXCLUSIVE, | |
| /* .queueFamilyIndexCount = */ 0, | |
| /* .pQueueFamilyIndices = */ nullptr, | |
| /* initialLayout = */ VK_IMAGE_LAYOUT_UNDEFINED, | |
| }; | |
| VkImage image = VK_NULL_HANDLE; | |
| VkResult result = vkCreateImage(mDevice, &imageCreateInfo, nullptr, &image); | |
| if (result != VK_SUCCESS) | |
| { | |
| return result; | |
| } | |
| VkMemoryPropertyFlags requestedMemoryPropertyFlags = 0; | |
| VkMemoryRequirements memoryRequirements; | |
| vkGetImageMemoryRequirements(mDevice, image, &memoryRequirements); | |
| uint32_t memoryTypeIndex = FindMemoryType(mMemoryProperties, memoryRequirements.memoryTypeBits, | |
| requestedMemoryPropertyFlags); | |
| ASSERT(memoryTypeIndex != UINT32_MAX); | |
| VkDeviceSize deviceMemorySize = memoryRequirements.size; | |
| VkExportMemoryAllocateInfo exportMemoryAllocateInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, | |
| /* .pNext = */ nullptr, | |
| /* .handleTypes = */ VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT, | |
| }; | |
| VkMemoryAllocateInfo memoryAllocateInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, | |
| /* .pNext = */ &exportMemoryAllocateInfo, | |
| /* .allocationSize = */ deviceMemorySize, | |
| /* .memoryTypeIndex = */ memoryTypeIndex, | |
| }; | |
| VkDeviceMemory deviceMemory = VK_NULL_HANDLE; | |
| result = vkAllocateMemory(mDevice, &memoryAllocateInfo, nullptr, &deviceMemory); | |
| if (result != VK_SUCCESS) | |
| { | |
| vkDestroyImage(mDevice, image, nullptr); | |
| return result; | |
| } | |
| VkDeviceSize memoryOffset = 0; | |
| result = vkBindImageMemory(mDevice, image, deviceMemory, memoryOffset); | |
| if (result != VK_SUCCESS) | |
| { | |
| vkFreeMemory(mDevice, deviceMemory, nullptr); | |
| vkDestroyImage(mDevice, image, nullptr); | |
| return result; | |
| } | |
| *imageOut = image; | |
| *deviceMemoryOut = deviceMemory; | |
| *deviceMemorySizeOut = deviceMemorySize; | |
| return VK_SUCCESS; | |
| } | |
| VkResult VulkanExternalHelper::exportMemoryOpaqueFd(VkDeviceMemory deviceMemory, int *fd) | |
| { | |
| VkMemoryGetFdInfoKHR memoryGetFdInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, | |
| /* .pNext = */ nullptr, | |
| /* .memory = */ deviceMemory, | |
| /* .handleType = */ VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT, | |
| }; | |
| return vkGetMemoryFdKHR(mDevice, &memoryGetFdInfo, fd); | |
| } | |
| bool VulkanExternalHelper::canCreateSemaphoreOpaqueFd() const | |
| { | |
| if (!mHasExternalSemaphoreFd || !vkGetPhysicalDeviceExternalSemaphorePropertiesKHR) | |
| { | |
| return false; | |
| } | |
| VkPhysicalDeviceExternalSemaphoreInfo externalSemaphoreInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO, | |
| /* .pNext = */ nullptr, | |
| /* .handleType = */ VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, | |
| }; | |
| VkExternalSemaphoreProperties externalSemaphoreProperties = {}; | |
| vkGetPhysicalDeviceExternalSemaphorePropertiesKHR(mPhysicalDevice, &externalSemaphoreInfo, | |
| &externalSemaphoreProperties); | |
| constexpr VkExternalSemaphoreFeatureFlags kRequiredFeatures = | |
| VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT | VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT; | |
| if ((externalSemaphoreProperties.externalSemaphoreFeatures & kRequiredFeatures) != | |
| kRequiredFeatures) | |
| { | |
| return false; | |
| } | |
| return true; | |
| } | |
| VkResult VulkanExternalHelper::createSemaphoreOpaqueFd(VkSemaphore *semaphore) | |
| { | |
| VkExportSemaphoreCreateInfo exportSemaphoreCreateInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, | |
| /* .pNext = */ nullptr, | |
| /* .handleTypes = */ VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, | |
| }; | |
| VkSemaphoreCreateInfo semaphoreCreateInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, | |
| /* .pNext = */ &exportSemaphoreCreateInfo, | |
| /* .flags = */ 0, | |
| }; | |
| return vkCreateSemaphore(mDevice, &semaphoreCreateInfo, nullptr, semaphore); | |
| } | |
| VkResult VulkanExternalHelper::exportSemaphoreOpaqueFd(VkSemaphore semaphore, int *fd) | |
| { | |
| VkSemaphoreGetFdInfoKHR semaphoreGetFdInfo = { | |
| /* .sType = */ VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR, | |
| /* .pNext = */ nullptr, | |
| /* .semaphore = */ semaphore, | |
| /* .handleType = */ VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, | |
| }; | |
| return vkGetSemaphoreFdKHR(mDevice, &semaphoreGetFdInfo, fd); | |
| } | |
| } // namespace angle |