Added DelayedDeletionQueue and SwapchainManager to abstract presentation

The DelayedDeletionQueue is a general utility for queueing up things to delete at a future time.
This simplifies swapchain recreation by making the deletion of in-use objects to be done later when they are no longer in use.

The SwapchainManager handles all aspects of a swapchain:
 * Creation - Pass a SwapchainBuilder to specify how the swapchain should be created.
 * Recreation - Recreates the swapchain without introducing a pipeline stall through the use of an internal DelayedDeletionQueue.
 * Semaphores - Handles setting up the semaphores for acquiring a swapchain image, submitting work to draw to it, then presenting it.
 * Fences - Owns the fences associated with the command buffer submissions which write to the swapchain image.

Optionally, the SwapchainManager can also provide command buffers in which to record with for ease of use in simple situations.

There is a ImagelessFramebufferBuilder to simplify creating imageless framebuffers into a few lines of code. Nothing special,
but is a nice thing to have

Also in these changes
Fixed issue with image usage flags inheriting the wrong flags and causing validation message spam.
This commit is contained in:
Charles Giessen 2021-03-28 18:53:34 -06:00
parent c05a277572
commit 4a30810a87
4 changed files with 1493 additions and 504 deletions

View File

@ -4,114 +4,201 @@
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <string> #include <string>
#include <thread>
#include <chrono>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <cmath>
#include "../tests/common.h" #include "../tests/common.h"
#include "example_config.h" #include "example_config.h"
const int MAX_FRAMES_IN_FLIGHT = 2; const size_t MAX_FRAMES_IN_FLIGHT = 2;
std::atomic_bool is_running;
std::atomic_bool should_resize;
uint32_t current_width = default_window_width;
uint32_t current_height = default_window_height;
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;
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; GLFWwindow* window;
VulkanLibrary vk_lib;
vkb::Instance instance; vkb::Instance instance;
VkSurfaceKHR surface; VkSurfaceKHR surface;
vkb::PhysicalDevice physical_device;
vkb::Device device; vkb::Device device;
vkb::Swapchain swapchain; vkb::DispatchTable dispatch;
//convenience
VulkanLibrary* operator->(){ return &vk_lib; }
};
struct RenderData {
VkQueue graphics_queue; VkQueue graphics_queue;
VkQueue present_queue; VkQueue present_queue;
std::vector<VkImage> swapchain_images; vkb::SwapchainManager swapchain_manager;
std::vector<VkImageView> swapchain_image_views; vkb::SwapchainInfo swap_info;
std::vector<VkFramebuffer> framebuffers;
vkb::DeletionQueue delete_queue;
VkRenderPass render_pass; VkRenderPass render_pass;
VkFramebuffer framebuffer;
VkPipelineLayout pipeline_layout; VkPipelineLayout pipeline_layout;
VkPipeline graphics_pipeline; VkPipeline graphics_pipeline;
VkCommandPool command_pool; 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; double current_time = 0;
std::vector<VkSemaphore> finished_semaphore;
std::vector<VkFence> in_flight_fences;
std::vector<VkFence> image_in_flight;
size_t current_frame = 0;
}; };
int device_initialization (Init& init) { bool try_lock(std::mutex& mutex) {
init.window = create_window_glfw ("Vulkan Triangle", true); if (run_multithreaded)
return mutex.try_lock();
else
return true;
}
void unlock(std::mutex& mutex) {
if (run_multithreaded) mutex.unlock();
}
int recreate_swapchain(Renderer& renderer);
int 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;
current_width = width;
current_height = height;
std::lock_guard<std::mutex> lg(main_mutex);
Renderer* renderer = reinterpret_cast<Renderer*>(glfwGetWindowUserPointer(window));
int res = recreate_swapchain(*renderer);
if (res < 0) {
is_running = false;
return;
}
if (!use_refresh_callback) {
res = draw_frame(*renderer);
if (res < 0) {
is_running = false;
}
}
should_resize = false;
render_wait_condition_variable.notify_one();
}
void glfw_refresh_callback(GLFWwindow* window) {
if (try_lock(main_mutex)) {
Renderer* renderer = reinterpret_cast<Renderer*>(glfwGetWindowUserPointer(window));
int res = draw_frame(*renderer);
if (res < 0) {
is_running = false;
}
unlock(main_mutex);
}
}
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; 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()
.require_api_version(1, 2)
.set_debug_callback(debug_callback)
.build();
if (!instance_ret) { if (!instance_ret) {
std::cout << instance_ret.error().message() << "\n"; std::cout << instance_ret.error().message() << "\n";
return -1; return -1;
} }
init.instance = instance_ret.value (); renderer.instance = instance_ret.value();
init.vk_lib.init(init.instance.instance); glfwSetWindowSizeCallback(renderer.window, glfw_resize_callback);
if (use_refresh_callback) glfwSetWindowRefreshCallback(renderer.window, glfw_refresh_callback);
init.surface = create_surface_glfw (init.instance.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); vkb::PhysicalDeviceSelector phys_device_selector(renderer.instance);
auto phys_device_ret = phys_device_selector.set_surface (init.surface).select (); auto phys_device_ret =
phys_device_selector.set_surface(renderer.surface).set_required_features_12(features_1_2).select();
if (!phys_device_ret) { if (!phys_device_ret) {
std::cout << phys_device_ret.error().message() << "\n"; std::cout << phys_device_ret.error().message() << "\n";
return -1; 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 }; int device_initialization(Renderer& renderer) {
vkb::DeviceBuilder device_builder{ renderer.physical_device };
auto device_ret = device_builder.build(); auto device_ret = device_builder.build();
if (!device_ret) { if (!device_ret) {
std::cout << device_ret.error().message() << "\n"; std::cout << device_ret.error().message() << "\n";
return -1; return -1;
} }
init.device = device_ret.value (); renderer.device = device_ret.value();
init.vk_lib.init(init.device.device);
return 0; renderer.dispatch = renderer.device.make_table();
}
int create_swapchain (Init& init) { renderer.delete_queue = vkb::DeletionQueue(renderer.device.device, MAX_FRAMES_IN_FLIGHT);
vkb::SwapchainBuilder swapchain_builder{ init.device }; auto swapchain_manager_ret = vkb::SwapchainManager::create(
auto swap_ret = swapchain_builder.set_old_swapchain (init.swapchain).build (); vkb::SwapchainBuilder{ renderer.device }.set_desired_present_mode(present_mode).set_desired_extent(default_window_width, default_window_height));
if (!swap_ret) { if (!swapchain_manager_ret) {
std::cout << swap_ret.error ().message () << " " << swap_ret.vk_result () << "\n"; std::cout << swapchain_manager_ret.error().message() << "\n";
return -1; return -1;
} }
vkb::destroy_swapchain(init.swapchain); renderer.swapchain_manager = std::move(swapchain_manager_ret.value());
init.swapchain = swap_ret.value (); renderer.swap_info = renderer.swapchain_manager.get_info().value();
return 0; return 0;
} }
int get_queues (Init& init, RenderData& data) { int get_queues(Renderer& renderer) {
auto gq = init.device.get_queue (vkb::QueueType::graphics); auto gq = renderer.device.get_queue(vkb::QueueType::graphics);
if (!gq.has_value()) { if (!gq.has_value()) {
std::cout << "failed to get graphics queue: " << gq.error().message() << "\n"; std::cout << "failed to get graphics queue: " << gq.error().message() << "\n";
return -1; return -1;
} }
data.graphics_queue = gq.value (); renderer.graphics_queue = gq.value();
auto pq = init.device.get_queue (vkb::QueueType::present); auto pq = renderer.device.get_queue(vkb::QueueType::present);
if (!pq.has_value()) { if (!pq.has_value()) {
std::cout << "failed to get present queue: " << pq.error().message() << "\n"; std::cout << "failed to get present queue: " << pq.error().message() << "\n";
return -1; return -1;
} }
data.present_queue = pq.value (); renderer.present_queue = pq.value();
return 0; return 0;
} }
int create_render_pass (Init& init, RenderData& data) { int create_render_pass(Renderer& renderer) {
VkAttachmentDescription color_attachment = {}; 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.samples = VK_SAMPLE_COUNT_1_BIT;
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
@ -146,13 +233,26 @@ int create_render_pass (Init& init, RenderData& data) {
render_pass_info.dependencyCount = 1; render_pass_info.dependencyCount = 1;
render_pass_info.pDependencies = &dependency; render_pass_info.pDependencies = &dependency;
if (init->vkCreateRenderPass (init.device.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"; std::cout << "failed to create render pass\n";
return -1; // failed to create render pass! return -1; // failed to create render pass!
} }
return 0; return 0;
} }
int create_framebuffer(Renderer& renderer) {
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::vector<char> readFile(const std::string& filename) {
std::ifstream file(filename, std::ios::ate | std::ios::binary); std::ifstream file(filename, std::ios::ate | std::ios::binary);
@ -171,26 +271,26 @@ std::vector<char> readFile (const std::string& filename) {
return buffer; return buffer;
} }
VkShaderModule createShaderModule (Init& init, const std::vector<char>& code) { VkShaderModule createShaderModule(Renderer& renderer, const std::vector<char>& code) {
VkShaderModuleCreateInfo create_info = {}; VkShaderModuleCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
create_info.codeSize = code.size(); create_info.codeSize = code.size();
create_info.pCode = reinterpret_cast<const uint32_t*>(code.data()); create_info.pCode = reinterpret_cast<const uint32_t*>(code.data());
VkShaderModule shaderModule; VkShaderModule shaderModule;
if (init->vkCreateShaderModule (init.device.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 VK_NULL_HANDLE; // failed to create shader module
} }
return shaderModule; 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 vert_code = readFile(std::string(EXAMPLE_BUILD_DIRECTORY) + "/vert.spv");
auto frag_code = readFile(std::string(EXAMPLE_BUILD_DIRECTORY) + "/frag.spv"); auto frag_code = readFile(std::string(EXAMPLE_BUILD_DIRECTORY) + "/frag.spv");
VkShaderModule vert_module = createShaderModule (init, vert_code); VkShaderModule vert_module = createShaderModule(renderer, vert_code);
VkShaderModule frag_module = createShaderModule (init, frag_code); VkShaderModule frag_module = createShaderModule(renderer, frag_code);
if (vert_module == VK_NULL_HANDLE || frag_module == VK_NULL_HANDLE) { if (vert_module == VK_NULL_HANDLE || frag_module == VK_NULL_HANDLE) {
std::cout << "failed to create shader module\n"; std::cout << "failed to create shader module\n";
return -1; // failed to create shader modules return -1; // failed to create shader modules
@ -223,14 +323,14 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
VkViewport viewport = {}; VkViewport viewport = {};
viewport.x = 0.0f; viewport.x = 0.0f;
viewport.y = 0.0f; viewport.y = 0.0f;
viewport.width = (float)init.swapchain.extent.width; viewport.width = (float)renderer.swap_info.extent.width;
viewport.height = (float)init.swapchain.extent.height; viewport.height = (float)renderer.swap_info.extent.height;
viewport.minDepth = 0.0f; viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f; viewport.maxDepth = 1.0f;
VkRect2D scissor = {}; VkRect2D scissor = {};
scissor.offset = { 0, 0 }; scissor.offset = { 0, 0 };
scissor.extent = init.swapchain.extent; scissor.extent = renderer.swap_info.extent;
VkPipelineViewportStateCreateInfo viewport_state = {}; VkPipelineViewportStateCreateInfo viewport_state = {};
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
@ -275,8 +375,8 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
pipeline_layout_info.setLayoutCount = 0; pipeline_layout_info.setLayoutCount = 0;
pipeline_layout_info.pushConstantRangeCount = 0; pipeline_layout_info.pushConstantRangeCount = 0;
if (init->vkCreatePipelineLayout ( if (renderer.dispatch.createPipelineLayout(
init.device.device, &pipeline_layout_info, nullptr, &data.pipeline_layout) != VK_SUCCESS) { &pipeline_layout_info, nullptr, &renderer.pipeline_layout) != VK_SUCCESS) {
std::cout << "failed to create pipeline layout\n"; std::cout << "failed to create pipeline layout\n";
return -1; // failed to create pipeline layout return -1; // failed to create pipeline layout
} }
@ -299,283 +399,259 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
pipeline_info.pMultisampleState = &multisampling; pipeline_info.pMultisampleState = &multisampling;
pipeline_info.pColorBlendState = &color_blending; pipeline_info.pColorBlendState = &color_blending;
pipeline_info.pDynamicState = &dynamic_info; pipeline_info.pDynamicState = &dynamic_info;
pipeline_info.layout = data.pipeline_layout; pipeline_info.layout = renderer.pipeline_layout;
pipeline_info.renderPass = data.render_pass; pipeline_info.renderPass = renderer.render_pass;
pipeline_info.subpass = 0; pipeline_info.subpass = 0;
pipeline_info.basePipelineHandle = VK_NULL_HANDLE; pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
if (init->vkCreateGraphicsPipelines ( if (renderer.dispatch.createGraphicsPipelines(
init.device.device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &data.graphics_pipeline) != VK_SUCCESS) { VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &renderer.graphics_pipeline) != VK_SUCCESS) {
std::cout << "failed to create pipline\n"; std::cout << "failed to create pipline\n";
return -1; // failed to create graphics pipeline return -1; // failed to create graphics pipeline
} }
init->vkDestroyShaderModule (init.device.device, frag_module, nullptr); renderer.dispatch.destroyShaderModule(frag_module, nullptr);
init->vkDestroyShaderModule (init.device.device, vert_module, nullptr); renderer.dispatch.destroyShaderModule(vert_module, nullptr);
return 0; return 0;
} }
int create_framebuffers (Init& init, RenderData& data) { int create_command_buffers(Renderer& renderer) {
data.swapchain_images = init.swapchain.get_images ().value (); VkCommandPoolCreateInfo pool_info{};
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.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 = {};
pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
pool_info.queueFamilyIndex = init.device.get_queue_index (vkb::QueueType::graphics).value (); pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
pool_info.queueFamilyIndex = renderer.device.get_queue_index(vkb::QueueType::graphics).value();
if (init->vkCreateCommandPool (init.device.device, &pool_info, nullptr, &data.command_pool) != VK_SUCCESS) { if (renderer.dispatch.createCommandPool(&pool_info, nullptr, &renderer.command_pool) != VK_SUCCESS) {
std::cout << "failed to create command pool\n"; std::cout << "failed to create command pool\n";
return -1; // failed to create command pool return -1;
}
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>(4);
if (renderer.dispatch.allocateCommandBuffers(&info, renderer.command_buffers.data()) != VK_SUCCESS) {
std::cout << "failed to allocate command buffers\n";
return -1;
}
VkFenceCreateInfo fence_info{};
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
for (auto& fence : renderer.fences) {
if (renderer.dispatch.createFence(&fence_info, nullptr, &fence) != VK_SUCCESS) {
std::cout << "failed to create fence\n";
return -1;
}
} }
return 0; return 0;
} }
int create_command_buffers (Init& init, RenderData& data) { int record_command_buffer(Renderer& renderer, VkCommandBuffer command_buffer, VkImageView image_view) {
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.device, &allocInfo, data.command_buffers.data ()) != VK_SUCCESS) {
return -1; // failed to allocate command buffers;
}
for (size_t i = 0; i < data.command_buffers.size (); i++) {
VkCommandBufferBeginInfo begin_info = {}; VkCommandBufferBeginInfo begin_info = {};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (init->vkBeginCommandBuffer (data.command_buffers[i], &begin_info) != VK_SUCCESS) { 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 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 = {}; VkRenderPassBeginInfo render_pass_info = {};
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_info.renderPass = data.render_pass; render_pass_info.pNext = &attach_begin_info;
render_pass_info.framebuffer = data.framebuffers[i]; render_pass_info.renderPass = renderer.render_pass;
render_pass_info.framebuffer = renderer.framebuffer;
render_pass_info.renderArea.offset = { 0, 0 }; render_pass_info.renderArea.offset = { 0, 0 };
render_pass_info.renderArea.extent = init.swapchain.extent; render_pass_info.renderArea.extent = renderer.swap_info.extent;
VkClearValue clearColor{ { { 0.0f, 0.0f, 0.0f, 1.0f } } };
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.clearValueCount = 1;
render_pass_info.pClearValues = &clearColor; render_pass_info.pClearValues = &clearColor;
VkViewport viewport = {}; VkViewport viewport = {};
viewport.x = 0.0f; viewport.x = 0.0f;
viewport.y = 0.0f; viewport.y = 0.0f;
viewport.width = (float)init.swapchain.extent.width; viewport.width = (float)renderer.swap_info.extent.width;
viewport.height = (float)init.swapchain.extent.height; viewport.height = (float)renderer.swap_info.extent.height;
viewport.minDepth = 0.0f; viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f; viewport.maxDepth = 1.0f;
VkRect2D scissor = {}; VkRect2D scissor = {};
scissor.offset = { 0, 0 }; scissor.offset = { 0, 0 };
scissor.extent = init.swapchain.extent; scissor.extent = renderer.swap_info.extent;
init->vkCmdSetViewport (data.command_buffers[i], 0, 1, &viewport); renderer.dispatch.cmdSetViewport(command_buffer, 0, 1, &viewport);
init->vkCmdSetScissor (data.command_buffers[i], 0, 1, &scissor); renderer.dispatch.cmdSetScissor(command_buffer, 0, 1, &scissor);
init->vkCmdBeginRenderPass (data.command_buffers[i], &render_pass_info, VK_SUBPASS_CONTENTS_INLINE); renderer.dispatch.cmdBeginRenderPass(command_buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE);
init->vkCmdBindPipeline (data.command_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, data.graphics_pipeline); renderer.dispatch.cmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer.graphics_pipeline);
init->vkCmdDraw (data.command_buffers[i], 3, 1, 0, 0); renderer.dispatch.cmdDraw(command_buffer, 3, 1, 0, 0);
init->vkCmdEndRenderPass (data.command_buffers[i]); renderer.dispatch.cmdEndRenderPass(command_buffer);
if (init->vkEndCommandBuffer (data.command_buffers[i]) != VK_SUCCESS) { if (renderer.dispatch.endCommandBuffer(command_buffer) != VK_SUCCESS) {
std::cout << "failed to record command buffer\n"; std::cout << "failed to record command buffer\n";
return -1; // failed to record command buffer! return -1; // failed to record command buffer!
} }
}
return 0; return 0;
} }
int create_sync_objects (Init& init, RenderData& data) { int recreate_swapchain(Renderer& renderer) {
data.available_semaphores.resize (MAX_FRAMES_IN_FLIGHT); renderer.delete_queue.add_framebuffer(renderer.framebuffer);
data.finished_semaphore.resize (MAX_FRAMES_IN_FLIGHT); renderer.framebuffer = VK_NULL_HANDLE;
data.in_flight_fences.resize (MAX_FRAMES_IN_FLIGHT); auto ret = renderer.swapchain_manager.recreate();
data.image_in_flight.resize (init.swapchain.image_count, VK_NULL_HANDLE); if (!ret) {
std::cout << "failed to recreate swapchain\n";
VkSemaphoreCreateInfo semaphore_info = {}; return -1;
semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_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.device, &semaphore_info, nullptr, &data.available_semaphores[i]) != VK_SUCCESS ||
init->vkCreateSemaphore (init.device.device, &semaphore_info, nullptr, &data.finished_semaphore[i]) != VK_SUCCESS ||
init->vkCreateFence (init.device.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
}
} }
renderer.swap_info = ret.value();
if (0 != create_framebuffer(renderer)) return -1;
return 0; return 0;
} }
int recreate_swapchain (Init& init, RenderData& data) { int draw_frame(Renderer& renderer) {
init->vkDeviceWaitIdle (init.device.device);
init->vkDestroyCommandPool (init.device.device, data.command_pool, nullptr); vkb::SwapchainAcquireInfo acquire_info;
auto acquire_ret = renderer.swapchain_manager.acquire_image();
for (auto framebuffer : data.framebuffers) { if (acquire_ret.matches_error(vkb::SwapchainManagerError::swapchain_out_of_date)) {
init->vkDestroyFramebuffer (init.device.device, framebuffer, nullptr); return 1;
} } else if (!acquire_ret.has_value()) {
std::cout << "failed to acquire swapchain image\n";
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.device, 1, &data.in_flight_fences[data.current_frame], VK_TRUE, UINT64_MAX);
uint32_t image_index = 0;
VkResult result = init->vkAcquireNextImageKHR (init.device.device,
init.swapchain.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; return -1;
} }
if (data.image_in_flight[image_index] != VK_NULL_HANDLE) { acquire_info = acquire_ret.value();
init->vkWaitForFences (init.device.device, 1, &data.image_in_flight[image_index], VK_TRUE, UINT64_MAX); if (should_resize) return 1;
renderer.dispatch.waitForFences(1, &renderer.fences[renderer.current_index], VK_TRUE, UINT64_MAX);
renderer.dispatch.resetFences(1, &renderer.fences[renderer.current_index]);
record_command_buffer(renderer, renderer.command_buffers[renderer.current_index], acquire_info.image_view);
auto semaphores = renderer.swapchain_manager.get_submit_semaphores().value();
VkSemaphore wait_semaphores[1] = { semaphores.wait };
VkSemaphore signal_semaphores[1] = { semaphores.signal };
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 (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 -1;
} }
data.image_in_flight[image_index] = data.in_flight_fences[data.current_frame]; renderer.current_index = (renderer.current_index + 1) % MAX_FRAMES_IN_FLIGHT;
if (should_resize) return 1;
VkSubmitInfo submitInfo = {}; auto present_ret = renderer.swapchain_manager.present();
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore wait_semaphores[] = { data.available_semaphores[data.current_frame] }; if (present_ret.matches_error(vkb::SwapchainManagerError::swapchain_out_of_date)) {
VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; return 1;
submitInfo.waitSemaphoreCount = 1; } else if (!present_ret) {
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.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.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"; std::cout << "failed to present swapchain image\n";
return -1; return -1;
} }
renderer.delete_queue.tick();
data.current_frame = (data.current_frame + 1) % MAX_FRAMES_IN_FLIGHT; renderer.current_time = glfwGetTime();
return 0; return 0;
} }
void cleanup (Init& init, RenderData& data) { void cleanup(Renderer& renderer) {
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { renderer.dispatch.deviceWaitIdle();
init->vkDestroySemaphore (init.device.device, data.finished_semaphore[i], nullptr);
init->vkDestroySemaphore (init.device.device, data.available_semaphores[i], nullptr); for (auto& fence : renderer.fences) {
init->vkDestroyFence (init.device.device, data.in_flight_fences[i], nullptr); 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);
} }
init->vkDestroyCommandPool (init.device.device, data.command_pool, nullptr);
for (auto framebuffer : data.framebuffers) { void render_loop(Renderer* renderer) {
init->vkDestroyFramebuffer (init.device.device, framebuffer, nullptr); while (is_running) {
std::unique_lock<std::mutex> lg(main_mutex, std::try_to_lock);
if (!lg.owns_lock()) {
std::unique_lock<std::mutex> ulg(render_wait_mutex);
render_wait_condition_variable.wait(ulg);
continue;
} else {
int res = draw_frame(*renderer);
if (res < 0) {
is_running = false;
}
if (res == 1) {
lg.unlock();
std::unique_lock<std::mutex> ulg(render_wait_mutex);
render_wait_condition_variable.wait(ulg);
}
}
} }
init->vkDestroyPipeline (init.device.device, data.graphics_pipeline, nullptr);
init->vkDestroyPipelineLayout (init.device.device, data.pipeline_layout, nullptr);
init->vkDestroyRenderPass (init.device.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() { int main() {
Init init; is_running = false;
RenderData render_data; should_resize = false;
Renderer renderer;
if (0 != device_initialization (init)) return -1; if (0 != instance_initialization(renderer)) return -1;
if (0 != create_swapchain (init)) return -1; if (0 != device_initialization(renderer)) return -1;
if (0 != get_queues (init, render_data)) return -1; if (0 != get_queues(renderer)) return -1;
if (0 != create_render_pass (init, render_data)) return -1; if (0 != create_render_pass(renderer)) return -1;
if (0 != create_graphics_pipeline (init, render_data)) return -1; if (0 != create_framebuffer(renderer)) return -1;
if (0 != create_framebuffers (init, render_data)) return -1; if (0 != create_graphics_pipeline(renderer)) return -1;
if (0 != create_command_pool (init, render_data)) return -1; if (0 != create_command_buffers(renderer)) return -1;
if (0 != create_command_buffers (init, render_data)) return -1; is_running = true;
if (0 != create_sync_objects (init, render_data)) return -1; renderer.current_time = glfwGetTime();
if (run_multithreaded) {
std::thread render_thread{ render_loop, &renderer };
while (!glfwWindowShouldClose (init.window)) { while (!glfwWindowShouldClose(renderer.window) && is_running) {
glfwPollEvents(); glfwPollEvents();
int res = draw_frame (init, render_data); glfwWaitEvents();
if (res != 0) { }
std::cout << "failed to draw frame \n"; is_running = false;
return -1; render_wait_condition_variable.notify_one();
render_thread.join();
} else {
while (!glfwWindowShouldClose(renderer.window) && is_running) {
glfwPollEvents();
int res = draw_frame(renderer);
if (res < 0) {
is_running = false;
} }
} }
init->vkDeviceWaitIdle (init.device.device); }
cleanup(renderer);
cleanup (init, render_data);
return 0; return 0;
} }

View File

@ -30,6 +30,7 @@
#include <dlfcn.h> #include <dlfcn.h>
#endif #endif
#include <algorithm>
#include <mutex> #include <mutex>
namespace vkb { namespace vkb {
@ -150,7 +151,6 @@ class VulkanFunctions {
PFN_vkGetDeviceProcAddr fp_vkGetDeviceProcAddr = nullptr; PFN_vkGetDeviceProcAddr fp_vkGetDeviceProcAddr = nullptr;
PFN_vkCreateDevice fp_vkCreateDevice = nullptr; PFN_vkCreateDevice fp_vkCreateDevice = nullptr;
PFN_vkEnumerateDeviceExtensionProperties fp_vkEnumerateDeviceExtensionProperties = nullptr; PFN_vkEnumerateDeviceExtensionProperties fp_vkEnumerateDeviceExtensionProperties = nullptr;
PFN_vkDestroySurfaceKHR fp_vkDestroySurfaceKHR = nullptr; PFN_vkDestroySurfaceKHR fp_vkDestroySurfaceKHR = nullptr;
PFN_vkGetPhysicalDeviceSurfaceSupportKHR fp_vkGetPhysicalDeviceSurfaceSupportKHR = nullptr; PFN_vkGetPhysicalDeviceSurfaceSupportKHR fp_vkGetPhysicalDeviceSurfaceSupportKHR = nullptr;
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR fp_vkGetPhysicalDeviceSurfaceFormatsKHR = nullptr; PFN_vkGetPhysicalDeviceSurfaceFormatsKHR fp_vkGetPhysicalDeviceSurfaceFormatsKHR = nullptr;
@ -198,6 +198,22 @@ VulkanFunctions& vulkan_functions() {
return v; return v;
} }
uint32_t vulkan_version(uint32_t set_value = 0) {
static uint32_t version = set_value != 0 ? set_value : VK_MAKE_VERSION(1, 0, 0);
return version;
}
#if !defined(NDEBUG)
#define VKB_CHECK(x) \
do { \
VkResult result = x; \
assert(result == VK_SUCCESS); \
} while (0)
#else
#define VKB_CHECK(x) x
#endif
// Helper for robustly executing the two-call pattern // Helper for robustly executing the two-call pattern
template <typename T, typename F, typename... Ts> template <typename T, typename F, typename... Ts>
auto get_vector(std::vector<T>& out, F&& f, Ts&&... ts) -> VkResult { auto get_vector(std::vector<T>& out, F&& f, Ts&&... ts) -> VkResult {
@ -372,13 +388,21 @@ struct DeviceErrorCategory : std::error_category {
const DeviceErrorCategory device_error_category; const DeviceErrorCategory device_error_category;
struct SwapchainErrorCategory : std::error_category { struct SwapchainErrorCategory : std::error_category {
const char* name() const noexcept override { return "vbk_swapchain"; } const char* name() const noexcept override { return "vkb_swapchain"; }
std::string message(int err) const override { std::string message(int err) const override {
return to_string(static_cast<SwapchainError>(err)); return to_string(static_cast<SwapchainError>(err));
} }
}; };
const SwapchainErrorCategory swapchain_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 } // namespace detail
std::error_code make_error_code(InstanceError instance_error) { std::error_code make_error_code(InstanceError instance_error) {
@ -396,87 +420,87 @@ std::error_code make_error_code(DeviceError device_error) {
std::error_code make_error_code(SwapchainError swapchain_error) { std::error_code make_error_code(SwapchainError swapchain_error) {
return { static_cast<int>(swapchain_error), detail::swapchain_error_category }; 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;
const char* to_string(InstanceError err) { const char* to_string(InstanceError err) {
switch (err) { switch (err) {
case InstanceError::vulkan_unavailable: CASE_TO_STRING(InstanceError, vulkan_unavailable)
return "vulkan_unavailable"; CASE_TO_STRING(InstanceError, vulkan_version_unavailable)
case InstanceError::vulkan_version_unavailable: CASE_TO_STRING(InstanceError, vulkan_version_1_1_unavailable)
return "vulkan_version_unavailable"; CASE_TO_STRING(InstanceError, vulkan_version_1_2_unavailable)
case InstanceError::vulkan_version_1_1_unavailable: CASE_TO_STRING(InstanceError, failed_create_debug_messenger)
return "vulkan_version_1_1_unavailable"; CASE_TO_STRING(InstanceError, failed_create_instance)
case InstanceError::vulkan_version_1_2_unavailable: CASE_TO_STRING(InstanceError, requested_layers_not_present)
return "vulkan_version_1_2_unavailable"; CASE_TO_STRING(InstanceError, requested_extensions_not_present)
case InstanceError::failed_create_debug_messenger: CASE_TO_STRING(InstanceError, windowing_extensions_not_present)
return "failed_create_debug_messenger";
case InstanceError::failed_create_instance:
return "failed_create_instance";
case InstanceError::requested_layers_not_present:
return "requested_layers_not_present";
case InstanceError::requested_extensions_not_present:
return "requested_extensions_not_present";
case InstanceError::windowing_extensions_not_present:
return "windowing_extensions_not_present";
default: default:
return ""; return "";
} }
} }
const char* to_string(PhysicalDeviceError err) { const char* to_string(PhysicalDeviceError err) {
switch (err) { switch (err) {
case PhysicalDeviceError::no_surface_provided: CASE_TO_STRING(PhysicalDeviceError, no_surface_provided)
return "no_surface_provided"; CASE_TO_STRING(PhysicalDeviceError, failed_enumerate_physical_devices)
case PhysicalDeviceError::failed_enumerate_physical_devices: CASE_TO_STRING(PhysicalDeviceError, no_physical_devices_found)
return "failed_enumerate_physical_devices"; CASE_TO_STRING(PhysicalDeviceError, no_suitable_device)
case PhysicalDeviceError::no_physical_devices_found:
return "no_physical_devices_found";
case PhysicalDeviceError::no_suitable_device:
return "no_suitable_device";
default: default:
return ""; return "";
} }
} }
const char* to_string(QueueError err) { const char* to_string(QueueError err) {
switch (err) { switch (err) {
case QueueError::present_unavailable: CASE_TO_STRING(QueueError, present_unavailable)
return "present_unavailable"; CASE_TO_STRING(QueueError, graphics_unavailable)
case QueueError::graphics_unavailable: CASE_TO_STRING(QueueError, compute_unavailable)
return "graphics_unavailable"; CASE_TO_STRING(QueueError, transfer_unavailable)
case QueueError::compute_unavailable: CASE_TO_STRING(QueueError, queue_index_out_of_range)
return "compute_unavailable"; CASE_TO_STRING(QueueError, invalid_queue_family_index)
case QueueError::transfer_unavailable:
return "transfer_unavailable";
case QueueError::queue_index_out_of_range:
return "queue_index_out_of_range";
case QueueError::invalid_queue_family_index:
return "invalid_queue_family_index";
default: default:
return ""; return "";
} }
} }
const char* to_string(DeviceError err) { const char* to_string(DeviceError err) {
switch (err) { switch (err) {
case DeviceError::failed_create_device: CASE_TO_STRING(DeviceError, failed_create_device)
return "failed_create_device";
default: default:
return ""; return "";
} }
} }
const char* to_string(SwapchainError err) { const char* to_string(SwapchainError err) {
switch (err) { switch (err) {
case SwapchainError::surface_handle_not_provided: CASE_TO_STRING(SwapchainError, surface_handle_not_provided)
return "surface_handle_not_provided"; CASE_TO_STRING(SwapchainError, failed_query_surface_support_details)
case SwapchainError::failed_query_surface_support_details: CASE_TO_STRING(SwapchainError, failed_create_swapchain)
return "failed_query_surface_support_details"; CASE_TO_STRING(SwapchainError, failed_get_swapchain_images)
case SwapchainError::failed_create_swapchain: CASE_TO_STRING(SwapchainError, failed_create_swapchain_image_views)
return "failed_create_swapchain";
case SwapchainError::failed_get_swapchain_images:
return "failed_get_swapchain_images";
case SwapchainError::failed_create_swapchain_image_views:
return "failed_create_swapchain_image_views";
default: default:
return ""; 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, 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
detail::Result<SystemInfo> SystemInfo::get_system_info() { detail::Result<SystemInfo> SystemInfo::get_system_info() {
if (!detail::vulkan_functions().init_vulkan_funcs(nullptr)) { if (!detail::vulkan_functions().init_vulkan_funcs(nullptr)) {
@ -593,6 +617,9 @@ detail::Result<Instance> InstanceBuilder::build() const {
} }
} }
// set the global that contains the api version.
detail::vulkan_version(api_version);
VkApplicationInfo app_info = {}; VkApplicationInfo app_info = {};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pNext = nullptr; app_info.pNext = nullptr;
@ -693,9 +720,11 @@ detail::Result<Instance> InstanceBuilder::build() const {
VkInstanceCreateInfo instance_create_info = {}; VkInstanceCreateInfo instance_create_info = {};
instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
detail::setup_pNext_chain(instance_create_info, pNext_chain); detail::setup_pNext_chain(instance_create_info, pNext_chain);
#if !defined(NDEBUG)
for (auto& node : pNext_chain) { for (auto& node : pNext_chain) {
assert(node->sType != VK_STRUCTURE_TYPE_APPLICATION_INFO); assert(node->sType != VK_STRUCTURE_TYPE_APPLICATION_INFO);
} }
#endif
instance_create_info.flags = info.flags; instance_create_info.flags = info.flags;
instance_create_info.pApplicationInfo = &app_info; instance_create_info.pApplicationInfo = &app_info;
instance_create_info.enabledExtensionCount = static_cast<uint32_t>(extensions.size()); instance_create_info.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
@ -1476,10 +1505,11 @@ detail::Result<Device> DeviceBuilder::build() const {
} }
detail::setup_pNext_chain(device_create_info, final_pnext_chain); detail::setup_pNext_chain(device_create_info, final_pnext_chain);
#if !defined(NDEBUG)
for (auto& node : final_pnext_chain) { for (auto& node : final_pnext_chain) {
assert(node->sType != VK_STRUCTURE_TYPE_APPLICATION_INFO); assert(node->sType != VK_STRUCTURE_TYPE_APPLICATION_INFO);
} }
#endif
device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_create_info.flags = info.flags; device_create_info.flags = info.flags;
device_create_info.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size()); device_create_info.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
@ -1726,9 +1756,11 @@ detail::Result<Swapchain> SwapchainBuilder::build() const {
VkSwapchainCreateInfoKHR swapchain_create_info = {}; VkSwapchainCreateInfoKHR swapchain_create_info = {};
swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
detail::setup_pNext_chain(swapchain_create_info, info.pNext_chain); detail::setup_pNext_chain(swapchain_create_info, info.pNext_chain);
#if !defined(NDEBUG)
for (auto& node : info.pNext_chain) { for (auto& node : info.pNext_chain) {
assert(node->sType != VK_STRUCTURE_TYPE_APPLICATION_INFO); assert(node->sType != VK_STRUCTURE_TYPE_APPLICATION_INFO);
} }
#endif
swapchain_create_info.flags = info.create_flags; swapchain_create_info.flags = info.create_flags;
swapchain_create_info.surface = info.surface; swapchain_create_info.surface = info.surface;
swapchain_create_info.minImageCount = image_count; swapchain_create_info.minImageCount = image_count;
@ -1762,6 +1794,7 @@ detail::Result<Swapchain> SwapchainBuilder::build() const {
} }
swapchain.device = info.device; swapchain.device = info.device;
swapchain.image_format = surface_format.format; swapchain.image_format = surface_format.format;
swapchain.image_usage_flags = info.image_usage_flags;
swapchain.extent = extent; swapchain.extent = extent;
detail::vulkan_functions().get_device_proc_addr( detail::vulkan_functions().get_device_proc_addr(
info.device, swapchain.internal_table.fp_vkGetSwapchainImagesKHR, "vkGetSwapchainImagesKHR"); info.device, swapchain.internal_table.fp_vkGetSwapchainImagesKHR, "vkGetSwapchainImagesKHR");
@ -1795,8 +1828,12 @@ detail::Result<std::vector<VkImageView>> Swapchain::get_image_views() {
if (!swapchain_images_ret) return swapchain_images_ret.error(); if (!swapchain_images_ret) return swapchain_images_ret.error();
auto swapchain_images = swapchain_images_ret.value(); auto swapchain_images = swapchain_images_ret.value();
#if defined(VK_VERSION_1_1)
VkImageViewUsageCreateInfo desired_flags{};
desired_flags.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO;
desired_flags.usage = image_usage_flags;
#endif
std::vector<VkImageView> views(swapchain_images.size()); std::vector<VkImageView> views(swapchain_images.size());
for (size_t i = 0; i < swapchain_images.size(); i++) { for (size_t i = 0; i < swapchain_images.size(); i++) {
VkImageViewCreateInfo createInfo = {}; VkImageViewCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
@ -1812,7 +1849,9 @@ detail::Result<std::vector<VkImageView>> Swapchain::get_image_views() {
createInfo.subresourceRange.levelCount = 1; createInfo.subresourceRange.levelCount = 1;
createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.baseArrayLayer = 0;
createInfo.subresourceRange.layerCount = 1; createInfo.subresourceRange.layerCount = 1;
if (detail::vulkan_version() >= VK_MAKE_VERSION(1, 1, 0)) {
createInfo.pNext = &desired_flags;
}
VkResult res = VkResult res =
internal_table.fp_vkCreateImageView(device, &createInfo, allocation_callbacks, &views[i]); internal_table.fp_vkCreateImageView(device, &createInfo, allocation_callbacks, &views[i]);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
@ -1922,4 +1961,568 @@ void SwapchainBuilder::add_desired_present_modes(std::vector<VkPresentModeKHR>&
modes.push_back(VK_PRESENT_MODE_MAILBOX_KHR); modes.push_back(VK_PRESENT_MODE_MAILBOX_KHR);
modes.push_back(VK_PRESENT_MODE_FIFO_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++) {
VKB_CHECK(detail.fp_vkCreateSemaphore(device, &info, nullptr, &detail.active_acquire_semaphores[i]));
VKB_CHECK(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) {
VKB_CHECK(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;
VKB_CHECK(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
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(), 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) }) {
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.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;
}
detail::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;
}
detail::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;
}
detail::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;
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;
return out;
}
detail::Result<SwapchainSubmitSemaphores> SwapchainManager::get_submit_semaphores() 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);
}
SwapchainSubmitSemaphores semaphores;
semaphores.signal = detail.semaphore_manager.get_submit_semaphore();
semaphores.wait = detail.semaphore_manager.get_acquire_semaphore();
return semaphores;
}
detail::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{};
}
detail::Result<SwapchainInfo> SwapchainManager::recreate(uint32_t width, uint32_t height) noexcept {
assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!");
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());
auto new_swapchain_ret = detail.builder.set_old_swapchain(detail.swapchain_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.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;
}
detail::Result<SwapchainResources> SwapchainManager::create_swapchain_resources(vkb::Swapchain swapchain,
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 (detail::vulkan_version() >= VK_MAKE_VERSION(1, 1, 0)) {
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;
}
} // namespace vkb } // namespace vkb

View File

@ -19,6 +19,7 @@
#include <cassert> #include <cassert>
#include <cstdio> #include <cstdio>
#include <array>
#include <vector> #include <vector>
#include <system_error> #include <system_error>
@ -37,22 +38,22 @@ struct Error {
template <typename T> class Result { template <typename T> class Result {
public: public:
Result(const T& value) : m_value{ value }, m_init{ true } {} Result(const T& value) noexcept : m_value{ value }, m_init{ true } {}
Result(T&& value) : m_value{ std::move(value) }, m_init{ true } {} Result(T&& value) noexcept : m_value{ std::move(value) }, m_init{ true } {}
Result(Error error) : m_error{ error }, m_init{ false } {} Result(Error error) noexcept : m_error{ error }, m_init{ false } {}
Result(std::error_code error_code, VkResult result = VK_SUCCESS) Result(std::error_code error_code, VkResult result = VK_SUCCESS) noexcept
: m_error{ error_code, result }, m_init{ false } {} : m_error{ error_code, result }, m_init{ false } {}
~Result() { destroy(); } ~Result() noexcept { destroy(); }
Result(Result const& expected) : m_init(expected.m_init) { Result(Result const& expected) noexcept : m_init(expected.m_init) {
if (m_init) if (m_init)
new (&m_value) T{ expected.m_value }; new (&m_value) T{ expected.m_value };
else else
m_error = expected.m_error; m_error = expected.m_error;
} }
Result(Result&& expected) : m_init(expected.m_init) { Result(Result&& expected) noexcept : m_init(expected.m_init) {
if (m_init) if (m_init)
new (&m_value) T{ std::move(expected.m_value) }; new (&m_value) T{ std::move(expected.m_value) };
else else
@ -60,40 +61,40 @@ template <typename T> class Result {
expected.destroy(); expected.destroy();
} }
Result& operator=(const T& expect) { Result& operator=(const T& expect) noexcept {
destroy(); destroy();
m_init = true; m_init = true;
new (&m_value) T{ expect }; new (&m_value) T{ expect };
return *this; return *this;
} }
Result& operator=(T&& expect) { Result& operator=(T&& expect) noexcept {
destroy(); destroy();
m_init = true; m_init = true;
new (&m_value) T{ std::move(expect) }; new (&m_value) T{ std::move(expect) };
return *this; return *this;
} }
Result& operator=(const Error& error) { Result& operator=(const Error& error) noexcept {
destroy(); destroy();
m_init = false; m_init = false;
m_error = error; m_error = error;
return *this; return *this;
} }
Result& operator=(Error&& error) { Result& operator=(Error&& error) noexcept {
destroy(); destroy();
m_init = false; m_init = false;
m_error = error; m_error = error;
return *this; return *this;
} }
// clang-format off // clang-format off
const T* operator-> () const { assert (m_init); return &m_value; } const T* operator-> () const noexcept { assert (m_init); return &m_value; }
T* operator-> () { assert (m_init); return &m_value; } T* operator-> () noexcept { assert (m_init); return &m_value; }
const T& operator* () const& { assert (m_init); return m_value; } const T& operator* () const& noexcept { assert (m_init); return m_value; }
T& operator* () & { assert (m_init); return m_value; } T& operator* () & noexcept { assert (m_init); return m_value; }
T&& operator* () && { assert (m_init); return std::move (m_value); } T&& operator* () && noexcept { assert (m_init); return std::move (m_value); }
const T& value () const& { assert (m_init); return m_value; } const T& value () const& noexcept { assert (m_init); return m_value; }
T& value () & { assert (m_init); return m_value; } T& value () & noexcept { assert (m_init); return m_value; }
const T&& value () const&& { assert (m_init); return std::move (m_value); } const T&& value () const&& noexcept { assert (m_init); return std::move (m_value); }
T&& value () && { assert (m_init); return std::move (m_value); } T&& value () && noexcept { assert (m_init); return std::move (m_value); }
// std::error_code associated with the error // std::error_code associated with the error
std::error_code error() const { assert (!m_init); return m_error.type; } std::error_code error() const { assert (!m_init); return m_error.type; }
@ -103,6 +104,11 @@ template <typename T> class Result {
Error full_error() const { assert (!m_init); return m_error; } Error full_error() const { assert (!m_init); return m_error; }
// clang-format on // clang-format on
// check if the result has an error that matches a specific error case
template <typename E> bool matches_error(E error_enum_value) const {
return !m_init && static_cast<E>(m_error.type.value()) == error_enum_value;
}
bool has_value() const { return m_init; } bool has_value() const { return m_init; }
explicit operator bool() const { return m_init; } explicit operator bool() const { return m_init; }
@ -121,8 +127,7 @@ struct GenericFeaturesPNextNode {
GenericFeaturesPNextNode(); GenericFeaturesPNextNode();
template <typename T> template <typename T> GenericFeaturesPNextNode(T const& features) noexcept {
GenericFeaturesPNextNode(T const& features) noexcept {
*reinterpret_cast<T*>(this) = features; *reinterpret_cast<T*>(this) = features;
} }
@ -171,12 +176,27 @@ enum class SwapchainError {
failed_get_swapchain_images, failed_get_swapchain_images,
failed_create_swapchain_image_views, failed_create_swapchain_image_views,
}; };
enum class SwapchainManagerError {
swapchain_suboptimal,
swapchain_out_of_date,
surface_lost,
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(InstanceError instance_error);
std::error_code make_error_code(PhysicalDeviceError physical_device_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(QueueError queue_error);
std::error_code make_error_code(DeviceError device_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(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_severity(VkDebugUtilsMessageSeverityFlagBitsEXT s);
const char* to_string_message_type(VkDebugUtilsMessageTypeFlagsEXT s); const char* to_string_message_type(VkDebugUtilsMessageTypeFlagsEXT s);
@ -186,6 +206,8 @@ const char* to_string(PhysicalDeviceError err);
const char* to_string(QueueError err); const char* to_string(QueueError err);
const char* to_string(DeviceError err); const char* to_string(DeviceError err);
const char* to_string(SwapchainError 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 // 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 // extensions. Use this for enabling features conditionally, ie if you would like an extension but
@ -554,6 +576,8 @@ enum class QueueType { present, graphics, compute, transfer };
namespace detail { namespace detail {
// Sentinel value, used in implementation only // Sentinel value, used in implementation only
const uint32_t QUEUE_INDEX_MAX_VALUE = 65536; const uint32_t QUEUE_INDEX_MAX_VALUE = 65536;
const uint32_t INDEX_MAX_VALUE = 65536;
} // namespace detail } // namespace detail
// ---- Device ---- // // ---- Device ---- //
@ -628,11 +652,13 @@ class DeviceBuilder {
}; };
// ---- Swapchain ---- // // ---- Swapchain ---- //
struct Swapchain { struct Swapchain {
VkDevice device = VK_NULL_HANDLE; VkDevice device = VK_NULL_HANDLE;
VkSwapchainKHR swapchain = VK_NULL_HANDLE; VkSwapchainKHR swapchain = VK_NULL_HANDLE;
uint32_t image_count = 0; uint32_t image_count = 0;
VkFormat image_format = VK_FORMAT_UNDEFINED; VkFormat image_format = VK_FORMAT_UNDEFINED;
VkImageUsageFlags image_usage_flags = 0;
VkExtent2D extent = { 0, 0 }; VkExtent2D extent = { 0, 0 };
VkAllocationCallbacks* allocation_callbacks = VK_NULL_HANDLE; VkAllocationCallbacks* allocation_callbacks = VK_NULL_HANDLE;
@ -768,8 +794,364 @@ class SwapchainBuilder {
VkSwapchainKHR old_swapchain = VK_NULL_HANDLE; VkSwapchainKHR old_swapchain = VK_NULL_HANDLE;
VkAllocationCallbacks* allocation_callbacks = VK_NULL_HANDLE; VkAllocationCallbacks* allocation_callbacks = VK_NULL_HANDLE;
} info; } info;
friend class SwapchainManager;
// To allow SwapchainManager to construct it 'emptily'
explicit SwapchainBuilder() = default;
}; };
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;
};
struct SwapchainSubmitSemaphores {
VkSemaphore signal;
VkSemaphore wait;
};
/**
* 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
* }
* VkImageView image_view = acquire_ret.value().image_view;
*
* // record command buffers that use image_view
*
* auto semaphores = renderer.swapchain_manager.get_submit_semaphores().value();
*
* VkSemaphore wait_semaphores[1] = { semaphores.wait }; //add in any user declared semaphores
* VkSemaphore signal_semaphores[1] = { semaphores.signal }; //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 detail::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
detail::Result<SwapchainAcquireInfo> acquire_image() noexcept;
detail::Result<SwapchainSubmitSemaphores> get_submit_semaphores() noexcept;
detail::Result<detail::void_t> present() noexcept;
// Recreate the swapchain, putting currently in-use internal resources in a delete queue
detail::Result<SwapchainInfo> recreate(uint32_t width = 0, uint32_t height = 0) noexcept;
// Get info about the swapchain
detail::Result<SwapchainInfo> get_info() noexcept;
// Get access to the swapchain and resources associated with it
detail::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;
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;
} 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 detail::Result<SwapchainResources> create_swapchain_resources(vkb::Swapchain swapchain,
PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR,
PFN_vkCreateImageView fp_vkCreateImageView) noexcept;
};
} // namespace vkb } // namespace vkb
@ -779,4 +1161,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::QueueError> : true_type {};
template <> struct is_error_code_enum<vkb::DeviceError> : 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::SwapchainError> : true_type {};
template <> struct is_error_code_enum<vkb::SwapchainManagerError> : true_type {};
} // namespace std } // namespace std

View File

@ -20,12 +20,15 @@
#include "../src/VkBootstrap.h" #include "../src/VkBootstrap.h"
const int default_window_width = 512;
const int default_window_height = 512;
GLFWwindow* create_window_glfw(const char* window_name = "", bool resize = true) { GLFWwindow* create_window_glfw(const char* window_name = "", bool resize = true) {
glfwInit(); glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 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) { void destroy_window_glfw(GLFWwindow* window) {
glfwDestroyWindow(window); glfwDestroyWindow(window);
@ -87,82 +90,5 @@ struct VulkanLibrary {
library = 0; 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_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;
}; };