diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index d6cdcde..947b1f5 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -7,6 +7,8 @@ target_link_libraries(vk-bootstrap-triangle vk-bootstrap-vulkan-headers) target_include_directories(vk-bootstrap-triangle PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) #path to build directory for shaders +target_compile_features(vk-bootstrap-triangle PUBLIC cxx_std_17) + add_custom_command( TARGET vk-bootstrap-triangle POST_BUILD diff --git a/example/triangle.cpp b/example/triangle.cpp index 2029212..2645dd7 100644 --- a/example/triangle.cpp +++ b/example/triangle.cpp @@ -4,114 +4,223 @@ #include #include #include +#include +#include +#include +#include +#include + +#include #include "../tests/common.h" #include "example_config.h" -const int MAX_FRAMES_IN_FLIGHT = 2; +const size_t MAX_FRAMES_IN_FLIGHT = 2; // number of command buffers and fences +std::atomic_bool is_running; +std::atomic_bool should_resize; -struct Init { +std::mutex main_mutex; + +std::mutex render_wait_mutex; +std::condition_variable render_wait_condition_variable; + +const bool run_multithreaded = true; +const bool use_refresh_callback = true; // should be true for WindowsOS +const bool use_validation_layer = false; // enabling layers can cause some stuttering +const VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR; +// options are: +// VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR + +struct Renderer { GLFWwindow* window; - VulkanLibrary vk_lib; vkb::Instance instance; VkSurfaceKHR surface; + vkb::PhysicalDevice physical_device; vkb::Device device; - vkb::Swapchain swapchain; - //convenience - VulkanLibrary* operator->(){ return &vk_lib; } -}; + vkb::DispatchTable dispatch; -struct RenderData { VkQueue graphics_queue; VkQueue present_queue; - std::vector swapchain_images; - std::vector swapchain_image_views; - std::vector framebuffers; + vkb::SwapchainManager swapchain_manager; + vkb::SwapchainInfo swap_info; + + vkb::DeletionQueue delete_queue; VkRenderPass render_pass; + VkFramebuffer framebuffer; VkPipelineLayout pipeline_layout; VkPipeline graphics_pipeline; VkCommandPool command_pool; - std::vector command_buffers; + std::array command_buffers; + std::array fences; + uint32_t current_index = 0; - std::vector available_semaphores; - std::vector finished_semaphore; - std::vector in_flight_fences; - std::vector image_in_flight; - size_t current_frame = 0; + double current_time = 0; }; -int device_initialization (Init& init) { - init.window = create_window_glfw ("Vulkan Triangle", true); +enum class RecreateSwapchainRet { success, fail }; +RecreateSwapchainRet recreate_swapchain(Renderer& renderer); + +enum class DrawFrameRet { success, fail, out_of_date }; +DrawFrameRet draw_frame(Renderer& renderer); + +void glfw_resize_callback(GLFWwindow* window, int width, int height) { + if (!is_running || width == 0 || height == 0) { + return; + } + should_resize = true; + bool should_notify = true; + std::unique_lock lg(main_mutex); + Renderer* renderer = reinterpret_cast(glfwGetWindowUserPointer(window)); + auto res = recreate_swapchain(*renderer); + if (res == RecreateSwapchainRet::fail) { + is_running = false; + return; + } + should_resize = false; // makes draw_frame exit early instead of submitting. + if (!use_refresh_callback) { + switch (draw_frame(*renderer)) { + case (DrawFrameRet::success): + break; + case (DrawFrameRet::out_of_date): { + should_resize = true; + should_notify = false; + break; + } + default: + case (DrawFrameRet::fail): + is_running = false; + break; + } + } + lg.unlock(); + if (!use_refresh_callback) { + if (should_notify) { + render_wait_condition_variable.notify_one(); + } + } +} +void glfw_refresh_callback(GLFWwindow* window) { + bool should_notify = false; + + std::unique_lock lg(main_mutex, std::try_to_lock); + if (lg.owns_lock()) { + if (!should_resize) { + should_notify = true; + Renderer* renderer = reinterpret_cast(glfwGetWindowUserPointer(window)); + switch (draw_frame(*renderer)) { + case (DrawFrameRet::success): + break; + case (DrawFrameRet::out_of_date): { + should_resize = true; + should_notify = false; + break; + } + default: + case (DrawFrameRet::fail): + is_running = false; + should_notify = false; + break; + } + } + lg.unlock(); + } + if (should_notify) { + render_wait_condition_variable.notify_one(); + } +} +inline VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void*) { + auto ms = vkb::to_string_message_severity(messageSeverity); + auto mt = vkb::to_string_message_type(messageType); + std::cerr << "[" << ms << ": " << mt << "]\n" << pCallbackData->pMessage << "\n"; + + return VK_FALSE; // Applications must return false here +} + +int instance_initialization(Renderer& renderer) { + renderer.window = create_window_glfw("Vulkan Triangle", true); + + glfwSetWindowUserPointer(renderer.window, &renderer); vkb::InstanceBuilder instance_builder; - auto instance_ret = instance_builder.use_default_debug_messenger ().request_validation_layers ().build (); + auto instance_ret = instance_builder.request_validation_layers(use_validation_layer) + .require_api_version(1, 2) + .set_debug_callback(debug_callback) + .build(); if (!instance_ret) { - std::cout << instance_ret.error ().message () << "\n"; + std::cout << instance_ret.error().message() << "\n"; return -1; } - init.instance = instance_ret.value (); + renderer.instance = instance_ret.value(); - init.vk_lib.init(init.instance); + glfwSetWindowSizeCallback(renderer.window, glfw_resize_callback); + if (use_refresh_callback) glfwSetWindowRefreshCallback(renderer.window, glfw_refresh_callback); - init.surface = create_surface_glfw (init.instance, init.window); + renderer.surface = create_surface_glfw(renderer.instance.instance, renderer.window); + VkPhysicalDeviceVulkan12Features features_1_2{}; + features_1_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + features_1_2.imagelessFramebuffer = true; - vkb::PhysicalDeviceSelector phys_device_selector (init.instance); - auto phys_device_ret = phys_device_selector.set_surface (init.surface).select (); + vkb::PhysicalDeviceSelector phys_device_selector(renderer.instance); + auto phys_device_ret = phys_device_selector.set_surface(renderer.surface).set_required_features_12(features_1_2).select(); if (!phys_device_ret) { - std::cout << phys_device_ret.error ().message () << "\n"; + std::cout << phys_device_ret.error().message() << "\n"; return -1; } - vkb::PhysicalDevice physical_device = phys_device_ret.value (); + renderer.physical_device = phys_device_ret.value(); + return 0; +} - vkb::DeviceBuilder device_builder{ physical_device }; - auto device_ret = device_builder.build (); +int device_initialization(Renderer& renderer) { + vkb::DeviceBuilder device_builder{ renderer.physical_device }; + auto device_ret = device_builder.build(); if (!device_ret) { - std::cout << device_ret.error ().message () << "\n"; + std::cout << device_ret.error().message() << "\n"; return -1; } - init.device = device_ret.value (); - init.vk_lib.init(init.device); + renderer.device = device_ret.value(); + renderer.dispatch = renderer.device.make_table(); + + renderer.delete_queue = vkb::DeletionQueue(renderer.device.device, MAX_FRAMES_IN_FLIGHT); + + auto swapchain_manager_ret = vkb::SwapchainManager::create( + vkb::SwapchainBuilder{ renderer.device }.set_desired_present_mode(present_mode).set_desired_extent(default_window_width, default_window_height)); + if (!swapchain_manager_ret) { + std::cout << swapchain_manager_ret.error().message() << "\n"; + return -1; + } + renderer.swapchain_manager = std::move(swapchain_manager_ret.value()); + renderer.swap_info = renderer.swapchain_manager.get_info().value(); return 0; } -int create_swapchain (Init& init) { - - vkb::SwapchainBuilder swapchain_builder{ init.device }; - auto swap_ret = swapchain_builder.set_old_swapchain (init.swapchain).build (); - if (!swap_ret) { - std::cout << swap_ret.error ().message () << " " << swap_ret.vk_result () << "\n"; - return -1; +int get_queues(Renderer& renderer) { + auto gq = renderer.device.get_queue(vkb::QueueType::graphics); + if (!gq.has_value()) { + std::cout << "failed to get graphics queue: " << gq.error().message() << "\n"; + return -1; } - vkb::destroy_swapchain(init.swapchain); - init.swapchain = swap_ret.value (); + renderer.graphics_queue = gq.value(); + + auto pq = renderer.device.get_queue(vkb::QueueType::present); + if (!pq.has_value()) { + std::cout << "failed to get present queue: " << pq.error().message() << "\n"; + return -1; + } + renderer.present_queue = pq.value(); return 0; } -int get_queues (Init& init, RenderData& data) { - auto gq = init.device.get_queue (vkb::QueueType::graphics); - if (!gq.has_value ()) { - std::cout << "failed to get graphics queue: " << gq.error ().message () << "\n"; - return -1; - } - data.graphics_queue = gq.value (); - - auto pq = init.device.get_queue (vkb::QueueType::present); - if (!pq.has_value ()) { - std::cout << "failed to get present queue: " << pq.error ().message () << "\n"; - return -1; - } - data.present_queue = pq.value (); - return 0; -} - -int create_render_pass (Init& init, RenderData& data) { +int create_render_pass(Renderer& renderer) { VkAttachmentDescription color_attachment = {}; - color_attachment.format = init.swapchain.image_format; + color_attachment.format = renderer.swap_info.image_format; color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; @@ -146,51 +255,63 @@ int create_render_pass (Init& init, RenderData& data) { render_pass_info.dependencyCount = 1; render_pass_info.pDependencies = &dependency; - if (init->vkCreateRenderPass (init.device, &render_pass_info, nullptr, &data.render_pass) != VK_SUCCESS) { + if (renderer.dispatch.createRenderPass(&render_pass_info, nullptr, &renderer.render_pass) != VK_SUCCESS) { std::cout << "failed to create render pass\n"; return -1; // failed to create render pass! } return 0; } -std::vector readFile (const std::string& filename) { - std::ifstream file (filename, std::ios::ate | std::ios::binary); +int create_framebuffer(Renderer& renderer) { - if (!file.is_open ()) { - throw std::runtime_error ("failed to open file!"); + vkb::ImagelessFramebufferBuilder if_builder(renderer.device); + renderer.framebuffer = if_builder.set_renderpass(renderer.render_pass) + .set_extent(renderer.swap_info.extent) + .set_layers(1) + .add_attachment(renderer.swap_info.image_usage_flags, renderer.swap_info.image_format) + .build(); + + return 0; +} + +std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); } - size_t file_size = (size_t)file.tellg (); - std::vector buffer (file_size); + size_t file_size = (size_t)file.tellg(); + std::vector buffer(file_size); - file.seekg (0); - file.read (buffer.data (), static_cast (file_size)); + file.seekg(0); + file.read(buffer.data(), static_cast(file_size)); - file.close (); + file.close(); return buffer; } -VkShaderModule createShaderModule (Init& init, const std::vector& code) { +VkShaderModule createShaderModule(Renderer& renderer, const std::vector& code) { VkShaderModuleCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - create_info.codeSize = code.size (); - create_info.pCode = reinterpret_cast (code.data ()); + create_info.codeSize = code.size(); + create_info.pCode = reinterpret_cast(code.data()); VkShaderModule shaderModule; - if (init->vkCreateShaderModule (init.device, &create_info, nullptr, &shaderModule) != VK_SUCCESS) { + if (renderer.dispatch.createShaderModule(&create_info, nullptr, &shaderModule) != VK_SUCCESS) { return VK_NULL_HANDLE; // failed to create shader module } return shaderModule; } -int create_graphics_pipeline (Init& init, RenderData& data) { +int create_graphics_pipeline(Renderer& renderer) { auto vert_code = readFile(std::string(EXAMPLE_BUILD_DIRECTORY) + "/vert.spv"); auto frag_code = readFile(std::string(EXAMPLE_BUILD_DIRECTORY) + "/frag.spv"); - VkShaderModule vert_module = createShaderModule (init, vert_code); - VkShaderModule frag_module = createShaderModule (init, frag_code); + VkShaderModule vert_module = createShaderModule(renderer, vert_code); + VkShaderModule frag_module = createShaderModule(renderer, frag_code); if (vert_module == VK_NULL_HANDLE || frag_module == VK_NULL_HANDLE) { std::cout << "failed to create shader module\n"; return -1; // failed to create shader modules @@ -223,14 +344,14 @@ int create_graphics_pipeline (Init& init, RenderData& data) { VkViewport viewport = {}; viewport.x = 0.0f; viewport.y = 0.0f; - viewport.width = (float)init.swapchain.extent.width; - viewport.height = (float)init.swapchain.extent.height; + viewport.width = (float)renderer.swap_info.extent.width; + viewport.height = (float)renderer.swap_info.extent.height; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; VkRect2D scissor = {}; scissor.offset = { 0, 0 }; - scissor.extent = init.swapchain.extent; + scissor.extent = renderer.swap_info.extent; VkPipelineViewportStateCreateInfo viewport_state = {}; viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; @@ -255,8 +376,8 @@ int create_graphics_pipeline (Init& init, RenderData& data) { multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; - colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | - VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; colorBlendAttachment.blendEnable = VK_FALSE; VkPipelineColorBlendStateCreateInfo color_blending = {}; @@ -275,8 +396,7 @@ int create_graphics_pipeline (Init& init, RenderData& data) { pipeline_layout_info.setLayoutCount = 0; pipeline_layout_info.pushConstantRangeCount = 0; - if (init->vkCreatePipelineLayout ( - init.device, &pipeline_layout_info, nullptr, &data.pipeline_layout) != VK_SUCCESS) { + if (renderer.dispatch.createPipelineLayout(&pipeline_layout_info, nullptr, &renderer.pipeline_layout) != VK_SUCCESS) { std::cout << "failed to create pipeline layout\n"; return -1; // failed to create pipeline layout } @@ -285,8 +405,8 @@ int create_graphics_pipeline (Init& init, RenderData& data) { VkPipelineDynamicStateCreateInfo dynamic_info = {}; dynamic_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamic_info.dynamicStateCount = static_cast (dynamic_states.size ()); - dynamic_info.pDynamicStates = dynamic_states.data (); + dynamic_info.dynamicStateCount = static_cast(dynamic_states.size()); + dynamic_info.pDynamicStates = dynamic_states.data(); VkGraphicsPipelineCreateInfo pipeline_info = {}; pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; @@ -299,283 +419,268 @@ int create_graphics_pipeline (Init& init, RenderData& data) { pipeline_info.pMultisampleState = &multisampling; pipeline_info.pColorBlendState = &color_blending; pipeline_info.pDynamicState = &dynamic_info; - pipeline_info.layout = data.pipeline_layout; - pipeline_info.renderPass = data.render_pass; + pipeline_info.layout = renderer.pipeline_layout; + pipeline_info.renderPass = renderer.render_pass; pipeline_info.subpass = 0; pipeline_info.basePipelineHandle = VK_NULL_HANDLE; - if (init->vkCreateGraphicsPipelines ( - init.device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &data.graphics_pipeline) != VK_SUCCESS) { + if (renderer.dispatch.createGraphicsPipelines(VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &renderer.graphics_pipeline) != VK_SUCCESS) { std::cout << "failed to create pipline\n"; return -1; // failed to create graphics pipeline } - init->vkDestroyShaderModule (init.device, frag_module, nullptr); - init->vkDestroyShaderModule (init.device, vert_module, nullptr); + renderer.dispatch.destroyShaderModule(frag_module, nullptr); + renderer.dispatch.destroyShaderModule(vert_module, nullptr); return 0; } -int create_framebuffers (Init& init, RenderData& data) { - data.swapchain_images = init.swapchain.get_images ().value (); - data.swapchain_image_views = init.swapchain.get_image_views ().value (); - - data.framebuffers.resize (data.swapchain_image_views.size ()); - - for (size_t i = 0; i < data.swapchain_image_views.size (); i++) { - VkImageView attachments[] = { data.swapchain_image_views[i] }; - - VkFramebufferCreateInfo framebuffer_info = {}; - framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebuffer_info.renderPass = data.render_pass; - framebuffer_info.attachmentCount = 1; - framebuffer_info.pAttachments = attachments; - framebuffer_info.width = init.swapchain.extent.width; - framebuffer_info.height = init.swapchain.extent.height; - framebuffer_info.layers = 1; - - if (init->vkCreateFramebuffer (init.device, &framebuffer_info, nullptr, &data.framebuffers[i]) != VK_SUCCESS) { - return -1; // failed to create framebuffer - } - } - return 0; -} - -int create_command_pool (Init& init, RenderData& data) { - VkCommandPoolCreateInfo pool_info = {}; +int create_command_buffers(Renderer& renderer) { + VkCommandPoolCreateInfo pool_info{}; pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - pool_info.queueFamilyIndex = init.device.get_queue_index (vkb::QueueType::graphics).value (); - - if (init->vkCreateCommandPool (init.device, &pool_info, nullptr, &data.command_pool) != VK_SUCCESS) { + pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + pool_info.queueFamilyIndex = renderer.device.get_queue_index(vkb::QueueType::graphics).value(); + if (renderer.dispatch.createCommandPool(&pool_info, nullptr, &renderer.command_pool) != VK_SUCCESS) { std::cout << "failed to create command pool\n"; - return -1; // failed to create command pool - } - return 0; -} - -int create_command_buffers (Init& init, RenderData& data) { - data.command_buffers.resize (data.framebuffers.size ()); - - VkCommandBufferAllocateInfo allocInfo = {}; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.commandPool = data.command_pool; - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandBufferCount = (uint32_t)data.command_buffers.size (); - - if (init->vkAllocateCommandBuffers (init.device, &allocInfo, data.command_buffers.data ()) != VK_SUCCESS) { - return -1; // failed to allocate command buffers; + return -1; } - for (size_t i = 0; i < data.command_buffers.size (); i++) { - VkCommandBufferBeginInfo begin_info = {}; - begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - - if (init->vkBeginCommandBuffer (data.command_buffers[i], &begin_info) != VK_SUCCESS) { - return -1; // failed to begin recording command buffer - } - - VkRenderPassBeginInfo render_pass_info = {}; - render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - render_pass_info.renderPass = data.render_pass; - render_pass_info.framebuffer = data.framebuffers[i]; - render_pass_info.renderArea.offset = { 0, 0 }; - render_pass_info.renderArea.extent = init.swapchain.extent; - VkClearValue clearColor{ { { 0.0f, 0.0f, 0.0f, 1.0f } } }; - render_pass_info.clearValueCount = 1; - render_pass_info.pClearValues = &clearColor; - - VkViewport viewport = {}; - viewport.x = 0.0f; - viewport.y = 0.0f; - viewport.width = (float)init.swapchain.extent.width; - viewport.height = (float)init.swapchain.extent.height; - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - - VkRect2D scissor = {}; - scissor.offset = { 0, 0 }; - scissor.extent = init.swapchain.extent; - - init->vkCmdSetViewport (data.command_buffers[i], 0, 1, &viewport); - init->vkCmdSetScissor (data.command_buffers[i], 0, 1, &scissor); - - init->vkCmdBeginRenderPass (data.command_buffers[i], &render_pass_info, VK_SUBPASS_CONTENTS_INLINE); - - init->vkCmdBindPipeline (data.command_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, data.graphics_pipeline); - - init->vkCmdDraw (data.command_buffers[i], 3, 1, 0, 0); - - init->vkCmdEndRenderPass (data.command_buffers[i]); - - if (init->vkEndCommandBuffer (data.command_buffers[i]) != VK_SUCCESS) { - std::cout << "failed to record command buffer\n"; - return -1; // failed to record command buffer! - } + VkCommandBufferAllocateInfo info{}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + info.commandPool = renderer.command_pool; + info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + info.commandBufferCount = static_cast(MAX_FRAMES_IN_FLIGHT); + if (renderer.dispatch.allocateCommandBuffers(&info, renderer.command_buffers.data()) != VK_SUCCESS) { + std::cout << "failed to allocate command buffers\n"; + return -1; } - return 0; -} -int create_sync_objects (Init& init, RenderData& data) { - data.available_semaphores.resize (MAX_FRAMES_IN_FLIGHT); - data.finished_semaphore.resize (MAX_FRAMES_IN_FLIGHT); - data.in_flight_fences.resize (MAX_FRAMES_IN_FLIGHT); - data.image_in_flight.resize (init.swapchain.image_count, VK_NULL_HANDLE); - - VkSemaphoreCreateInfo semaphore_info = {}; - semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - - VkFenceCreateInfo fence_info = {}; + VkFenceCreateInfo fence_info{}; fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT; - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - if (init->vkCreateSemaphore (init.device, &semaphore_info, nullptr, &data.available_semaphores[i]) != VK_SUCCESS || - init->vkCreateSemaphore (init.device, &semaphore_info, nullptr, &data.finished_semaphore[i]) != VK_SUCCESS || - init->vkCreateFence (init.device, &fence_info, nullptr, &data.in_flight_fences[i]) != VK_SUCCESS) { - std::cout << "failed to create sync objects\n"; - return -1; // failed to create synchronization objects for a frame - } - } - return 0; -} - -int recreate_swapchain (Init& init, RenderData& data) { - init->vkDeviceWaitIdle (init.device); - - init->vkDestroyCommandPool (init.device, data.command_pool, nullptr); - - for (auto framebuffer : data.framebuffers) { - init->vkDestroyFramebuffer (init.device, framebuffer, nullptr); - } - - init.swapchain.destroy_image_views (data.swapchain_image_views); - - if (0 != create_swapchain (init)) return -1; - if (0 != create_framebuffers (init, data)) return -1; - if (0 != create_command_pool (init, data)) return -1; - if (0 != create_command_buffers (init, data)) return -1; - return 0; -} - -int draw_frame (Init& init, RenderData& data) { - init->vkWaitForFences (init.device, 1, &data.in_flight_fences[data.current_frame], VK_TRUE, UINT64_MAX); - - uint32_t image_index = 0; - VkResult result = init->vkAcquireNextImageKHR (init.device, - init.swapchain, - UINT64_MAX, - data.available_semaphores[data.current_frame], - VK_NULL_HANDLE, - &image_index); - - if (result == VK_ERROR_OUT_OF_DATE_KHR) { - return recreate_swapchain (init, data); - } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { - std::cout << "failed to acquire swapchain image. Error " << result << "\n"; - return -1; - } - - if (data.image_in_flight[image_index] != VK_NULL_HANDLE) { - init->vkWaitForFences (init.device, 1, &data.image_in_flight[image_index], VK_TRUE, UINT64_MAX); - } - data.image_in_flight[image_index] = data.in_flight_fences[data.current_frame]; - - VkSubmitInfo submitInfo = {}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - - VkSemaphore wait_semaphores[] = { data.available_semaphores[data.current_frame] }; - VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pWaitSemaphores = wait_semaphores; - submitInfo.pWaitDstStageMask = wait_stages; - - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &data.command_buffers[image_index]; - - VkSemaphore signal_semaphores[] = { data.finished_semaphore[data.current_frame] }; - submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = signal_semaphores; - - init->vkResetFences (init.device, 1, &data.in_flight_fences[data.current_frame]); - - if (init->vkQueueSubmit (data.graphics_queue, 1, &submitInfo, data.in_flight_fences[data.current_frame]) != VK_SUCCESS) { - std::cout << "failed to submit draw command buffer\n"; - return -1; //"failed to submit draw command buffer - } - - VkPresentInfoKHR present_info = {}; - present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; - - present_info.waitSemaphoreCount = 1; - present_info.pWaitSemaphores = signal_semaphores; - - VkSwapchainKHR swapChains[] = { init.swapchain }; - present_info.swapchainCount = 1; - present_info.pSwapchains = swapChains; - - present_info.pImageIndices = &image_index; - - result = init->vkQueuePresentKHR (data.present_queue, &present_info); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { - return recreate_swapchain (init, data); - } else if (result != VK_SUCCESS) { - std::cout << "failed to present swapchain image\n"; - return -1; - } - - data.current_frame = (data.current_frame + 1) % MAX_FRAMES_IN_FLIGHT; - return 0; -} - -void cleanup (Init& init, RenderData& data) { - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - init->vkDestroySemaphore (init.device, data.finished_semaphore[i], nullptr); - init->vkDestroySemaphore (init.device, data.available_semaphores[i], nullptr); - init->vkDestroyFence (init.device, data.in_flight_fences[i], nullptr); - } - - init->vkDestroyCommandPool (init.device, data.command_pool, nullptr); - - for (auto framebuffer : data.framebuffers) { - init->vkDestroyFramebuffer (init.device, framebuffer, nullptr); - } - - init->vkDestroyPipeline (init.device, data.graphics_pipeline, nullptr); - init->vkDestroyPipelineLayout (init.device, data.pipeline_layout, nullptr); - init->vkDestroyRenderPass (init.device, data.render_pass, nullptr); - - init.swapchain.destroy_image_views (data.swapchain_image_views); - - vkb::destroy_swapchain (init.swapchain); - vkb::destroy_device (init.device); - vkb::destroy_surface(init.instance, init.surface); - vkb::destroy_instance (init.instance); - destroy_window_glfw (init.window); -} - -int main () { - Init init; - RenderData render_data; - - if (0 != device_initialization (init)) return -1; - if (0 != create_swapchain (init)) return -1; - if (0 != get_queues (init, render_data)) return -1; - if (0 != create_render_pass (init, render_data)) return -1; - if (0 != create_graphics_pipeline (init, render_data)) return -1; - if (0 != create_framebuffers (init, render_data)) return -1; - if (0 != create_command_pool (init, render_data)) return -1; - if (0 != create_command_buffers (init, render_data)) return -1; - if (0 != create_sync_objects (init, render_data)) return -1; - - while (!glfwWindowShouldClose (init.window)) { - glfwPollEvents (); - int res = draw_frame (init, render_data); - if (res != 0) { - std::cout << "failed to draw frame \n"; + for (auto& fence : renderer.fences) { + if (renderer.dispatch.createFence(&fence_info, nullptr, &fence) != VK_SUCCESS) { + std::cout << "failed to create fence\n"; return -1; } } - init->vkDeviceWaitIdle (init.device); - - cleanup (init, render_data); return 0; -} \ No newline at end of file +} + +int record_command_buffer(Renderer& renderer, VkCommandBuffer command_buffer, VkImageView image_view) { + VkCommandBufferBeginInfo begin_info = {}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (renderer.dispatch.beginCommandBuffer(command_buffer, &begin_info) != VK_SUCCESS) { + std::cout << "failed to begin recording command buffer\n"; + return -1; // failed to begin recording command buffer + } + + VkRenderPassAttachmentBeginInfo attach_begin_info{}; + attach_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_ATTACHMENT_BEGIN_INFO; + attach_begin_info.attachmentCount = 1; + attach_begin_info.pAttachments = &image_view; + + VkRenderPassBeginInfo render_pass_info = {}; + render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + render_pass_info.pNext = &attach_begin_info; + render_pass_info.renderPass = renderer.render_pass; + render_pass_info.framebuffer = renderer.framebuffer; + render_pass_info.renderArea.offset = { 0, 0 }; + render_pass_info.renderArea.extent = renderer.swap_info.extent; + + float x = static_cast(std::sin(renderer.current_time * 1.5) * 0.5 + 0.5); + float z = static_cast(std::cos(renderer.current_time * 1.5) * 0.5 + 0.5); + + VkClearValue clearColor{ { { x, 0.0f, z, 1.0f } } }; + render_pass_info.clearValueCount = 1; + render_pass_info.pClearValues = &clearColor; + + VkViewport viewport = {}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)renderer.swap_info.extent.width; + viewport.height = (float)renderer.swap_info.extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = { 0, 0 }; + scissor.extent = renderer.swap_info.extent; + + renderer.dispatch.cmdSetViewport(command_buffer, 0, 1, &viewport); + renderer.dispatch.cmdSetScissor(command_buffer, 0, 1, &scissor); + + renderer.dispatch.cmdBeginRenderPass(command_buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE); + + renderer.dispatch.cmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer.graphics_pipeline); + + renderer.dispatch.cmdDraw(command_buffer, 3, 1, 0, 0); + + renderer.dispatch.cmdEndRenderPass(command_buffer); + + if (renderer.dispatch.endCommandBuffer(command_buffer) != VK_SUCCESS) { + std::cout << "failed to record command buffer\n"; + return -1; // failed to record command buffer! + } + return 0; +} + +RecreateSwapchainRet recreate_swapchain(Renderer& renderer) { + renderer.delete_queue.add_framebuffer(renderer.framebuffer); + renderer.framebuffer = VK_NULL_HANDLE; + auto ret = renderer.swapchain_manager.recreate(); + if (!ret) { + std::cout << "failed to recreate swapchain\n"; + return RecreateSwapchainRet::fail; + } + renderer.swap_info = ret.value(); + if (0 != create_framebuffer(renderer)) return RecreateSwapchainRet::fail; + return RecreateSwapchainRet::success; +} + +DrawFrameRet draw_frame(Renderer& renderer) { + + vkb::SwapchainAcquireInfo acquire_info; + auto acquire_ret = renderer.swapchain_manager.acquire_image(); + if (acquire_ret.matches_error(vkb::SwapchainManagerError::swapchain_out_of_date)) { + return DrawFrameRet::out_of_date; + } else if (!acquire_ret.has_value()) { + std::cout << "failed to acquire swapchain image\n"; + return DrawFrameRet::fail; + } + + acquire_info = acquire_ret.value(); + if (should_resize) { + renderer.swapchain_manager.cancel_acquire_frame(); + return DrawFrameRet::out_of_date; + } + + renderer.dispatch.waitForFences(1, &renderer.fences[renderer.current_index], VK_TRUE, UINT64_MAX); + + record_command_buffer(renderer, renderer.command_buffers[renderer.current_index], acquire_info.image_view); + + VkSemaphore wait_semaphores[1] = { acquire_info.wait_semaphore }; + VkSemaphore signal_semaphores[1] = { acquire_info.signal_semaphore }; + VkPipelineStageFlags wait_stages[1] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + + VkSubmitInfo submit_info = {}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = wait_semaphores; + submit_info.pWaitDstStageMask = wait_stages; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &renderer.command_buffers[renderer.current_index]; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = signal_semaphores; + + if (should_resize) { + renderer.swapchain_manager.cancel_acquire_frame(); + return DrawFrameRet::out_of_date; + } + // only reset if we are going to submit, this prevents the issue where we reset the fence but try to wait on it again + renderer.dispatch.resetFences(1, &renderer.fences[renderer.current_index]); + if (renderer.dispatch.queueSubmit(renderer.graphics_queue, 1, &submit_info, renderer.fences[renderer.current_index]) != VK_SUCCESS) { + std::cout << "failed to submit command buffer\n"; + return DrawFrameRet::fail; + } + renderer.current_index = (renderer.current_index + 1) % MAX_FRAMES_IN_FLIGHT; + + // No need to cancel, if a resize has started, then present will bail + auto present_ret = renderer.swapchain_manager.present(); + + if (present_ret.matches_error(vkb::SwapchainManagerError::swapchain_out_of_date)) { + return DrawFrameRet::out_of_date; + } else if (!present_ret) { + std::cout << "failed to present swapchain image\n"; + return DrawFrameRet::fail; + } + renderer.delete_queue.tick(); + renderer.current_time = glfwGetTime(); + return DrawFrameRet::success; +} + +void cleanup(Renderer& renderer) { + renderer.dispatch.deviceWaitIdle(); + + for (auto& fence : renderer.fences) { + renderer.dispatch.destroyFence(fence, nullptr); + } + renderer.dispatch.destroyCommandPool(renderer.command_pool, nullptr); + + renderer.dispatch.destroyPipeline(renderer.graphics_pipeline, nullptr); + renderer.dispatch.destroyPipelineLayout(renderer.pipeline_layout, nullptr); + renderer.dispatch.destroyFramebuffer(renderer.framebuffer, nullptr); + renderer.dispatch.destroyRenderPass(renderer.render_pass, nullptr); + + renderer.delete_queue.destroy(); + renderer.swapchain_manager.destroy(); + + vkb::destroy_device(renderer.device); + vkb::destroy_surface(renderer.instance, renderer.surface); + vkb::destroy_instance(renderer.instance); + destroy_window_glfw(renderer.window); +} + + +void render_loop(Renderer* renderer) { + while (is_running) { + std::unique_lock lg(main_mutex, std::try_to_lock); + if (lg.owns_lock()) { + switch (draw_frame(*renderer)) { + case (DrawFrameRet::success): + break; + case (DrawFrameRet::out_of_date): { + lg.unlock(); + std::unique_lock ulg(render_wait_mutex); + render_wait_condition_variable.wait(ulg); + break; + } + default: + case (DrawFrameRet::fail): + is_running = false; + break; + } + } else { + std::unique_lock ulg(render_wait_mutex); + render_wait_condition_variable.wait(ulg); + } + } + renderer->dispatch.deviceWaitIdle(); +} + +int main() { + is_running = false; + should_resize = false; + Renderer renderer; + + if (0 != instance_initialization(renderer)) return -1; + if (0 != device_initialization(renderer)) return -1; + if (0 != get_queues(renderer)) return -1; + if (0 != create_render_pass(renderer)) return -1; + if (0 != create_framebuffer(renderer)) return -1; + if (0 != create_graphics_pipeline(renderer)) return -1; + if (0 != create_command_buffers(renderer)) return -1; + is_running = true; + renderer.current_time = glfwGetTime(); + if (run_multithreaded) { + std::thread render_thread{ render_loop, &renderer }; + + while (!glfwWindowShouldClose(renderer.window) && is_running) { + glfwPollEvents(); + glfwWaitEvents(); + } + is_running = false; + render_wait_condition_variable.notify_one(); + render_thread.join(); + } else { + while (!glfwWindowShouldClose(renderer.window) && is_running) { + glfwPollEvents(); + DrawFrameRet res = draw_frame(renderer); + if (res == DrawFrameRet::fail) { + is_running = false; + } + } + } + cleanup(renderer); + return 0; +} diff --git a/src/VkBootstrap.cpp b/src/VkBootstrap.cpp index 6ee9851..21e7c6a 100644 --- a/src/VkBootstrap.cpp +++ b/src/VkBootstrap.cpp @@ -15,6 +15,7 @@ */ #include "VkBootstrap.h" +#include "VkSwapchainManager.h" #include @@ -30,9 +31,12 @@ #include #endif +#include #include #include +#include + namespace vkb { namespace detail { @@ -370,6 +374,12 @@ struct SwapchainErrorCategory : std::error_category { }; const SwapchainErrorCategory swapchain_error_category; +struct SwapchainManagerErrorCategory : std::error_category { + const char* name() const noexcept override { return "vkb_swapchain_manager"; } + std::string message(int err) const override { return to_string(static_cast(err)); } +}; +const SwapchainManagerErrorCategory swapchain_manager_error_category; + } // namespace detail std::error_code make_error_code(InstanceError instance_error) { @@ -387,6 +397,10 @@ std::error_code make_error_code(DeviceError device_error) { std::error_code make_error_code(SwapchainError swapchain_error) { return { static_cast(swapchain_error), detail::swapchain_error_category }; } +std::error_code make_error_code(SwapchainManagerError swapchain_manager_error) { + return { static_cast(swapchain_manager_error), detail::swapchain_manager_error_category }; +} + #define CASE_TO_STRING(CATEGORY, TYPE) \ case CATEGORY::TYPE: \ return #TYPE; @@ -446,6 +460,25 @@ const char* to_string(SwapchainError err) { return ""; } } +const char* to_string(SwapchainManagerError err) { + switch (err) { + CASE_TO_STRING(SwapchainManagerError, swapchain_suboptimal) + CASE_TO_STRING(SwapchainManagerError, swapchain_out_of_date) + CASE_TO_STRING(SwapchainManagerError, surface_lost) + CASE_TO_STRING(SwapchainManagerError, queue_submit_failed) + CASE_TO_STRING(SwapchainManagerError, must_call_acquire_image_first) + CASE_TO_STRING(SwapchainManagerError, acquire_next_image_error) + CASE_TO_STRING(SwapchainManagerError, queue_present_error) + CASE_TO_STRING(SwapchainManagerError, surface_handle_not_provided) + CASE_TO_STRING(SwapchainManagerError, failed_query_surface_support_details) + CASE_TO_STRING(SwapchainManagerError, failed_create_swapchain) + CASE_TO_STRING(SwapchainManagerError, failed_get_swapchain_images) + CASE_TO_STRING(SwapchainManagerError, failed_create_swapchain_image_views) + default: + return ""; + } +} +#undef CASE_TO_STRING Result SystemInfo::get_system_info() { if (!detail::vulkan_functions().init_vulkan_funcs(nullptr)) { @@ -2017,4 +2050,560 @@ void SwapchainBuilder::add_desired_present_modes(std::vector& modes.push_back(VK_PRESENT_MODE_MAILBOX_KHR); modes.push_back(VK_PRESENT_MODE_FIFO_KHR); } + +// Imageless framebuffer builder + +ImagelessFramebufferBuilder::ImagelessFramebufferBuilder(Device const& device) noexcept : device(device.device) { + fp_vkCreateFramebuffer = + reinterpret_cast(device.fp_vkGetDeviceProcAddr(device.device, "vkCreateFramebuffer")); + fp_vkDestroyFramebuffer = + reinterpret_cast(device.fp_vkGetDeviceProcAddr(device.device, "vkDestroyFramebuffer")); +} +VkFramebuffer ImagelessFramebufferBuilder::build() noexcept { + std::vector 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(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(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(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(1, format) }); + return *this; +} +ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::add_attachment( + VkImageUsageFlags usage_flags, std::vector const& formats) { + _attachments.push_back(Attachment{ usage_flags, formats }); + return *this; +} + +DeletionQueue ::DeletionQueue(Device const& device, uint32_t deletion_delay) noexcept +: DeletionQueue(device.device, deletion_delay) {} + +DeletionQueue::DeletionQueue(VkDevice device, uint32_t queue_depth) noexcept +: device(device), queue_depth(queue_depth) { + assert(queue_depth <= detail::MAX_SWAPCHAIN_IMAGE_COUNT && + "queue_depth cannot exceed the max swapchain image count (8)"); + detail::vulkan_functions().get_device_proc_addr(device, internal_table.fp_vkDestroyImage, "vkDestroyImage"); + detail::vulkan_functions().get_device_proc_addr(device, internal_table.fp_vkDestroyImageView, "vkDestroyImageView"); + detail::vulkan_functions().get_device_proc_addr(device, internal_table.fp_vkDestroyFramebuffer, "vkDestroyFramebuffer"); + detail::vulkan_functions().get_device_proc_addr(device, internal_table.fp_vkDestroySwapchainKHR, "vkDestroySwapchainKHR"); + for (uint32_t i = 0; i < queue_depth; i++) { + sets[i].images.reserve(10); + sets[i].views.reserve(10); + sets[i].framebuffers.reserve(10); + sets[i].swapchains.reserve(1); + } +} +DeletionQueue::~DeletionQueue() noexcept { destroy(); } + +DeletionQueue::DeletionQueue(DeletionQueue&& other) noexcept { + device = other.device; + queue_depth = other.queue_depth; + current_index = other.current_index; + sets = std::move(other.sets); + internal_table = other.internal_table; + other.device = VK_NULL_HANDLE; +} +DeletionQueue& DeletionQueue::operator=(DeletionQueue&& other) noexcept { + destroy(); + device = other.device; + queue_depth = other.queue_depth; + current_index = other.current_index; + sets = std::move(other.sets); + internal_table = other.internal_table; + other.device = VK_NULL_HANDLE; + return *this; +} +void DeletionQueue::add_image(VkImage image) noexcept { sets[current_index].images.push_back(image); } +void DeletionQueue::add_images(uint32_t images_count, const VkImage* images) noexcept { + for (size_t i = 0; i < images_count; i++) { + sets[current_index].images.push_back(images[i]); + } +} +void DeletionQueue::add_image_view(VkImageView image_view) noexcept { sets[current_index].views.push_back(image_view); } +void DeletionQueue::add_image_views(uint32_t image_view_count, const VkImageView* image_view) noexcept { + for (size_t i = 0; i < image_view_count; i++) { + sets[current_index].views.push_back(image_view[i]); + } +} +void DeletionQueue::add_framebuffer(VkFramebuffer framebuffer) noexcept { + sets[current_index].framebuffers.push_back(framebuffer); +} +void DeletionQueue::add_framebuffers(uint32_t framebuffer_count, const VkFramebuffer* framebuffers) noexcept { + for (size_t i = 0; i < framebuffer_count; i++) { + sets[current_index].framebuffers.push_back(framebuffers[i]); + } +} +void DeletionQueue::add_swapchain(VkSwapchainKHR swapchain) noexcept { + sets[current_index].swapchains.push_back(swapchain); +} +void DeletionQueue::clear_set(vkb::DeletionQueue::DelaySets& set) noexcept { + for (auto const& image : set.images) + internal_table.fp_vkDestroyImage(device, image, nullptr); + for (auto const& image_view : set.views) + internal_table.fp_vkDestroyImageView(device, image_view, nullptr); + for (auto const& framebuffer : set.framebuffers) + internal_table.fp_vkDestroyFramebuffer(device, framebuffer, nullptr); + for (auto const& swapchain : set.swapchains) + internal_table.fp_vkDestroySwapchainKHR(device, swapchain, nullptr); + set.images.clear(); + set.views.clear(); + set.framebuffers.clear(); + set.swapchains.clear(); +} +void DeletionQueue::tick() noexcept { + assert(queue_depth != 0 && "Must construct DeletionQueue with valid VkDevice and non-zero queue_depth!"); + current_index = (current_index + 1) % queue_depth; + clear_set(sets[current_index]); +} + +void DeletionQueue::destroy() noexcept { + if (device != VK_NULL_HANDLE) { + for (auto& set : sets) { + clear_set(set); + } + device = VK_NULL_HANDLE; + } +} + +namespace detail { + +// Swapchain Synchronization resources (semaphores, fences, & tracking) +SemaphoreManager::SemaphoreManager(VkDevice device, uint32_t image_count) noexcept : device(device) { + detail.swapchain_image_count = image_count; + detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkCreateSemaphore, "vkCreateSemaphore"); + detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkDestroySemaphore, "vkDestroySemaphore"); + + VkSemaphoreCreateInfo info{}; + info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + for (uint32_t i = 0; i < detail.swapchain_image_count; i++) { + detail.fp_vkCreateSemaphore(device, &info, nullptr, &detail.active_acquire_semaphores[i]); + detail.fp_vkCreateSemaphore(device, &info, nullptr, &detail.active_submit_semaphores[i]); + } + // Make sure there are some semaphores to use when needed + detail.idle_semaphores.resize(10); + for (auto& semaphore : detail.idle_semaphores) { + detail.fp_vkCreateSemaphore(device, &info, nullptr, &semaphore); + } + for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) + detail.expired_semaphores[i].reserve(10); + detail.current_acquire_semaphore = get_fresh_semaphore(); +} +SemaphoreManager::~SemaphoreManager() noexcept { destroy(); } + +void SemaphoreManager::destroy() noexcept { + if (device != VK_NULL_HANDLE) { + for (uint32_t i = 0; i < detail.swapchain_image_count; i++) { + detail.fp_vkDestroySemaphore(device, detail.active_acquire_semaphores[i], nullptr); + detail.fp_vkDestroySemaphore(device, detail.active_submit_semaphores[i], nullptr); + } + for (auto& set : detail.expired_semaphores) + for (auto& semaphore : set) + detail.fp_vkDestroySemaphore(device, semaphore, nullptr); + for (auto& semaphore : detail.idle_semaphores) + detail.fp_vkDestroySemaphore(device, semaphore, nullptr); + detail.fp_vkDestroySemaphore(device, detail.current_acquire_semaphore, nullptr); + } + device = VK_NULL_HANDLE; +} + +SemaphoreManager::SemaphoreManager(SemaphoreManager&& other) noexcept { + device = other.device; + detail = other.detail; + other.device = VK_NULL_HANDLE; // destroy only does stuff if device isn't null, therefore its a sentinel +} +SemaphoreManager& SemaphoreManager::operator=(SemaphoreManager&& other) noexcept { + destroy(); + device = other.device; + detail = other.detail; + other.device = VK_NULL_HANDLE; // destroy only does stuff if device isn't null, therefore its a sentinel + return *this; +} + +VkSemaphore SemaphoreManager::get_next_acquire_semaphore() noexcept { return detail.current_acquire_semaphore; } +void SemaphoreManager::update_current_semaphore_index(uint32_t index) noexcept { + detail.current_swapchain_index = index; + std::swap(detail.active_acquire_semaphores[index], detail.current_acquire_semaphore); + detail.in_use[index] = true; + detail.current_submit_index = (detail.current_submit_index + 1) % FRAMES_IN_FLIGHT; + detail.idle_semaphores.insert(detail.idle_semaphores.end(), + detail.expired_semaphores[detail.current_submit_index].begin(), + detail.expired_semaphores[detail.current_submit_index].end()); + detail.expired_semaphores[detail.current_submit_index].clear(); +} +VkSemaphore SemaphoreManager::get_acquire_semaphore() noexcept { + return detail.active_acquire_semaphores[detail.current_swapchain_index]; +} +VkSemaphore SemaphoreManager::get_submit_semaphore() noexcept { + return detail.active_submit_semaphores[detail.current_swapchain_index]; +} + +void SemaphoreManager::recreate_swapchain_resources() noexcept { + for (uint32_t i = 0; i < detail.swapchain_image_count; i++) { + if (detail.in_use[i]) { + detail.expired_semaphores[detail.current_submit_index].push_back(detail.active_acquire_semaphores[i]); + detail.expired_semaphores[detail.current_submit_index].push_back(detail.active_submit_semaphores[i]); + detail.active_acquire_semaphores[i] = get_fresh_semaphore(); + detail.active_submit_semaphores[i] = get_fresh_semaphore(); + } + detail.in_use[i] = false; + } + detail.expired_semaphores[detail.current_submit_index].push_back(detail.current_acquire_semaphore); + detail.current_acquire_semaphore = get_fresh_semaphore(); + detail.current_swapchain_index = detail::INDEX_MAX_VALUE; +} + +VkSemaphore SemaphoreManager::get_fresh_semaphore() noexcept { + VkSemaphore semaphore{}; + if (detail.idle_semaphores.size() > 1) { + semaphore = detail.idle_semaphores.back(); + detail.idle_semaphores.pop_back(); + } else { + VkSemaphoreCreateInfo info{}; + info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + detail.fp_vkCreateSemaphore(device, &info, nullptr, &semaphore); + } + return semaphore; +} + +Result convert(Result err) { + switch (err.error().value()) { + case (static_cast(SwapchainError::surface_handle_not_provided)): + return { make_error_code(SwapchainManagerError::surface_handle_not_provided), err.vk_result() }; + case (static_cast(SwapchainError::failed_query_surface_support_details)): + return { make_error_code(SwapchainManagerError::failed_query_surface_support_details), err.vk_result() }; + case (static_cast(SwapchainError::failed_create_swapchain)): + return { make_error_code(SwapchainManagerError::failed_create_swapchain), err.vk_result() }; + case (static_cast(SwapchainError::failed_get_swapchain_images)): + return { make_error_code(SwapchainManagerError::failed_get_swapchain_images), err.vk_result() }; + case (static_cast(SwapchainError::failed_create_swapchain_image_views)): + return { make_error_code(SwapchainManagerError::failed_create_swapchain_image_views), err.vk_result() }; + default: + assert(false && "Should never reach this"); + return { make_error_code(SwapchainManagerError::surface_handle_not_provided) }; + } +} + +} // namespace detail + +Result SwapchainManager::create(SwapchainBuilder const& builder) noexcept { + PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR; + PFN_vkCreateImageView fp_vkCreateImageView; + detail::vulkan_functions().get_device_proc_addr(builder.info.device, fp_vkGetSwapchainImagesKHR, "vkGetSwapchainImagesKHR"); + detail::vulkan_functions().get_device_proc_addr(builder.info.device, fp_vkCreateImageView, "vkCreateImageView"); + + auto swapchain_ret = builder.build(); + if (!swapchain_ret.has_value()) { + return detail::convert(swapchain_ret); + } + + auto swapchain_resources_ret = SwapchainManager::create_swapchain_resources( + swapchain_ret.value(), swapchain_ret.value().instance_version, fp_vkGetSwapchainImagesKHR, fp_vkCreateImageView); + if (!swapchain_resources_ret.has_value()) { + return swapchain_resources_ret.error(); + } + return SwapchainManager( + builder, swapchain_ret.value(), swapchain_resources_ret.value(), fp_vkGetSwapchainImagesKHR, fp_vkCreateImageView); +} + +SwapchainManager::SwapchainManager(SwapchainBuilder const& builder, + vkb::Swapchain swapchain, + SwapchainResources resources, + PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR, + PFN_vkCreateImageView fp_vkCreateImageView) noexcept +: device(builder.info.device), + detail({ builder, + swapchain, + resources, + detail::SemaphoreManager(device, swapchain.image_count), + DeletionQueue(device, swapchain.image_count), + swapchain.instance_version }) { + detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkGetDeviceQueue, "vkGetDeviceQueue"); + detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkAcquireNextImageKHR, "vkAcquireNextImageKHR"); + detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkQueuePresentKHR, "vkQueuePresentKHR"); + detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkQueueSubmit, "vkQueueSubmit"); + detail.fp_vkGetSwapchainImagesKHR = fp_vkGetSwapchainImagesKHR; + detail.fp_vkCreateImageView = fp_vkCreateImageView; + detail.fp_vkGetDeviceQueue(device, detail.builder.info.graphics_queue_index, 0, &detail.graphics_queue); + detail.fp_vkGetDeviceQueue(device, detail.builder.info.present_queue_index, 0, &detail.present_queue); + update_swapchain_info(); +} + +void SwapchainManager::destroy() noexcept { + if (device != VK_NULL_HANDLE) { + detail.semaphore_manager = detail::SemaphoreManager{}; + detail.delete_queue.add_swapchain(detail.swapchain_resources.swapchain); + detail.delete_queue.add_image_views( + detail.current_swapchain.image_count, &detail.swapchain_resources.image_views.front()); + detail.delete_queue.destroy(); + device = VK_NULL_HANDLE; + } +} +SwapchainManager::~SwapchainManager() noexcept { destroy(); } + +SwapchainManager::SwapchainManager(SwapchainManager&& other) noexcept +: device(other.device), detail(std::move(other.detail)) { + other.device = VK_NULL_HANDLE; +} +SwapchainManager& SwapchainManager::operator=(SwapchainManager&& other) noexcept { + destroy(); + device = other.device; + detail = std::move(other.detail); + other.device = VK_NULL_HANDLE; + return *this; +} + +SwapchainBuilder& SwapchainManager::get_builder() noexcept { + assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!"); + return detail.builder; +} +Result SwapchainManager::get_info() noexcept { + assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!"); + if (detail.current_status == Status::expired) { + return make_error_code(SwapchainManagerError::swapchain_out_of_date); + } + return detail.current_info; +} +Result SwapchainManager::get_swapchain_resources() noexcept { + assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!"); + if (detail.current_status == Status::expired) { + return make_error_code(SwapchainManagerError::swapchain_out_of_date); + } + return detail.swapchain_resources; +} + +Result SwapchainManager::acquire_image() noexcept { + assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!"); + if (detail.current_status == Status::expired) { + return make_error_code(SwapchainManagerError::swapchain_out_of_date); + } else if (detail.current_status == Status::ready_to_present) { + // dont do anything + SwapchainAcquireInfo out{}; + out.image_view = detail.swapchain_resources.image_views[detail.current_image_index]; + out.image_index = detail.current_image_index; + out.wait_semaphore = detail.semaphore_manager.get_acquire_semaphore(); + out.signal_semaphore = detail.semaphore_manager.get_submit_semaphore(); + return out; + } + + // reset the current image index in case acquiring fails + detail.current_image_index = detail::INDEX_MAX_VALUE; + + VkSemaphore acquire_semaphore = detail.semaphore_manager.get_next_acquire_semaphore(); + + VkResult result = detail.fp_vkAcquireNextImageKHR( + device, detail.swapchain_resources.swapchain, UINT64_MAX, acquire_semaphore, VK_NULL_HANDLE, &detail.current_image_index); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + detail.current_status = Status::expired; + return { make_error_code(SwapchainManagerError::swapchain_out_of_date) }; + } else if (result == VK_SUBOPTIMAL_KHR) { + } else if (result == VK_ERROR_SURFACE_LOST_KHR) { + return { make_error_code(SwapchainManagerError::surface_lost) }; + } else if (result != VK_SUCCESS) { + return { make_error_code(SwapchainManagerError::acquire_next_image_error), result }; + } + detail.semaphore_manager.update_current_semaphore_index(detail.current_image_index); + detail.current_status = Status::ready_to_present; + SwapchainAcquireInfo out{}; + out.image_view = detail.swapchain_resources.image_views[detail.current_image_index]; + out.image_index = detail.current_image_index; + out.wait_semaphore = detail.semaphore_manager.get_acquire_semaphore(); + out.signal_semaphore = detail.semaphore_manager.get_submit_semaphore(); + return out; +} + +Result SwapchainManager::present() noexcept { + assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!"); + if (detail.current_status == Status::expired) { + return make_error_code(SwapchainManagerError::swapchain_out_of_date); + } + if (detail.current_status == Status::ready_to_acquire) { + return make_error_code(SwapchainManagerError::must_call_acquire_image_first); + } + + VkSemaphore wait_semaphores[1] = { detail.semaphore_manager.get_submit_semaphore() }; + + VkPresentInfoKHR present_info = {}; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = wait_semaphores; + present_info.swapchainCount = 1; + present_info.pSwapchains = &detail.swapchain_resources.swapchain; + present_info.pImageIndices = &detail.current_image_index; + + VkResult result = detail.fp_vkQueuePresentKHR(detail.present_queue, &present_info); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + detail.current_status = Status::expired; + return { make_error_code(SwapchainManagerError::swapchain_out_of_date) }; + } else if (result == VK_SUBOPTIMAL_KHR) { + + } else if (result == VK_ERROR_SURFACE_LOST_KHR) { + return { make_error_code(SwapchainManagerError::surface_lost) }; + } else if (result != VK_SUCCESS) { + return { make_error_code(SwapchainManagerError::queue_present_error), result }; + } else { + detail.current_status = Status::ready_to_acquire; + } + + // clean up old swapchain resources + detail.delete_queue.tick(); + + return detail::void_t{}; +} + +Result SwapchainManager::cancel_acquire_frame() noexcept { + VkResult result = WaitForSemaphore(detail.semaphore_manager.get_acquire_semaphore()); + if (result != VK_SUCCESS) { + return { make_error_code(SwapchainManagerError::queue_submit_failed), result }; + } + detail.current_status = Status::ready_to_acquire; + return detail::void_t{}; +} +Result SwapchainManager::cancel_present_frame() noexcept { + VkResult result = WaitForSemaphore(detail.semaphore_manager.get_submit_semaphore()); + if (result != VK_SUCCESS) { + return { make_error_code(SwapchainManagerError::queue_submit_failed), result }; + } + detail.current_status = Status::ready_to_acquire; + return detail::void_t{}; +} + +Result SwapchainManager::recreate(uint32_t width, uint32_t height) noexcept { + assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!"); + + auto old_resources = detail.swapchain_resources; + detail.swapchain_resources = {}; + detail.delete_queue.add_swapchain(old_resources.swapchain); + detail.delete_queue.add_image_views(old_resources.image_count, &old_resources.image_views.front()); + auto new_swapchain_ret = detail.builder.set_old_swapchain(old_resources.swapchain).set_desired_extent(width, height).build(); + if (!new_swapchain_ret) { + return new_swapchain_ret.error(); + } + detail.current_swapchain = new_swapchain_ret.value(); + auto new_resources_ret = SwapchainManager::create_swapchain_resources( + detail.current_swapchain, detail.instance_version, detail.fp_vkGetSwapchainImagesKHR, detail.fp_vkCreateImageView); + if (!new_resources_ret) { + return new_resources_ret.error(); + } + detail.swapchain_resources = new_resources_ret.value(); + detail.semaphore_manager.recreate_swapchain_resources(); + update_swapchain_info(); + detail.current_status = Status::ready_to_acquire; + return detail.current_info; +} + +void SwapchainManager::update_swapchain_info() noexcept { + detail.current_info.image_count = detail.current_swapchain.image_count; + detail.current_info.image_format = detail.current_swapchain.image_format; + detail.current_info.extent = detail.current_swapchain.extent; + detail.current_info.image_usage_flags = detail.current_swapchain.image_usage_flags; +} + +Result SwapchainManager::create_swapchain_resources(vkb::Swapchain swapchain, + uint32_t instance_version, + PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR, + PFN_vkCreateImageView fp_vkCreateImageView) noexcept { + + SwapchainResources out{}; + out.swapchain = swapchain.swapchain; + out.image_count = swapchain.image_count; + assert(out.image_count <= detail::MAX_SWAPCHAIN_IMAGE_COUNT); + + VkResult result = fp_vkGetSwapchainImagesKHR(swapchain.device, swapchain.swapchain, &out.image_count, nullptr); + if (result != VK_SUCCESS) return { make_error_code(SwapchainManagerError::failed_get_swapchain_images), result }; + + result = fp_vkGetSwapchainImagesKHR(swapchain.device, swapchain.swapchain, &out.image_count, out.images.data()); + if (result != VK_SUCCESS) return { make_error_code(SwapchainManagerError::failed_get_swapchain_images), result }; + +#if defined(VK_VERSION_1_1) + VkImageViewUsageCreateInfo desired_flags{}; + desired_flags.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO; + desired_flags.usage = swapchain.image_usage_flags; +#endif + for (size_t i = 0; i < out.image_count; i++) { + VkImageViewCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = out.images[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapchain.image_format; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; +#if defined(VK_VERSION_1_1) + if (instance_version >= VKB_VK_API_VERSION_1_1) { + createInfo.pNext = &desired_flags; + } +#endif + result = fp_vkCreateImageView(swapchain.device, &createInfo, nullptr, &out.image_views[i]); + if (result != VK_SUCCESS) + return { make_error_code(SwapchainManagerError::failed_create_swapchain_image_views), result }; + } + return out; +} +VkResult SwapchainManager::WaitForSemaphore(VkSemaphore semaphore) noexcept { + VkPipelineStageFlags wait_stages[1] = { VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT }; + + VkSubmitInfo submit_info = {}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &semaphore; + submit_info.pWaitDstStageMask = wait_stages; + + return detail.fp_vkQueueSubmit(detail.graphics_queue, 1, &submit_info, VK_NULL_HANDLE); +} + } // namespace vkb diff --git a/src/VkBootstrap.h b/src/VkBootstrap.h index bb4a83f..602d98e 100644 --- a/src/VkBootstrap.h +++ b/src/VkBootstrap.h @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -214,12 +215,28 @@ enum class SwapchainError { failed_create_swapchain_image_views, required_min_image_count_too_low, }; +enum class SwapchainManagerError { + swapchain_suboptimal, + swapchain_out_of_date, + surface_lost, + queue_submit_failed, + must_call_acquire_image_first, + acquire_next_image_error, + queue_present_error, + surface_handle_not_provided, + failed_query_surface_support_details, + failed_create_swapchain, + failed_get_swapchain_images, + failed_create_swapchain_image_views, +}; std::error_code make_error_code(InstanceError instance_error); std::error_code make_error_code(PhysicalDeviceError physical_device_error); std::error_code make_error_code(QueueError queue_error); std::error_code make_error_code(DeviceError device_error); std::error_code make_error_code(SwapchainError swapchain_error); +std::error_code make_error_code(SwapchainManagerError swapchain_error); + const char* to_string_message_severity(VkDebugUtilsMessageSeverityFlagBitsEXT s); const char* to_string_message_type(VkDebugUtilsMessageTypeFlagsEXT s); @@ -229,6 +246,8 @@ const char* to_string(PhysicalDeviceError err); const char* to_string(QueueError err); const char* to_string(DeviceError err); const char* to_string(SwapchainError err); +const char* to_string(SwapchainManagerError err); + // Gathers useful information about the available vulkan capabilities, like layers and instance // extensions. Use this for enabling features conditionally, ie if you would like an extension but @@ -359,11 +378,11 @@ class InstanceBuilder { // Prefer a vulkan instance API version. If the desired version isn't available, it will use the // highest version available. Should be constructed with VK_MAKE_VERSION or VK_MAKE_API_VERSION. - [[deprecated("Use require_api_version + set_minimum_instance_version instead.")]] - InstanceBuilder& desire_api_version(uint32_t preferred_vulkan_version); + [[deprecated("Use require_api_version + set_minimum_instance_version instead.")]] InstanceBuilder& + desire_api_version(uint32_t preferred_vulkan_version); // Prefer a vulkan instance API version. If the desired version isn't available, it will use the highest version available. - [[deprecated("Use require_api_version + set_minimum_instance_version instead.")]] - InstanceBuilder& desire_api_version(uint32_t major, uint32_t minor, uint32_t patch = 0); + [[deprecated("Use require_api_version + set_minimum_instance_version instead.")]] InstanceBuilder& + desire_api_version(uint32_t major, uint32_t minor, uint32_t patch = 0); // Adds a layer to be enabled. Will fail to create an instance if the layer isn't available. InstanceBuilder& enable_layer(const char* layer_name); @@ -581,8 +600,8 @@ class PhysicalDeviceSelector { PhysicalDeviceSelector& add_desired_extensions(std::vector extensions); // Prefer a physical device that supports a (major, minor) version of vulkan. - [[deprecated("Use set_minimum_version + InstanceBuilder::require_api_version.")]] - PhysicalDeviceSelector& set_desired_version(uint32_t major, uint32_t minor); + [[deprecated("Use set_minimum_version + InstanceBuilder::require_api_version.")]] PhysicalDeviceSelector& + set_desired_version(uint32_t major, uint32_t minor); // Require a physical device that supports a (major, minor) version of vulkan. PhysicalDeviceSelector& set_minimum_version(uint32_t major, uint32_t minor); @@ -675,6 +694,8 @@ enum class QueueType { present, graphics, compute, transfer }; namespace detail { // Sentinel value, used in implementation only const uint32_t QUEUE_INDEX_MAX_VALUE = 65536; +const uint32_t INDEX_MAX_VALUE = 65536; + } // namespace detail // ---- Device ---- // @@ -930,6 +951,10 @@ class SwapchainBuilder { VkSwapchainKHR old_swapchain = VK_NULL_HANDLE; VkAllocationCallbacks* allocation_callbacks = VK_NULL_HANDLE; } info; + + friend class SwapchainManager; + // To allow SwapchainManager to construct it 'emptily' + explicit SwapchainBuilder() = default; }; } // namespace vkb @@ -941,4 +966,6 @@ template <> struct is_error_code_enum : true_type {}; template <> struct is_error_code_enum : true_type {}; template <> struct is_error_code_enum : true_type {}; template <> struct is_error_code_enum : true_type {}; +template <> struct is_error_code_enum : true_type {}; + } // namespace std diff --git a/src/VkSwapchainManager.h b/src/VkSwapchainManager.h new file mode 100644 index 0000000..f6fa1ea --- /dev/null +++ b/src/VkSwapchainManager.h @@ -0,0 +1,378 @@ +/* + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the “Software”), to deal in the Software without restriction, including without + * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Copyright © 2022 Charles Giessen (charles@lunarg.com) + */ + +#pragma once + +#include "VkBootstrap.h" + +namespace vkb { +namespace detail { +// constants used for DeletionQueue and SwapchainManager +const int FRAMES_IN_FLIGHT = 3; +const int MAX_SWAPCHAIN_IMAGE_COUNT = 8; + +} // namespace detail + +// ImagelessFramebufferBuilder + +struct ImagelessFramebufferBuilder { + ImagelessFramebufferBuilder(Device const& device) noexcept; + VkFramebuffer build() noexcept; + ImagelessFramebufferBuilder& set_renderpass(VkRenderPass render_pass); + ImagelessFramebufferBuilder& set_width(uint32_t width); + ImagelessFramebufferBuilder& set_height(uint32_t height); + ImagelessFramebufferBuilder& set_extent(VkExtent2D extent); + ImagelessFramebufferBuilder& set_layers(uint32_t layer_count); + ImagelessFramebufferBuilder& add_attachment(VkImageUsageFlags usage_flags, VkFormat format); + ImagelessFramebufferBuilder& add_attachment(VkImageUsageFlags usage_flags, std::vector const& formats); + + private: + struct Attachment { + VkImageUsageFlags usage_flags; + std::vector 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 _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 images; + std::vector views; + std::vector framebuffers; + std::vector swapchains; + }; + VkDevice device; + uint32_t queue_depth = 0; + uint32_t current_index = 0; + std::array 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 in_use = {}; + std::array active_acquire_semaphores{}; + std::array active_submit_semaphores{}; + uint32_t current_submit_index = 0; + std::array, FRAMES_IN_FLIGHT> expired_semaphores; + std::vector 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 images{}; + std::array image_views{}; +}; + +// Struct returned from SwapchainManager::acquire_image() +struct SwapchainAcquireInfo { + // image view to use this frame + VkImageView image_view{}; + // index of the swapchain image to use this frame + uint32_t image_index = detail::INDEX_MAX_VALUE; + VkSemaphore signal_semaphore; + VkSemaphore wait_semaphore; +}; + +/** + * Swapchain Manager + * + * Creation: + * Call the static function `SwapchainManager::create(SwapchainBuilder const& builder)` and pass + * in a vkb::SwapchainBuilder. To configure the swapchain, use the member functions on the + * SwapchainBuilder before it is passed in. SwapchainManager will keep an internal + * copy of this builder. This function will also create a swapchain, so that the application can + * query the current extent, format, and usage flags of the created swapchain. + * + * Destruction: + * To destroy the SwapchainManager, call `destroy()`. Additionally, SwapchainManager will call + * `destroy()` automatically in its destructor. + * + * SYNCHRONIZATION GUARANTEES (or lack thereof): + * The SwapchainManager does NOT have internal synchronization. Thus applications must never call any + * of its functions from multiple threads at the same time. + * + * QUEUE SUBMISSION: + * The SwapchainManager uses the Presentation VkQueue. Thus, the user must guarantee that calling `present()` + * does not overlap with any other calls which use the present queue (which in most cases is the graphics queue). + * + * Suggested Vulkan feature to enable: + * Imageless Framebuffer - This feature makes it possible to create a framebuffer without a + * VkImageView by making it a parameter that is passed in at vkCmdBeginRenderPass time. It is + * recommended because it means one VkFrameBuffer is necessary for the entire swapchain, rather than + * the previous case of N framebuffers, one for each swapchain image. Vulkan 1.2 guarantees support + * for it. + * + * Example Initialization of SwapchainManager: + * { + * auto swapchain_manager_ret = vkb::SwapchainManager::create(vkb::SwapchainBuilder{ vk_device }); + * if (!swapchain_manager_ret) { + * std::cout << swapchain_manager_ret.error().message() << "\n"; + * return -1; + * } + * swapchain_manager = std::move(swapchain_manager_ret.value()); + * swapchain_info = renderer.swapchain_manager.get_info(); + * } + * + * Example Application Usage during main rendering: + * { + * auto acquire_ret = renderer.swapchain_manager.acquire_image(); + * if (!acquire_ret.has_value() && acquire_ret.error().value() == + *(int)vkb::SwapchainManagerError::swapchain_out_of_date) { should_recreate = true; return; //abort rendering this frame + * } else if (!acquire_ret.has_value()) { + * return; //error out + * } + * + * auto acquire_info = acquire_ret.value(); //holds the image view, image index, and semaphores needed for + *submission VkImageView image_view = acquire_info.image_view; + * + * // record command buffers that use image_view + * + * // The semaphore that was passed into vkAcquireNextImageKHR (by the swapchain manager) + * VkSemaphore wait_semaphores[1] = { acquire_info.wait_semaphore }; //add in any user declared semaphores + * + * // The semaphore that gets passed into vkQueuePresentKHR (by the swapchain manager) + * VkSemaphore signal_semaphores[1] = { acquire_info.signal_semaphore }; //add in any user declared semaphores + * VkPipelineStageFlags wait_stages[1] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + * + * VkSubmitInfo submit_info = {}; + * submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + * submit_info.waitSemaphoreCount = 1; + * submit_info.pWaitSemaphores = wait_semaphores; + * submit_info.pWaitDstStageMask = wait_stages; + * submit_info.commandBufferCount = 1; + * submit_info.pCommandBuffers = &renderer.command_buffers[renderer.current_index]; + * submit_info.signalSemaphoreCount = 1; + * submit_info.pSignalSemaphores = signal_semaphores; + * + * } + * + * Example Application Recreation + * + * + * The SwapchainManager interally keeps track of the current 'state' of the swapchain. These states are: + * - ready_to_acquire: initial state, indicates is is safe to call `acquire_image` + * - ready_to_present: set after acquiring, indicates it is ready to accept command buffers to submit. + * - expired: the swapchain needs to be recreated, calling `acquire_image` while in this state will fail + * - destroyed: the swapchain was destroyed (likely manually), prevents further usage of the SwapchainManager + * After a successful call to `acquire_image()`, the state is set to `ready_to_present` + * After a successful call to `present()`, the state is set back to `ready_to_present` + * After a successful call to `recreate()`, the state is set back to `ready_to_acquire` + * Note that a successful call indicates that the return value didn't produce an error *and* the `out_of_date` parameter + *in `SwapchainAcquireInfo` or `SwapchainSubmitInfo` is false. + * + * Type Characteristics: + * SwapchainManager contains a default constructor, allowing it to be created in null state and initialized at a later + *point. SwapchainManager is a move-only type, due to it owning various Vulkan objects. Therefore it will make any + *struct or class that has a SwapchainManager member into a move-only type. + * + * Informative Details - Semaphores: + * SwapchainManager keeps all semaphore management internal, thus they are not an aspect a user + * needs to be involved with. However, it is possible to add additional wait and signal semaphores + * in the call to vkQueueSubmit if so desired by passing in the semaphores into `submit`. + **/ +class SwapchainManager { + public: + static Result create(SwapchainBuilder const& builder) noexcept; + + explicit SwapchainManager() = default; + ~SwapchainManager() noexcept; + void destroy() noexcept; + + SwapchainManager(SwapchainManager const& other) = delete; + SwapchainManager& operator=(SwapchainManager const& other) = delete; + SwapchainManager(SwapchainManager&& other) noexcept; + SwapchainManager& operator=(SwapchainManager&& other) noexcept; + + // Primary API + + // Get a VkImageView handle to use in rendering + Result acquire_image() noexcept; + + Result cancel_acquire_frame() noexcept; + Result cancel_present_frame() noexcept; + + Result present() noexcept; + + // Recreate the swapchain, putting currently in-use internal resources in a delete queue + Result recreate(uint32_t width = 0, uint32_t height = 0) noexcept; + + // Get info about the swapchain + Result get_info() noexcept; + + // Get access to the swapchain and resources associated with it + Result get_swapchain_resources() noexcept; + + // Access the internal builder. This is how an application can alter how the swapchain is recreated. + SwapchainBuilder& get_builder() noexcept; + + private: + enum class Status { + ready_to_acquire, + ready_to_present, + expired, // needs to be recreated + destroyed, // no longer usable + }; + VkDevice device = VK_NULL_HANDLE; + struct Details { + vkb::SwapchainBuilder builder; + vkb::Swapchain current_swapchain; + SwapchainResources swapchain_resources; + detail::SemaphoreManager semaphore_manager; + DeletionQueue delete_queue; + uint32_t instance_version = VKB_VK_API_VERSION_1_0; + Status current_status = Status::ready_to_acquire; + VkQueue graphics_queue{}; + VkQueue present_queue{}; + SwapchainInfo current_info{}; + uint32_t current_image_index = detail::INDEX_MAX_VALUE; + PFN_vkGetDeviceQueue fp_vkGetDeviceQueue{}; + PFN_vkAcquireNextImageKHR fp_vkAcquireNextImageKHR{}; + PFN_vkQueuePresentKHR fp_vkQueuePresentKHR{}; + PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR{}; + PFN_vkCreateImageView fp_vkCreateImageView{}; + PFN_vkQueueSubmit fp_vkQueueSubmit{}; + } detail; + + explicit SwapchainManager(SwapchainBuilder const& builder, + Swapchain swapchain, + SwapchainResources resources, + PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR, + PFN_vkCreateImageView fp_vkCreateImageView) noexcept; + + void update_swapchain_info() noexcept; + + static Result create_swapchain_resources(vkb::Swapchain swapchain, + uint32_t instance_version, + PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR, + PFN_vkCreateImageView fp_vkCreateImageView) noexcept; + + VkResult WaitForSemaphore(VkSemaphore semaphore) noexcept; +}; +} // namespace vkb diff --git a/tests/common.h b/tests/common.h index c8d0bdc..5bdfb89 100644 --- a/tests/common.h +++ b/tests/common.h @@ -19,20 +19,23 @@ #include "GLFW/glfw3.h" #include "../src/VkBootstrap.h" +#include "../src/VkSwapchainManager.h" + +const int default_window_width = 512; +const int default_window_height = 512; GLFWwindow* create_window_glfw(const char* window_name = "", bool resize = true) { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - if (!resize) glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + glfwWindowHint(GLFW_RESIZABLE, resize ? GLFW_TRUE : GLFW_FALSE); - return glfwCreateWindow(1024, 1024, window_name, NULL, NULL); + return glfwCreateWindow(default_window_width, default_window_height, window_name, NULL, NULL); } void destroy_window_glfw(GLFWwindow* window) { glfwDestroyWindow(window); glfwTerminate(); } -VkSurfaceKHR create_surface_glfw( - VkInstance instance, GLFWwindow* window, VkAllocationCallbacks* allocator = nullptr) { +VkSurfaceKHR create_surface_glfw(VkInstance instance, GLFWwindow* window, VkAllocationCallbacks* allocator = nullptr) { VkSurfaceKHR surface = VK_NULL_HANDLE; VkResult err = glfwCreateWindowSurface(instance, window, allocator, &surface); if (err) { @@ -70,11 +73,9 @@ struct VulkanLibrary { #endif if (!library) return; #if defined(__linux__) || defined(__APPLE__) - vkGetInstanceProcAddr = - reinterpret_cast(dlsym(library, "vkGetInstanceProcAddr")); + vkGetInstanceProcAddr = reinterpret_cast(dlsym(library, "vkGetInstanceProcAddr")); #elif defined(_WIN32) - vkGetInstanceProcAddr = - reinterpret_cast(GetProcAddress(library, "vkGetInstanceProcAddr")); + vkGetInstanceProcAddr = reinterpret_cast(GetProcAddress(library, "vkGetInstanceProcAddr")); #endif } @@ -87,82 +88,5 @@ struct VulkanLibrary { library = 0; } - void init(VkInstance instance) { - vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr(instance, "vkGetDeviceProcAddr"); - vkDestroySurfaceKHR = (PFN_vkDestroySurfaceKHR)vkGetInstanceProcAddr(instance, "vkDestroySurfaceKHR"); - } - - void init(VkDevice device) { - vkCreateRenderPass = (PFN_vkCreateRenderPass)vkGetDeviceProcAddr(device, "vkCreateRenderPass"); - vkCreateShaderModule = (PFN_vkCreateShaderModule)vkGetDeviceProcAddr(device, "vkCreateShaderModule"); - vkCreatePipelineLayout = - (PFN_vkCreatePipelineLayout)vkGetDeviceProcAddr(device, "vkCreatePipelineLayout"); - vkCreateGraphicsPipelines = - (PFN_vkCreateGraphicsPipelines)vkGetDeviceProcAddr(device, "vkCreateGraphicsPipelines"); - vkDestroyShaderModule = (PFN_vkDestroyShaderModule)vkGetDeviceProcAddr(device, "vkDestroyShaderModule"); - vkCreateFramebuffer = (PFN_vkCreateFramebuffer)vkGetDeviceProcAddr(device, "vkCreateFramebuffer"); - vkCreateCommandPool = (PFN_vkCreateCommandPool)vkGetDeviceProcAddr(device, "vkCreateCommandPool"); - vkAllocateCommandBuffers = - (PFN_vkAllocateCommandBuffers)vkGetDeviceProcAddr(device, "vkAllocateCommandBuffers"); - vkBeginCommandBuffer = (PFN_vkBeginCommandBuffer)vkGetDeviceProcAddr(device, "vkBeginCommandBuffer"); - vkEndCommandBuffer = (PFN_vkEndCommandBuffer)vkGetDeviceProcAddr(device, "vkEndCommandBuffer"); - vkCmdSetViewport = (PFN_vkCmdSetViewport)vkGetDeviceProcAddr(device, "vkCmdSetViewport"); - vkCmdSetScissor = (PFN_vkCmdSetScissor)vkGetDeviceProcAddr(device, "vkCmdSetScissor"); - vkCmdBeginRenderPass = (PFN_vkCmdBeginRenderPass)vkGetDeviceProcAddr(device, "vkCmdBeginRenderPass"); - vkCmdEndRenderPass = (PFN_vkCmdEndRenderPass)vkGetDeviceProcAddr(device, "vkCmdEndRenderPass"); - vkCmdBindPipeline = (PFN_vkCmdBindPipeline)vkGetDeviceProcAddr(device, "vkCmdBindPipeline"); - vkCmdDraw = (PFN_vkCmdDraw)vkGetDeviceProcAddr(device, "vkCmdDraw"); - vkCreateSemaphore = (PFN_vkCreateSemaphore)vkGetDeviceProcAddr(device, "vkCreateSemaphore"); - vkCreateFence = (PFN_vkCreateFence)vkGetDeviceProcAddr(device, "vkCreateFence"); - vkDeviceWaitIdle = (PFN_vkDeviceWaitIdle)vkGetDeviceProcAddr(device, "vkDeviceWaitIdle"); - vkDestroyCommandPool = (PFN_vkDestroyCommandPool)vkGetDeviceProcAddr(device, "vkDestroyCommandPool"); - vkDestroyFramebuffer = (PFN_vkDestroyFramebuffer)vkGetDeviceProcAddr(device, "vkDestroyFramebuffer"); - vkWaitForFences = (PFN_vkWaitForFences)vkGetDeviceProcAddr(device, "vkWaitForFences"); - vkAcquireNextImageKHR = (PFN_vkAcquireNextImageKHR)vkGetDeviceProcAddr(device, "vkAcquireNextImageKHR"); - vkResetFences = (PFN_vkResetFences)vkGetDeviceProcAddr(device, "vkResetFences"); - vkQueueSubmit = (PFN_vkQueueSubmit)vkGetDeviceProcAddr(device, "vkQueueSubmit"); - vkQueuePresentKHR = (PFN_vkQueuePresentKHR)vkGetDeviceProcAddr(device, "vkQueuePresentKHR"); - vkDestroySemaphore = (PFN_vkDestroySemaphore)vkGetDeviceProcAddr(device, "vkDestroySemaphore"); - vkDestroyFence = (PFN_vkDestroyFence)vkGetDeviceProcAddr(device, "vkDestroyFence"); - vkDestroyPipeline = (PFN_vkDestroyPipeline)vkGetDeviceProcAddr(device, "vkDestroyPipeline"); - vkDestroyPipelineLayout = - (PFN_vkDestroyPipelineLayout)vkGetDeviceProcAddr(device, "vkDestroyPipelineLayout"); - vkDestroyRenderPass = (PFN_vkDestroyRenderPass)vkGetDeviceProcAddr(device, "vkDestroyRenderPass"); - } - PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = VK_NULL_HANDLE; - PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = VK_NULL_HANDLE; - - PFN_vkCreateRenderPass vkCreateRenderPass = VK_NULL_HANDLE; - PFN_vkCreateShaderModule vkCreateShaderModule = VK_NULL_HANDLE; - PFN_vkCreatePipelineLayout vkCreatePipelineLayout = VK_NULL_HANDLE; - PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines = VK_NULL_HANDLE; - PFN_vkDestroyShaderModule vkDestroyShaderModule = VK_NULL_HANDLE; - PFN_vkCreateFramebuffer vkCreateFramebuffer = VK_NULL_HANDLE; - PFN_vkCreateCommandPool vkCreateCommandPool = VK_NULL_HANDLE; - PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers = VK_NULL_HANDLE; - PFN_vkBeginCommandBuffer vkBeginCommandBuffer = VK_NULL_HANDLE; - PFN_vkEndCommandBuffer vkEndCommandBuffer = VK_NULL_HANDLE; - PFN_vkCmdSetViewport vkCmdSetViewport = VK_NULL_HANDLE; - PFN_vkCmdSetScissor vkCmdSetScissor = VK_NULL_HANDLE; - PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass = VK_NULL_HANDLE; - PFN_vkCmdEndRenderPass vkCmdEndRenderPass = VK_NULL_HANDLE; - PFN_vkCmdBindPipeline vkCmdBindPipeline = VK_NULL_HANDLE; - PFN_vkCmdDraw vkCmdDraw = VK_NULL_HANDLE; - PFN_vkCreateSemaphore vkCreateSemaphore = VK_NULL_HANDLE; - PFN_vkCreateFence vkCreateFence = VK_NULL_HANDLE; - PFN_vkDeviceWaitIdle vkDeviceWaitIdle = VK_NULL_HANDLE; - PFN_vkDestroyCommandPool vkDestroyCommandPool = VK_NULL_HANDLE; - PFN_vkDestroyFramebuffer vkDestroyFramebuffer = VK_NULL_HANDLE; - PFN_vkWaitForFences vkWaitForFences = VK_NULL_HANDLE; - PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR = VK_NULL_HANDLE; - PFN_vkResetFences vkResetFences = VK_NULL_HANDLE; - PFN_vkQueueSubmit vkQueueSubmit = VK_NULL_HANDLE; - PFN_vkQueuePresentKHR vkQueuePresentKHR = VK_NULL_HANDLE; - PFN_vkDestroySemaphore vkDestroySemaphore = VK_NULL_HANDLE; - PFN_vkDestroyFence vkDestroyFence = VK_NULL_HANDLE; - PFN_vkDestroyPipeline vkDestroyPipeline = VK_NULL_HANDLE; - PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout = VK_NULL_HANDLE; - PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR = VK_NULL_HANDLE; - PFN_vkDestroyRenderPass vkDestroyRenderPass = VK_NULL_HANDLE; };