diff --git a/cpp/examples/Shakti/Vulkan/hello_vulkan_image/main.cpp b/cpp/examples/Shakti/Vulkan/hello_vulkan_image/main.cpp index 0da6d39c8..7a859ec20 100644 --- a/cpp/examples/Shakti/Vulkan/hello_vulkan_image/main.cpp +++ b/cpp/examples/Shakti/Vulkan/hello_vulkan_image/main.cpp @@ -95,6 +95,13 @@ class VulkanImageRenderer : public kvk::GraphicsBackend const std::filesystem::path& program_dir_path, const bool debug_vulkan = true) { + // Set the GLFW callbacks. + { + _window = window; + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebuffer_resize_callback); + } + // General vulkan context objects. init_instance(app_name, debug_vulkan); init_surface(window); @@ -102,7 +109,7 @@ class VulkanImageRenderer : public kvk::GraphicsBackend init_device_and_queues(); init_swapchain(window); init_render_pass(); - init_framebuffers(); + init_swapchain_fbos(); // Graphics pipeline. const auto shader_dir_path = @@ -558,7 +565,8 @@ class VulkanImageRenderer : public kvk::GraphicsBackend const VkDescriptorSet& descriptor_set) -> void { - SARA_DEBUG << "[VK] Recording graphics command buffer...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Recording graphics command buffer...\n"; auto begin_info = VkCommandBufferBeginInfo{}; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.flags = 0; @@ -587,7 +595,8 @@ class VulkanImageRenderer : public kvk::GraphicsBackend render_pass_begin_info.pClearValues = &clear_white_color; } - SARA_DEBUG << "[VK] Begin render pass...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Begin render pass...\n"; vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); { @@ -632,7 +641,8 @@ class VulkanImageRenderer : public kvk::GraphicsBackend static_cast(indices.size()), 1, 0, 0, 0); } - SARA_DEBUG << "[VK] End render pass...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] End render pass...\n"; vkCmdEndRenderPass(command_buffer); status = vkEndCommandBuffer(command_buffer); @@ -647,7 +657,8 @@ class VulkanImageRenderer : public kvk::GraphicsBackend static constexpr auto forever = std::numeric_limits::max(); auto result = VkResult{}; - SARA_CHECK(_current_frame); + if constexpr (_verbose) + SARA_CHECK(_current_frame); // The number of images in-flight is the number of swapchain images. // And there are as many fences as swapchain images. @@ -662,7 +673,8 @@ class VulkanImageRenderer : public kvk::GraphicsBackend // // The function call `vkQueueSubmit(...)` at the end of this `draw_frame` // method uses this `_in_flight_fences[_current_frame]` fence. - SARA_DEBUG << "[VK] Waiting for the render fence to signal...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Waiting for the render fence to signal...\n"; _render_fences[_current_frame].wait(forever); // This function call blocks. // @@ -670,7 +682,8 @@ class VulkanImageRenderer : public kvk::GraphicsBackend // flow on the CPU side. // Acquire the next image ready to be rendered. - SARA_DEBUG << "[VK] Acquiring the next image ready to be rendered...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Acquiring the next image ready to be rendered...\n"; auto index_of_next_image_to_render = std::uint32_t{}; result = vkAcquireNextImageKHR( // _device, // @@ -680,40 +693,42 @@ class VulkanImageRenderer : public kvk::GraphicsBackend VK_NULL_HANDLE, // fence to signal &index_of_next_image_to_render); // Sanity check. - if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) - throw std::runtime_error("failed to acquire the next swapchain image!"); -#if defined(RECREATE_SWAPCHAIN_IMPLEMENTED) - // Recreate the swapchain if the size of the window surface has changed. if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreate_swapchain(); return; } -#endif - SARA_CHECK(index_of_next_image_to_render); + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) + throw std::runtime_error("Failed to acquire the next swapchain image!"); + if constexpr (_verbose) + SARA_CHECK(index_of_next_image_to_render); update_mvp_uniform(index_of_next_image_to_render); // Reset the signaled fence associated to the current frame to an // unsignaled state. So that the GPU can reuse it to signal. - SARA_DEBUG << "[VK] Resetting for the render fence...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Resetting for the render fence...\n"; _render_fences[_current_frame].reset(); // Reset the command buffer associated to the current frame. - SARA_DEBUG << "[VK] Resetting for the command buffer...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Resetting for the command buffer...\n"; _graphics_cmd_bufs.reset(_current_frame, /*VkCommandBufferResetFlagBits*/ 0); // Record the draw command to be performed on this swapchain image. - SARA_CHECK(_framebuffers.fbs.size()); + if constexpr (_verbose) + SARA_CHECK(_swapchain_fbos.fbs.size()); const auto& descriptor_set = static_cast( _desc_sets[index_of_next_image_to_render]); - record_graphics_command_buffer(_graphics_cmd_bufs[_current_frame], - _framebuffers[index_of_next_image_to_render], - descriptor_set); + record_graphics_command_buffer( + _graphics_cmd_bufs[_current_frame], + _swapchain_fbos[index_of_next_image_to_render], descriptor_set); // Submit the draw command to the graphics queue. - SARA_DEBUG << "[VK] Specifying the graphics command submission...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Specifying the graphics command submission...\n"; auto submit_info = VkSubmitInfo{}; submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; @@ -791,17 +806,17 @@ class VulkanImageRenderer : public kvk::GraphicsBackend present_info.pImageIndices = &index_of_next_image_to_render; result = vkQueuePresentKHR(_present_queue, &present_info); - if (result != VK_SUCCESS) - throw std::runtime_error{fmt::format( - "failed to present the swapchain image {}!", _current_frame)}; -#if 0 if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || - framebufferResized) + _framebuffer_resized) { - framebuffer_resized = false; + _framebuffer_resized = false; recreate_swapchain(); } -#endif + else if (result != VK_SUCCESS) + { + throw std::runtime_error{fmt::format( + "failed to present the swapchain image {}!", _current_frame)}; + } // Update the current frame to the next one for the next `draw_frame` // call. @@ -809,8 +824,47 @@ class VulkanImageRenderer : public kvk::GraphicsBackend _current_frame = (_current_frame + 1) % max_frames_in_flight; } +private: /* Swapchain recreation */ + static auto framebuffer_resize_callback(GLFWwindow* window, + [[maybe_unused]] const int width, + [[maybe_unused]] const int height) + -> void + { + auto app = reinterpret_cast( + glfwGetWindowUserPointer(window)); + app->_framebuffer_resized = true; + } + + auto recreate_swapchain() -> void + { + auto w = int{}; + auto h = int{}; + while (w == 0 || h == 0) + { + glfwGetFramebufferSize(_window, &w, &h); + glfwWaitEvents(); + } + vkDeviceWaitIdle(_device); + + // It is not possible to create two swapchains apparently, so we have to + // destroy the current swapchain. + if constexpr (_verbose) + SARA_DEBUG << "DESTROYING THE CURRENT SWAPCHAIN...\n"; + _swapchain_fbos.destroy(); + _swapchain.destroy(); + + if constexpr (_verbose) + SARA_DEBUG << "RECREATING THE SWAPCHAIN (with the correct image " + "dimensions)...\n"; + init_swapchain(_window); + init_swapchain_fbos(); + } + private: + GLFWwindow* _window = nullptr; int _current_frame = 0; + bool _framebuffer_resized = false; + static constexpr bool _verbose = false; sara::VideoStream _vstream; diff --git a/cpp/examples/Shakti/Vulkan/hello_vulkan_triangle/main.cpp b/cpp/examples/Shakti/Vulkan/hello_vulkan_triangle/main.cpp index fe9926d40..ca1101eb5 100644 --- a/cpp/examples/Shakti/Vulkan/hello_vulkan_triangle/main.cpp +++ b/cpp/examples/Shakti/Vulkan/hello_vulkan_triangle/main.cpp @@ -60,13 +60,20 @@ class VulkanTriangleRenderer : public kvk::GraphicsBackend const std::filesystem::path& shader_dirpath, const bool debug_vulkan = true) { + // Set the GLFW callbacks. + { + _window = window; + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebuffer_resize_callback); + } + init_instance(app_name, debug_vulkan); init_surface(window); init_physical_device(); init_device_and_queues(); init_swapchain(window); init_render_pass(); - init_framebuffers(); + init_swapchain_fbos(); init_graphics_pipeline(window, // shader_dirpath / "vert.spv", shader_dirpath / "frag.spv"); @@ -290,7 +297,8 @@ class VulkanTriangleRenderer : public kvk::GraphicsBackend static constexpr auto forever = std::numeric_limits::max(); auto result = VkResult{}; - SARA_CHECK(_current_frame); + if constexpr (_verbose) + SARA_CHECK(_current_frame); // The number of images in-flight is the number of swapchain images. // And there are as many fences as swapchain images. @@ -301,11 +309,13 @@ class VulkanTriangleRenderer : public kvk::GraphicsBackend // get synchronization machinery to work correctly. // - the current frame index is 0; - // Wait for the GPU signal that the current frame becomes available. + // Wait for the GPU signal notifying that the current frame becomes + // available. // // The function call `vkQueueSubmit(...)` at the end of this `draw_frame` // method uses this `_in_flight_fences[_current_frame]` fence. - SARA_DEBUG << "[VK] Waiting for the render fence to signal...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Waiting for the render fence to signal...\n"; _render_fences[_current_frame].wait(forever); // This function call blocks. // @@ -313,7 +323,8 @@ class VulkanTriangleRenderer : public kvk::GraphicsBackend // flow on the CPU side. // Acquire the next image ready to be rendered. - SARA_DEBUG << "[VK] Acquiring the next image ready to be rendered...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Acquiring the next image ready to be rendered...\n"; auto index_of_next_image_to_render = std::uint32_t{}; result = vkAcquireNextImageKHR( // _device, // @@ -322,41 +333,43 @@ class VulkanTriangleRenderer : public kvk::GraphicsBackend _image_available_semaphores[_current_frame], // semaphore to signal VK_NULL_HANDLE, // fence to signal &index_of_next_image_to_render); - // Sanity check. - if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) - throw std::runtime_error("failed to acquire the next swapchain image!"); -#if defined(RECREATE_SWAPCHAIN_IMPLEMENTED) - // Recreate the swapchain if the size of the window surface has changed. + if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreate_swapchain(); return; } -#endif - SARA_CHECK(index_of_next_image_to_render); + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) + throw std::runtime_error("Failed to acquire the next swapchain image!"); + if constexpr (_verbose) + SARA_CHECK(index_of_next_image_to_render); update_mvp_uniform(index_of_next_image_to_render); // Reset the signaled fence associated to the current frame to an // unsignaled state. So that the GPU can reuse it to signal. - SARA_DEBUG << "[VK] Resetting for the render fence...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Resetting for the render fence...\n"; _render_fences[_current_frame].reset(); // Reset the command buffer associated to the current frame. - SARA_DEBUG << "[VK] Resetting for the command buffer...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Resetting for the command buffer...\n"; _graphics_cmd_bufs.reset(_current_frame, /*VkCommandBufferResetFlagBits*/ 0); // Record the draw command to be performed on this swapchain image. - SARA_CHECK(_framebuffers.fbs.size()); + if constexpr (_verbose) + SARA_CHECK(_swapchain_fbos.fbs.size()); const auto& descriptor_set = static_cast( _ubo_desc_sets[index_of_next_image_to_render]); - record_graphics_command_buffer(_graphics_cmd_bufs[_current_frame], - _framebuffers[index_of_next_image_to_render], - descriptor_set); + record_graphics_command_buffer( + _graphics_cmd_bufs[_current_frame], + _swapchain_fbos[index_of_next_image_to_render], descriptor_set); // Submit the draw command to the graphics queue. - SARA_DEBUG << "[VK] Specifying the graphics command submission...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Specifying the graphics command submission...\n"; auto submit_info = VkSubmitInfo{}; submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; @@ -434,17 +447,17 @@ class VulkanTriangleRenderer : public kvk::GraphicsBackend present_info.pImageIndices = &index_of_next_image_to_render; result = vkQueuePresentKHR(_present_queue, &present_info); - if (result != VK_SUCCESS) - throw std::runtime_error{fmt::format( - "failed to present the swapchain image {}!", _current_frame)}; -#if 0 if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || - framebufferResized) + _framebuffer_resized) { - framebuffer_resized = false; + _framebuffer_resized = false; recreate_swapchain(); } -#endif + else if (result != VK_SUCCESS) + { + throw std::runtime_error{fmt::format( + "failed to present the swapchain image {}!", _current_frame)}; + } // Update the current frame to the next one for the next `draw_frame` // call. @@ -471,7 +484,8 @@ class VulkanTriangleRenderer : public kvk::GraphicsBackend const VkDescriptorSet& descriptor_set) -> void { - SARA_DEBUG << "[VK] Recording graphics command buffer...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Recording graphics command buffer...\n"; auto begin_info = VkCommandBufferBeginInfo{}; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.flags = 0; @@ -500,7 +514,8 @@ class VulkanTriangleRenderer : public kvk::GraphicsBackend render_pass_begin_info.pClearValues = &clear_white_color; } - SARA_DEBUG << "[VK] Begin render pass...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] Begin render pass...\n"; vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); { @@ -545,7 +560,8 @@ class VulkanTriangleRenderer : public kvk::GraphicsBackend static_cast(indices.size()), 1, 0, 0, 0); } - SARA_DEBUG << "[VK] End render pass...\n"; + if constexpr (_verbose) + SARA_DEBUG << "[VK] End render pass...\n"; vkCmdEndRenderPass(command_buffer); status = vkEndCommandBuffer(command_buffer); @@ -555,8 +571,47 @@ class VulkanTriangleRenderer : public kvk::GraphicsBackend static_cast(status))}; } +private: /* Swapchain recreation */ + static auto framebuffer_resize_callback(GLFWwindow* window, + [[maybe_unused]] const int width, + [[maybe_unused]] const int height) + -> void + { + auto app = reinterpret_cast( + glfwGetWindowUserPointer(window)); + app->_framebuffer_resized = true; + } + + auto recreate_swapchain() -> void + { + auto w = int{}; + auto h = int{}; + while (w == 0 || h == 0) + { + glfwGetFramebufferSize(_window, &w, &h); + glfwWaitEvents(); + } + vkDeviceWaitIdle(_device); + + // It is not possible to create two swapchains apparently, so we have to + // destroy the current swapchain. + if constexpr (_verbose) + SARA_DEBUG << "DESTROYING THE CURRENT SWAPCHAIN...\n"; + _swapchain_fbos.destroy(); + _swapchain.destroy(); + + if constexpr (_verbose) + SARA_DEBUG << "RECREATING THE SWAPCHAIN (with the correct image " + "dimensions)...\n"; + init_swapchain(_window); + init_swapchain_fbos(); + } + private: + GLFWwindow* _window = nullptr; int _current_frame = 0; + bool _framebuffer_resized = false; + static constexpr bool _verbose = false; // Geometry data (quad) svk::Buffer _vbo; diff --git a/cpp/src/DO/Shakti/Vulkan/Framebuffer.hpp b/cpp/src/DO/Shakti/Vulkan/Framebuffer.hpp index b380f44dd..d66edf6e0 100644 --- a/cpp/src/DO/Shakti/Vulkan/Framebuffer.hpp +++ b/cpp/src/DO/Shakti/Vulkan/Framebuffer.hpp @@ -12,6 +12,7 @@ #pragma once #include +#include namespace DO::Kalpana::Vulkan { @@ -56,9 +57,16 @@ namespace DO::Kalpana::Vulkan { } ~FramebufferSequence() + { + destroy(); + } + + auto destroy() -> void { for (const auto fb : fbs) vkDestroyFramebuffer(device, fb, nullptr); + fbs.clear(); + device = VK_NULL_HANDLE; } auto operator=(const FramebufferSequence&) -> FramebufferSequence& = delete; @@ -85,7 +93,7 @@ namespace DO::Kalpana::Vulkan { fbs.swap(other.fbs); } - VkDevice device = nullptr; + VkDevice device = VK_NULL_HANDLE; std::vector fbs; }; diff --git a/cpp/src/DO/Shakti/Vulkan/GraphicsBackend.cpp b/cpp/src/DO/Shakti/Vulkan/GraphicsBackend.cpp index fb39805d9..4dccffe90 100644 --- a/cpp/src/DO/Shakti/Vulkan/GraphicsBackend.cpp +++ b/cpp/src/DO/Shakti/Vulkan/GraphicsBackend.cpp @@ -121,9 +121,9 @@ auto GraphicsBackend::init_swapchain(GLFWwindow* window) -> void _swapchain = Swapchain{_physical_device, _device, _surface, window}; } -auto GraphicsBackend::init_framebuffers() -> void +auto GraphicsBackend::init_swapchain_fbos() -> void { - _framebuffers = FramebufferSequence{_swapchain, _render_pass}; + _swapchain_fbos = FramebufferSequence{_swapchain, _render_pass}; } auto GraphicsBackend::init_render_pass() -> void diff --git a/cpp/src/DO/Shakti/Vulkan/GraphicsBackend.hpp b/cpp/src/DO/Shakti/Vulkan/GraphicsBackend.hpp index 04b60ca7c..2063a0c26 100644 --- a/cpp/src/DO/Shakti/Vulkan/GraphicsBackend.hpp +++ b/cpp/src/DO/Shakti/Vulkan/GraphicsBackend.hpp @@ -57,7 +57,7 @@ namespace DO::Kalpana::Vulkan { auto init_swapchain(GLFWwindow* window) -> void; - auto init_framebuffers() -> void; + auto init_swapchain_fbos() -> void; auto init_render_pass() -> void; @@ -100,7 +100,7 @@ namespace DO::Kalpana::Vulkan { RenderPass _render_pass; // Framebuffers are wrapped swapchain image views... (somehow) // and associated to a render pass. - FramebufferSequence _framebuffers; + FramebufferSequence _swapchain_fbos; GraphicsPipeline _graphics_pipeline; diff --git a/cpp/src/DO/Shakti/Vulkan/Swapchain.hpp b/cpp/src/DO/Shakti/Vulkan/Swapchain.hpp index d8c74b0bc..40472f914 100644 --- a/cpp/src/DO/Shakti/Vulkan/Swapchain.hpp +++ b/cpp/src/DO/Shakti/Vulkan/Swapchain.hpp @@ -11,6 +11,7 @@ #pragma once +#include #define GLFW_INCLUDE_VULKAN #include @@ -106,16 +107,7 @@ namespace DO::Kalpana::Vulkan { ~Swapchain() { - if (handle == nullptr) - return; - - SARA_DEBUG << "[VK] Destroying swapchain image views...\n"; - for (const auto image_view : image_views) - vkDestroyImageView(device_handle, image_view, nullptr); - - SARA_DEBUG << fmt::format("[VK] Destroying swapchain: {}...\n", - fmt::ptr(handle)); - vkDestroySwapchainKHR(device_handle, handle, nullptr); + destroy(); } auto operator=(const Swapchain&) -> Swapchain& = delete; @@ -138,6 +130,23 @@ namespace DO::Kalpana::Vulkan { image_views.swap(other.image_views); } + auto destroy() -> void + { + if (handle == nullptr) + return; + + SARA_DEBUG << "[VK] Destroying swapchain image views...\n"; + for (const auto image_view : image_views) + vkDestroyImageView(device_handle, image_view, nullptr); + image_views.clear(); + + SARA_DEBUG << fmt::format("[VK] Destroying swapchain: {}...\n", + fmt::ptr(handle)); + vkDestroySwapchainKHR(device_handle, handle, nullptr); + device_handle = VK_NULL_HANDLE; + handle = VK_NULL_HANDLE; + } + public: /* initialization methods */ auto init_swapchain(const Shakti::Vulkan::PhysicalDevice& physical_device, const Surface& surface, GLFWwindow* window) -> void @@ -259,13 +268,15 @@ namespace DO::Kalpana::Vulkan { //! We want a surface with the BGRA 32bit pixel format preferably, otherwise //! choose the first available surface format. auto choose_swap_surface_format( - const std::vector& available_formats) const - -> VkSurfaceFormatKHR + const std::vector& available_formats, + const VkFormat preferred_pixel_format = VK_FORMAT_B8G8R8A8_SRGB, + const VkColorSpaceKHR preferred_color_space = + VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) const -> VkSurfaceFormatKHR { for (const auto& available_format : available_formats) { - if (available_format.format == VK_FORMAT_B8G8R8A8_SRGB && - available_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) + if (available_format.format == preferred_pixel_format && + available_format.colorSpace == preferred_color_space) return available_format; } diff --git a/cpp/test/Shakti/Vulkan/test_vulkan_graphics_backend.cpp b/cpp/test/Shakti/Vulkan/test_vulkan_graphics_backend.cpp index 1d5332897..1287a0b98 100644 --- a/cpp/test/Shakti/Vulkan/test_vulkan_graphics_backend.cpp +++ b/cpp/test/Shakti/Vulkan/test_vulkan_graphics_backend.cpp @@ -74,7 +74,7 @@ class MyGraphicsBackend : public kvk::GraphicsBackend init_device_and_queues(); init_swapchain(window); init_render_pass(); - init_framebuffers(); + init_swapchain_fbos(); init_graphics_pipeline(window, vertex_shader_path, fragment_shader_path); init_command_pool_and_buffers(); init_synchronization_objects();