From ef2f728cd6e0ad09dbfcc010467db706f1ed0032 Mon Sep 17 00:00:00 2001 From: light7734 Date: Thu, 2 Oct 2025 23:53:28 +0330 Subject: [PATCH] feat(renderer): swapchain recreation, bug fixes & other stuff --- modules/renderer/private/system.cpp | 99 +++++++ modules/renderer/private/system.test.cpp | 3 + .../renderer/private/vk/context/context.cpp | 3 + .../renderer/private/vk/context/context.hpp | 6 + .../renderer/private/vk/context/device.cpp | 10 + .../renderer/private/vk/context/instance.cpp | 2 +- .../renderer/private/vk/context/surface.cpp | 19 +- .../renderer/private/vk/context/surface.hpp | 9 +- .../renderer/private/vk/context/swapchain.cpp | 30 +- .../renderer/private/vk/context/swapchain.hpp | 35 ++- .../renderer/private/vk/debug/validation.hpp | 55 +++- modules/renderer/private/vk/renderer/pass.hpp | 34 +-- .../renderer/private/vk/renderer/renderer.hpp | 258 ++++++++++++------ .../private/vk/renderer/renderer.test.cpp | 52 +++- modules/renderer/private/vk/test_utils.hpp | 10 + modules/renderer/public/system.hpp | 34 ++- 16 files changed, 499 insertions(+), 160 deletions(-) diff --git a/modules/renderer/private/system.cpp b/modules/renderer/private/system.cpp index 21e3039..d00e7e9 100644 --- a/modules/renderer/private/system.cpp +++ b/modules/renderer/private/system.cpp @@ -1,7 +1,97 @@ #include +#include +#include +#include +#include + +using ::lt::assets::ShaderAsset; + +namespace lt::renderer::vk { +class ValidationObserver +{ + using Messenger = lt::renderer::vk::Messenger; + using enum Messenger::Type; + using enum Messenger::Severity; + +public: + ValidationObserver() + : m_messenger( + Messenger::CreateInfo { + .severity = static_cast(warning | error), + .type = lt::renderer::vk::Messenger::all_type, + .callback = &callback, + .user_data = &m_had_any_messages, + } + ) + { + } + + ~ValidationObserver() + { + } + + [[nodiscard]] auto had_any_messages() const -> bool + { + return m_had_any_messages; + } + +private: + static void callback( + Messenger::Severity message_severity, + Messenger::Type message_type, + Messenger::CallbackData_T vulkan_data, + void *user_data + ) + { + std::ignore = message_severity; + std::ignore = message_type; + for (auto idx = 0; idx < vulkan_data->objectCount; ++idx) + { + auto object = vulkan_data->pObjects[idx]; + std::println( + "0x{:x}({}) = {}", + object.objectHandle, + string_VkObjectType(object.objectType), + object.pObjectName ? object.pObjectName : "unnamed" + ); + } + + std::println("Validation message: {}", vulkan_data->pMessage); + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + *(bool *)user_data = true; + } + + Messenger m_messenger; + bool m_had_any_messages = false; +}; +} // namespace lt::renderer::vk + namespace lt::renderer { +System::System(CreateInfo info) + : m_registry(std::move(info.registry)) + , m_stats(info.system_stats) + , m_context(create_scope(info.surface_entity)) +{ + m_validation_observer = new vk::ValidationObserver(); + // ensure(m_stats, "Failed to initialize system: null stats"); + ensure(m_registry, "Failed to initialize renderer system: null registry"); + + m_pass = create_ref( + *m_context, + ShaderAsset { "./data/test_assets/triangle.vert.asset" }, + ShaderAsset { "./data/test_assets/triangle.frag.asset" } + ); + + m_renderer = create_scope(*m_context, m_pass); +} + +System::~System() +{ +} + void System::on_register() { } @@ -12,6 +102,15 @@ void System::on_unregister() void System::tick(app::TickInfo tick) { + if (!m_renderer->draw(m_frame_idx)) + { + m_context->recreate_swapchain(); + m_renderer->replace_swapchain(m_context->swapchain()); + m_pass->replace_swapchain(m_context->swapchain()); + m_renderer->draw(m_frame_idx); + } + + m_frame_idx = (m_frame_idx + 1) % vk::Renderer::max_frames_in_flight; } } // namespace lt::renderer diff --git a/modules/renderer/private/system.test.cpp b/modules/renderer/private/system.test.cpp index 04f587f..a25c344 100644 --- a/modules/renderer/private/system.test.cpp +++ b/modules/renderer/private/system.test.cpp @@ -1,5 +1,8 @@ #include #include +#include +#include +#include #include #include diff --git a/modules/renderer/private/vk/context/context.cpp b/modules/renderer/private/vk/context/context.cpp index ad25078..576cc02 100644 --- a/modules/renderer/private/vk/context/context.cpp +++ b/modules/renderer/private/vk/context/context.cpp @@ -8,6 +8,9 @@ Context::Context(const ecs::Entity &surface_entity) , m_device(m_surface) , m_swapchain(m_device, m_surface) { + ensure(m_surface.vk(), "Failed to create vulkan context: null surface"); + ensure(m_device.vk(), "Failed to create vulkan context: null device"); + ensure(m_swapchain.vk(), "Failed to create vulkan context: null swapchain"); } } // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/context/context.hpp b/modules/renderer/private/vk/context/context.hpp index d046945..1240785 100644 --- a/modules/renderer/private/vk/context/context.hpp +++ b/modules/renderer/private/vk/context/context.hpp @@ -35,6 +35,12 @@ public: return m_swapchain; } + void recreate_swapchain() + { + m_swapchain.destroy(); + m_swapchain = Swapchain { m_device, m_surface }; + } + private: Surface m_surface; diff --git a/modules/renderer/private/vk/context/device.cpp b/modules/renderer/private/vk/context/device.cpp index 88c8993..9eba491 100644 --- a/modules/renderer/private/vk/context/device.cpp +++ b/modules/renderer/private/vk/context/device.cpp @@ -16,6 +16,16 @@ Device::Device(const Surface &surface) vk_get_device_queue(m_device, m_graphics_queue_family_index, 0, &m_graphics_queue); vk_get_device_queue(m_device, m_present_queue_family_index, 0, &m_present_queue); + + if (m_present_queue.get() == m_graphics_queue.get()) + { + set_object_name(m_device, m_present_queue.get(), "unified queue"); + } + else + { + set_object_name(m_device, m_graphics_queue.get(), "graphics queue"); + set_object_name(m_device, m_present_queue.get(), "present queue"); + } } Device::~Device() diff --git a/modules/renderer/private/vk/context/instance.cpp b/modules/renderer/private/vk/context/instance.cpp index a66b400..88082c6 100644 --- a/modules/renderer/private/vk/context/instance.cpp +++ b/modules/renderer/private/vk/context/instance.cpp @@ -147,7 +147,7 @@ void Instance::initialize_instance() const auto setting_thread_safety = VkBool32 { VK_TRUE }; const auto *setting_debug_action = ""; const auto setting_enable_message_limit = VkBool32 { VK_TRUE }; - const auto setting_duplicate_message_limit = uint32_t { 3u }; + const auto setting_duplicate_message_limit = uint32_t { UINT32_MAX }; auto setting_report_flags = std::array { "info", "warn", "perf", "error", "verbose", }; diff --git a/modules/renderer/private/vk/context/surface.cpp b/modules/renderer/private/vk/context/surface.cpp index d5833dd..05e378d 100644 --- a/modules/renderer/private/vk/context/surface.cpp +++ b/modules/renderer/private/vk/context/surface.cpp @@ -3,7 +3,7 @@ namespace lt::renderer::vk { -Surface::Surface(const ecs::Entity &surface_entity) +Surface::Surface(ecs::Entity surface_entity): m_surface_entity(surface_entity) { const auto &component = surface_entity.get(); @@ -18,12 +18,6 @@ Surface::Surface(const ecs::Entity &surface_entity) auto *instance = Instance::get(); auto result = vk_create_xlib_surface_khr(instance, &create_info, nullptr, &m_surface); - - const auto &[width, height] = component.get_resolution(); - m_framebuffer_size = { - .width = width, - .height = height, - }; } Surface::~Surface() @@ -34,4 +28,15 @@ Surface::~Surface() } } +[[nodiscard]] auto Surface::get_framebuffer_size() const -> VkExtent2D +{ + const auto &[width, height] = // + m_surface_entity.get().get_resolution(); + + return { + .width = width, + .height = height, + }; +} + } // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/context/surface.hpp b/modules/renderer/private/vk/context/surface.hpp index a7a5091..82abc39 100644 --- a/modules/renderer/private/vk/context/surface.hpp +++ b/modules/renderer/private/vk/context/surface.hpp @@ -9,7 +9,7 @@ namespace lt::renderer::vk { class Surface { public: - Surface(const ecs::Entity &surface_entity); + Surface(ecs::Entity surface_entity); ~Surface(); @@ -26,15 +26,12 @@ public: return m_surface; } - [[nodiscard]] auto get_framebuffer_size() const -> VkExtent2D - { - return m_framebuffer_size; - } + [[nodiscard]] auto get_framebuffer_size() const -> VkExtent2D; private: memory::NullOnMove m_surface = VK_NULL_HANDLE; - VkExtent2D m_framebuffer_size {}; + ecs::Entity m_surface_entity; }; } // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/context/swapchain.cpp b/modules/renderer/private/vk/context/swapchain.cpp index 9ebbbdd..114a62d 100644 --- a/modules/renderer/private/vk/context/swapchain.cpp +++ b/modules/renderer/private/vk/context/swapchain.cpp @@ -11,6 +11,7 @@ Swapchain::Swapchain(const Device &device, const Surface &surface) : m_device(device.vk()) , m_resolution(surface.get_framebuffer_size()) { + static auto idx = 0u; auto *physical_device = device.physical(); auto capabilities = VkSurfaceCapabilitiesKHR {}; @@ -55,15 +56,16 @@ Swapchain::Swapchain(const Device &device, const Surface &surface) vkc(vk_create_swapchain_khr(device.vk(), &create_info, nullptr, &m_swapchain)); vkc(vk_device_wait_idle(device.vk())); + set_object_name(device.vk(), m_swapchain.get(), "swapchain {}", idx++); auto image_count = uint32_t { 0u }; vk_get_swapchain_images_khr(device.vk(), m_swapchain, &image_count, nullptr); - m_swapchain_images.resize(image_count); - m_swapchain_image_views.resize(image_count); - vk_get_swapchain_images_khr(device.vk(), m_swapchain, &image_count, m_swapchain_images.data()); + m_images.resize(image_count); + m_image_views.resize(image_count); + vk_get_swapchain_images_khr(device.vk(), m_swapchain, &image_count, m_images.data()); - for (auto [image, view] : std::views::zip(m_swapchain_images, m_swapchain_image_views)) + for (auto [image, view] : std::views::zip(m_images, m_image_views)) { auto create_info = VkImageViewCreateInfo { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, @@ -87,29 +89,11 @@ Swapchain::Swapchain(const Device &device, const Surface &surface) vkc(vk_create_image_view(device.vk(), &create_info, nullptr, &view)); } - } Swapchain::~Swapchain() { - try - { - if (m_device) - { - vkc(vk_device_wait_idle(m_device)); - for (auto &view : m_swapchain_image_views) - { - vk_destroy_image_view(m_device, view, nullptr); - } - - vk_destroy_swapchain_khr(m_device, m_swapchain, nullptr); - } - } - catch (const std::exception &exp) - { - log_err("Failed to destroy swapchain:"); - log_err("\twhat: {}", exp.what()); - } + destroy(); } [[nodiscard]] auto Swapchain::get_optimal_image_count( diff --git a/modules/renderer/private/vk/context/swapchain.hpp b/modules/renderer/private/vk/context/swapchain.hpp index 93e5b07..70ef2b0 100644 --- a/modules/renderer/private/vk/context/swapchain.hpp +++ b/modules/renderer/private/vk/context/swapchain.hpp @@ -21,6 +21,28 @@ public: auto operator=(const Swapchain &) const -> Swapchain & = delete; + void destroy() + { + try + { + if (m_device) + { + vkc(vk_device_wait_idle(m_device)); + for (auto &view : m_image_views) + { + vk_destroy_image_view(m_device, view, nullptr); + } + + vk_destroy_swapchain_khr(m_device, m_swapchain, nullptr); + } + } + catch (const std::exception &exp) + { + log_err("Failed to destroy swapchain:"); + log_err("\twhat: {}", exp.what()); + } + } + [[nodiscard]] auto vk() const -> VkSwapchainKHR { return m_swapchain; @@ -36,10 +58,15 @@ public: return m_format; } + [[nodiscard]] auto get_image_count() const -> size_t + { + return m_images.size(); + } + [[nodiscard]] auto create_framebuffers_for_pass(VkRenderPass pass) const -> std::vector { - auto framebuffers = std::vector(m_swapchain_image_views.size()); + auto framebuffers = std::vector(m_image_views.size()); for (auto idx = 0u; auto &framebuffer : framebuffers) { @@ -47,7 +74,7 @@ public: .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = pass, .attachmentCount = 1u, - .pAttachments = &m_swapchain_image_views[idx++], + .pAttachments = &m_image_views[idx++], .width = m_resolution.width, .height = m_resolution.height, .layers = 1u @@ -69,9 +96,9 @@ private: memory::NullOnMove m_swapchain = VK_NULL_HANDLE; - std::vector m_swapchain_images; + std::vector m_images; - std::vector m_swapchain_image_views; + std::vector m_image_views; VkExtent2D m_resolution; diff --git a/modules/renderer/private/vk/debug/validation.hpp b/modules/renderer/private/vk/debug/validation.hpp index 3f7d900..af54c25 100644 --- a/modules/renderer/private/vk/debug/validation.hpp +++ b/modules/renderer/private/vk/debug/validation.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include + namespace lt::renderer::vk { @@ -8,10 +10,57 @@ inline void vkc(VkResult result) { if (result) { - throw std::runtime_error { - std::format("Vulkan call failed with result: {}", std::to_underlying(result)) - }; + throw std::runtime_error { std::format( + "Vulkan call failed with result: {}({})", + string_VkResult(result), + std::to_underlying(result) + ) }; } } +template +inline auto get_object_type(T object) -> VkObjectType +{ + if constexpr (std::is_same_v) + { + return VK_OBJECT_TYPE_QUEUE; + } + + if constexpr (std::is_same_v) + { + return VK_OBJECT_TYPE_FENCE; + } + + if constexpr (std::is_same_v) + { + return VK_OBJECT_TYPE_SEMAPHORE; + } + + if constexpr (std::is_same_v) + { + return VK_OBJECT_TYPE_SWAPCHAIN_KHR; + } + + static_assert("invalid type"); +} + +template +inline void set_object_name( + VkDevice device, + T object, + std::format_string fmt, + Args &&...args +) +{ + const auto name = std::format(fmt, std::forward(args)...); + auto info = VkDebugUtilsObjectNameInfoEXT { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, + .objectType = get_object_type(object), + .objectHandle = (uint64_t)(object), + .pObjectName = name.c_str(), + }; + + vk_set_debug_object_name(device, &info); +} + } // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/renderer/pass.hpp b/modules/renderer/private/vk/renderer/pass.hpp index 23d5382..86018ed 100644 --- a/modules/renderer/private/vk/renderer/pass.hpp +++ b/modules/renderer/private/vk/renderer/pass.hpp @@ -62,20 +62,6 @@ public: .primitiveRestartEnable = VK_FALSE, }; - auto viewport = VkViewport { - .x = 0u, - .y = 0u, - .width = static_cast(context.swapchain().get_resolution().width), - .height = static_cast(context.swapchain().get_resolution().height), - .minDepth = 0.0f, - .maxDepth = 0.0f, - }; - - auto scissor = VkRect2D { - .offset = { 0u, 0u }, - .extent = context.swapchain().get_resolution(), - }; - auto viewport_state = VkPipelineViewportStateCreateInfo { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1u, @@ -103,8 +89,6 @@ public: }; auto color_blend_attachment = VkPipelineColorBlendAttachmentState { - .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT - | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, .blendEnable = VK_FALSE, .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, @@ -112,6 +96,8 @@ public: .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT + | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, }; auto color_blend = VkPipelineColorBlendStateCreateInfo { @@ -235,6 +221,22 @@ public: auto operator=(const Pass &) -> Pass & = delete; + void replace_swapchain(const Swapchain &swapchain) + { + if (!m_device) + { + return; + } + + vk_device_wait_idle(m_device); + for (auto &framebuffer : m_framebuffers) + { + vk_destroy_frame_buffer(m_device, framebuffer, nullptr); + } + + m_framebuffers = swapchain.create_framebuffers_for_pass(m_pass); + } + [[nodiscard]] auto get_pass() -> VkRenderPass { return m_pass; diff --git a/modules/renderer/private/vk/renderer/renderer.hpp b/modules/renderer/private/vk/renderer/renderer.hpp index d3e3b2b..61872e5 100644 --- a/modules/renderer/private/vk/renderer/renderer.hpp +++ b/modules/renderer/private/vk/renderer/renderer.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -10,6 +11,8 @@ namespace lt::renderer::vk { class Renderer { public: + static constexpr auto max_frames_in_flight = uint32_t { 3u }; + Renderer(Context &context, Ref pass) : m_device(context.device().vk()) , m_graphics_queue(context.device().get_graphics_queue()) @@ -18,6 +21,11 @@ public: , m_pass(std::move(pass)) , m_resolution(context.swapchain().get_resolution()) { + ensure(m_device, "Failed to initialize renderer: null device"); + ensure(m_graphics_queue, "Failed to initialize renderer: null graphics queue"); + ensure(m_present_queue, "Failed to initialize renderer: null present queue"); + ensure(m_swapchain, "Failed to initialize renderer: null swapchain"); + auto pool_info = VkCommandPoolCreateInfo { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, @@ -30,9 +38,9 @@ public: .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = m_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, - .commandBufferCount = 1u, + .commandBufferCount = static_cast(m_cmds.size()), }; - vkc(vk_allocate_command_buffers(m_device, &cmd_info, &m_cmd)); + vkc(vk_allocate_command_buffers(m_device, &cmd_info, &m_cmds[0])); auto semaphore_info = VkSemaphoreCreateInfo { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, @@ -43,11 +51,168 @@ public: .flags = VK_FENCE_CREATE_SIGNALED_BIT, }; - vkc(vk_create_semaphore(m_device, &semaphore_info, nullptr, &m_image_available_semaphore)); - vkc(vk_create_semaphore(m_device, &semaphore_info, nullptr, &m_render_finished_semaphore)); - vkc(vk_create_fence(m_device, &fence_info, nullptr, &m_in_flight_fence)); + for (auto idx : std::views::iota(0u, max_frames_in_flight)) + { + vkc(vk_create_semaphore( + m_device, + &semaphore_info, + nullptr, + &m_aquire_image_semaphores[idx] + )); + + vkc(vk_create_fence(m_device, &fence_info, nullptr, &m_in_flight_fences[idx])); + + set_object_name( + m_device, + m_aquire_image_semaphores[idx].get(), + "aquire semaphore {}", + idx + ); + + set_object_name(m_device, m_in_flight_fences[idx].get(), "frame fence {}", idx); + + { + const auto name = std::format("frame fence {}", idx); + auto debug_info = VkDebugUtilsObjectNameInfoEXT { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, + .objectType = VK_OBJECT_TYPE_FENCE, + .objectHandle = reinterpret_cast( + static_cast(m_in_flight_fences[idx].get()) + ), + .pObjectName = name.c_str(), + }; + vk_set_debug_object_name(m_device, &debug_info); + } + } + + + m_submit_semaphores.resize(context.swapchain().get_image_count()); + for (auto idx = 0; auto &semaphore : m_submit_semaphores) + { + vkc(vk_create_semaphore(m_device, &semaphore_info, nullptr, &semaphore)); + set_object_name(m_device, semaphore.get(), "submit semaphore {}", idx++); + } }; + ~Renderer() + { + if (!m_device) + { + return; + } + + vkc(vk_device_wait_idle(m_device)); + + for (auto [semaphore, fence] : + std::views::zip(m_aquire_image_semaphores, m_in_flight_fences)) + { + vk_destroy_semaphore(m_device, semaphore, nullptr); + vk_destroy_fence(m_device, fence, nullptr); + } + + + for (auto &semaphore : m_submit_semaphores) + { + vk_destroy_semaphore(m_device, semaphore, nullptr); + } + + vk_destroy_command_pool(m_device, m_pool, nullptr); + } + + Renderer(Renderer &&) = default; + + Renderer(const Renderer &) = delete; + + auto operator=(Renderer &&) -> Renderer & = default; + + auto operator=(const Renderer &) -> Renderer & = delete; + + auto draw(uint32_t frame_idx) -> bool + { + ensure( + frame_idx < max_frames_in_flight, + "Failed to draw: frame_idx >= max_frames_in_flight" + ); + + auto &flight_fence = m_in_flight_fences[frame_idx]; + auto &aquire_semaphore = m_aquire_image_semaphores[frame_idx]; + auto &cmd = m_cmds[frame_idx]; + + try + { + vkc(vk_wait_for_fences( + m_device, + 1u, + &flight_fence, + VK_TRUE, + std::numeric_limits::max() + )); + + auto image_idx = uint32_t {}; + auto result = vk_acquire_next_image_khr( + m_device, + m_swapchain, + UINT64_MAX, + aquire_semaphore, + VK_NULL_HANDLE, + &image_idx + ); + if (result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_OUT_OF_DATE_KHR) + { + return false; + } + + vkc(vk_reset_fences(m_device, 1u, &flight_fence)); + vkc(vk_reset_command_buffer(cmd, {})); + record_cmd(cmd, image_idx); + + auto wait_stage = VkPipelineStageFlags { + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT + }; + auto &submit_semaphore = m_submit_semaphores[image_idx]; + auto submit_info = VkSubmitInfo { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 1u, + .pWaitSemaphores = &aquire_semaphore, + .pWaitDstStageMask = &wait_stage, + .commandBufferCount = 1u, + .pCommandBuffers = &cmd, + .signalSemaphoreCount = 1u, + .pSignalSemaphores = &submit_semaphore, + }; + + vkc(vk_queue_submit(m_graphics_queue, 1u, &submit_info, flight_fence)); + + auto present_info = VkPresentInfoKHR { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1u, + .pWaitSemaphores = &submit_semaphore, + .swapchainCount = 1u, + .pSwapchains = &m_swapchain, + .pImageIndices = &image_idx, + .pResults = nullptr, + }; + + vk_queue_present_khr(m_present_queue, &present_info); + } + catch (const std::exception &exp) + { + log_dbg("EXCEPTION: {}", exp.what()); + } + + return true; + } + + void replace_swapchain(const Swapchain &swapchain) + { + vk_device_wait_idle(m_device); + + m_swapchain = swapchain.vk(); + m_resolution = swapchain.get_resolution(); + ensure(m_swapchain, "Failed to replace renderer's swapchain: null swapchain"); + } + +private: void record_cmd(VkCommandBuffer cmd, uint32_t image_idx) { auto cmd_begin_info = VkCommandBufferBeginInfo { @@ -101,93 +266,18 @@ public: vkc(vk_end_command_buffer(cmd)); } - void draw() - { - try - { - vkc(vk_wait_for_fences(m_device, 1u, &m_in_flight_fence, VK_TRUE, UINT64_MAX)); - vkc(vk_reset_fences(m_device, 1u, &m_in_flight_fence)); - auto image_idx = uint32_t {}; - vkc(vk_acquire_next_image_khr( - m_device, - m_swapchain, - UINT64_MAX, - m_image_available_semaphore, - VK_NULL_HANDLE, - &image_idx - )); - - vkc(vk_reset_command_buffer(m_cmd, {})); - record_cmd(m_cmd, image_idx); - - auto wait_stage = VkPipelineStageFlags { - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT - }; - auto submit_info = VkSubmitInfo { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .waitSemaphoreCount = 1u, - .pWaitSemaphores = &m_image_available_semaphore, - .pWaitDstStageMask = &wait_stage, - .commandBufferCount = 1u, - .pCommandBuffers = &m_cmd, - .signalSemaphoreCount = 1u, - .pSignalSemaphores = &m_render_finished_semaphore, - }; - - vkc(vk_queue_submit(m_graphics_queue, 1u, &submit_info, m_in_flight_fence)); - - auto present_info = VkPresentInfoKHR { - .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .waitSemaphoreCount = 1u, - .pWaitSemaphores = &m_render_finished_semaphore, - .swapchainCount = 1u, - .pSwapchains = &m_swapchain, - .pImageIndices = &image_idx, - .pResults = nullptr, - }; - - vk_queue_present_khr(m_present_queue, &present_info); - } - catch (const std::exception &exp) - { - log_dbg("EXCEPTION: {}", exp.what()); - } - } - - ~Renderer() - { - if (!m_device) - { - return; - } - - vk_destroy_semaphore(m_device, m_render_finished_semaphore, nullptr); - vk_destroy_semaphore(m_device, m_image_available_semaphore, nullptr); - vk_destroy_fence(m_device, m_in_flight_fence, nullptr); - vk_destroy_command_pool(m_device, m_pool, nullptr); - } - - Renderer(Renderer &&) = default; - - Renderer(const Renderer &) = delete; - - auto operator=(Renderer &&) -> Renderer & = default; - - auto operator=(const Renderer &) -> Renderer & = delete; - -private: memory::NullOnMove m_device = VK_NULL_HANDLE; memory::NullOnMove m_pool = VK_NULL_HANDLE; - memory::NullOnMove m_cmd = VK_NULL_HANDLE; + std::array, max_frames_in_flight> m_cmds {}; - memory::NullOnMove m_image_available_semaphore = VK_NULL_HANDLE; + std::array, max_frames_in_flight> m_aquire_image_semaphores {}; - memory::NullOnMove m_render_finished_semaphore = VK_NULL_HANDLE; + std::vector> m_submit_semaphores; - memory::NullOnMove m_in_flight_fence = VK_NULL_HANDLE; + std::array, max_frames_in_flight> m_in_flight_fences {}; memory::NullOnMove m_swapchain = VK_NULL_HANDLE; diff --git a/modules/renderer/private/vk/renderer/renderer.test.cpp b/modules/renderer/private/vk/renderer/renderer.test.cpp index d393340..68234e0 100644 --- a/modules/renderer/private/vk/renderer/renderer.test.cpp +++ b/modules/renderer/private/vk/renderer/renderer.test.cpp @@ -10,18 +10,62 @@ Suite raii = "renderer_raii"_suite = [] { auto observer = ValidationObserver {}; auto [context, _] = create_context(); + std::ignore = Renderer( + context, + lt::create_ref( + context, + ShaderAsset { "./data/test_assets/triangle.vert.asset" }, + ShaderAsset { "./data/test_assets/triangle.frag.asset" } + ) + ); + + expect_false(observer.had_any_messages()); + }; +}; + +Suite draw = "renderer_draw"_suite = [] { + Case { "renderer draw" } = [] { + auto observer = ValidationObserver {}; + auto [context, _] = create_context(); + + auto renderer = Renderer( + context, + lt::create_ref( + context, + ShaderAsset { "./data/test_assets/triangle.vert.asset" }, + ShaderAsset { "./data/test_assets/triangle.frag.asset" } + ) + ); + + for (auto frame_idx : std::views::iota(0u, 30u)) + { + expect_true(renderer.draw(frame_idx % Renderer::max_frames_in_flight)); + } + expect_false(observer.had_any_messages()); + }; + + Case { "post swapchain replacement renderer draw" } = [] { + auto observer = ValidationObserver {}; + auto [context, _] = create_context(); auto pass = lt::create_ref( context, ShaderAsset { "./data/test_assets/triangle.vert.asset" }, ShaderAsset { "./data/test_assets/triangle.frag.asset" } ); + auto renderer = Renderer { context, pass }; - auto renderer = Renderer(context, pass); - - for (;;) + for (auto frame_idx : std::views::iota(0u, 15u)) { - renderer.draw(); + expect_true(renderer.draw(frame_idx % Renderer::max_frames_in_flight)); + } + + context.recreate_swapchain(); + renderer.replace_swapchain(context.swapchain()); + pass->replace_swapchain(context.swapchain()); + for (auto frame_idx : std::views::iota(0u, 15u)) + { + expect_true(renderer.draw(frame_idx % Renderer::max_frames_in_flight)); } expect_false(observer.had_any_messages()); diff --git a/modules/renderer/private/vk/test_utils.hpp b/modules/renderer/private/vk/test_utils.hpp index 40fe871..09f90d5 100644 --- a/modules/renderer/private/vk/test_utils.hpp +++ b/modules/renderer/private/vk/test_utils.hpp @@ -57,6 +57,16 @@ private: { std::ignore = message_severity; std::ignore = message_type; + for (auto idx = 0; idx < vulkan_data->objectCount; ++idx) + { + auto object = vulkan_data->pObjects[idx]; + std::println( + "0x{:x}({}) = {}", + object.objectHandle, + string_VkObjectType(object.objectType), + object.pObjectName ? object.pObjectName : "unnamed" + ); + } std::println("Validation message: {}", vulkan_data->pMessage); diff --git a/modules/renderer/public/system.hpp b/modules/renderer/public/system.hpp index c3018a6..81cd493 100644 --- a/modules/renderer/public/system.hpp +++ b/modules/renderer/public/system.hpp @@ -2,11 +2,17 @@ #include #include -#include namespace lt::renderer { -class System: app::ISystem +namespace vk { +class Context; +class Renderer; +class Pass; +class ValidationObserver; +} // namespace vk + +class System: public app::ISystem { public: struct CreateInfo @@ -18,16 +24,9 @@ public: Ref system_stats; }; - [[nodiscard]] System(CreateInfo info) - : m_registry(std::move(info.registry)) - , m_stats(info.system_stats) - , m_context(info.surface_entity) - { - ensure(m_stats, "Failed to initialize system: null stats"); - ensure(m_registry, "Failed to initialize renderer system: null registry"); - } + System(CreateInfo info); - ~System() override = default; + ~System() override; System(System &&) = default; @@ -41,8 +40,10 @@ public: void on_unregister() override; + void tick(app::TickInfo tick) override; + [[nodiscard]] auto get_stats() const -> const app::SystemStats & { return *m_stats; @@ -54,13 +55,22 @@ public: } private: + class vk::ValidationObserver *m_validation_observer; + Ref m_registry; Ref m_stats; - vk::Context m_context; + Scope m_context; + + Ref m_pass; + + Scope m_renderer; + app::TickResult m_last_tick_result {}; + + uint32_t m_frame_idx {}; }; } // namespace lt::renderer