diff options
author | 2022-12-28 01:58:31 +0000 | |
---|---|---|
committer | 2022-12-28 01:58:31 +0000 | |
commit | 9d0d9ff3d92bea4584f9b6f14bc2a348268c42b4 (patch) | |
tree | 17c9d1fb0cc288de002198aefa80405557ee2c35 | |
parent | 334a4105c8d52848c48c4c843a33522423010c4c (diff) |
Revert "Revert "Reland: "libvulkan: Implement EXT_swapchain_maintenance1""""
This reverts commit 334a4105c8d52848c48c4c843a33522423010c4c.
Reason for revert: all failures tracked to original CL, not the reland. No issues seen now in manual hwasan tests.
Change-Id: Icd2f12f18b5f1a77237703983e11bced16cb8865
-rw-r--r-- | vulkan/libvulkan/driver.cpp | 62 | ||||
-rw-r--r-- | vulkan/libvulkan/driver_gen.cpp | 20 | ||||
-rw-r--r-- | vulkan/libvulkan/driver_gen.h | 4 | ||||
-rw-r--r-- | vulkan/libvulkan/swapchain.cpp | 624 | ||||
-rw-r--r-- | vulkan/libvulkan/swapchain.h | 1 | ||||
-rw-r--r-- | vulkan/scripts/driver_generator.py | 6 |
6 files changed, 534 insertions, 183 deletions
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp index 9ed992b230..273cdd547e 100644 --- a/vulkan/libvulkan/driver.cpp +++ b/vulkan/libvulkan/driver.cpp @@ -636,6 +636,7 @@ void CreateInfoWrapper::FilterExtension(const char* name) { case ProcHook::EXT_swapchain_colorspace: case ProcHook::KHR_get_surface_capabilities2: case ProcHook::GOOGLE_surfaceless_query: + case ProcHook::EXT_surface_maintenance1: hook_extensions_.set(ext_bit); // return now as these extensions do not require HAL support return; @@ -657,9 +658,11 @@ void CreateInfoWrapper::FilterExtension(const char* name) { case ProcHook::KHR_shared_presentable_image: case ProcHook::KHR_swapchain: case ProcHook::EXT_hdr_metadata: + case ProcHook::EXT_swapchain_maintenance1: case ProcHook::ANDROID_external_memory_android_hardware_buffer: case ProcHook::ANDROID_native_buffer: case ProcHook::GOOGLE_display_timing: + case ProcHook::KHR_external_fence_fd: case ProcHook::EXTENSION_CORE_1_0: case ProcHook::EXTENSION_CORE_1_1: case ProcHook::EXTENSION_CORE_1_2: @@ -690,16 +693,22 @@ void CreateInfoWrapper::FilterExtension(const char* name) { ext_bit = ProcHook::ANDROID_native_buffer; break; case ProcHook::KHR_incremental_present: - case ProcHook::GOOGLE_display_timing: case ProcHook::KHR_shared_presentable_image: + case ProcHook::GOOGLE_display_timing: hook_extensions_.set(ext_bit); // return now as these extensions do not require HAL support return; + case ProcHook::EXT_swapchain_maintenance1: + // map VK_KHR_swapchain_maintenance1 to KHR_external_fence_fd + name = VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME; + ext_bit = ProcHook::KHR_external_fence_fd; + break; case ProcHook::EXT_hdr_metadata: case ProcHook::KHR_bind_memory2: hook_extensions_.set(ext_bit); break; case ProcHook::ANDROID_external_memory_android_hardware_buffer: + case ProcHook::KHR_external_fence_fd: case ProcHook::EXTENSION_UNKNOWN: // Extensions we don't need to do anything about at this level break; @@ -715,6 +724,7 @@ void CreateInfoWrapper::FilterExtension(const char* name) { case ProcHook::KHR_surface_protected_capabilities: case ProcHook::EXT_debug_report: case ProcHook::EXT_swapchain_colorspace: + case ProcHook::EXT_surface_maintenance1: case ProcHook::GOOGLE_surfaceless_query: case ProcHook::ANDROID_native_buffer: case ProcHook::EXTENSION_CORE_1_0: @@ -747,10 +757,18 @@ void CreateInfoWrapper::FilterExtension(const char* name) { if (strcmp(name, props.extensionName) != 0) continue; + if (ext_bit != ProcHook::EXTENSION_UNKNOWN && + hal_extensions_.test(ext_bit)) { + ALOGI("CreateInfoWrapper::FilterExtension: already have '%s'.", name); + continue; + } + filter.names[filter.name_count++] = name; if (ext_bit != ProcHook::EXTENSION_UNKNOWN) { if (ext_bit == ProcHook::ANDROID_native_buffer) hook_extensions_.set(ProcHook::KHR_swapchain); + if (ext_bit == ProcHook::KHR_external_fence_fd) + hook_extensions_.set(ProcHook::EXT_swapchain_maintenance1); hal_extensions_.set(ext_bit); } @@ -940,6 +958,9 @@ VkResult EnumerateInstanceExtensionProperties( VK_KHR_GET_SURFACE_CAPABILITIES_2_SPEC_VERSION}); loader_extensions.push_back({VK_GOOGLE_SURFACELESS_QUERY_EXTENSION_NAME, VK_GOOGLE_SURFACELESS_QUERY_SPEC_VERSION}); + loader_extensions.push_back({ + VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME, + VK_EXT_SURFACE_MAINTENANCE_1_SPEC_VERSION}); static const VkExtensionProperties loader_debug_report_extension = { VK_EXT_DEBUG_REPORT_EXTENSION_NAME, VK_EXT_DEBUG_REPORT_SPEC_VERSION, @@ -1072,6 +1093,33 @@ VkResult GetAndroidNativeBufferSpecVersion9Support( return result; } +bool CanSupportSwapchainMaintenance1Extension(VkPhysicalDevice physicalDevice) { + const auto& driver = GetData(physicalDevice).driver; + if (!driver.GetPhysicalDeviceExternalFenceProperties) + return false; + + // Requires support for external fences imported from sync fds. + // This is _almost_ universal on Android, but may be missing on + // some extremely old drivers, or on strange implementations like + // cuttlefish. + VkPhysicalDeviceExternalFenceInfo fenceInfo = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO, + nullptr, + VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT + }; + VkExternalFenceProperties fenceProperties = { + VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES, + nullptr, + 0, 0, 0 + }; + + GetPhysicalDeviceExternalFenceProperties(physicalDevice, &fenceInfo, &fenceProperties); + if (fenceProperties.externalFenceFeatures & VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT) + return true; + + return false; +} + VkResult EnumerateDeviceExtensionProperties( VkPhysicalDevice physicalDevice, const char* pLayerName, @@ -1149,6 +1197,12 @@ VkResult EnumerateDeviceExtensionProperties( VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_SPEC_VERSION}); } + if (CanSupportSwapchainMaintenance1Extension(physicalDevice)) { + loader_extensions.push_back({ + VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, + VK_EXT_SWAPCHAIN_MAINTENANCE_1_SPEC_VERSION}); + } + // enumerate our extensions first if (!pLayerName && pProperties) { uint32_t count = std::min( @@ -1644,6 +1698,12 @@ void GetPhysicalDeviceFeatures2(VkPhysicalDevice physicalDevice, imageCompressionControlSwapchainInChain = true; } break; + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT: { + auto smf = reinterpret_cast<VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT *>( + pFeats); + smf->swapchainMaintenance1 = true; + } break; + default: break; } diff --git a/vulkan/libvulkan/driver_gen.cpp b/vulkan/libvulkan/driver_gen.cpp index de98aa7e61..798af5c6a6 100644 --- a/vulkan/libvulkan/driver_gen.cpp +++ b/vulkan/libvulkan/driver_gen.cpp @@ -162,6 +162,15 @@ VKAPI_ATTR void checkedGetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2 } } +VKAPI_ATTR VkResult checkedReleaseSwapchainImagesEXT(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) { + if (GetData(device).hook_extensions[ProcHook::EXT_swapchain_maintenance1]) { + return ReleaseSwapchainImagesEXT(device, pReleaseInfo); + } else { + Logger(device).Err(device, "VK_EXT_swapchain_maintenance1 not enabled. vkReleaseSwapchainImagesEXT not executed."); + return VK_SUCCESS; + } +} + // clang-format on const ProcHook g_proc_hooks[] = { @@ -545,6 +554,13 @@ const ProcHook g_proc_hooks[] = { nullptr, }, { + "vkReleaseSwapchainImagesEXT", + ProcHook::DEVICE, + ProcHook::EXT_swapchain_maintenance1, + reinterpret_cast<PFN_vkVoidFunction>(ReleaseSwapchainImagesEXT), + reinterpret_cast<PFN_vkVoidFunction>(checkedReleaseSwapchainImagesEXT), + }, + { "vkSetHdrMetadataEXT", ProcHook::DEVICE, ProcHook::EXT_hdr_metadata, @@ -580,6 +596,8 @@ ProcHook::Extension GetProcHookExtension(const char* name) { if (strcmp(name, "VK_KHR_surface") == 0) return ProcHook::KHR_surface; if (strcmp(name, "VK_KHR_surface_protected_capabilities") == 0) return ProcHook::KHR_surface_protected_capabilities; if (strcmp(name, "VK_KHR_swapchain") == 0) return ProcHook::KHR_swapchain; + if (strcmp(name, "VK_EXT_swapchain_maintenance1") == 0) return ProcHook::EXT_swapchain_maintenance1; + if (strcmp(name, "VK_EXT_surface_maintenance1") == 0) return ProcHook::EXT_surface_maintenance1; if (strcmp(name, "VK_ANDROID_external_memory_android_hardware_buffer") == 0) return ProcHook::ANDROID_external_memory_android_hardware_buffer; if (strcmp(name, "VK_KHR_bind_memory2") == 0) return ProcHook::KHR_bind_memory2; if (strcmp(name, "VK_KHR_get_physical_device_properties2") == 0) return ProcHook::KHR_get_physical_device_properties2; @@ -587,6 +605,7 @@ ProcHook::Extension GetProcHookExtension(const char* name) { if (strcmp(name, "VK_KHR_external_memory_capabilities") == 0) return ProcHook::KHR_external_memory_capabilities; if (strcmp(name, "VK_KHR_external_semaphore_capabilities") == 0) return ProcHook::KHR_external_semaphore_capabilities; if (strcmp(name, "VK_KHR_external_fence_capabilities") == 0) return ProcHook::KHR_external_fence_capabilities; + if (strcmp(name, "VK_KHR_external_fence_fd") == 0) return ProcHook::KHR_external_fence_fd; // clang-format on return ProcHook::EXTENSION_UNKNOWN; } @@ -666,6 +685,7 @@ bool InitDriverTable(VkDevice dev, INIT_PROC(true, dev, CreateImage); INIT_PROC(true, dev, DestroyImage); INIT_PROC(true, dev, AllocateCommandBuffers); + INIT_PROC_EXT(KHR_external_fence_fd, true, dev, ImportFenceFdKHR); INIT_PROC(false, dev, BindImageMemory2); INIT_PROC_EXT(KHR_bind_memory2, true, dev, BindImageMemory2KHR); INIT_PROC(false, dev, GetDeviceQueue2); diff --git a/vulkan/libvulkan/driver_gen.h b/vulkan/libvulkan/driver_gen.h index 2f60086a27..31ba04ba1f 100644 --- a/vulkan/libvulkan/driver_gen.h +++ b/vulkan/libvulkan/driver_gen.h @@ -49,6 +49,8 @@ struct ProcHook { KHR_surface, KHR_surface_protected_capabilities, KHR_swapchain, + EXT_swapchain_maintenance1, + EXT_surface_maintenance1, ANDROID_external_memory_android_hardware_buffer, KHR_bind_memory2, KHR_get_physical_device_properties2, @@ -56,6 +58,7 @@ struct ProcHook { KHR_external_memory_capabilities, KHR_external_semaphore_capabilities, KHR_external_fence_capabilities, + KHR_external_fence_fd, EXTENSION_CORE_1_0, EXTENSION_CORE_1_1, @@ -118,6 +121,7 @@ struct DeviceDriverTable { PFN_vkCreateImage CreateImage; PFN_vkDestroyImage DestroyImage; PFN_vkAllocateCommandBuffers AllocateCommandBuffers; + PFN_vkImportFenceFdKHR ImportFenceFdKHR; PFN_vkBindImageMemory2 BindImageMemory2; PFN_vkBindImageMemory2KHR BindImageMemory2KHR; PFN_vkGetDeviceQueue2 GetDeviceQueue2; diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index b03200e4a7..1bff50dd7f 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -291,6 +291,9 @@ struct Swapchain { release_fence(-1), dequeued(false) {} VkImage image; + // If the image is bound to memory, an sp to the underlying gralloc buffer. + // Otherwise, nullptr; the image will be bound to memory as part of + // AcquireNextImage. android::sp<ANativeWindowBuffer> buffer; // The fence is only valid when the buffer is dequeued, and should be // -1 any other time. When valid, we own the fd, and must ensure it is @@ -656,100 +659,40 @@ VkResult GetPhysicalDeviceSurfaceCapabilitiesKHR( VkSurfaceCapabilitiesKHR* capabilities) { ATRACE_CALL(); - int err; - int width, height; - int transform_hint; - int max_buffer_count; - if (surface == VK_NULL_HANDLE) { - const InstanceData& instance_data = GetData(pdev); - ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query; - bool surfaceless_enabled = - instance_data.hook_extensions.test(surfaceless); - if (!surfaceless_enabled) { - // It is an error to pass a surface==VK_NULL_HANDLE unless the - // VK_GOOGLE_surfaceless_query extension is enabled - return VK_ERROR_SURFACE_LOST_KHR; - } - // Support for VK_GOOGLE_surfaceless_query. The primary purpose of this - // extension for this function is for - // VkSurfaceProtectedCapabilitiesKHR::supportsProtected. The following - // four values cannot be known without a surface. Default values will - // be supplied anyway, but cannot be relied upon. - width = 0xFFFFFFFF; - height = 0xFFFFFFFF; - transform_hint = VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR; - capabilities->minImageCount = 0xFFFFFFFF; - capabilities->maxImageCount = 0xFFFFFFFF; - } else { - ANativeWindow* window = SurfaceFromHandle(surface)->window.get(); + // Implement in terms of GetPhysicalDeviceSurfaceCapabilities2KHR - err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width); - if (err != android::OK) { - ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)", - strerror(-err), err); - return VK_ERROR_SURFACE_LOST_KHR; - } - err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height); - if (err != android::OK) { - ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)", - strerror(-err), err); - return VK_ERROR_SURFACE_LOST_KHR; - } - - err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT, - &transform_hint); - if (err != android::OK) { - ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)", - strerror(-err), err); - return VK_ERROR_SURFACE_LOST_KHR; - } - - err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT, - &max_buffer_count); - if (err != android::OK) { - ALOGE("NATIVE_WINDOW_MAX_BUFFER_COUNT query failed: %s (%d)", - strerror(-err), err); - return VK_ERROR_SURFACE_LOST_KHR; - } - capabilities->minImageCount = std::min(max_buffer_count, 3); - capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count); - } - - capabilities->currentExtent = - VkExtent2D{static_cast<uint32_t>(width), static_cast<uint32_t>(height)}; + VkPhysicalDeviceSurfaceInfo2KHR info2 = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR, + nullptr, + surface + }; - // TODO(http://b/134182502): Figure out what the max extent should be. - capabilities->minImageExtent = VkExtent2D{1, 1}; - capabilities->maxImageExtent = VkExtent2D{4096, 4096}; + VkSurfaceCapabilities2KHR caps2 = { + VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR, + nullptr, + {}, + }; - if (capabilities->maxImageExtent.height < - capabilities->currentExtent.height) { - capabilities->maxImageExtent.height = - capabilities->currentExtent.height; - } + VkResult result = GetPhysicalDeviceSurfaceCapabilities2KHR(pdev, &info2, &caps2); + *capabilities = caps2.surfaceCapabilities; + return result; +} - if (capabilities->maxImageExtent.width < - capabilities->currentExtent.width) { - capabilities->maxImageExtent.width = capabilities->currentExtent.width; +// Does the call-twice and VK_INCOMPLETE handling for querying lists +// of things, where we already have the full set built in a vector. +template <typename T> +VkResult CopyWithIncomplete(std::vector<T> const& things, + T* callerPtr, uint32_t* callerCount) { + VkResult result = VK_SUCCESS; + if (callerPtr) { + if (things.size() > *callerCount) + result = VK_INCOMPLETE; + *callerCount = std::min(uint32_t(things.size()), *callerCount); + std::copy(things.begin(), things.begin() + *callerCount, callerPtr); + } else { + *callerCount = things.size(); } - - capabilities->maxImageArrayLayers = 1; - - capabilities->supportedTransforms = kSupportedTransforms; - capabilities->currentTransform = - TranslateNativeToVulkanTransform(transform_hint); - - // On Android, window composition is a WindowManager property, not something - // associated with the bufferqueue. It can't be changed from here. - capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; - - capabilities->supportedUsageFlags = - 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; - - return VK_SUCCESS; + return result; } VKAPI_ATTR @@ -882,21 +825,7 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, // Android users. This includes the ANGLE team (a layered implementation of // OpenGL-ES). - VkResult result = VK_SUCCESS; - if (formats) { - uint32_t transfer_count = all_formats.size(); - if (transfer_count > *count) { - transfer_count = *count; - result = VK_INCOMPLETE; - } - std::copy(all_formats.begin(), all_formats.begin() + transfer_count, - formats); - *count = transfer_count; - } else { - *count = all_formats.size(); - } - - return result; + return CopyWithIncomplete(all_formats, formats, count); } VKAPI_ATTR @@ -906,19 +835,134 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( VkSurfaceCapabilities2KHR* pSurfaceCapabilities) { ATRACE_CALL(); - VkResult result = GetPhysicalDeviceSurfaceCapabilitiesKHR( - physicalDevice, pSurfaceInfo->surface, - &pSurfaceCapabilities->surfaceCapabilities); + auto surface = pSurfaceInfo->surface; + auto capabilities = &pSurfaceCapabilities->surfaceCapabilities; + + VkSurfacePresentModeEXT const *pPresentMode = nullptr; + for (auto pNext = reinterpret_cast<VkBaseInStructure const *>(pSurfaceInfo->pNext); + pNext; pNext = reinterpret_cast<VkBaseInStructure const *>(pNext->pNext)) { + switch (pNext->sType) { + case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT: + pPresentMode = reinterpret_cast<VkSurfacePresentModeEXT const *>(pNext); + break; + + default: + break; + } + } - VkSurfaceCapabilities2KHR* caps = pSurfaceCapabilities; - while (caps->pNext) { - caps = reinterpret_cast<VkSurfaceCapabilities2KHR*>(caps->pNext); + int err; + int width, height; + int transform_hint; + int max_buffer_count; + if (surface == VK_NULL_HANDLE) { + const InstanceData& instance_data = GetData(physicalDevice); + ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query; + bool surfaceless_enabled = + instance_data.hook_extensions.test(surfaceless); + if (!surfaceless_enabled) { + // It is an error to pass a surface==VK_NULL_HANDLE unless the + // VK_GOOGLE_surfaceless_query extension is enabled + return VK_ERROR_SURFACE_LOST_KHR; + } + // Support for VK_GOOGLE_surfaceless_query. The primary purpose of this + // extension for this function is for + // VkSurfaceProtectedCapabilitiesKHR::supportsProtected. The following + // four values cannot be known without a surface. Default values will + // be supplied anyway, but cannot be relied upon. + width = 0xFFFFFFFF; + height = 0xFFFFFFFF; + transform_hint = VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR; + capabilities->minImageCount = 0xFFFFFFFF; + capabilities->maxImageCount = 0xFFFFFFFF; + } else { + ANativeWindow* window = SurfaceFromHandle(surface)->window.get(); - switch (caps->sType) { + err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } + err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } + + err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT, + &transform_hint); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } + + err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT, + &max_buffer_count); + if (err != android::OK) { + ALOGE("NATIVE_WINDOW_MAX_BUFFER_COUNT query failed: %s (%d)", + strerror(-err), err); + return VK_ERROR_SURFACE_LOST_KHR; + } + + if (pPresentMode && IsSharedPresentMode(pPresentMode->presentMode)) { + capabilities->minImageCount = 1; + capabilities->maxImageCount = 1; + } else if (pPresentMode && pPresentMode->presentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + // TODO: use undequeued buffer requirement for more precise bound + capabilities->minImageCount = std::min(max_buffer_count, 4); + capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count); + } else { + // TODO: if we're able to, provide better bounds on the number of buffers + // for other modes as well. + capabilities->minImageCount = std::min(max_buffer_count, 3); + capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count); + } + } + + capabilities->currentExtent = + VkExtent2D{static_cast<uint32_t>(width), static_cast<uint32_t>(height)}; + + // TODO(http://b/134182502): Figure out what the max extent should be. + capabilities->minImageExtent = VkExtent2D{1, 1}; + capabilities->maxImageExtent = VkExtent2D{4096, 4096}; + + if (capabilities->maxImageExtent.height < + capabilities->currentExtent.height) { + capabilities->maxImageExtent.height = + capabilities->currentExtent.height; + } + + if (capabilities->maxImageExtent.width < + capabilities->currentExtent.width) { + capabilities->maxImageExtent.width = capabilities->currentExtent.width; + } + + capabilities->maxImageArrayLayers = 1; + + capabilities->supportedTransforms = kSupportedTransforms; + capabilities->currentTransform = + TranslateNativeToVulkanTransform(transform_hint); + + // On Android, window composition is a WindowManager property, not something + // associated with the bufferqueue. It can't be changed from here. + capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + + capabilities->supportedUsageFlags = + 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; + + for (auto pNext = reinterpret_cast<VkBaseOutStructure*>(pSurfaceCapabilities->pNext); + pNext; pNext = reinterpret_cast<VkBaseOutStructure*>(pNext->pNext)) { + + switch (pNext->sType) { case VK_STRUCTURE_TYPE_SHARED_PRESENT_SURFACE_CAPABILITIES_KHR: { VkSharedPresentSurfaceCapabilitiesKHR* shared_caps = - reinterpret_cast<VkSharedPresentSurfaceCapabilitiesKHR*>( - caps); + reinterpret_cast<VkSharedPresentSurfaceCapabilitiesKHR*>(pNext); // Claim same set of usage flags are supported for // shared present modes as for other modes. shared_caps->sharedPresentSupportedUsageFlags = @@ -928,17 +972,64 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( case VK_STRUCTURE_TYPE_SURFACE_PROTECTED_CAPABILITIES_KHR: { VkSurfaceProtectedCapabilitiesKHR* protected_caps = - reinterpret_cast<VkSurfaceProtectedCapabilitiesKHR*>(caps); + reinterpret_cast<VkSurfaceProtectedCapabilitiesKHR*>(pNext); protected_caps->supportsProtected = VK_TRUE; } break; + case VK_STRUCTURE_TYPE_SURFACE_PRESENT_SCALING_CAPABILITIES_EXT: { + VkSurfacePresentScalingCapabilitiesEXT* scaling_caps = + reinterpret_cast<VkSurfacePresentScalingCapabilitiesEXT*>(pNext); + // By default, Android stretches the buffer to fit the window, + // without preserving aspect ratio. Other modes are technically possible + // but consult with CoGS team before exposing them here! + scaling_caps->supportedPresentScaling = VK_PRESENT_SCALING_STRETCH_BIT_EXT; + + // Since we always scale, we don't support any gravity. + scaling_caps->supportedPresentGravityX = 0; + scaling_caps->supportedPresentGravityY = 0; + + // Scaled image limits are just the basic image limits + scaling_caps->minScaledImageExtent = capabilities->minImageExtent; + scaling_caps->maxScaledImageExtent = capabilities->maxImageExtent; + } break; + + case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_COMPATIBILITY_EXT: { + VkSurfacePresentModeCompatibilityEXT* mode_caps = + reinterpret_cast<VkSurfacePresentModeCompatibilityEXT*>(pNext); + + ALOG_ASSERT(pPresentMode, + "querying VkSurfacePresentModeCompatibilityEXT " + "requires VkSurfacePresentModeEXT to be provided"); + std::vector<VkPresentModeKHR> compatibleModes; + compatibleModes.push_back(pPresentMode->presentMode); + + switch (pPresentMode->presentMode) { + // Shared modes are both compatible with each other. + case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR: + compatibleModes.push_back(VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR); + break; + case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR: + compatibleModes.push_back(VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR); + break; + default: + // Other modes are only compatible with themselves. + // TODO: consider whether switching between FIFO and MAILBOX is reasonable + break; + } + + // Note: this does not generate VK_INCOMPLETE since we're nested inside + // a larger query and there would be no way to determine exactly where it came from. + CopyWithIncomplete(compatibleModes, mode_caps->pPresentModes, + &mode_caps->presentModeCount); + } break; + default: // Ignore all other extension structs break; } } - return result; + return VK_SUCCESS; } VKAPI_ATTR @@ -1097,18 +1188,7 @@ VkResult GetPhysicalDeviceSurfacePresentModesKHR(VkPhysicalDevice pdev, present_modes.push_back(VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR); } - uint32_t num_modes = uint32_t(present_modes.size()); - - VkResult result = VK_SUCCESS; - if (modes) { - if (*count < num_modes) - result = VK_INCOMPLETE; - *count = std::min(*count, num_modes); - std::copy_n(present_modes.data(), *count, modes); - } else { - *count = num_modes; - } - return result; + return CopyWithIncomplete(present_modes, modes, count); } VKAPI_ATTR @@ -1394,8 +1474,7 @@ VkResult CreateSwapchainKHR(VkDevice device, } VkSwapchainImageUsageFlagsANDROID swapchain_image_usage = 0; - if (create_info->presentMode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR || - create_info->presentMode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR) { + if (IsSharedPresentMode(create_info->presentMode)) { swapchain_image_usage |= VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID; err = native_window_set_shared_buffer_mode(window, true); if (err != android::OK) { @@ -1545,8 +1624,6 @@ VkResult CreateSwapchainKHR(VkDevice device, Swapchain* swapchain = new (mem) Swapchain(surface, num_images, create_info->presentMode, TranslateVulkanToNativeTransform(create_info->preTransform)); - // -- Dequeue all buffers and create a VkImage for each -- - // Any failures during or after this must cancel the dequeued buffers. VkSwapchainImageCreateInfoANDROID swapchain_image_create = { #pragma clang diagnostic push @@ -1563,13 +1640,18 @@ VkResult CreateSwapchainKHR(VkDevice device, #pragma clang diagnostic pop .pNext = &swapchain_image_create, }; + VkImageCreateInfo image_create = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, - .pNext = &image_native_buffer, + .pNext = nullptr, .flags = createProtectedSwapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u, .imageType = VK_IMAGE_TYPE_2D, .format = create_info->imageFormat, - .extent = {0, 0, 1}, + .extent = { + create_info->imageExtent.width, + create_info->imageExtent.height, + 1 + }, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, @@ -1580,60 +1662,87 @@ VkResult CreateSwapchainKHR(VkDevice device, .pQueueFamilyIndices = create_info->pQueueFamilyIndices, }; - for (uint32_t i = 0; i < num_images; i++) { - Swapchain::Image& img = swapchain->images[i]; + // Note: don't do deferred allocation for shared present modes. There's only one buffer + // involved so very little benefit. + if ((create_info->flags & VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT) && + !IsSharedPresentMode(create_info->presentMode)) { + // Don't want to touch the underlying gralloc buffers yet; + // instead just create unbound VkImages which will later be bound to memory inside + // AcquireNextImage. + VkImageSwapchainCreateInfoKHR image_swapchain_create = { + .sType = VK_STRUCTURE_TYPE_IMAGE_SWAPCHAIN_CREATE_INFO_KHR, + .pNext = nullptr, + .swapchain = HandleFromSwapchain(swapchain), + }; + image_create.pNext = &image_swapchain_create; - ANativeWindowBuffer* buffer; - err = window->dequeueBuffer(window, &buffer, &img.dequeue_fence); - if (err != android::OK) { - ALOGE("dequeueBuffer[%u] failed: %s (%d)", i, strerror(-err), err); - switch (-err) { - case ENOMEM: - result = VK_ERROR_OUT_OF_DEVICE_MEMORY; - break; - default: - result = VK_ERROR_SURFACE_LOST_KHR; - break; + for (uint32_t i = 0; i < num_images; i++) { + Swapchain::Image& img = swapchain->images[i]; + img.buffer = nullptr; + img.dequeued = false; + + result = dispatch.CreateImage(device, &image_create, nullptr, &img.image); + if (result != VK_SUCCESS) { + ALOGD("vkCreateImage w/ for deferred swapchain image failed: %u", result); + break; } - break; } - img.buffer = buffer; - img.dequeued = true; - - image_create.extent = - VkExtent3D{static_cast<uint32_t>(img.buffer->width), - static_cast<uint32_t>(img.buffer->height), - 1}; - image_native_buffer.handle = img.buffer->handle; - image_native_buffer.stride = img.buffer->stride; - image_native_buffer.format = img.buffer->format; - image_native_buffer.usage = int(img.buffer->usage); - android_convertGralloc0To1Usage(int(img.buffer->usage), - &image_native_buffer.usage2.producer, - &image_native_buffer.usage2.consumer); - image_native_buffer.usage3 = img.buffer->usage; - - ATRACE_BEGIN("CreateImage"); - result = - dispatch.CreateImage(device, &image_create, nullptr, &img.image); - ATRACE_END(); - if (result != VK_SUCCESS) { - ALOGD("vkCreateImage w/ native buffer failed: %u", result); - break; + } else { + // -- Dequeue all buffers and create a VkImage for each -- + // Any failures during or after this must cancel the dequeued buffers. + + for (uint32_t i = 0; i < num_images; i++) { + Swapchain::Image& img = swapchain->images[i]; + + ANativeWindowBuffer* buffer; + err = window->dequeueBuffer(window, &buffer, &img.dequeue_fence); + if (err != android::OK) { + ALOGE("dequeueBuffer[%u] failed: %s (%d)", i, strerror(-err), err); + switch (-err) { + case ENOMEM: + result = VK_ERROR_OUT_OF_DEVICE_MEMORY; + break; + default: + result = VK_ERROR_SURFACE_LOST_KHR; + break; + } + break; + } + img.buffer = buffer; + img.dequeued = true; + + image_native_buffer.handle = img.buffer->handle; + image_native_buffer.stride = img.buffer->stride; + image_native_buffer.format = img.buffer->format; + image_native_buffer.usage = int(img.buffer->usage); + android_convertGralloc0To1Usage(int(img.buffer->usage), + &image_native_buffer.usage2.producer, + &image_native_buffer.usage2.consumer); + image_native_buffer.usage3 = img.buffer->usage; + image_create.pNext = &image_native_buffer; + + ATRACE_BEGIN("CreateImage"); + result = + dispatch.CreateImage(device, &image_create, nullptr, &img.image); + ATRACE_END(); + if (result != VK_SUCCESS) { + ALOGD("vkCreateImage w/ native buffer failed: %u", result); + break; + } } - } - // -- Cancel all buffers, returning them to the queue -- - // If an error occurred before, also destroy the VkImage and release the - // buffer reference. Otherwise, we retain a strong reference to the buffer. - for (uint32_t i = 0; i < num_images; i++) { - Swapchain::Image& img = swapchain->images[i]; - if (img.dequeued) { - if (!swapchain->shared) { - window->cancelBuffer(window, img.buffer.get(), - img.dequeue_fence); - img.dequeue_fence = -1; - img.dequeued = false; + // -- Cancel all buffers, returning them to the queue -- + // If an error occurred before, also destroy the VkImage and release the + // buffer reference. Otherwise, we retain a strong reference to the buffer. + for (uint32_t i = 0; i < num_images; i++) { + Swapchain::Image& img = swapchain->images[i]; + if (img.dequeued) { + if (!swapchain->shared) { + window->cancelBuffer(window, img.buffer.get(), + img.dequeue_fence); + img.dequeue_fence = -1; + img.dequeued = false; + } } } } @@ -1756,6 +1865,64 @@ VkResult AcquireNextImageKHR(VkDevice device, break; } } + + // If this is a deferred alloc swapchain, this may be the first time we've + // seen a particular buffer. If so, there should be an empty slot. Find it, + // and bind the gralloc buffer to the VkImage for that slot. If there is no + // empty slot, then we dequeued an unexpected buffer. Non-deferred swapchains + // will also take this path, but will never have an empty slot since we + // populated them all upfront. + if (idx == swapchain.num_images) { + for (idx = 0; idx < swapchain.num_images; idx++) { + if (!swapchain.images[idx].buffer) { + // Note: this structure is technically required for + // Vulkan correctness, even though the driver is probably going + // to use everything from the VkNativeBufferANDROID below. + // This is kindof silly, but it's how we did the ANB + // side of VK_KHR_swapchain v69, so we're stuck with it unless + // we want to go tinkering with the ANB spec some more. + VkBindImageMemorySwapchainInfoKHR bimsi = { + .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR, + .pNext = nullptr, + .swapchain = swapchain_handle, + .imageIndex = idx, + }; + VkNativeBufferANDROID nb = { + .sType = VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID, + .pNext = &bimsi, + .handle = buffer->handle, + .stride = buffer->stride, + .format = buffer->format, + .usage = int(buffer->usage), + }; + VkBindImageMemoryInfo bimi = { + .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO, + .pNext = &nb, + .image = swapchain.images[idx].image, + .memory = VK_NULL_HANDLE, + .memoryOffset = 0, + }; + result = GetData(device).driver.BindImageMemory2(device, 1, &bimi); + if (result != VK_SUCCESS) { + // This shouldn't really happen. If it does, something is probably + // unrecoverably wrong with the swapchain and its images. Cancel + // the buffer and declare the swapchain broken. + ALOGE("failed to do deferred gralloc buffer bind"); + window->cancelBuffer(window, buffer, fence_fd); + return VK_ERROR_OUT_OF_DATE_KHR; + } + + swapchain.images[idx].dequeued = true; + swapchain.images[idx].dequeue_fence = fence_fd; + swapchain.images[idx].buffer = buffer; + break; + } + } + } + + // The buffer doesn't match any slot. This shouldn't normally happen, but is + // possible if the bufferqueue is reconfigured behind libvulkan's back. If this + // happens, just declare the swapchain to be broken and the app will recreate it. if (idx == swapchain.num_images) { ALOGE("dequeueBuffer returned unrecognized buffer"); window->cancelBuffer(window, buffer, fence_fd); @@ -1881,12 +2048,32 @@ static void SetSwapchainFrameTimestamp(Swapchain &swapchain, const VkPresentTime } } +// EXT_swapchain_maintenance1 present mode change +static bool SetSwapchainPresentMode(ANativeWindow *window, VkPresentModeKHR mode) { + // There is no dynamic switching between non-shared present modes. + // All we support is switching between demand and continuous refresh. + if (!IsSharedPresentMode(mode)) + return true; + + int err = native_window_set_auto_refresh(window, + mode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR); + if (err != android::OK) { + ALOGE("native_window_set_auto_refresh() failed: %s (%d)", + strerror(-err), err); + return false; + } + + return true; +} + static VkResult PresentOneSwapchain( VkQueue queue, Swapchain& swapchain, uint32_t imageIndex, const VkPresentRegionKHR *pRegion, const VkPresentTimeGOOGLE *pTime, + VkFence presentFence, + const VkPresentModeKHR *pPresentMode, uint32_t waitSemaphoreCount, const VkSemaphore *pWaitSemaphores) { @@ -1917,12 +2104,33 @@ static VkResult PresentOneSwapchain( ANativeWindow* window = swapchain.surface.window.get(); if (swapchain_result == VK_SUCCESS) { + if (presentFence != VK_NULL_HANDLE) { + int fence_copy = fence < 0 ? -1 : dup(fence); + VkImportFenceFdInfoKHR iffi = { + VK_STRUCTURE_TYPE_IMPORT_FENCE_FD_INFO_KHR, + nullptr, + presentFence, + VK_FENCE_IMPORT_TEMPORARY_BIT, + VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT, + fence_copy, + }; + if (VK_SUCCESS != dispatch.ImportFenceFdKHR(device, &iffi) && fence_copy >= 0) { + // ImportFenceFdKHR takes ownership only if it succeeds + close(fence_copy); + } + } + if (pRegion) { SetSwapchainSurfaceDamage(window, pRegion); } if (pTime) { SetSwapchainFrameTimestamp(swapchain, pTime); } + if (pPresentMode) { + if (!SetSwapchainPresentMode(window, *pPresentMode)) + swapchain_result = WorstPresentResult(swapchain_result, + VK_ERROR_SURFACE_LOST_KHR); + } err = window->queueBuffer(window, img.buffer.get(), fence); // queueBuffer always closes fence, even on error @@ -2003,6 +2211,9 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { // Look at the pNext chain for supported extension structs: const VkPresentRegionsKHR* present_regions = nullptr; const VkPresentTimesInfoGOOGLE* present_times = nullptr; + const VkSwapchainPresentFenceInfoEXT* present_fences = nullptr; + const VkSwapchainPresentModeInfoEXT* present_modes = nullptr; + const VkPresentRegionsKHR* next = reinterpret_cast<const VkPresentRegionsKHR*>(present_info->pNext); while (next) { @@ -2014,6 +2225,14 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { present_times = reinterpret_cast<const VkPresentTimesInfoGOOGLE*>(next); break; + case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_FENCE_INFO_EXT: + present_fences = + reinterpret_cast<const VkSwapchainPresentFenceInfoEXT*>(next); + break; + case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT: + present_modes = + reinterpret_cast<const VkSwapchainPresentModeInfoEXT*>(next); + break; default: ALOGV("QueuePresentKHR ignoring unrecognized pNext->sType = %x", next->sType); @@ -2029,6 +2248,15 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { present_times->swapchainCount != present_info->swapchainCount, "VkPresentTimesInfoGOOGLE::swapchainCount != " "VkPresentInfo::swapchainCount"); + ALOGV_IF(present_fences && + present_fences->swapchainCount != present_info->swapchainCount, + "VkSwapchainPresentFenceInfoEXT::swapchainCount != " + "VkPresentInfo::swapchainCount"); + ALOGV_IF(present_modes && + present_modes->swapchainCount != present_info->swapchainCount, + "VkSwapchainPresentModeInfoEXT::swapchainCount != " + "VkPresentInfo::swapchainCount"); + const VkPresentRegionKHR* regions = (present_regions) ? present_regions->pRegions : nullptr; const VkPresentTimeGOOGLE* times = @@ -2044,6 +2272,8 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { present_info->pImageIndices[sc], (regions && !swapchain.mailbox_mode) ? ®ions[sc] : nullptr, times ? ×[sc] : nullptr, + present_fences ? present_fences->pFences[sc] : VK_NULL_HANDLE, + present_modes ? &present_modes->pPresentModes[sc] : nullptr, present_info->waitSemaphoreCount, present_info->pWaitSemaphores); @@ -2268,5 +2498,35 @@ VkResult BindImageMemory2KHR(VkDevice device, out_bind_infos.empty() ? pBindInfos : out_bind_infos.data()); } +VKAPI_ATTR +VkResult ReleaseSwapchainImagesEXT(VkDevice /*device*/, + const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) { + ATRACE_CALL(); + + Swapchain& swapchain = *SwapchainFromHandle(pReleaseInfo->swapchain); + ANativeWindow* window = swapchain.surface.window.get(); + + // If in shared present mode, don't actually release the image back to the BQ. + // Both sides share it forever. + if (swapchain.shared) + return VK_SUCCESS; + + for (uint32_t i = 0; i < pReleaseInfo->imageIndexCount; i++) { + Swapchain::Image& img = swapchain.images[pReleaseInfo->pImageIndices[i]]; + window->cancelBuffer(window, img.buffer.get(), img.dequeue_fence); + + // cancelBuffer has taken ownership of the dequeue fence + img.dequeue_fence = -1; + // if we're still holding a release fence, get rid of it now + if (img.release_fence >= 0) { + close(img.release_fence); + img.release_fence = -1; + } + img.dequeued = false; + } + + return VK_SUCCESS; +} + } // namespace driver } // namespace vulkan diff --git a/vulkan/libvulkan/swapchain.h b/vulkan/libvulkan/swapchain.h index 4912ef1a33..280fe9b5a3 100644 --- a/vulkan/libvulkan/swapchain.h +++ b/vulkan/libvulkan/swapchain.h @@ -46,6 +46,7 @@ VKAPI_ATTR VkResult GetPhysicalDeviceSurfaceCapabilities2KHR(VkPhysicalDevice ph VKAPI_ATTR VkResult GetPhysicalDeviceSurfaceFormats2KHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, uint32_t* pSurfaceFormatCount, VkSurfaceFormat2KHR* pSurfaceFormats); VKAPI_ATTR VkResult BindImageMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos); VKAPI_ATTR VkResult BindImageMemory2KHR(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos); +VKAPI_ATTR VkResult ReleaseSwapchainImagesEXT(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo); // clang-format on } // namespace driver diff --git a/vulkan/scripts/driver_generator.py b/vulkan/scripts/driver_generator.py index af56764a21..78b550c202 100644 --- a/vulkan/scripts/driver_generator.py +++ b/vulkan/scripts/driver_generator.py @@ -35,6 +35,8 @@ _INTERCEPTED_EXTENSIONS = [ 'VK_KHR_surface', 'VK_KHR_surface_protected_capabilities', 'VK_KHR_swapchain', + 'VK_EXT_swapchain_maintenance1', + 'VK_EXT_surface_maintenance1', ] # Extensions known to vulkan::driver level. @@ -46,6 +48,7 @@ _KNOWN_EXTENSIONS = _INTERCEPTED_EXTENSIONS + [ 'VK_KHR_external_memory_capabilities', 'VK_KHR_external_semaphore_capabilities', 'VK_KHR_external_fence_capabilities', + 'VK_KHR_external_fence_fd', ] # Functions needed at vulkan::driver level. @@ -112,6 +115,9 @@ _NEEDED_COMMANDS = [ # For promoted VK_KHR_external_fence_capabilities 'vkGetPhysicalDeviceExternalFenceProperties', 'vkGetPhysicalDeviceExternalFencePropertiesKHR', + + # VK_KHR_swapchain_maintenance1 requirement + 'vkImportFenceFdKHR', ] # Functions intercepted at vulkan::driver level. |