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
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index 9ed992b..273cdd5 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -636,6 +636,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 de98aa7..798af5c 100644
--- a/vulkan/libvulkan/driver_gen.cpp
+++ b/vulkan/libvulkan/driver_gen.cpp
@@ -162,6 +162,15 @@
}
}
+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 @@
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 @@
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 @@
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 @@
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 2f60086..31ba04b 100644
--- a/vulkan/libvulkan/driver_gen.h
+++ b/vulkan/libvulkan/driver_gen.h
@@ -49,6 +49,8 @@
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 @@
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 @@
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 b03200e..1bff50d 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -291,6 +291,9 @@
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 @@
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;
+ // Implement in terms of GetPhysicalDeviceSurfaceCapabilities2KHR
+
+ VkPhysicalDeviceSurfaceInfo2KHR info2 = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR,
+ nullptr,
+ surface
+ };
+
+ VkSurfaceCapabilities2KHR caps2 = {
+ VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR,
+ nullptr,
+ {},
+ };
+
+ VkResult result = GetPhysicalDeviceSurfaceCapabilities2KHR(pdev, &info2, &caps2);
+ *capabilities = caps2.surfaceCapabilities;
+ return result;
+}
+
+// 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 {
- ANativeWindow* window = SurfaceFromHandle(surface)->window.get();
-
- 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);
+ *callerCount = things.size();
}
-
- 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;
-
- return VK_SUCCESS;
+ return result;
}
VKAPI_ATTR
@@ -882,21 +825,7 @@
// 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 @@
VkSurfaceCapabilities2KHR* pSurfaceCapabilities) {
ATRACE_CALL();
- VkResult result = GetPhysicalDeviceSurfaceCapabilitiesKHR(
- physicalDevice, pSurfaceInfo->surface,
- &pSurfaceCapabilities->surfaceCapabilities);
+ auto surface = pSurfaceInfo->surface;
+ auto capabilities = &pSurfaceCapabilities->surfaceCapabilities;
- VkSurfaceCapabilities2KHR* caps = pSurfaceCapabilities;
- while (caps->pNext) {
- caps = reinterpret_cast<VkSurfaceCapabilities2KHR*>(caps->pNext);
+ 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;
- switch (caps->sType) {
+ default:
+ break;
+ }
+ }
+
+ 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();
+
+ 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 @@
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 @@
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 @@
}
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 @@
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 @@
#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 @@
.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;
+ } else {
+ // -- Dequeue all buffers and create a VkImage for each --
+ // Any failures during or after this must cancel the dequeued buffers.
- 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;
+ for (uint32_t i = 0; i < num_images; i++) {
+ Swapchain::Image& img = swapchain->images[i];
- 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;
+ 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 @@
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 @@
}
}
+// 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 4912ef1..280fe9b 100644
--- a/vulkan/libvulkan/swapchain.h
+++ b/vulkan/libvulkan/swapchain.h
@@ -46,6 +46,7 @@
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 af56764..78b550c 100644
--- a/vulkan/scripts/driver_generator.py
+++ b/vulkan/scripts/driver_generator.py
@@ -35,6 +35,8 @@
'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 @@
'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 @@
# For promoted VK_KHR_external_fence_capabilities
'vkGetPhysicalDeviceExternalFenceProperties',
'vkGetPhysicalDeviceExternalFencePropertiesKHR',
+
+ # VK_KHR_swapchain_maintenance1 requirement
+ 'vkImportFenceFdKHR',
]
# Functions intercepted at vulkan::driver level.