From 6e28e2ea1e400a68b1dbfa3f198c86cbbb9680d2 Mon Sep 17 00:00:00 2001 From: Shahbaz Youssefi Date: Fri, 3 Feb 2023 16:47:14 -0500 Subject: [PATCH] Switch between compatible modes without swapchain recreation --- .../swapchain_recreation.cpp | 119 ++++++++++++++++-- .../swapchain_recreation.h | 10 ++ 2 files changed, 122 insertions(+), 7 deletions(-) diff --git a/samples/api/swapchain_recreation/swapchain_recreation.cpp b/samples/api/swapchain_recreation/swapchain_recreation.cpp index 5210ba1321..e94bcfb77f 100644 --- a/samples/api/swapchain_recreation/swapchain_recreation.cpp +++ b/samples/api/swapchain_recreation/swapchain_recreation.cpp @@ -36,7 +36,7 @@ void SwapchainRecreation::get_queue() if (!supports_present) { - throw std::runtime_error("Default graphics queue does not support preesnt."); + throw std::runtime_error("Default graphics queue does not support present."); } } @@ -51,6 +51,12 @@ void SwapchainRecreation::check_for_maintenance1() return; } + VkResult result = volkInitialize(); + if (result) + { + throw vkb::VulkanException(result, "Failed to initialize volk."); + } + // Check to see if VK_EXT_surface_maintenance1 is supported in the first place. // Assume that VK_EXT_swapchain_maintenance1 is also supported in that case. uint32_t instance_extension_count; @@ -112,6 +118,41 @@ void SwapchainRecreation::query_present_modes() adjust_desired_present_mode(); } +/** + * @brief Get the list of present modes compatible with the current mode. If present mode is + * changed and the two modes are compatible, swapchain is not recreated. + */ +void SwapchainRecreation::query_compatible_present_modes(VkPresentModeKHR present_mode) +{ + // If manually overriden, or if VK_EXT_surface_maintenance1 is not supported, assume no + // compatible present modes. + if (!has_maintenance1 || recreate_swapchain_on_present_mode_change) + { + compatible_modes.resize(1); + compatible_modes[0] = present_mode; + return; + } + + VkPhysicalDeviceSurfaceInfo2KHR surface_info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR}; + surface_info.surface = get_surface(); + + VkSurfacePresentModeEXT surface_present_mode{VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT}; + surface_present_mode.presentMode = present_mode; + surface_info.pNext = &surface_present_mode; + + VkSurfaceCapabilities2KHR surface_caps{VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR}; + + VkSurfacePresentModeCompatibilityEXT modes{VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_COMPATIBILITY_EXT}; + modes.presentModeCount = 0; + surface_caps.pNext = &modes; + + VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilities2KHR(get_gpu_handle(), &surface_info, &surface_caps)); + + compatible_modes.resize(modes.presentModeCount); + modes.pPresentModes = compatible_modes.data(); + VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilities2KHR(get_gpu_handle(), &surface_info, &surface_caps)); +} + void SwapchainRecreation::adjust_desired_present_mode() { // The FIFO present mode is guaranteed to be present. @@ -176,6 +217,18 @@ void SwapchainRecreation::create_render_pass() VK_CHECK(vkCreateRenderPass(get_device_handle(), &rp_info, nullptr, &render_pass)); } +bool SwapchainRecreation::are_present_modes_compatible() +{ + // Look in the list of compatible present modes (which was created for + // current_present_mode). If desired_present_mode is in that list, then there's no need to + // recreate the swapchain. Note that current_present_mode is in this list as well. + // + // While this functionality was introduced by VK_EXT_surface_maintenance1, compatible_modes + // is always set up such that every present mode is assumed to be compatible only with + // itself; there is no need for an extension check here. + return std::find(compatible_modes.begin(), compatible_modes.end(), desired_present_mode) != compatible_modes.end(); +} + /** * @brief Initializes the Vulkan swapchain. */ @@ -193,7 +246,7 @@ void SwapchainRecreation::init_swapchain() swapchain_extents = surface_properties.currentExtent; } - // Do tripple-buffering when possible. This is clamped to the min and max image count limits. + // Do triple-buffering when possible. This is clamped to the min and max image count limits. uint32_t desired_swapchain_images = std::max(surface_properties.minImageCount, 3u); if (surface_properties.maxImageCount > 0) { @@ -242,14 +295,29 @@ void SwapchainRecreation::init_swapchain() // be used instead. However, the application is required to handle preTransform elsewhere // accordingly. + query_compatible_present_modes(desired_present_mode); + + VkSwapchainPresentModesCreateInfoEXT compatible_modes_info{VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODES_CREATE_INFO_EXT}; if (has_maintenance1) { // When VK_EXT_swapchain_maintenance1 is available, you can optionally amortize the // cost of swapchain image allocations over multiple frames. info.flags |= VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT; + + // If there are multiple present modes that are compatible, give that list to create + // info. When switching present modes between compatible ones, swapchain doesn't + // need to be recreated. + if (compatible_modes.size() > 1) + { + compatible_modes_info.presentModeCount = static_cast(compatible_modes.size()); + compatible_modes_info.pPresentModes = compatible_modes.data(); + info.pNext = &compatible_modes_info; + } } + LOGI("Creating new swapchain"); VK_CHECK(vkCreateSwapchainKHR(get_device_handle(), &info, nullptr, &swapchain)); + ++swapchain_creation_count; current_present_mode = desired_present_mode; @@ -343,7 +411,7 @@ bool SwapchainRecreation::recreate_swapchain() // Only rebuild the swapchain if the dimensions have changed if (surface_properties.currentExtent.width == swapchain_extents.width && surface_properties.currentExtent.height == swapchain_extents.height && - desired_present_mode == current_present_mode) + are_present_modes_compatible()) { return false; } @@ -577,6 +645,7 @@ VkResult SwapchainRecreation::present_image(uint32_t index) // which is signaled when the resources associated with present operation can be freed. VkFence present_fence = VK_NULL_HANDLE; VkSwapchainPresentFenceInfoEXT fence_info{VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_FENCE_INFO_EXT}; + VkSwapchainPresentModeInfoEXT present_mode{VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT}; if (has_maintenance1) { present_fence = get_fence(); @@ -585,6 +654,21 @@ VkResult SwapchainRecreation::present_image(uint32_t index) fence_info.pFences = &present_fence; present.pNext = &fence_info; + + // If present mode has changed but the two modes are compatible, change the present + // mode at present time. + if (current_present_mode != desired_present_mode) + { + // Can't reach here if the modes are not compatible. + assert(are_present_modes_compatible()); + + current_present_mode = desired_present_mode; + + present_mode.swapchainCount = 1; + present_mode.pPresentModes = ¤t_present_mode; + + fence_info.pNext = &present_mode; + } } VkResult result = vkQueuePresentKHR(queue->get_handle(), &present); @@ -895,6 +979,7 @@ SwapchainRecreation::~SwapchainRecreation() cleanup_present_info(present_info); } + LOGI("During the lifetime of this sample, {} swapchains were created", swapchain_creation_count); LOGI("Old swapchain count at destruction: {}", old_swapchains.size()); for (SwapchainCleanupData &old_swapchain : old_swapchains) @@ -929,17 +1014,21 @@ SwapchainRecreation::~SwapchainRecreation() bool SwapchainRecreation::prepare(const vkb::ApplicationOptions &options) { + // Add the relevant instance extensions before creating the instance in + // VulkanSample::prepare. + check_for_maintenance1(); + if (!VulkanSample::prepare(options)) { return false; } - check_for_maintenance1(); - LOGI("------------------------------------"); LOGI("USAGE:"); - LOGI(" - Press v to enable v-sync"); + LOGI(" - Press v to enable v-sync (default)"); LOGI(" - Press n to disable v-sync"); + LOGI(" - Press p to enable switching between compatible present modes (default)"); + LOGI(" - Press r to disable switching between compatible present modes"); if (has_maintenance1) { LOGI("Set environment variable USE_MAINTENANCE1=no to avoid VK_EXT_surface_maintenance1"); @@ -976,7 +1065,7 @@ void SwapchainRecreation::update(float delta_time) setup_frame(); - if (desired_present_mode != current_present_mode) + if (!are_present_modes_compatible()) { recreate_swapchain(); } @@ -1050,6 +1139,22 @@ void SwapchainRecreation::input_event(const vkb::InputEvent &input_event) desired_present_mode = VK_PRESENT_MODE_MAILBOX_KHR; } break; + case vkb::KeyCode::P: + if (recreate_swapchain_on_present_mode_change) + { + LOGI("Switch between compatible present modes: Enabled"); + recreate_swapchain_on_present_mode_change = false; + compatible_modes.clear(); + } + break; + case vkb::KeyCode::R: + if (!recreate_swapchain_on_present_mode_change) + { + LOGI("Switch between compatible present modes: Disabled"); + recreate_swapchain_on_present_mode_change = true; + compatible_modes.clear(); + } + break; default: break; } diff --git a/samples/api/swapchain_recreation/swapchain_recreation.h b/samples/api/swapchain_recreation/swapchain_recreation.h index acf5a8dee3..1f85da4a8b 100644 --- a/samples/api/swapchain_recreation/swapchain_recreation.h +++ b/samples/api/swapchain_recreation/swapchain_recreation.h @@ -121,6 +121,7 @@ class SwapchainRecreation : public vkb::VulkanSample /// Surface data. VkSurfaceFormatKHR surface_format = {}; std::vector present_modes = {}; + std::vector compatible_modes = {}; VkExtent2D swapchain_extents = {}; /// The swapchain. @@ -163,13 +164,22 @@ class SwapchainRecreation : public vkb::VulkanSample float fps_timer = 0; uint32_t fps_last_logged_frame_number = 0; + // Other statistics + uint32_t swapchain_creation_count = 0; + + // User toggles. + bool recreate_swapchain_on_present_mode_change = false; + void get_queue(); void check_for_maintenance1(); void query_surface_format(); void query_present_modes(); + void query_compatible_present_modes(VkPresentModeKHR present_mode); void adjust_desired_present_mode(); void create_render_pass(); + bool are_present_modes_compatible(); + void init_swapchain(); void init_swapchain_image(uint32_t index); void cleanup_swapchain_objects(SwapchainObjects &garbage);