Files
Vulkan-Samples/samples/api/swapchain_recreation/swapchain_recreation.h
T
2025-09-04 10:54:47 +08:00

217 lines
7.3 KiB
C++

/* Copyright (c) 2023-2025, Google
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "common/vk_common.h"
#include "platform/application.h"
#include "vulkan_sample.h"
/**
* @brief A sample that implements best practices in handling present resources and swapchain
* recreation, for example due to window resizing or present mode changes.
*/
class SwapchainRecreation : public vkb::VulkanSampleC
{
struct SwapchainObjects
{
std::vector<VkImage> images;
std::vector<VkImageView> views;
std::vector<VkFramebuffer> framebuffers;
};
/**
* @brief Per-frame data. This is not per swapchain image!
* A queue of this data structure is used to remember the history of submissions. To avoid
* the CPU getting too far ahead of the GPU, the sample paces itself by waiting for the
* submission before last to finish before recording commands for the new frame. This means
* that frame N+1 doesn't start recording until frame N-1 finishes executing on the GPU (and
* likely frame N starts). In a real application, this minimizes latency from input to
* screen.
*/
struct PerFrame
{
VkFence submit_fence = VK_NULL_HANDLE;
VkCommandPool command_pool = VK_NULL_HANDLE;
VkCommandBuffer command_buffer = VK_NULL_HANDLE;
VkSemaphore acquire_semaphore = VK_NULL_HANDLE;
VkSemaphore present_semaphore = VK_NULL_HANDLE;
// Garbage to clean up once the submit_fence is signaled, if any.
std::vector<SwapchainObjects> swapchain_garbage;
};
struct SwapchainCleanupData
{
/// The old swapchain to be destroyed.
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
/**
* @brief Any present semaphores that were pending recycle at the time the swapchain
* was recreated will be scheduled for recycling at the same time as the swapchain's
* destruction.
*/
std::vector<VkSemaphore> semaphores;
};
struct PresentOperationInfo
{
/**
* @brief Fence that tells when the present semaphore can be destroyed. Without
* VK_EXT_swapchain_maintenance1, the fence used with the vkAcquireNextImageKHR that
* returns the same image index in the future is used to know when the semaphore can
* be recycled.
*/
VkFence cleanup_fence = VK_NULL_HANDLE;
VkSemaphore present_semaphore = VK_NULL_HANDLE;
/**
* @brief Old swapchains are scheduled to be destroyed at the same time as the last
* wait semaphore used to present an image to the old swapchains can be recycled.
*/
std::vector<SwapchainCleanupData> old_swapchains;
/**
* @brief Used to associate an acquire fence with the previous present operation of
* the image. Only relevant when VK_EXT_swapchain_maintenance1 is not supported;
* otherwise a fence is always associated with the present operation.
*/
uint32_t image_index = std::numeric_limits<uint32_t>::max();
};
public:
SwapchainRecreation();
virtual ~SwapchainRecreation() override;
void create_render_context() override;
void prepare_render_context() override;
void update(float delta_time) override;
bool resize(uint32_t width, uint32_t height) override;
void input_event(const vkb::InputEvent &input_event) override;
private:
/// Submission and present queue.
const vkb::Queue *queue = nullptr;
/// Allow enabling VK_EXT_surface_maintenance1 and VK_EXT_swapchain_maintenance1.
///
/// Can be set to false by setting environment variable `USE_MAINTENANCE1=no`
bool allow_maintenance1 = true;
/// Whether the VK_EXT_surface_maintenance1 and VK_EXT_swapchain_maintenance1 extensions are
/// enabled.
bool has_maintenance1 = false;
/// Surface data.
VkSurfaceFormatKHR surface_format = {};
std::vector<VkPresentModeKHR> present_modes = {};
std::vector<VkPresentModeKHR> compatible_modes = {};
VkExtent2D swapchain_extents = {};
/// The swapchain.
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
/// Swapchain data.
VkPresentModeKHR current_present_mode = VK_PRESENT_MODE_FIFO_KHR;
VkPresentModeKHR desired_present_mode = VK_PRESENT_MODE_FIFO_KHR;
SwapchainObjects swapchain_objects;
/// The render pass used for rendering.
VkRenderPass render_pass = VK_NULL_HANDLE;
/// The submission history. This is a fixed-size queue, implemented as a circular buffer.
std::array<PerFrame, 2> submit_history = {};
size_t submit_history_index = 0;
/// The present operation history. This is used to clean up present semaphores and old swapchains.
std::deque<PresentOperationInfo> present_history;
/**
* @brief The previous swapchain which needs to be scheduled for destruction when
* appropriate. This will be done when the first image of the current swapchain is
* presented. If there were older swapchains pending destruction when the swapchain is
* recreated, they will accumulate and be destroyed with the previous swapchain.
*
* Note that if the user resizes the window such that the swapchain is recreated every
* frame, this array can go grow indefinitely.
*/
std::vector<SwapchainCleanupData> old_swapchains;
/// Resource pools.
std::vector<VkSemaphore> semaphore_pool;
std::vector<VkFence> fence_pool;
/// Time.
uint32_t frame_number = 0;
// FPS log.
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;
// from vkb::VulkanSample
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
std::unique_ptr<vkb::core::DeviceC> create_device(vkb::PhysicalDevice &gpu) override;
void get_queue();
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);
bool recreate_swapchain();
void setup_frame();
void render(uint32_t index);
VkResult acquire_next_image(uint32_t *index);
VkResult present_image(uint32_t index);
void add_present_to_history(uint32_t index, VkFence present_fence);
void cleanup_present_history();
void cleanup_present_info(PresentOperationInfo &present_info);
void cleanup_old_swapchain(SwapchainCleanupData &old_swapchain);
void associate_fence_with_present_history(uint32_t index, VkFence acquire_fence);
void schedule_old_swapchain_for_destruction(VkSwapchainKHR old_swapchain);
VkSemaphore get_semaphore();
void recycle_semaphore(VkSemaphore semaphore);
VkFence get_fence();
void recycle_fence(VkFence fence);
VkPhysicalDevice get_gpu_handle();
VkDevice get_device_handle();
};
std::unique_ptr<vkb::Application> create_swapchain_recreation();