Skip to content

Commit

Permalink
Switch between compatible modes without swapchain recreation
Browse files Browse the repository at this point in the history
  • Loading branch information
ShabbyX committed Aug 1, 2023
1 parent 3b02c64 commit 6e28e2e
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 7 deletions.
119 changes: 112 additions & 7 deletions samples/api/swapchain_recreation/swapchain_recreation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
}

Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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)
{
Expand Down Expand Up @@ -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<uint32_t>(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;

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();
Expand All @@ -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 = &current_present_mode;

fence_info.pNext = &present_mode;
}
}

VkResult result = vkQueuePresentKHR(queue->get_handle(), &present);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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;
}
Expand Down
10 changes: 10 additions & 0 deletions samples/api/swapchain_recreation/swapchain_recreation.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ class SwapchainRecreation : public vkb::VulkanSample
/// Surface data.
VkSurfaceFormatKHR surface_format = {};
std::vector<VkPresentModeKHR> present_modes = {};
std::vector<VkPresentModeKHR> compatible_modes = {};
VkExtent2D swapchain_extents = {};

/// The swapchain.
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 6e28e2e

Please sign in to comment.