Add SwapchainManager library

This library is a utility which tracks all the required details so that applications
can have a simplified interface to the swapchain.

* Creation - Pass a SwapchainBuilder to specify how the swapchain should be created.
* Recreation - Recreates the swapchain upon request.
* Semaphores - Handles creation and usage of them, a particularly thorny part of swapchain management.

Additional Libraries:
 - DelayedDeletionQueue; for easy deletion of objects currently in flight
 - ImagelessFramebufferBuilder; for easy creation of imageless framebuffers

The new code lives in VkSwapchainManager.h, so its easier to distinguish the core parts of vk-bootstrap
from the optional.
This commit is contained in:
Charles Giessen 2022-10-07 12:19:29 -06:00
parent 01de71738c
commit 6150e408ee
6 changed files with 1461 additions and 436 deletions

View File

@ -7,6 +7,8 @@ target_link_libraries(vk-bootstrap-triangle
vk-bootstrap-vulkan-headers)
target_include_directories(vk-bootstrap-triangle PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) #path to build directory for shaders
target_compile_features(vk-bootstrap-triangle PUBLIC cxx_std_17)
add_custom_command(
TARGET vk-bootstrap-triangle
POST_BUILD

View File

@ -4,114 +4,223 @@
#include <iostream>
#include <fstream>
#include <string>
#include <thread>
#include <chrono>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <cmath>
#include "../tests/common.h"
#include "example_config.h"
const int MAX_FRAMES_IN_FLIGHT = 2;
const size_t MAX_FRAMES_IN_FLIGHT = 2; // number of command buffers and fences
std::atomic_bool is_running;
std::atomic_bool should_resize;
struct Init {
std::mutex main_mutex;
std::mutex render_wait_mutex;
std::condition_variable render_wait_condition_variable;
const bool run_multithreaded = true;
const bool use_refresh_callback = true; // should be true for WindowsOS
const bool use_validation_layer = false; // enabling layers can cause some stuttering
const VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR;
// options are:
// VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR
struct Renderer {
GLFWwindow* window;
VulkanLibrary vk_lib;
vkb::Instance instance;
VkSurfaceKHR surface;
vkb::PhysicalDevice physical_device;
vkb::Device device;
vkb::Swapchain swapchain;
//convenience
VulkanLibrary* operator->(){ return &vk_lib; }
};
vkb::DispatchTable dispatch;
struct RenderData {
VkQueue graphics_queue;
VkQueue present_queue;
std::vector<VkImage> swapchain_images;
std::vector<VkImageView> swapchain_image_views;
std::vector<VkFramebuffer> framebuffers;
vkb::SwapchainManager swapchain_manager;
vkb::SwapchainInfo swap_info;
vkb::DeletionQueue delete_queue;
VkRenderPass render_pass;
VkFramebuffer framebuffer;
VkPipelineLayout pipeline_layout;
VkPipeline graphics_pipeline;
VkCommandPool command_pool;
std::vector<VkCommandBuffer> command_buffers;
std::array<VkCommandBuffer, MAX_FRAMES_IN_FLIGHT> command_buffers;
std::array<VkFence, MAX_FRAMES_IN_FLIGHT> fences;
uint32_t current_index = 0;
std::vector<VkSemaphore> available_semaphores;
std::vector<VkSemaphore> finished_semaphore;
std::vector<VkFence> in_flight_fences;
std::vector<VkFence> image_in_flight;
size_t current_frame = 0;
double current_time = 0;
};
int device_initialization (Init& init) {
init.window = create_window_glfw ("Vulkan Triangle", true);
enum class RecreateSwapchainRet { success, fail };
RecreateSwapchainRet recreate_swapchain(Renderer& renderer);
enum class DrawFrameRet { success, fail, out_of_date };
DrawFrameRet draw_frame(Renderer& renderer);
void glfw_resize_callback(GLFWwindow* window, int width, int height) {
if (!is_running || width == 0 || height == 0) {
return;
}
should_resize = true;
bool should_notify = true;
std::unique_lock<std::mutex> lg(main_mutex);
Renderer* renderer = reinterpret_cast<Renderer*>(glfwGetWindowUserPointer(window));
auto res = recreate_swapchain(*renderer);
if (res == RecreateSwapchainRet::fail) {
is_running = false;
return;
}
should_resize = false; // makes draw_frame exit early instead of submitting.
if (!use_refresh_callback) {
switch (draw_frame(*renderer)) {
case (DrawFrameRet::success):
break;
case (DrawFrameRet::out_of_date): {
should_resize = true;
should_notify = false;
break;
}
default:
case (DrawFrameRet::fail):
is_running = false;
break;
}
}
lg.unlock();
if (!use_refresh_callback) {
if (should_notify) {
render_wait_condition_variable.notify_one();
}
}
}
void glfw_refresh_callback(GLFWwindow* window) {
bool should_notify = false;
std::unique_lock<std::mutex> lg(main_mutex, std::try_to_lock);
if (lg.owns_lock()) {
if (!should_resize) {
should_notify = true;
Renderer* renderer = reinterpret_cast<Renderer*>(glfwGetWindowUserPointer(window));
switch (draw_frame(*renderer)) {
case (DrawFrameRet::success):
break;
case (DrawFrameRet::out_of_date): {
should_resize = true;
should_notify = false;
break;
}
default:
case (DrawFrameRet::fail):
is_running = false;
should_notify = false;
break;
}
}
lg.unlock();
}
if (should_notify) {
render_wait_condition_variable.notify_one();
}
}
inline VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void*) {
auto ms = vkb::to_string_message_severity(messageSeverity);
auto mt = vkb::to_string_message_type(messageType);
std::cerr << "[" << ms << ": " << mt << "]\n" << pCallbackData->pMessage << "\n";
return VK_FALSE; // Applications must return false here
}
int instance_initialization(Renderer& renderer) {
renderer.window = create_window_glfw("Vulkan Triangle", true);
glfwSetWindowUserPointer(renderer.window, &renderer);
vkb::InstanceBuilder instance_builder;
auto instance_ret = instance_builder.use_default_debug_messenger ().request_validation_layers ().build ();
auto instance_ret = instance_builder.request_validation_layers(use_validation_layer)
.require_api_version(1, 2)
.set_debug_callback(debug_callback)
.build();
if (!instance_ret) {
std::cout << instance_ret.error ().message () << "\n";
std::cout << instance_ret.error().message() << "\n";
return -1;
}
init.instance = instance_ret.value ();
renderer.instance = instance_ret.value();
init.vk_lib.init(init.instance);
glfwSetWindowSizeCallback(renderer.window, glfw_resize_callback);
if (use_refresh_callback) glfwSetWindowRefreshCallback(renderer.window, glfw_refresh_callback);
init.surface = create_surface_glfw (init.instance, init.window);
renderer.surface = create_surface_glfw(renderer.instance.instance, renderer.window);
VkPhysicalDeviceVulkan12Features features_1_2{};
features_1_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
features_1_2.imagelessFramebuffer = true;
vkb::PhysicalDeviceSelector phys_device_selector (init.instance);
auto phys_device_ret = phys_device_selector.set_surface (init.surface).select ();
vkb::PhysicalDeviceSelector phys_device_selector(renderer.instance);
auto phys_device_ret = phys_device_selector.set_surface(renderer.surface).set_required_features_12(features_1_2).select();
if (!phys_device_ret) {
std::cout << phys_device_ret.error ().message () << "\n";
std::cout << phys_device_ret.error().message() << "\n";
return -1;
}
vkb::PhysicalDevice physical_device = phys_device_ret.value ();
renderer.physical_device = phys_device_ret.value();
return 0;
}
vkb::DeviceBuilder device_builder{ physical_device };
auto device_ret = device_builder.build ();
int device_initialization(Renderer& renderer) {
vkb::DeviceBuilder device_builder{ renderer.physical_device };
auto device_ret = device_builder.build();
if (!device_ret) {
std::cout << device_ret.error ().message () << "\n";
std::cout << device_ret.error().message() << "\n";
return -1;
}
init.device = device_ret.value ();
init.vk_lib.init(init.device);
renderer.device = device_ret.value();
renderer.dispatch = renderer.device.make_table();
renderer.delete_queue = vkb::DeletionQueue(renderer.device.device, MAX_FRAMES_IN_FLIGHT);
auto swapchain_manager_ret = vkb::SwapchainManager::create(
vkb::SwapchainBuilder{ renderer.device }.set_desired_present_mode(present_mode).set_desired_extent(default_window_width, default_window_height));
if (!swapchain_manager_ret) {
std::cout << swapchain_manager_ret.error().message() << "\n";
return -1;
}
renderer.swapchain_manager = std::move(swapchain_manager_ret.value());
renderer.swap_info = renderer.swapchain_manager.get_info().value();
return 0;
}
int create_swapchain (Init& init) {
vkb::SwapchainBuilder swapchain_builder{ init.device };
auto swap_ret = swapchain_builder.set_old_swapchain (init.swapchain).build ();
if (!swap_ret) {
std::cout << swap_ret.error ().message () << " " << swap_ret.vk_result () << "\n";
return -1;
int get_queues(Renderer& renderer) {
auto gq = renderer.device.get_queue(vkb::QueueType::graphics);
if (!gq.has_value()) {
std::cout << "failed to get graphics queue: " << gq.error().message() << "\n";
return -1;
}
vkb::destroy_swapchain(init.swapchain);
init.swapchain = swap_ret.value ();
renderer.graphics_queue = gq.value();
auto pq = renderer.device.get_queue(vkb::QueueType::present);
if (!pq.has_value()) {
std::cout << "failed to get present queue: " << pq.error().message() << "\n";
return -1;
}
renderer.present_queue = pq.value();
return 0;
}
int get_queues (Init& init, RenderData& data) {
auto gq = init.device.get_queue (vkb::QueueType::graphics);
if (!gq.has_value ()) {
std::cout << "failed to get graphics queue: " << gq.error ().message () << "\n";
return -1;
}
data.graphics_queue = gq.value ();
auto pq = init.device.get_queue (vkb::QueueType::present);
if (!pq.has_value ()) {
std::cout << "failed to get present queue: " << pq.error ().message () << "\n";
return -1;
}
data.present_queue = pq.value ();
return 0;
}
int create_render_pass (Init& init, RenderData& data) {
int create_render_pass(Renderer& renderer) {
VkAttachmentDescription color_attachment = {};
color_attachment.format = init.swapchain.image_format;
color_attachment.format = renderer.swap_info.image_format;
color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
@ -146,51 +255,63 @@ int create_render_pass (Init& init, RenderData& data) {
render_pass_info.dependencyCount = 1;
render_pass_info.pDependencies = &dependency;
if (init->vkCreateRenderPass (init.device, &render_pass_info, nullptr, &data.render_pass) != VK_SUCCESS) {
if (renderer.dispatch.createRenderPass(&render_pass_info, nullptr, &renderer.render_pass) != VK_SUCCESS) {
std::cout << "failed to create render pass\n";
return -1; // failed to create render pass!
}
return 0;
}
std::vector<char> readFile (const std::string& filename) {
std::ifstream file (filename, std::ios::ate | std::ios::binary);
int create_framebuffer(Renderer& renderer) {
if (!file.is_open ()) {
throw std::runtime_error ("failed to open file!");
vkb::ImagelessFramebufferBuilder if_builder(renderer.device);
renderer.framebuffer = if_builder.set_renderpass(renderer.render_pass)
.set_extent(renderer.swap_info.extent)
.set_layers(1)
.add_attachment(renderer.swap_info.image_usage_flags, renderer.swap_info.image_format)
.build();
return 0;
}
std::vector<char> readFile(const std::string& filename) {
std::ifstream file(filename, std::ios::ate | std::ios::binary);
if (!file.is_open()) {
throw std::runtime_error("failed to open file!");
}
size_t file_size = (size_t)file.tellg ();
std::vector<char> buffer (file_size);
size_t file_size = (size_t)file.tellg();
std::vector<char> buffer(file_size);
file.seekg (0);
file.read (buffer.data (), static_cast<std::streamsize> (file_size));
file.seekg(0);
file.read(buffer.data(), static_cast<std::streamsize>(file_size));
file.close ();
file.close();
return buffer;
}
VkShaderModule createShaderModule (Init& init, const std::vector<char>& code) {
VkShaderModule createShaderModule(Renderer& renderer, const std::vector<char>& code) {
VkShaderModuleCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
create_info.codeSize = code.size ();
create_info.pCode = reinterpret_cast<const uint32_t*> (code.data ());
create_info.codeSize = code.size();
create_info.pCode = reinterpret_cast<const uint32_t*>(code.data());
VkShaderModule shaderModule;
if (init->vkCreateShaderModule (init.device, &create_info, nullptr, &shaderModule) != VK_SUCCESS) {
if (renderer.dispatch.createShaderModule(&create_info, nullptr, &shaderModule) != VK_SUCCESS) {
return VK_NULL_HANDLE; // failed to create shader module
}
return shaderModule;
}
int create_graphics_pipeline (Init& init, RenderData& data) {
int create_graphics_pipeline(Renderer& renderer) {
auto vert_code = readFile(std::string(EXAMPLE_BUILD_DIRECTORY) + "/vert.spv");
auto frag_code = readFile(std::string(EXAMPLE_BUILD_DIRECTORY) + "/frag.spv");
VkShaderModule vert_module = createShaderModule (init, vert_code);
VkShaderModule frag_module = createShaderModule (init, frag_code);
VkShaderModule vert_module = createShaderModule(renderer, vert_code);
VkShaderModule frag_module = createShaderModule(renderer, frag_code);
if (vert_module == VK_NULL_HANDLE || frag_module == VK_NULL_HANDLE) {
std::cout << "failed to create shader module\n";
return -1; // failed to create shader modules
@ -223,14 +344,14 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
VkViewport viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float)init.swapchain.extent.width;
viewport.height = (float)init.swapchain.extent.height;
viewport.width = (float)renderer.swap_info.extent.width;
viewport.height = (float)renderer.swap_info.extent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor = {};
scissor.offset = { 0, 0 };
scissor.extent = init.swapchain.extent;
scissor.extent = renderer.swap_info.extent;
VkPipelineViewportStateCreateInfo viewport_state = {};
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
@ -255,8 +376,8 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.colorWriteMask =
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_FALSE;
VkPipelineColorBlendStateCreateInfo color_blending = {};
@ -275,8 +396,7 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
pipeline_layout_info.setLayoutCount = 0;
pipeline_layout_info.pushConstantRangeCount = 0;
if (init->vkCreatePipelineLayout (
init.device, &pipeline_layout_info, nullptr, &data.pipeline_layout) != VK_SUCCESS) {
if (renderer.dispatch.createPipelineLayout(&pipeline_layout_info, nullptr, &renderer.pipeline_layout) != VK_SUCCESS) {
std::cout << "failed to create pipeline layout\n";
return -1; // failed to create pipeline layout
}
@ -285,8 +405,8 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
VkPipelineDynamicStateCreateInfo dynamic_info = {};
dynamic_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamic_info.dynamicStateCount = static_cast<uint32_t> (dynamic_states.size ());
dynamic_info.pDynamicStates = dynamic_states.data ();
dynamic_info.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
dynamic_info.pDynamicStates = dynamic_states.data();
VkGraphicsPipelineCreateInfo pipeline_info = {};
pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
@ -299,283 +419,268 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
pipeline_info.pMultisampleState = &multisampling;
pipeline_info.pColorBlendState = &color_blending;
pipeline_info.pDynamicState = &dynamic_info;
pipeline_info.layout = data.pipeline_layout;
pipeline_info.renderPass = data.render_pass;
pipeline_info.layout = renderer.pipeline_layout;
pipeline_info.renderPass = renderer.render_pass;
pipeline_info.subpass = 0;
pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
if (init->vkCreateGraphicsPipelines (
init.device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &data.graphics_pipeline) != VK_SUCCESS) {
if (renderer.dispatch.createGraphicsPipelines(VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &renderer.graphics_pipeline) != VK_SUCCESS) {
std::cout << "failed to create pipline\n";
return -1; // failed to create graphics pipeline
}
init->vkDestroyShaderModule (init.device, frag_module, nullptr);
init->vkDestroyShaderModule (init.device, vert_module, nullptr);
renderer.dispatch.destroyShaderModule(frag_module, nullptr);
renderer.dispatch.destroyShaderModule(vert_module, nullptr);
return 0;
}
int create_framebuffers (Init& init, RenderData& data) {
data.swapchain_images = init.swapchain.get_images ().value ();
data.swapchain_image_views = init.swapchain.get_image_views ().value ();
data.framebuffers.resize (data.swapchain_image_views.size ());
for (size_t i = 0; i < data.swapchain_image_views.size (); i++) {
VkImageView attachments[] = { data.swapchain_image_views[i] };
VkFramebufferCreateInfo framebuffer_info = {};
framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebuffer_info.renderPass = data.render_pass;
framebuffer_info.attachmentCount = 1;
framebuffer_info.pAttachments = attachments;
framebuffer_info.width = init.swapchain.extent.width;
framebuffer_info.height = init.swapchain.extent.height;
framebuffer_info.layers = 1;
if (init->vkCreateFramebuffer (init.device, &framebuffer_info, nullptr, &data.framebuffers[i]) != VK_SUCCESS) {
return -1; // failed to create framebuffer
}
}
return 0;
}
int create_command_pool (Init& init, RenderData& data) {
VkCommandPoolCreateInfo pool_info = {};
int create_command_buffers(Renderer& renderer) {
VkCommandPoolCreateInfo pool_info{};
pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
pool_info.queueFamilyIndex = init.device.get_queue_index (vkb::QueueType::graphics).value ();
if (init->vkCreateCommandPool (init.device, &pool_info, nullptr, &data.command_pool) != VK_SUCCESS) {
pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
pool_info.queueFamilyIndex = renderer.device.get_queue_index(vkb::QueueType::graphics).value();
if (renderer.dispatch.createCommandPool(&pool_info, nullptr, &renderer.command_pool) != VK_SUCCESS) {
std::cout << "failed to create command pool\n";
return -1; // failed to create command pool
}
return 0;
}
int create_command_buffers (Init& init, RenderData& data) {
data.command_buffers.resize (data.framebuffers.size ());
VkCommandBufferAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = data.command_pool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = (uint32_t)data.command_buffers.size ();
if (init->vkAllocateCommandBuffers (init.device, &allocInfo, data.command_buffers.data ()) != VK_SUCCESS) {
return -1; // failed to allocate command buffers;
return -1;
}
for (size_t i = 0; i < data.command_buffers.size (); i++) {
VkCommandBufferBeginInfo begin_info = {};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (init->vkBeginCommandBuffer (data.command_buffers[i], &begin_info) != VK_SUCCESS) {
return -1; // failed to begin recording command buffer
}
VkRenderPassBeginInfo render_pass_info = {};
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_info.renderPass = data.render_pass;
render_pass_info.framebuffer = data.framebuffers[i];
render_pass_info.renderArea.offset = { 0, 0 };
render_pass_info.renderArea.extent = init.swapchain.extent;
VkClearValue clearColor{ { { 0.0f, 0.0f, 0.0f, 1.0f } } };
render_pass_info.clearValueCount = 1;
render_pass_info.pClearValues = &clearColor;
VkViewport viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float)init.swapchain.extent.width;
viewport.height = (float)init.swapchain.extent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor = {};
scissor.offset = { 0, 0 };
scissor.extent = init.swapchain.extent;
init->vkCmdSetViewport (data.command_buffers[i], 0, 1, &viewport);
init->vkCmdSetScissor (data.command_buffers[i], 0, 1, &scissor);
init->vkCmdBeginRenderPass (data.command_buffers[i], &render_pass_info, VK_SUBPASS_CONTENTS_INLINE);
init->vkCmdBindPipeline (data.command_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, data.graphics_pipeline);
init->vkCmdDraw (data.command_buffers[i], 3, 1, 0, 0);
init->vkCmdEndRenderPass (data.command_buffers[i]);
if (init->vkEndCommandBuffer (data.command_buffers[i]) != VK_SUCCESS) {
std::cout << "failed to record command buffer\n";
return -1; // failed to record command buffer!
}
VkCommandBufferAllocateInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
info.commandPool = renderer.command_pool;
info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
info.commandBufferCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
if (renderer.dispatch.allocateCommandBuffers(&info, renderer.command_buffers.data()) != VK_SUCCESS) {
std::cout << "failed to allocate command buffers\n";
return -1;
}
return 0;
}
int create_sync_objects (Init& init, RenderData& data) {
data.available_semaphores.resize (MAX_FRAMES_IN_FLIGHT);
data.finished_semaphore.resize (MAX_FRAMES_IN_FLIGHT);
data.in_flight_fences.resize (MAX_FRAMES_IN_FLIGHT);
data.image_in_flight.resize (init.swapchain.image_count, VK_NULL_HANDLE);
VkSemaphoreCreateInfo semaphore_info = {};
semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkFenceCreateInfo fence_info = {};
VkFenceCreateInfo fence_info{};
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
if (init->vkCreateSemaphore (init.device, &semaphore_info, nullptr, &data.available_semaphores[i]) != VK_SUCCESS ||
init->vkCreateSemaphore (init.device, &semaphore_info, nullptr, &data.finished_semaphore[i]) != VK_SUCCESS ||
init->vkCreateFence (init.device, &fence_info, nullptr, &data.in_flight_fences[i]) != VK_SUCCESS) {
std::cout << "failed to create sync objects\n";
return -1; // failed to create synchronization objects for a frame
}
}
return 0;
}
int recreate_swapchain (Init& init, RenderData& data) {
init->vkDeviceWaitIdle (init.device);
init->vkDestroyCommandPool (init.device, data.command_pool, nullptr);
for (auto framebuffer : data.framebuffers) {
init->vkDestroyFramebuffer (init.device, framebuffer, nullptr);
}
init.swapchain.destroy_image_views (data.swapchain_image_views);
if (0 != create_swapchain (init)) return -1;
if (0 != create_framebuffers (init, data)) return -1;
if (0 != create_command_pool (init, data)) return -1;
if (0 != create_command_buffers (init, data)) return -1;
return 0;
}
int draw_frame (Init& init, RenderData& data) {
init->vkWaitForFences (init.device, 1, &data.in_flight_fences[data.current_frame], VK_TRUE, UINT64_MAX);
uint32_t image_index = 0;
VkResult result = init->vkAcquireNextImageKHR (init.device,
init.swapchain,
UINT64_MAX,
data.available_semaphores[data.current_frame],
VK_NULL_HANDLE,
&image_index);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
return recreate_swapchain (init, data);
} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
std::cout << "failed to acquire swapchain image. Error " << result << "\n";
return -1;
}
if (data.image_in_flight[image_index] != VK_NULL_HANDLE) {
init->vkWaitForFences (init.device, 1, &data.image_in_flight[image_index], VK_TRUE, UINT64_MAX);
}
data.image_in_flight[image_index] = data.in_flight_fences[data.current_frame];
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore wait_semaphores[] = { data.available_semaphores[data.current_frame] };
VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = wait_semaphores;
submitInfo.pWaitDstStageMask = wait_stages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &data.command_buffers[image_index];
VkSemaphore signal_semaphores[] = { data.finished_semaphore[data.current_frame] };
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signal_semaphores;
init->vkResetFences (init.device, 1, &data.in_flight_fences[data.current_frame]);
if (init->vkQueueSubmit (data.graphics_queue, 1, &submitInfo, data.in_flight_fences[data.current_frame]) != VK_SUCCESS) {
std::cout << "failed to submit draw command buffer\n";
return -1; //"failed to submit draw command buffer
}
VkPresentInfoKHR present_info = {};
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present_info.waitSemaphoreCount = 1;
present_info.pWaitSemaphores = signal_semaphores;
VkSwapchainKHR swapChains[] = { init.swapchain };
present_info.swapchainCount = 1;
present_info.pSwapchains = swapChains;
present_info.pImageIndices = &image_index;
result = init->vkQueuePresentKHR (data.present_queue, &present_info);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
return recreate_swapchain (init, data);
} else if (result != VK_SUCCESS) {
std::cout << "failed to present swapchain image\n";
return -1;
}
data.current_frame = (data.current_frame + 1) % MAX_FRAMES_IN_FLIGHT;
return 0;
}
void cleanup (Init& init, RenderData& data) {
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
init->vkDestroySemaphore (init.device, data.finished_semaphore[i], nullptr);
init->vkDestroySemaphore (init.device, data.available_semaphores[i], nullptr);
init->vkDestroyFence (init.device, data.in_flight_fences[i], nullptr);
}
init->vkDestroyCommandPool (init.device, data.command_pool, nullptr);
for (auto framebuffer : data.framebuffers) {
init->vkDestroyFramebuffer (init.device, framebuffer, nullptr);
}
init->vkDestroyPipeline (init.device, data.graphics_pipeline, nullptr);
init->vkDestroyPipelineLayout (init.device, data.pipeline_layout, nullptr);
init->vkDestroyRenderPass (init.device, data.render_pass, nullptr);
init.swapchain.destroy_image_views (data.swapchain_image_views);
vkb::destroy_swapchain (init.swapchain);
vkb::destroy_device (init.device);
vkb::destroy_surface(init.instance, init.surface);
vkb::destroy_instance (init.instance);
destroy_window_glfw (init.window);
}
int main () {
Init init;
RenderData render_data;
if (0 != device_initialization (init)) return -1;
if (0 != create_swapchain (init)) return -1;
if (0 != get_queues (init, render_data)) return -1;
if (0 != create_render_pass (init, render_data)) return -1;
if (0 != create_graphics_pipeline (init, render_data)) return -1;
if (0 != create_framebuffers (init, render_data)) return -1;
if (0 != create_command_pool (init, render_data)) return -1;
if (0 != create_command_buffers (init, render_data)) return -1;
if (0 != create_sync_objects (init, render_data)) return -1;
while (!glfwWindowShouldClose (init.window)) {
glfwPollEvents ();
int res = draw_frame (init, render_data);
if (res != 0) {
std::cout << "failed to draw frame \n";
for (auto& fence : renderer.fences) {
if (renderer.dispatch.createFence(&fence_info, nullptr, &fence) != VK_SUCCESS) {
std::cout << "failed to create fence\n";
return -1;
}
}
init->vkDeviceWaitIdle (init.device);
cleanup (init, render_data);
return 0;
}
}
int record_command_buffer(Renderer& renderer, VkCommandBuffer command_buffer, VkImageView image_view) {
VkCommandBufferBeginInfo begin_info = {};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (renderer.dispatch.beginCommandBuffer(command_buffer, &begin_info) != VK_SUCCESS) {
std::cout << "failed to begin recording command buffer\n";
return -1; // failed to begin recording command buffer
}
VkRenderPassAttachmentBeginInfo attach_begin_info{};
attach_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_ATTACHMENT_BEGIN_INFO;
attach_begin_info.attachmentCount = 1;
attach_begin_info.pAttachments = &image_view;
VkRenderPassBeginInfo render_pass_info = {};
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_info.pNext = &attach_begin_info;
render_pass_info.renderPass = renderer.render_pass;
render_pass_info.framebuffer = renderer.framebuffer;
render_pass_info.renderArea.offset = { 0, 0 };
render_pass_info.renderArea.extent = renderer.swap_info.extent;
float x = static_cast<float>(std::sin(renderer.current_time * 1.5) * 0.5 + 0.5);
float z = static_cast<float>(std::cos(renderer.current_time * 1.5) * 0.5 + 0.5);
VkClearValue clearColor{ { { x, 0.0f, z, 1.0f } } };
render_pass_info.clearValueCount = 1;
render_pass_info.pClearValues = &clearColor;
VkViewport viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float)renderer.swap_info.extent.width;
viewport.height = (float)renderer.swap_info.extent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor = {};
scissor.offset = { 0, 0 };
scissor.extent = renderer.swap_info.extent;
renderer.dispatch.cmdSetViewport(command_buffer, 0, 1, &viewport);
renderer.dispatch.cmdSetScissor(command_buffer, 0, 1, &scissor);
renderer.dispatch.cmdBeginRenderPass(command_buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE);
renderer.dispatch.cmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer.graphics_pipeline);
renderer.dispatch.cmdDraw(command_buffer, 3, 1, 0, 0);
renderer.dispatch.cmdEndRenderPass(command_buffer);
if (renderer.dispatch.endCommandBuffer(command_buffer) != VK_SUCCESS) {
std::cout << "failed to record command buffer\n";
return -1; // failed to record command buffer!
}
return 0;
}
RecreateSwapchainRet recreate_swapchain(Renderer& renderer) {
renderer.delete_queue.add_framebuffer(renderer.framebuffer);
renderer.framebuffer = VK_NULL_HANDLE;
auto ret = renderer.swapchain_manager.recreate();
if (!ret) {
std::cout << "failed to recreate swapchain\n";
return RecreateSwapchainRet::fail;
}
renderer.swap_info = ret.value();
if (0 != create_framebuffer(renderer)) return RecreateSwapchainRet::fail;
return RecreateSwapchainRet::success;
}
DrawFrameRet draw_frame(Renderer& renderer) {
vkb::SwapchainAcquireInfo acquire_info;
auto acquire_ret = renderer.swapchain_manager.acquire_image();
if (acquire_ret.matches_error(vkb::SwapchainManagerError::swapchain_out_of_date)) {
return DrawFrameRet::out_of_date;
} else if (!acquire_ret.has_value()) {
std::cout << "failed to acquire swapchain image\n";
return DrawFrameRet::fail;
}
acquire_info = acquire_ret.value();
if (should_resize) {
renderer.swapchain_manager.cancel_acquire_frame();
return DrawFrameRet::out_of_date;
}
renderer.dispatch.waitForFences(1, &renderer.fences[renderer.current_index], VK_TRUE, UINT64_MAX);
record_command_buffer(renderer, renderer.command_buffers[renderer.current_index], acquire_info.image_view);
VkSemaphore wait_semaphores[1] = { acquire_info.wait_semaphore };
VkSemaphore signal_semaphores[1] = { acquire_info.signal_semaphore };
VkPipelineStageFlags wait_stages[1] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
VkSubmitInfo submit_info = {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = wait_semaphores;
submit_info.pWaitDstStageMask = wait_stages;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &renderer.command_buffers[renderer.current_index];
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = signal_semaphores;
if (should_resize) {
renderer.swapchain_manager.cancel_acquire_frame();
return DrawFrameRet::out_of_date;
}
// only reset if we are going to submit, this prevents the issue where we reset the fence but try to wait on it again
renderer.dispatch.resetFences(1, &renderer.fences[renderer.current_index]);
if (renderer.dispatch.queueSubmit(renderer.graphics_queue, 1, &submit_info, renderer.fences[renderer.current_index]) != VK_SUCCESS) {
std::cout << "failed to submit command buffer\n";
return DrawFrameRet::fail;
}
renderer.current_index = (renderer.current_index + 1) % MAX_FRAMES_IN_FLIGHT;
// No need to cancel, if a resize has started, then present will bail
auto present_ret = renderer.swapchain_manager.present();
if (present_ret.matches_error(vkb::SwapchainManagerError::swapchain_out_of_date)) {
return DrawFrameRet::out_of_date;
} else if (!present_ret) {
std::cout << "failed to present swapchain image\n";
return DrawFrameRet::fail;
}
renderer.delete_queue.tick();
renderer.current_time = glfwGetTime();
return DrawFrameRet::success;
}
void cleanup(Renderer& renderer) {
renderer.dispatch.deviceWaitIdle();
for (auto& fence : renderer.fences) {
renderer.dispatch.destroyFence(fence, nullptr);
}
renderer.dispatch.destroyCommandPool(renderer.command_pool, nullptr);
renderer.dispatch.destroyPipeline(renderer.graphics_pipeline, nullptr);
renderer.dispatch.destroyPipelineLayout(renderer.pipeline_layout, nullptr);
renderer.dispatch.destroyFramebuffer(renderer.framebuffer, nullptr);
renderer.dispatch.destroyRenderPass(renderer.render_pass, nullptr);
renderer.delete_queue.destroy();
renderer.swapchain_manager.destroy();
vkb::destroy_device(renderer.device);
vkb::destroy_surface(renderer.instance, renderer.surface);
vkb::destroy_instance(renderer.instance);
destroy_window_glfw(renderer.window);
}
void render_loop(Renderer* renderer) {
while (is_running) {
std::unique_lock<std::mutex> lg(main_mutex, std::try_to_lock);
if (lg.owns_lock()) {
switch (draw_frame(*renderer)) {
case (DrawFrameRet::success):
break;
case (DrawFrameRet::out_of_date): {
lg.unlock();
std::unique_lock<std::mutex> ulg(render_wait_mutex);
render_wait_condition_variable.wait(ulg);
break;
}
default:
case (DrawFrameRet::fail):
is_running = false;
break;
}
} else {
std::unique_lock<std::mutex> ulg(render_wait_mutex);
render_wait_condition_variable.wait(ulg);
}
}
renderer->dispatch.deviceWaitIdle();
}
int main() {
is_running = false;
should_resize = false;
Renderer renderer;
if (0 != instance_initialization(renderer)) return -1;
if (0 != device_initialization(renderer)) return -1;
if (0 != get_queues(renderer)) return -1;
if (0 != create_render_pass(renderer)) return -1;
if (0 != create_framebuffer(renderer)) return -1;
if (0 != create_graphics_pipeline(renderer)) return -1;
if (0 != create_command_buffers(renderer)) return -1;
is_running = true;
renderer.current_time = glfwGetTime();
if (run_multithreaded) {
std::thread render_thread{ render_loop, &renderer };
while (!glfwWindowShouldClose(renderer.window) && is_running) {
glfwPollEvents();
glfwWaitEvents();
}
is_running = false;
render_wait_condition_variable.notify_one();
render_thread.join();
} else {
while (!glfwWindowShouldClose(renderer.window) && is_running) {
glfwPollEvents();
DrawFrameRet res = draw_frame(renderer);
if (res == DrawFrameRet::fail) {
is_running = false;
}
}
}
cleanup(renderer);
return 0;
}

View File

@ -15,6 +15,7 @@
*/
#include "VkBootstrap.h"
#include "VkSwapchainManager.h"
#include <cstring>
@ -30,9 +31,12 @@
#include <dlfcn.h>
#endif
#include <algorithm>
#include <mutex>
#include <algorithm>
#include <iostream>
namespace vkb {
namespace detail {
@ -370,6 +374,12 @@ struct SwapchainErrorCategory : std::error_category {
};
const SwapchainErrorCategory swapchain_error_category;
struct SwapchainManagerErrorCategory : std::error_category {
const char* name() const noexcept override { return "vkb_swapchain_manager"; }
std::string message(int err) const override { return to_string(static_cast<SwapchainManagerError>(err)); }
};
const SwapchainManagerErrorCategory swapchain_manager_error_category;
} // namespace detail
std::error_code make_error_code(InstanceError instance_error) {
@ -387,6 +397,10 @@ std::error_code make_error_code(DeviceError device_error) {
std::error_code make_error_code(SwapchainError swapchain_error) {
return { static_cast<int>(swapchain_error), detail::swapchain_error_category };
}
std::error_code make_error_code(SwapchainManagerError swapchain_manager_error) {
return { static_cast<int>(swapchain_manager_error), detail::swapchain_manager_error_category };
}
#define CASE_TO_STRING(CATEGORY, TYPE) \
case CATEGORY::TYPE: \
return #TYPE;
@ -446,6 +460,25 @@ const char* to_string(SwapchainError err) {
return "";
}
}
const char* to_string(SwapchainManagerError err) {
switch (err) {
CASE_TO_STRING(SwapchainManagerError, swapchain_suboptimal)
CASE_TO_STRING(SwapchainManagerError, swapchain_out_of_date)
CASE_TO_STRING(SwapchainManagerError, surface_lost)
CASE_TO_STRING(SwapchainManagerError, queue_submit_failed)
CASE_TO_STRING(SwapchainManagerError, must_call_acquire_image_first)
CASE_TO_STRING(SwapchainManagerError, acquire_next_image_error)
CASE_TO_STRING(SwapchainManagerError, queue_present_error)
CASE_TO_STRING(SwapchainManagerError, surface_handle_not_provided)
CASE_TO_STRING(SwapchainManagerError, failed_query_surface_support_details)
CASE_TO_STRING(SwapchainManagerError, failed_create_swapchain)
CASE_TO_STRING(SwapchainManagerError, failed_get_swapchain_images)
CASE_TO_STRING(SwapchainManagerError, failed_create_swapchain_image_views)
default:
return "";
}
}
#undef CASE_TO_STRING
Result<SystemInfo> SystemInfo::get_system_info() {
if (!detail::vulkan_functions().init_vulkan_funcs(nullptr)) {
@ -2017,4 +2050,560 @@ void SwapchainBuilder::add_desired_present_modes(std::vector<VkPresentModeKHR>&
modes.push_back(VK_PRESENT_MODE_MAILBOX_KHR);
modes.push_back(VK_PRESENT_MODE_FIFO_KHR);
}
// Imageless framebuffer builder
ImagelessFramebufferBuilder::ImagelessFramebufferBuilder(Device const& device) noexcept : device(device.device) {
fp_vkCreateFramebuffer =
reinterpret_cast<PFN_vkCreateFramebuffer>(device.fp_vkGetDeviceProcAddr(device.device, "vkCreateFramebuffer"));
fp_vkDestroyFramebuffer =
reinterpret_cast<PFN_vkDestroyFramebuffer>(device.fp_vkGetDeviceProcAddr(device.device, "vkDestroyFramebuffer"));
}
VkFramebuffer ImagelessFramebufferBuilder::build() noexcept {
std::vector<VkFramebufferAttachmentImageInfo> attachment_infos;
for (auto& attachment : _attachments) {
VkFramebufferAttachmentImageInfo attach_image_info{};
attach_image_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENT_IMAGE_INFO;
attach_image_info.usage = attachment.usage_flags;
attach_image_info.width = _width;
attach_image_info.height = _height;
attach_image_info.layerCount = _layers;
attach_image_info.viewFormatCount = static_cast<uint32_t>(attachment.formats.size());
attach_image_info.pViewFormats = attachment.formats.data();
attachment_infos.push_back(attach_image_info);
}
VkFramebufferAttachmentsCreateInfo attach_create_info{};
attach_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENTS_CREATE_INFO;
attach_create_info.attachmentImageInfoCount = static_cast<uint32_t>(attachment_infos.size());
attach_create_info.pAttachmentImageInfos = attachment_infos.data();
VkFramebufferCreateInfo framebuffer_info = {};
framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebuffer_info.pNext = &attach_create_info;
framebuffer_info.flags = VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT;
framebuffer_info.renderPass = _render_pass;
framebuffer_info.attachmentCount = static_cast<uint32_t>(attachment_infos.size());
framebuffer_info.width = _width;
framebuffer_info.height = _height;
framebuffer_info.layers = _layers;
VkFramebuffer framebuffer = VK_NULL_HANDLE;
if (fp_vkCreateFramebuffer(device, &framebuffer_info, nullptr, &framebuffer) != VK_SUCCESS) {
assert(false && "TODO");
}
return framebuffer;
}
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::set_renderpass(VkRenderPass render_pass) {
_render_pass = render_pass;
return *this;
}
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::set_width(uint32_t width) {
_width = width;
return *this;
}
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::set_height(uint32_t height) {
_height = height;
return *this;
}
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::set_extent(VkExtent2D extent) {
_width = extent.width;
_height = extent.height;
return *this;
}
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::set_layers(uint32_t layer_count) {
_layers = layer_count;
return *this;
}
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::add_attachment(VkImageUsageFlags usage_flags, VkFormat format) {
_attachments.push_back(Attachment{ usage_flags, std::vector<VkFormat>(1, format) });
return *this;
}
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::add_attachment(
VkImageUsageFlags usage_flags, std::vector<VkFormat> const& formats) {
_attachments.push_back(Attachment{ usage_flags, formats });
return *this;
}
DeletionQueue ::DeletionQueue(Device const& device, uint32_t deletion_delay) noexcept
: DeletionQueue(device.device, deletion_delay) {}
DeletionQueue::DeletionQueue(VkDevice device, uint32_t queue_depth) noexcept
: device(device), queue_depth(queue_depth) {
assert(queue_depth <= detail::MAX_SWAPCHAIN_IMAGE_COUNT &&
"queue_depth cannot exceed the max swapchain image count (8)");
detail::vulkan_functions().get_device_proc_addr(device, internal_table.fp_vkDestroyImage, "vkDestroyImage");
detail::vulkan_functions().get_device_proc_addr(device, internal_table.fp_vkDestroyImageView, "vkDestroyImageView");
detail::vulkan_functions().get_device_proc_addr(device, internal_table.fp_vkDestroyFramebuffer, "vkDestroyFramebuffer");
detail::vulkan_functions().get_device_proc_addr(device, internal_table.fp_vkDestroySwapchainKHR, "vkDestroySwapchainKHR");
for (uint32_t i = 0; i < queue_depth; i++) {
sets[i].images.reserve(10);
sets[i].views.reserve(10);
sets[i].framebuffers.reserve(10);
sets[i].swapchains.reserve(1);
}
}
DeletionQueue::~DeletionQueue() noexcept { destroy(); }
DeletionQueue::DeletionQueue(DeletionQueue&& other) noexcept {
device = other.device;
queue_depth = other.queue_depth;
current_index = other.current_index;
sets = std::move(other.sets);
internal_table = other.internal_table;
other.device = VK_NULL_HANDLE;
}
DeletionQueue& DeletionQueue::operator=(DeletionQueue&& other) noexcept {
destroy();
device = other.device;
queue_depth = other.queue_depth;
current_index = other.current_index;
sets = std::move(other.sets);
internal_table = other.internal_table;
other.device = VK_NULL_HANDLE;
return *this;
}
void DeletionQueue::add_image(VkImage image) noexcept { sets[current_index].images.push_back(image); }
void DeletionQueue::add_images(uint32_t images_count, const VkImage* images) noexcept {
for (size_t i = 0; i < images_count; i++) {
sets[current_index].images.push_back(images[i]);
}
}
void DeletionQueue::add_image_view(VkImageView image_view) noexcept { sets[current_index].views.push_back(image_view); }
void DeletionQueue::add_image_views(uint32_t image_view_count, const VkImageView* image_view) noexcept {
for (size_t i = 0; i < image_view_count; i++) {
sets[current_index].views.push_back(image_view[i]);
}
}
void DeletionQueue::add_framebuffer(VkFramebuffer framebuffer) noexcept {
sets[current_index].framebuffers.push_back(framebuffer);
}
void DeletionQueue::add_framebuffers(uint32_t framebuffer_count, const VkFramebuffer* framebuffers) noexcept {
for (size_t i = 0; i < framebuffer_count; i++) {
sets[current_index].framebuffers.push_back(framebuffers[i]);
}
}
void DeletionQueue::add_swapchain(VkSwapchainKHR swapchain) noexcept {
sets[current_index].swapchains.push_back(swapchain);
}
void DeletionQueue::clear_set(vkb::DeletionQueue::DelaySets& set) noexcept {
for (auto const& image : set.images)
internal_table.fp_vkDestroyImage(device, image, nullptr);
for (auto const& image_view : set.views)
internal_table.fp_vkDestroyImageView(device, image_view, nullptr);
for (auto const& framebuffer : set.framebuffers)
internal_table.fp_vkDestroyFramebuffer(device, framebuffer, nullptr);
for (auto const& swapchain : set.swapchains)
internal_table.fp_vkDestroySwapchainKHR(device, swapchain, nullptr);
set.images.clear();
set.views.clear();
set.framebuffers.clear();
set.swapchains.clear();
}
void DeletionQueue::tick() noexcept {
assert(queue_depth != 0 && "Must construct DeletionQueue with valid VkDevice and non-zero queue_depth!");
current_index = (current_index + 1) % queue_depth;
clear_set(sets[current_index]);
}
void DeletionQueue::destroy() noexcept {
if (device != VK_NULL_HANDLE) {
for (auto& set : sets) {
clear_set(set);
}
device = VK_NULL_HANDLE;
}
}
namespace detail {
// Swapchain Synchronization resources (semaphores, fences, & tracking)
SemaphoreManager::SemaphoreManager(VkDevice device, uint32_t image_count) noexcept : device(device) {
detail.swapchain_image_count = image_count;
detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkCreateSemaphore, "vkCreateSemaphore");
detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkDestroySemaphore, "vkDestroySemaphore");
VkSemaphoreCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
for (uint32_t i = 0; i < detail.swapchain_image_count; i++) {
detail.fp_vkCreateSemaphore(device, &info, nullptr, &detail.active_acquire_semaphores[i]);
detail.fp_vkCreateSemaphore(device, &info, nullptr, &detail.active_submit_semaphores[i]);
}
// Make sure there are some semaphores to use when needed
detail.idle_semaphores.resize(10);
for (auto& semaphore : detail.idle_semaphores) {
detail.fp_vkCreateSemaphore(device, &info, nullptr, &semaphore);
}
for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++)
detail.expired_semaphores[i].reserve(10);
detail.current_acquire_semaphore = get_fresh_semaphore();
}
SemaphoreManager::~SemaphoreManager() noexcept { destroy(); }
void SemaphoreManager::destroy() noexcept {
if (device != VK_NULL_HANDLE) {
for (uint32_t i = 0; i < detail.swapchain_image_count; i++) {
detail.fp_vkDestroySemaphore(device, detail.active_acquire_semaphores[i], nullptr);
detail.fp_vkDestroySemaphore(device, detail.active_submit_semaphores[i], nullptr);
}
for (auto& set : detail.expired_semaphores)
for (auto& semaphore : set)
detail.fp_vkDestroySemaphore(device, semaphore, nullptr);
for (auto& semaphore : detail.idle_semaphores)
detail.fp_vkDestroySemaphore(device, semaphore, nullptr);
detail.fp_vkDestroySemaphore(device, detail.current_acquire_semaphore, nullptr);
}
device = VK_NULL_HANDLE;
}
SemaphoreManager::SemaphoreManager(SemaphoreManager&& other) noexcept {
device = other.device;
detail = other.detail;
other.device = VK_NULL_HANDLE; // destroy only does stuff if device isn't null, therefore its a sentinel
}
SemaphoreManager& SemaphoreManager::operator=(SemaphoreManager&& other) noexcept {
destroy();
device = other.device;
detail = other.detail;
other.device = VK_NULL_HANDLE; // destroy only does stuff if device isn't null, therefore its a sentinel
return *this;
}
VkSemaphore SemaphoreManager::get_next_acquire_semaphore() noexcept { return detail.current_acquire_semaphore; }
void SemaphoreManager::update_current_semaphore_index(uint32_t index) noexcept {
detail.current_swapchain_index = index;
std::swap(detail.active_acquire_semaphores[index], detail.current_acquire_semaphore);
detail.in_use[index] = true;
detail.current_submit_index = (detail.current_submit_index + 1) % FRAMES_IN_FLIGHT;
detail.idle_semaphores.insert(detail.idle_semaphores.end(),
detail.expired_semaphores[detail.current_submit_index].begin(),
detail.expired_semaphores[detail.current_submit_index].end());
detail.expired_semaphores[detail.current_submit_index].clear();
}
VkSemaphore SemaphoreManager::get_acquire_semaphore() noexcept {
return detail.active_acquire_semaphores[detail.current_swapchain_index];
}
VkSemaphore SemaphoreManager::get_submit_semaphore() noexcept {
return detail.active_submit_semaphores[detail.current_swapchain_index];
}
void SemaphoreManager::recreate_swapchain_resources() noexcept {
for (uint32_t i = 0; i < detail.swapchain_image_count; i++) {
if (detail.in_use[i]) {
detail.expired_semaphores[detail.current_submit_index].push_back(detail.active_acquire_semaphores[i]);
detail.expired_semaphores[detail.current_submit_index].push_back(detail.active_submit_semaphores[i]);
detail.active_acquire_semaphores[i] = get_fresh_semaphore();
detail.active_submit_semaphores[i] = get_fresh_semaphore();
}
detail.in_use[i] = false;
}
detail.expired_semaphores[detail.current_submit_index].push_back(detail.current_acquire_semaphore);
detail.current_acquire_semaphore = get_fresh_semaphore();
detail.current_swapchain_index = detail::INDEX_MAX_VALUE;
}
VkSemaphore SemaphoreManager::get_fresh_semaphore() noexcept {
VkSemaphore semaphore{};
if (detail.idle_semaphores.size() > 1) {
semaphore = detail.idle_semaphores.back();
detail.idle_semaphores.pop_back();
} else {
VkSemaphoreCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
detail.fp_vkCreateSemaphore(device, &info, nullptr, &semaphore);
}
return semaphore;
}
Result<vkb::SwapchainManager> convert(Result<vkb::Swapchain> err) {
switch (err.error().value()) {
case (static_cast<int>(SwapchainError::surface_handle_not_provided)):
return { make_error_code(SwapchainManagerError::surface_handle_not_provided), err.vk_result() };
case (static_cast<int>(SwapchainError::failed_query_surface_support_details)):
return { make_error_code(SwapchainManagerError::failed_query_surface_support_details), err.vk_result() };
case (static_cast<int>(SwapchainError::failed_create_swapchain)):
return { make_error_code(SwapchainManagerError::failed_create_swapchain), err.vk_result() };
case (static_cast<int>(SwapchainError::failed_get_swapchain_images)):
return { make_error_code(SwapchainManagerError::failed_get_swapchain_images), err.vk_result() };
case (static_cast<int>(SwapchainError::failed_create_swapchain_image_views)):
return { make_error_code(SwapchainManagerError::failed_create_swapchain_image_views), err.vk_result() };
default:
assert(false && "Should never reach this");
return { make_error_code(SwapchainManagerError::surface_handle_not_provided) };
}
}
} // namespace detail
Result<SwapchainManager> SwapchainManager::create(SwapchainBuilder const& builder) noexcept {
PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR;
PFN_vkCreateImageView fp_vkCreateImageView;
detail::vulkan_functions().get_device_proc_addr(builder.info.device, fp_vkGetSwapchainImagesKHR, "vkGetSwapchainImagesKHR");
detail::vulkan_functions().get_device_proc_addr(builder.info.device, fp_vkCreateImageView, "vkCreateImageView");
auto swapchain_ret = builder.build();
if (!swapchain_ret.has_value()) {
return detail::convert(swapchain_ret);
}
auto swapchain_resources_ret = SwapchainManager::create_swapchain_resources(
swapchain_ret.value(), swapchain_ret.value().instance_version, fp_vkGetSwapchainImagesKHR, fp_vkCreateImageView);
if (!swapchain_resources_ret.has_value()) {
return swapchain_resources_ret.error();
}
return SwapchainManager(
builder, swapchain_ret.value(), swapchain_resources_ret.value(), fp_vkGetSwapchainImagesKHR, fp_vkCreateImageView);
}
SwapchainManager::SwapchainManager(SwapchainBuilder const& builder,
vkb::Swapchain swapchain,
SwapchainResources resources,
PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR,
PFN_vkCreateImageView fp_vkCreateImageView) noexcept
: device(builder.info.device),
detail({ builder,
swapchain,
resources,
detail::SemaphoreManager(device, swapchain.image_count),
DeletionQueue(device, swapchain.image_count),
swapchain.instance_version }) {
detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkGetDeviceQueue, "vkGetDeviceQueue");
detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkAcquireNextImageKHR, "vkAcquireNextImageKHR");
detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkQueuePresentKHR, "vkQueuePresentKHR");
detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkQueueSubmit, "vkQueueSubmit");
detail.fp_vkGetSwapchainImagesKHR = fp_vkGetSwapchainImagesKHR;
detail.fp_vkCreateImageView = fp_vkCreateImageView;
detail.fp_vkGetDeviceQueue(device, detail.builder.info.graphics_queue_index, 0, &detail.graphics_queue);
detail.fp_vkGetDeviceQueue(device, detail.builder.info.present_queue_index, 0, &detail.present_queue);
update_swapchain_info();
}
void SwapchainManager::destroy() noexcept {
if (device != VK_NULL_HANDLE) {
detail.semaphore_manager = detail::SemaphoreManager{};
detail.delete_queue.add_swapchain(detail.swapchain_resources.swapchain);
detail.delete_queue.add_image_views(
detail.current_swapchain.image_count, &detail.swapchain_resources.image_views.front());
detail.delete_queue.destroy();
device = VK_NULL_HANDLE;
}
}
SwapchainManager::~SwapchainManager() noexcept { destroy(); }
SwapchainManager::SwapchainManager(SwapchainManager&& other) noexcept
: device(other.device), detail(std::move(other.detail)) {
other.device = VK_NULL_HANDLE;
}
SwapchainManager& SwapchainManager::operator=(SwapchainManager&& other) noexcept {
destroy();
device = other.device;
detail = std::move(other.detail);
other.device = VK_NULL_HANDLE;
return *this;
}
SwapchainBuilder& SwapchainManager::get_builder() noexcept {
assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!");
return detail.builder;
}
Result<SwapchainInfo> SwapchainManager::get_info() noexcept {
assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!");
if (detail.current_status == Status::expired) {
return make_error_code(SwapchainManagerError::swapchain_out_of_date);
}
return detail.current_info;
}
Result<SwapchainResources> SwapchainManager::get_swapchain_resources() noexcept {
assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!");
if (detail.current_status == Status::expired) {
return make_error_code(SwapchainManagerError::swapchain_out_of_date);
}
return detail.swapchain_resources;
}
Result<SwapchainAcquireInfo> SwapchainManager::acquire_image() noexcept {
assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!");
if (detail.current_status == Status::expired) {
return make_error_code(SwapchainManagerError::swapchain_out_of_date);
} else if (detail.current_status == Status::ready_to_present) {
// dont do anything
SwapchainAcquireInfo out{};
out.image_view = detail.swapchain_resources.image_views[detail.current_image_index];
out.image_index = detail.current_image_index;
out.wait_semaphore = detail.semaphore_manager.get_acquire_semaphore();
out.signal_semaphore = detail.semaphore_manager.get_submit_semaphore();
return out;
}
// reset the current image index in case acquiring fails
detail.current_image_index = detail::INDEX_MAX_VALUE;
VkSemaphore acquire_semaphore = detail.semaphore_manager.get_next_acquire_semaphore();
VkResult result = detail.fp_vkAcquireNextImageKHR(
device, detail.swapchain_resources.swapchain, UINT64_MAX, acquire_semaphore, VK_NULL_HANDLE, &detail.current_image_index);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
detail.current_status = Status::expired;
return { make_error_code(SwapchainManagerError::swapchain_out_of_date) };
} else if (result == VK_SUBOPTIMAL_KHR) {
} else if (result == VK_ERROR_SURFACE_LOST_KHR) {
return { make_error_code(SwapchainManagerError::surface_lost) };
} else if (result != VK_SUCCESS) {
return { make_error_code(SwapchainManagerError::acquire_next_image_error), result };
}
detail.semaphore_manager.update_current_semaphore_index(detail.current_image_index);
detail.current_status = Status::ready_to_present;
SwapchainAcquireInfo out{};
out.image_view = detail.swapchain_resources.image_views[detail.current_image_index];
out.image_index = detail.current_image_index;
out.wait_semaphore = detail.semaphore_manager.get_acquire_semaphore();
out.signal_semaphore = detail.semaphore_manager.get_submit_semaphore();
return out;
}
Result<detail::void_t> SwapchainManager::present() noexcept {
assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!");
if (detail.current_status == Status::expired) {
return make_error_code(SwapchainManagerError::swapchain_out_of_date);
}
if (detail.current_status == Status::ready_to_acquire) {
return make_error_code(SwapchainManagerError::must_call_acquire_image_first);
}
VkSemaphore wait_semaphores[1] = { detail.semaphore_manager.get_submit_semaphore() };
VkPresentInfoKHR present_info = {};
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present_info.waitSemaphoreCount = 1;
present_info.pWaitSemaphores = wait_semaphores;
present_info.swapchainCount = 1;
present_info.pSwapchains = &detail.swapchain_resources.swapchain;
present_info.pImageIndices = &detail.current_image_index;
VkResult result = detail.fp_vkQueuePresentKHR(detail.present_queue, &present_info);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
detail.current_status = Status::expired;
return { make_error_code(SwapchainManagerError::swapchain_out_of_date) };
} else if (result == VK_SUBOPTIMAL_KHR) {
} else if (result == VK_ERROR_SURFACE_LOST_KHR) {
return { make_error_code(SwapchainManagerError::surface_lost) };
} else if (result != VK_SUCCESS) {
return { make_error_code(SwapchainManagerError::queue_present_error), result };
} else {
detail.current_status = Status::ready_to_acquire;
}
// clean up old swapchain resources
detail.delete_queue.tick();
return detail::void_t{};
}
Result<detail::void_t> SwapchainManager::cancel_acquire_frame() noexcept {
VkResult result = WaitForSemaphore(detail.semaphore_manager.get_acquire_semaphore());
if (result != VK_SUCCESS) {
return { make_error_code(SwapchainManagerError::queue_submit_failed), result };
}
detail.current_status = Status::ready_to_acquire;
return detail::void_t{};
}
Result<detail::void_t> SwapchainManager::cancel_present_frame() noexcept {
VkResult result = WaitForSemaphore(detail.semaphore_manager.get_submit_semaphore());
if (result != VK_SUCCESS) {
return { make_error_code(SwapchainManagerError::queue_submit_failed), result };
}
detail.current_status = Status::ready_to_acquire;
return detail::void_t{};
}
Result<SwapchainInfo> SwapchainManager::recreate(uint32_t width, uint32_t height) noexcept {
assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!");
auto old_resources = detail.swapchain_resources;
detail.swapchain_resources = {};
detail.delete_queue.add_swapchain(old_resources.swapchain);
detail.delete_queue.add_image_views(old_resources.image_count, &old_resources.image_views.front());
auto new_swapchain_ret = detail.builder.set_old_swapchain(old_resources.swapchain).set_desired_extent(width, height).build();
if (!new_swapchain_ret) {
return new_swapchain_ret.error();
}
detail.current_swapchain = new_swapchain_ret.value();
auto new_resources_ret = SwapchainManager::create_swapchain_resources(
detail.current_swapchain, detail.instance_version, detail.fp_vkGetSwapchainImagesKHR, detail.fp_vkCreateImageView);
if (!new_resources_ret) {
return new_resources_ret.error();
}
detail.swapchain_resources = new_resources_ret.value();
detail.semaphore_manager.recreate_swapchain_resources();
update_swapchain_info();
detail.current_status = Status::ready_to_acquire;
return detail.current_info;
}
void SwapchainManager::update_swapchain_info() noexcept {
detail.current_info.image_count = detail.current_swapchain.image_count;
detail.current_info.image_format = detail.current_swapchain.image_format;
detail.current_info.extent = detail.current_swapchain.extent;
detail.current_info.image_usage_flags = detail.current_swapchain.image_usage_flags;
}
Result<SwapchainResources> SwapchainManager::create_swapchain_resources(vkb::Swapchain swapchain,
uint32_t instance_version,
PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR,
PFN_vkCreateImageView fp_vkCreateImageView) noexcept {
SwapchainResources out{};
out.swapchain = swapchain.swapchain;
out.image_count = swapchain.image_count;
assert(out.image_count <= detail::MAX_SWAPCHAIN_IMAGE_COUNT);
VkResult result = fp_vkGetSwapchainImagesKHR(swapchain.device, swapchain.swapchain, &out.image_count, nullptr);
if (result != VK_SUCCESS) return { make_error_code(SwapchainManagerError::failed_get_swapchain_images), result };
result = fp_vkGetSwapchainImagesKHR(swapchain.device, swapchain.swapchain, &out.image_count, out.images.data());
if (result != VK_SUCCESS) return { make_error_code(SwapchainManagerError::failed_get_swapchain_images), result };
#if defined(VK_VERSION_1_1)
VkImageViewUsageCreateInfo desired_flags{};
desired_flags.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO;
desired_flags.usage = swapchain.image_usage_flags;
#endif
for (size_t i = 0; i < out.image_count; i++) {
VkImageViewCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
createInfo.image = out.images[i];
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
createInfo.format = swapchain.image_format;
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
createInfo.subresourceRange.baseMipLevel = 0;
createInfo.subresourceRange.levelCount = 1;
createInfo.subresourceRange.baseArrayLayer = 0;
createInfo.subresourceRange.layerCount = 1;
#if defined(VK_VERSION_1_1)
if (instance_version >= VKB_VK_API_VERSION_1_1) {
createInfo.pNext = &desired_flags;
}
#endif
result = fp_vkCreateImageView(swapchain.device, &createInfo, nullptr, &out.image_views[i]);
if (result != VK_SUCCESS)
return { make_error_code(SwapchainManagerError::failed_create_swapchain_image_views), result };
}
return out;
}
VkResult SwapchainManager::WaitForSemaphore(VkSemaphore semaphore) noexcept {
VkPipelineStageFlags wait_stages[1] = { VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT };
VkSubmitInfo submit_info = {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &semaphore;
submit_info.pWaitDstStageMask = wait_stages;
return detail.fp_vkQueueSubmit(detail.graphics_queue, 1, &submit_info, VK_NULL_HANDLE);
}
} // namespace vkb

View File

@ -20,6 +20,7 @@
#include <cstdio>
#include <cstring>
#include <array>
#include <vector>
#include <string>
#include <system_error>
@ -214,12 +215,28 @@ enum class SwapchainError {
failed_create_swapchain_image_views,
required_min_image_count_too_low,
};
enum class SwapchainManagerError {
swapchain_suboptimal,
swapchain_out_of_date,
surface_lost,
queue_submit_failed,
must_call_acquire_image_first,
acquire_next_image_error,
queue_present_error,
surface_handle_not_provided,
failed_query_surface_support_details,
failed_create_swapchain,
failed_get_swapchain_images,
failed_create_swapchain_image_views,
};
std::error_code make_error_code(InstanceError instance_error);
std::error_code make_error_code(PhysicalDeviceError physical_device_error);
std::error_code make_error_code(QueueError queue_error);
std::error_code make_error_code(DeviceError device_error);
std::error_code make_error_code(SwapchainError swapchain_error);
std::error_code make_error_code(SwapchainManagerError swapchain_error);
const char* to_string_message_severity(VkDebugUtilsMessageSeverityFlagBitsEXT s);
const char* to_string_message_type(VkDebugUtilsMessageTypeFlagsEXT s);
@ -229,6 +246,8 @@ const char* to_string(PhysicalDeviceError err);
const char* to_string(QueueError err);
const char* to_string(DeviceError err);
const char* to_string(SwapchainError err);
const char* to_string(SwapchainManagerError err);
// Gathers useful information about the available vulkan capabilities, like layers and instance
// extensions. Use this for enabling features conditionally, ie if you would like an extension but
@ -359,11 +378,11 @@ class InstanceBuilder {
// Prefer a vulkan instance API version. If the desired version isn't available, it will use the
// highest version available. Should be constructed with VK_MAKE_VERSION or VK_MAKE_API_VERSION.
[[deprecated("Use require_api_version + set_minimum_instance_version instead.")]]
InstanceBuilder& desire_api_version(uint32_t preferred_vulkan_version);
[[deprecated("Use require_api_version + set_minimum_instance_version instead.")]] InstanceBuilder&
desire_api_version(uint32_t preferred_vulkan_version);
// Prefer a vulkan instance API version. If the desired version isn't available, it will use the highest version available.
[[deprecated("Use require_api_version + set_minimum_instance_version instead.")]]
InstanceBuilder& desire_api_version(uint32_t major, uint32_t minor, uint32_t patch = 0);
[[deprecated("Use require_api_version + set_minimum_instance_version instead.")]] InstanceBuilder&
desire_api_version(uint32_t major, uint32_t minor, uint32_t patch = 0);
// Adds a layer to be enabled. Will fail to create an instance if the layer isn't available.
InstanceBuilder& enable_layer(const char* layer_name);
@ -581,8 +600,8 @@ class PhysicalDeviceSelector {
PhysicalDeviceSelector& add_desired_extensions(std::vector<const char*> extensions);
// Prefer a physical device that supports a (major, minor) version of vulkan.
[[deprecated("Use set_minimum_version + InstanceBuilder::require_api_version.")]]
PhysicalDeviceSelector& set_desired_version(uint32_t major, uint32_t minor);
[[deprecated("Use set_minimum_version + InstanceBuilder::require_api_version.")]] PhysicalDeviceSelector&
set_desired_version(uint32_t major, uint32_t minor);
// Require a physical device that supports a (major, minor) version of vulkan.
PhysicalDeviceSelector& set_minimum_version(uint32_t major, uint32_t minor);
@ -675,6 +694,8 @@ enum class QueueType { present, graphics, compute, transfer };
namespace detail {
// Sentinel value, used in implementation only
const uint32_t QUEUE_INDEX_MAX_VALUE = 65536;
const uint32_t INDEX_MAX_VALUE = 65536;
} // namespace detail
// ---- Device ---- //
@ -930,6 +951,10 @@ class SwapchainBuilder {
VkSwapchainKHR old_swapchain = VK_NULL_HANDLE;
VkAllocationCallbacks* allocation_callbacks = VK_NULL_HANDLE;
} info;
friend class SwapchainManager;
// To allow SwapchainManager to construct it 'emptily'
explicit SwapchainBuilder() = default;
};
} // namespace vkb
@ -941,4 +966,6 @@ template <> struct is_error_code_enum<vkb::PhysicalDeviceError> : true_type {};
template <> struct is_error_code_enum<vkb::QueueError> : true_type {};
template <> struct is_error_code_enum<vkb::DeviceError> : true_type {};
template <> struct is_error_code_enum<vkb::SwapchainError> : true_type {};
template <> struct is_error_code_enum<vkb::SwapchainManagerError> : true_type {};
} // namespace std

378
src/VkSwapchainManager.h Normal file
View File

@ -0,0 +1,378 @@
/*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the Software), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Copyright © 2022 Charles Giessen (charles@lunarg.com)
*/
#pragma once
#include "VkBootstrap.h"
namespace vkb {
namespace detail {
// constants used for DeletionQueue and SwapchainManager
const int FRAMES_IN_FLIGHT = 3;
const int MAX_SWAPCHAIN_IMAGE_COUNT = 8;
} // namespace detail
// ImagelessFramebufferBuilder
struct ImagelessFramebufferBuilder {
ImagelessFramebufferBuilder(Device const& device) noexcept;
VkFramebuffer build() noexcept;
ImagelessFramebufferBuilder& set_renderpass(VkRenderPass render_pass);
ImagelessFramebufferBuilder& set_width(uint32_t width);
ImagelessFramebufferBuilder& set_height(uint32_t height);
ImagelessFramebufferBuilder& set_extent(VkExtent2D extent);
ImagelessFramebufferBuilder& set_layers(uint32_t layer_count);
ImagelessFramebufferBuilder& add_attachment(VkImageUsageFlags usage_flags, VkFormat format);
ImagelessFramebufferBuilder& add_attachment(VkImageUsageFlags usage_flags, std::vector<VkFormat> const& formats);
private:
struct Attachment {
VkImageUsageFlags usage_flags;
std::vector<VkFormat> formats;
};
VkDevice device = VK_NULL_HANDLE;
PFN_vkCreateFramebuffer fp_vkCreateFramebuffer = nullptr;
PFN_vkDestroyFramebuffer fp_vkDestroyFramebuffer = nullptr;
VkRenderPass _render_pass = VK_NULL_HANDLE;
uint32_t _width = 0;
uint32_t _height = 0;
uint32_t _layers = 1;
std::vector<Attachment> _attachments;
};
/**
* DeletionQueue is a helper utility that manages deleting of VkImages, VkImageViews,
*VkFramebuffers, and VkSwapchains at a later time. It works by keeping a list of all submitted
*handles in separate sets, and when a set is old enough, the DeletionQueue will iterate and delete
*all of the handles.
*
* The `tick()` function is when the deletion happens as well as incrementing the internal frame
*counter. Call `tick()` once per frame, preferably at the end.
*
* DeletionQueue is *not* a generic deletion queue that can execute arbitrary code to delete
*objects. Thus it may not be suitable for all applications.
*
* To destroy the DeletionQueue, call `destroy()`. Additionally, `destroy()` will be called
*automatically in DeletionQueue's destructor.
**/
class DeletionQueue {
public:
// default constructor, must replace with non-default constructed DeletionQueue to be useable
explicit DeletionQueue() noexcept = default;
// Main Contstructor Parameters:
// VkDevice device - VkDevice to use in the DeletionQueue
// uint32_t deletion_delay - how many ticks objects elapse before an added handle is destroyed
// Ex: deletion_delay of 4 means that a VkImage added will be destroyed after 4 calls to `tick()`
explicit DeletionQueue(VkDevice device, uint32_t deletion_delay) noexcept;
explicit DeletionQueue(Device const& device, uint32_t deletion_delay) noexcept;
~DeletionQueue() noexcept;
DeletionQueue(DeletionQueue const& other) = delete;
DeletionQueue& operator=(DeletionQueue const& other) = delete;
DeletionQueue(DeletionQueue&& other) noexcept;
DeletionQueue& operator=(DeletionQueue&& other) noexcept;
// Add the corresponding handle or handles to be deleted in 'queue_depth' frames
void add_image(VkImage) noexcept;
void add_images(uint32_t images_count, const VkImage* images) noexcept;
void add_image_view(VkImageView image_view) noexcept;
void add_image_views(uint32_t image_view_count, const VkImageView* image_view) noexcept;
void add_framebuffer(VkFramebuffer framebuffer) noexcept;
void add_framebuffers(uint32_t framebuffer_count, const VkFramebuffer* framebuffers) noexcept;
void add_swapchain(VkSwapchainKHR swapchain) noexcept;
// Destroy the objects which have waited for `deletion_delay` ticks, then increment the current frame counter
void tick() noexcept;
// Destroys the DeletionQueue, including destroying all contained Vulkan handles. Make sure all contained handles are not in use.
void destroy() noexcept;
private:
struct DelaySets {
std::vector<VkImage> images;
std::vector<VkImageView> views;
std::vector<VkFramebuffer> framebuffers;
std::vector<VkSwapchainKHR> swapchains;
};
VkDevice device;
uint32_t queue_depth = 0;
uint32_t current_index = 0;
std::array<DelaySets, detail::MAX_SWAPCHAIN_IMAGE_COUNT> sets;
struct {
PFN_vkDestroyImage fp_vkDestroyImage;
PFN_vkDestroyImageView fp_vkDestroyImageView;
PFN_vkDestroyFramebuffer fp_vkDestroyFramebuffer;
PFN_vkDestroySwapchainKHR fp_vkDestroySwapchainKHR;
} internal_table;
void clear_set(vkb::DeletionQueue::DelaySets& set) noexcept;
};
/**
* The SwapchainManager is a utility class that handles the creation and recreation of the
* swapchain, the acquiring of swapchain image indices, the submission of command buffers which
* write to swapchain images, and the synchronization of the writing and presenting.
* */
namespace detail {
class SemaphoreManager {
public:
explicit SemaphoreManager() noexcept = default;
explicit SemaphoreManager(VkDevice device, uint32_t image_count) noexcept;
~SemaphoreManager() noexcept;
SemaphoreManager(SemaphoreManager const& other) = delete;
SemaphoreManager& operator=(SemaphoreManager const& other) = delete;
SemaphoreManager(SemaphoreManager&& other) noexcept;
SemaphoreManager& operator=(SemaphoreManager&& other) noexcept;
VkSemaphore get_next_acquire_semaphore() noexcept;
void update_current_semaphore_index(uint32_t index) noexcept;
VkSemaphore get_acquire_semaphore() noexcept;
VkSemaphore get_submit_semaphore() noexcept;
void recreate_swapchain_resources() noexcept;
private:
VkDevice device = VK_NULL_HANDLE;
struct Details {
uint32_t swapchain_image_count = 0;
uint32_t current_swapchain_index = detail::INDEX_MAX_VALUE;
std::array<bool, MAX_SWAPCHAIN_IMAGE_COUNT> in_use = {};
std::array<VkSemaphore, MAX_SWAPCHAIN_IMAGE_COUNT> active_acquire_semaphores{};
std::array<VkSemaphore, MAX_SWAPCHAIN_IMAGE_COUNT> active_submit_semaphores{};
uint32_t current_submit_index = 0;
std::array<std::vector<VkSemaphore>, FRAMES_IN_FLIGHT> expired_semaphores;
std::vector<VkSemaphore> idle_semaphores;
VkSemaphore current_acquire_semaphore = VK_NULL_HANDLE;
PFN_vkCreateSemaphore fp_vkCreateSemaphore;
PFN_vkDestroySemaphore fp_vkDestroySemaphore;
} detail;
void destroy() noexcept;
VkSemaphore get_fresh_semaphore() noexcept;
};
// Used to signal that there may be an error that must be checked but there is no value returned
struct void_t {};
} // namespace detail
// Descriptive information about the current swapchain.
// Contains: image count, image format, extent (width & height), image usage flags
struct SwapchainInfo {
uint32_t image_count = 0;
VkFormat image_format = VK_FORMAT_UNDEFINED;
VkExtent2D extent = { 0, 0 };
VkImageUsageFlags image_usage_flags = 0;
};
// Vulkan Handles associated with the current swapchain
// Contains: VkSwapchainKHR handle, image count, array of VkImages, array of VkImageViews
// Caution: The image and image view arrays have `image_count` image/image views in them.
// This is for performance reasons, that way there isn't an extra indirection.
struct SwapchainResources {
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
uint32_t image_count = 0;
std::array<VkImage, detail::MAX_SWAPCHAIN_IMAGE_COUNT> images{};
std::array<VkImageView, detail::MAX_SWAPCHAIN_IMAGE_COUNT> image_views{};
};
// Struct returned from SwapchainManager::acquire_image()
struct SwapchainAcquireInfo {
// image view to use this frame
VkImageView image_view{};
// index of the swapchain image to use this frame
uint32_t image_index = detail::INDEX_MAX_VALUE;
VkSemaphore signal_semaphore;
VkSemaphore wait_semaphore;
};
/**
* Swapchain Manager
*
* Creation:
* Call the static function `SwapchainManager::create(SwapchainBuilder const& builder)` and pass
* in a vkb::SwapchainBuilder. To configure the swapchain, use the member functions on the
* SwapchainBuilder before it is passed in. SwapchainManager will keep an internal
* copy of this builder. This function will also create a swapchain, so that the application can
* query the current extent, format, and usage flags of the created swapchain.
*
* Destruction:
* To destroy the SwapchainManager, call `destroy()`. Additionally, SwapchainManager will call
* `destroy()` automatically in its destructor.
*
* SYNCHRONIZATION GUARANTEES (or lack thereof):
* The SwapchainManager does NOT have internal synchronization. Thus applications must never call any
* of its functions from multiple threads at the same time.
*
* QUEUE SUBMISSION:
* The SwapchainManager uses the Presentation VkQueue. Thus, the user must guarantee that calling `present()`
* does not overlap with any other calls which use the present queue (which in most cases is the graphics queue).
*
* Suggested Vulkan feature to enable:
* Imageless Framebuffer - This feature makes it possible to create a framebuffer without a
* VkImageView by making it a parameter that is passed in at vkCmdBeginRenderPass time. It is
* recommended because it means one VkFrameBuffer is necessary for the entire swapchain, rather than
* the previous case of N framebuffers, one for each swapchain image. Vulkan 1.2 guarantees support
* for it.
*
* Example Initialization of SwapchainManager:
* {
* auto swapchain_manager_ret = vkb::SwapchainManager::create(vkb::SwapchainBuilder{ vk_device });
* if (!swapchain_manager_ret) {
* std::cout << swapchain_manager_ret.error().message() << "\n";
* return -1;
* }
* swapchain_manager = std::move(swapchain_manager_ret.value());
* swapchain_info = renderer.swapchain_manager.get_info();
* }
*
* Example Application Usage during main rendering:
* {
* auto acquire_ret = renderer.swapchain_manager.acquire_image();
* if (!acquire_ret.has_value() && acquire_ret.error().value() ==
*(int)vkb::SwapchainManagerError::swapchain_out_of_date) { should_recreate = true; return; //abort rendering this frame
* } else if (!acquire_ret.has_value()) {
* return; //error out
* }
*
* auto acquire_info = acquire_ret.value(); //holds the image view, image index, and semaphores needed for
*submission VkImageView image_view = acquire_info.image_view;
*
* // record command buffers that use image_view
*
* // The semaphore that was passed into vkAcquireNextImageKHR (by the swapchain manager)
* VkSemaphore wait_semaphores[1] = { acquire_info.wait_semaphore }; //add in any user declared semaphores
*
* // The semaphore that gets passed into vkQueuePresentKHR (by the swapchain manager)
* VkSemaphore signal_semaphores[1] = { acquire_info.signal_semaphore }; //add in any user declared semaphores
* VkPipelineStageFlags wait_stages[1] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
*
* VkSubmitInfo submit_info = {};
* submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
* submit_info.waitSemaphoreCount = 1;
* submit_info.pWaitSemaphores = wait_semaphores;
* submit_info.pWaitDstStageMask = wait_stages;
* submit_info.commandBufferCount = 1;
* submit_info.pCommandBuffers = &renderer.command_buffers[renderer.current_index];
* submit_info.signalSemaphoreCount = 1;
* submit_info.pSignalSemaphores = signal_semaphores;
*
* }
*
* Example Application Recreation
*
*
* The SwapchainManager interally keeps track of the current 'state' of the swapchain. These states are:
* - ready_to_acquire: initial state, indicates is is safe to call `acquire_image`
* - ready_to_present: set after acquiring, indicates it is ready to accept command buffers to submit.
* - expired: the swapchain needs to be recreated, calling `acquire_image` while in this state will fail
* - destroyed: the swapchain was destroyed (likely manually), prevents further usage of the SwapchainManager
* After a successful call to `acquire_image()`, the state is set to `ready_to_present`
* After a successful call to `present()`, the state is set back to `ready_to_present`
* After a successful call to `recreate()`, the state is set back to `ready_to_acquire`
* Note that a successful call indicates that the return value didn't produce an error *and* the `out_of_date` parameter
*in `SwapchainAcquireInfo` or `SwapchainSubmitInfo` is false.
*
* Type Characteristics:
* SwapchainManager contains a default constructor, allowing it to be created in null state and initialized at a later
*point. SwapchainManager is a move-only type, due to it owning various Vulkan objects. Therefore it will make any
*struct or class that has a SwapchainManager member into a move-only type.
*
* Informative Details - Semaphores:
* SwapchainManager keeps all semaphore management internal, thus they are not an aspect a user
* needs to be involved with. However, it is possible to add additional wait and signal semaphores
* in the call to vkQueueSubmit if so desired by passing in the semaphores into `submit`.
**/
class SwapchainManager {
public:
static Result<SwapchainManager> create(SwapchainBuilder const& builder) noexcept;
explicit SwapchainManager() = default;
~SwapchainManager() noexcept;
void destroy() noexcept;
SwapchainManager(SwapchainManager const& other) = delete;
SwapchainManager& operator=(SwapchainManager const& other) = delete;
SwapchainManager(SwapchainManager&& other) noexcept;
SwapchainManager& operator=(SwapchainManager&& other) noexcept;
// Primary API
// Get a VkImageView handle to use in rendering
Result<SwapchainAcquireInfo> acquire_image() noexcept;
Result<detail::void_t> cancel_acquire_frame() noexcept;
Result<detail::void_t> cancel_present_frame() noexcept;
Result<detail::void_t> present() noexcept;
// Recreate the swapchain, putting currently in-use internal resources in a delete queue
Result<SwapchainInfo> recreate(uint32_t width = 0, uint32_t height = 0) noexcept;
// Get info about the swapchain
Result<SwapchainInfo> get_info() noexcept;
// Get access to the swapchain and resources associated with it
Result<SwapchainResources> get_swapchain_resources() noexcept;
// Access the internal builder. This is how an application can alter how the swapchain is recreated.
SwapchainBuilder& get_builder() noexcept;
private:
enum class Status {
ready_to_acquire,
ready_to_present,
expired, // needs to be recreated
destroyed, // no longer usable
};
VkDevice device = VK_NULL_HANDLE;
struct Details {
vkb::SwapchainBuilder builder;
vkb::Swapchain current_swapchain;
SwapchainResources swapchain_resources;
detail::SemaphoreManager semaphore_manager;
DeletionQueue delete_queue;
uint32_t instance_version = VKB_VK_API_VERSION_1_0;
Status current_status = Status::ready_to_acquire;
VkQueue graphics_queue{};
VkQueue present_queue{};
SwapchainInfo current_info{};
uint32_t current_image_index = detail::INDEX_MAX_VALUE;
PFN_vkGetDeviceQueue fp_vkGetDeviceQueue{};
PFN_vkAcquireNextImageKHR fp_vkAcquireNextImageKHR{};
PFN_vkQueuePresentKHR fp_vkQueuePresentKHR{};
PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR{};
PFN_vkCreateImageView fp_vkCreateImageView{};
PFN_vkQueueSubmit fp_vkQueueSubmit{};
} detail;
explicit SwapchainManager(SwapchainBuilder const& builder,
Swapchain swapchain,
SwapchainResources resources,
PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR,
PFN_vkCreateImageView fp_vkCreateImageView) noexcept;
void update_swapchain_info() noexcept;
static Result<SwapchainResources> create_swapchain_resources(vkb::Swapchain swapchain,
uint32_t instance_version,
PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR,
PFN_vkCreateImageView fp_vkCreateImageView) noexcept;
VkResult WaitForSemaphore(VkSemaphore semaphore) noexcept;
};
} // namespace vkb

View File

@ -19,20 +19,23 @@
#include "GLFW/glfw3.h"
#include "../src/VkBootstrap.h"
#include "../src/VkSwapchainManager.h"
const int default_window_width = 512;
const int default_window_height = 512;
GLFWwindow* create_window_glfw(const char* window_name = "", bool resize = true) {
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
if (!resize) glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE, resize ? GLFW_TRUE : GLFW_FALSE);
return glfwCreateWindow(1024, 1024, window_name, NULL, NULL);
return glfwCreateWindow(default_window_width, default_window_height, window_name, NULL, NULL);
}
void destroy_window_glfw(GLFWwindow* window) {
glfwDestroyWindow(window);
glfwTerminate();
}
VkSurfaceKHR create_surface_glfw(
VkInstance instance, GLFWwindow* window, VkAllocationCallbacks* allocator = nullptr) {
VkSurfaceKHR create_surface_glfw(VkInstance instance, GLFWwindow* window, VkAllocationCallbacks* allocator = nullptr) {
VkSurfaceKHR surface = VK_NULL_HANDLE;
VkResult err = glfwCreateWindowSurface(instance, window, allocator, &surface);
if (err) {
@ -70,11 +73,9 @@ struct VulkanLibrary {
#endif
if (!library) return;
#if defined(__linux__) || defined(__APPLE__)
vkGetInstanceProcAddr =
reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(library, "vkGetInstanceProcAddr"));
vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(library, "vkGetInstanceProcAddr"));
#elif defined(_WIN32)
vkGetInstanceProcAddr =
reinterpret_cast<PFN_vkGetInstanceProcAddr>(GetProcAddress(library, "vkGetInstanceProcAddr"));
vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(GetProcAddress(library, "vkGetInstanceProcAddr"));
#endif
}
@ -87,82 +88,5 @@ struct VulkanLibrary {
library = 0;
}
void init(VkInstance instance) {
vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr(instance, "vkGetDeviceProcAddr");
vkDestroySurfaceKHR = (PFN_vkDestroySurfaceKHR)vkGetInstanceProcAddr(instance, "vkDestroySurfaceKHR");
}
void init(VkDevice device) {
vkCreateRenderPass = (PFN_vkCreateRenderPass)vkGetDeviceProcAddr(device, "vkCreateRenderPass");
vkCreateShaderModule = (PFN_vkCreateShaderModule)vkGetDeviceProcAddr(device, "vkCreateShaderModule");
vkCreatePipelineLayout =
(PFN_vkCreatePipelineLayout)vkGetDeviceProcAddr(device, "vkCreatePipelineLayout");
vkCreateGraphicsPipelines =
(PFN_vkCreateGraphicsPipelines)vkGetDeviceProcAddr(device, "vkCreateGraphicsPipelines");
vkDestroyShaderModule = (PFN_vkDestroyShaderModule)vkGetDeviceProcAddr(device, "vkDestroyShaderModule");
vkCreateFramebuffer = (PFN_vkCreateFramebuffer)vkGetDeviceProcAddr(device, "vkCreateFramebuffer");
vkCreateCommandPool = (PFN_vkCreateCommandPool)vkGetDeviceProcAddr(device, "vkCreateCommandPool");
vkAllocateCommandBuffers =
(PFN_vkAllocateCommandBuffers)vkGetDeviceProcAddr(device, "vkAllocateCommandBuffers");
vkBeginCommandBuffer = (PFN_vkBeginCommandBuffer)vkGetDeviceProcAddr(device, "vkBeginCommandBuffer");
vkEndCommandBuffer = (PFN_vkEndCommandBuffer)vkGetDeviceProcAddr(device, "vkEndCommandBuffer");
vkCmdSetViewport = (PFN_vkCmdSetViewport)vkGetDeviceProcAddr(device, "vkCmdSetViewport");
vkCmdSetScissor = (PFN_vkCmdSetScissor)vkGetDeviceProcAddr(device, "vkCmdSetScissor");
vkCmdBeginRenderPass = (PFN_vkCmdBeginRenderPass)vkGetDeviceProcAddr(device, "vkCmdBeginRenderPass");
vkCmdEndRenderPass = (PFN_vkCmdEndRenderPass)vkGetDeviceProcAddr(device, "vkCmdEndRenderPass");
vkCmdBindPipeline = (PFN_vkCmdBindPipeline)vkGetDeviceProcAddr(device, "vkCmdBindPipeline");
vkCmdDraw = (PFN_vkCmdDraw)vkGetDeviceProcAddr(device, "vkCmdDraw");
vkCreateSemaphore = (PFN_vkCreateSemaphore)vkGetDeviceProcAddr(device, "vkCreateSemaphore");
vkCreateFence = (PFN_vkCreateFence)vkGetDeviceProcAddr(device, "vkCreateFence");
vkDeviceWaitIdle = (PFN_vkDeviceWaitIdle)vkGetDeviceProcAddr(device, "vkDeviceWaitIdle");
vkDestroyCommandPool = (PFN_vkDestroyCommandPool)vkGetDeviceProcAddr(device, "vkDestroyCommandPool");
vkDestroyFramebuffer = (PFN_vkDestroyFramebuffer)vkGetDeviceProcAddr(device, "vkDestroyFramebuffer");
vkWaitForFences = (PFN_vkWaitForFences)vkGetDeviceProcAddr(device, "vkWaitForFences");
vkAcquireNextImageKHR = (PFN_vkAcquireNextImageKHR)vkGetDeviceProcAddr(device, "vkAcquireNextImageKHR");
vkResetFences = (PFN_vkResetFences)vkGetDeviceProcAddr(device, "vkResetFences");
vkQueueSubmit = (PFN_vkQueueSubmit)vkGetDeviceProcAddr(device, "vkQueueSubmit");
vkQueuePresentKHR = (PFN_vkQueuePresentKHR)vkGetDeviceProcAddr(device, "vkQueuePresentKHR");
vkDestroySemaphore = (PFN_vkDestroySemaphore)vkGetDeviceProcAddr(device, "vkDestroySemaphore");
vkDestroyFence = (PFN_vkDestroyFence)vkGetDeviceProcAddr(device, "vkDestroyFence");
vkDestroyPipeline = (PFN_vkDestroyPipeline)vkGetDeviceProcAddr(device, "vkDestroyPipeline");
vkDestroyPipelineLayout =
(PFN_vkDestroyPipelineLayout)vkGetDeviceProcAddr(device, "vkDestroyPipelineLayout");
vkDestroyRenderPass = (PFN_vkDestroyRenderPass)vkGetDeviceProcAddr(device, "vkDestroyRenderPass");
}
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = VK_NULL_HANDLE;
PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = VK_NULL_HANDLE;
PFN_vkCreateRenderPass vkCreateRenderPass = VK_NULL_HANDLE;
PFN_vkCreateShaderModule vkCreateShaderModule = VK_NULL_HANDLE;
PFN_vkCreatePipelineLayout vkCreatePipelineLayout = VK_NULL_HANDLE;
PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines = VK_NULL_HANDLE;
PFN_vkDestroyShaderModule vkDestroyShaderModule = VK_NULL_HANDLE;
PFN_vkCreateFramebuffer vkCreateFramebuffer = VK_NULL_HANDLE;
PFN_vkCreateCommandPool vkCreateCommandPool = VK_NULL_HANDLE;
PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers = VK_NULL_HANDLE;
PFN_vkBeginCommandBuffer vkBeginCommandBuffer = VK_NULL_HANDLE;
PFN_vkEndCommandBuffer vkEndCommandBuffer = VK_NULL_HANDLE;
PFN_vkCmdSetViewport vkCmdSetViewport = VK_NULL_HANDLE;
PFN_vkCmdSetScissor vkCmdSetScissor = VK_NULL_HANDLE;
PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass = VK_NULL_HANDLE;
PFN_vkCmdEndRenderPass vkCmdEndRenderPass = VK_NULL_HANDLE;
PFN_vkCmdBindPipeline vkCmdBindPipeline = VK_NULL_HANDLE;
PFN_vkCmdDraw vkCmdDraw = VK_NULL_HANDLE;
PFN_vkCreateSemaphore vkCreateSemaphore = VK_NULL_HANDLE;
PFN_vkCreateFence vkCreateFence = VK_NULL_HANDLE;
PFN_vkDeviceWaitIdle vkDeviceWaitIdle = VK_NULL_HANDLE;
PFN_vkDestroyCommandPool vkDestroyCommandPool = VK_NULL_HANDLE;
PFN_vkDestroyFramebuffer vkDestroyFramebuffer = VK_NULL_HANDLE;
PFN_vkWaitForFences vkWaitForFences = VK_NULL_HANDLE;
PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR = VK_NULL_HANDLE;
PFN_vkResetFences vkResetFences = VK_NULL_HANDLE;
PFN_vkQueueSubmit vkQueueSubmit = VK_NULL_HANDLE;
PFN_vkQueuePresentKHR vkQueuePresentKHR = VK_NULL_HANDLE;
PFN_vkDestroySemaphore vkDestroySemaphore = VK_NULL_HANDLE;
PFN_vkDestroyFence vkDestroyFence = VK_NULL_HANDLE;
PFN_vkDestroyPipeline vkDestroyPipeline = VK_NULL_HANDLE;
PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout = VK_NULL_HANDLE;
PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR = VK_NULL_HANDLE;
PFN_vkDestroyRenderPass vkDestroyRenderPass = VK_NULL_HANDLE;
};