From fa1bfaae1e635572228b518aaff415f42583f2b0 Mon Sep 17 00:00:00 2001 From: light7734 Date: Thu, 25 Sep 2025 22:23:08 +0330 Subject: [PATCH] refactor(renderer): split up context into multiple objectes & fix some edge cases --- modules/renderer/CMakeLists.txt | 15 +- modules/renderer/private/system.test.cpp | 70 +++- .../renderer/private/vk/context/context.cpp | 15 + .../renderer/private/vk/context/context.hpp | 48 +++ .../renderer/private/vk/context/device.cpp | 151 ++++++++ .../renderer/private/vk/context/device.hpp | 60 +++ .../vk/{context.cpp => context/instance.cpp} | 353 ++---------------- .../vk/{context.hpp => context/instance.hpp} | 137 +++---- .../renderer/private/vk/context/surface.cpp | 34 ++ .../renderer/private/vk/context/surface.hpp | 40 ++ .../renderer/private/vk/context/swapchain.cpp | 135 +++++++ .../renderer/private/vk/context/swapchain.hpp | 39 ++ modules/renderer/private/vk/pipeline.cpp | 18 + modules/renderer/private/vk/pipeline.hpp | 36 ++ modules/renderer/private/vk/pipeline.test.cpp | 69 ++++ modules/renderer/private/vk/surface.hpp | 26 -- modules/renderer/private/vk/swapchain.hpp | 22 -- modules/renderer/public/system.hpp | 11 +- 18 files changed, 801 insertions(+), 478 deletions(-) create mode 100644 modules/renderer/private/vk/context/context.cpp create mode 100644 modules/renderer/private/vk/context/context.hpp create mode 100644 modules/renderer/private/vk/context/device.cpp create mode 100644 modules/renderer/private/vk/context/device.hpp rename modules/renderer/private/vk/{context.cpp => context/instance.cpp} (60%) rename modules/renderer/private/vk/{context.hpp => context/instance.hpp} (70%) create mode 100644 modules/renderer/private/vk/context/surface.cpp create mode 100644 modules/renderer/private/vk/context/surface.hpp create mode 100644 modules/renderer/private/vk/context/swapchain.cpp create mode 100644 modules/renderer/private/vk/context/swapchain.hpp create mode 100644 modules/renderer/private/vk/pipeline.cpp create mode 100644 modules/renderer/private/vk/pipeline.hpp create mode 100644 modules/renderer/private/vk/pipeline.test.cpp delete mode 100644 modules/renderer/private/vk/surface.hpp delete mode 100644 modules/renderer/private/vk/swapchain.hpp diff --git a/modules/renderer/CMakeLists.txt b/modules/renderer/CMakeLists.txt index 46ef651..7741f23 100644 --- a/modules/renderer/CMakeLists.txt +++ b/modules/renderer/CMakeLists.txt @@ -1,15 +1,28 @@ add_library_module(renderer system.cpp - vk/context.cpp + vk/context/instance.cpp + vk/context/surface.cpp + vk/context/device.cpp + vk/context/swapchain.cpp + vk/context/context.cpp + vk/pipeline.cpp ) target_link_libraries(renderer PUBLIC app ecs +PRIVATE surface + pthread ) add_test_module(renderer system.test.cpp + vk/context/instance.test.cpp + vk/context/surface.test.cpp + vk/context/device.test.cpp + vk/context/swapchain.test.cpp + vk/context/context.test.cpp + vk/pipeline.test.cpp ) diff --git a/modules/renderer/private/system.test.cpp b/modules/renderer/private/system.test.cpp index 33026b7..f5e997a 100644 --- a/modules/renderer/private/system.test.cpp +++ b/modules/renderer/private/system.test.cpp @@ -69,29 +69,73 @@ struct RendererContext }; } +class SystemTest +{ +public: + SystemTest() + { + m_surface_entity->add(surface::SurfaceComponent::CreateInfo { + .title = "", + .resolution = resolution, + }); + } + + [[nodiscard]] auto registry() const -> Ref + { + return m_registry; + } + + [[nodiscard]] auto surface_entity() const -> const ecs::Entity & + { + return *m_surface_entity; + } + + [[nodiscard]] auto stats() const -> Ref + { + return m_stats; + } + +private: + Ref m_stats = create_ref(); + + Ref m_registry = create_ref(); + + Ref m_surface_system = create_ref(m_registry); + + Scope m_surface_entity = create_scope( + m_registry, + m_registry->create_entity() + ); +}; + Suite raii = [] { Case { "happy path won't throw" } = [&] { ignore = create_system(); }; Case { "happy path has no validation errors" } = [&] { - auto [surface, renderer] = create_system(); - expect_true(renderer.system.get_stats().empty_diagnosis()); + auto fixture = SystemTest {}; + std::ignore = System( + { + .registry = fixture.registry(), + .surface_entity = fixture.surface_entity(), + .system_stats = fixture.stats(), + } + ); + + expect_true(fixture.stats()->empty_diagnosis()); }; Case { "unhappy path throws" } = [] { - auto [surface_system, surface_entity] = create_surface(); - auto empty_registry = create_ref(); - auto empty_entity = ecs::Entity { empty_registry, empty_registry->create_entity() }; - auto registry = create_ref(); - auto stats = create_ref(); + auto fixture = SystemTest {}; + auto empty_entity = ecs::Entity { fixture.registry(), fixture.registry()->create_entity() }; expect_throw([&] { ignore = System( { .registry = {}, - .surface_entity = surface_entity, - .system_stats = stats, + .surface_entity = fixture.surface_entity(), + .system_stats = fixture.stats(), } ); }); @@ -99,9 +143,9 @@ Suite raii = [] { expect_throw([&] { ignore = System( System::CreateInfo { - .registry = surface_entity.get_registry(), + .registry = fixture.registry(), .surface_entity = empty_entity, - .system_stats = stats, + .system_stats = fixture.stats(), } ); }); @@ -109,8 +153,8 @@ Suite raii = [] { expect_throw([&] { ignore = System( System::CreateInfo { - .registry = surface_entity.get_registry(), - .surface_entity = surface_entity, + .registry = fixture.registry(), + .surface_entity = fixture.surface_entity(), .system_stats = {}, } ); diff --git a/modules/renderer/private/vk/context/context.cpp b/modules/renderer/private/vk/context/context.cpp new file mode 100644 index 0000000..4c92712 --- /dev/null +++ b/modules/renderer/private/vk/context/context.cpp @@ -0,0 +1,15 @@ +#include +#include +#include + +namespace lt::renderer::vk { + +Context::Context(const ecs::Entity &surface_entity, Ref system_stats) + : m_stats(std::move(system_stats)) + , m_surface(surface_entity) + , m_device(m_surface) + , m_swapchain(m_device, m_surface) +{ +} + +} // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/context/context.hpp b/modules/renderer/private/vk/context/context.hpp new file mode 100644 index 0000000..80c0aa1 --- /dev/null +++ b/modules/renderer/private/vk/context/context.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include + +// +#include +#include +#include +#include + +namespace lt::renderer::vk { + +using memory::NullOnMove; + +class Context +{ +public: + Context(const ecs::Entity &surface_entity, Ref system_stats); + + [[nodiscard]] auto instance() const -> VkInstance + { + return Instance::get(); + } + + [[nodiscard]] auto device() const -> const Device & + { + return m_device; + } + + [[nodiscard]] auto swapchain() const -> const Swapchain & + { + return m_swapchain; + } + +private: + Ref m_stats; + + Surface m_surface; + + Device m_device; + + Swapchain m_swapchain; +}; + +} // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/context/device.cpp b/modules/renderer/private/vk/context/device.cpp new file mode 100644 index 0000000..2bd2eea --- /dev/null +++ b/modules/renderer/private/vk/context/device.cpp @@ -0,0 +1,151 @@ +#include + +namespace lt::renderer::vk { + +Device::Device(const Surface &surface) +{ + initialize_physical_device(); + initialize_logical_device(); + Instance::load_device_functions(m_device); + initialize_queue(surface); +} + +Device::~Device() +{ + if (m_device) + { + vkc(vk_device_wait_idle(m_device)); + vk_destroy_device(m_device, nullptr); + } +} + +void Device::initialize_physical_device() +{ + auto count = 0u; + vkc(vk_enumerate_physical_devices(Instance::get(), &count, nullptr)); + ensure(count != 0u, "Failed to find any physical devices with Vulkan support"); + + auto devices = std::vector(count); + vkc(vk_enumerate_physical_devices(Instance::get(), &count, devices.data())); + + for (auto &device : devices) + { + auto properties = VkPhysicalDeviceProperties {}; + auto features = VkPhysicalDeviceFeatures {}; + + vk_get_physical_device_properties(device, &properties); + vk_get_physical_device_features(device, &features); + + if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU + && features.geometryShader) + { + m_physical_device = device; + } + } + ensure(m_physical_device, "Failed to find any suitable Vulkan physical device"); +} + +void Device::initialize_logical_device() +{ + const float priorities = .0f; + + auto queue_info = VkDeviceQueueCreateInfo { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = find_suitable_queue_family(), + .queueCount = 1u, + .pQueuePriorities = &priorities, + }; + + auto physical_device_features = VkPhysicalDeviceFeatures {}; + + auto extensions = std::vector { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + }; + + auto device_info = VkDeviceCreateInfo { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &queue_info, + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data(), + .pEnabledFeatures = &physical_device_features, + }; + + ensure( + !vk_create_device(m_physical_device, &device_info, nullptr, &m_device), + "Failed to create logical vulkan device" + ); +} + +[[nodiscard]] auto Device::find_suitable_queue_family() const -> uint32_t +{ + auto count = 0u; + vk_get_physical_device_queue_family_properties(m_physical_device, &count, nullptr); + ensure(count != 0u, "Failed to find any physical devices with Vulkan support"); + + auto families = std::vector(count); + vk_get_physical_device_queue_family_properties(m_physical_device, &count, families.data()); + + const auto required_flags = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT; + for (auto idx = 0u; auto &family : families) + { + if ((family.queueFlags & required_flags) == required_flags) + { + return idx; + } + } + + ensure(false, "Failed to find a suitable Vulkan queue family"); + return 0; +} + +void Device::initialize_queue(const Surface &surface) +{ + vk_get_device_queue(m_device, find_suitable_queue_family(), 0, &m_queue); + + auto count = uint32_t { 0u }; + vk_get_physical_device_queue_family_properties(m_physical_device, &count, nullptr); + + auto properties = std::vector(count); + vk_get_physical_device_queue_family_properties(m_physical_device, &count, properties.data()); + + for (auto idx = uint32_t { 0u }; const auto &property : properties) + { + if (property.queueFlags & VK_QUEUE_GRAPHICS_BIT) + { + m_graphics_queue_family_index = idx; + } + + auto has_presentation_support = VkBool32 { false }; + vkc(vk_get_physical_device_surface_support( + m_physical_device, + idx, + surface.vk(), + &has_presentation_support + )); + if (has_presentation_support) + { + m_present_queue_family_index = idx; + } + + ++idx; + + if (m_graphics_queue_family_index != VK_QUEUE_FAMILY_IGNORED + && m_present_queue_family_index != VK_QUEUE_FAMILY_IGNORED) + { + break; + } + } + + ensure( + m_graphics_queue_family_index != VK_QUEUE_FAMILY_IGNORED, + "Failed to find graphics queue family" + ); + + ensure( + m_present_queue_family_index != VK_QUEUE_FAMILY_IGNORED, + "Failed to find presentation queue family" + ); +} + +} // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/context/device.hpp b/modules/renderer/private/vk/context/device.hpp new file mode 100644 index 0000000..f3992cd --- /dev/null +++ b/modules/renderer/private/vk/context/device.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include + +namespace lt::renderer::vk { + +class Device +{ +public: + Device(const Surface &surface); + + ~Device(); + + Device(Device &&) = default; + + Device(const Device &) = delete; + + auto operator=(Device &&) -> Device & = default; + + auto operator=(const Device &) const -> Device & = delete; + + [[nodiscard]] auto vk() const -> VkDevice + { + return m_device; + } + + [[nodiscard]] auto physical() const -> VkPhysicalDevice + { + return m_physical_device; + } + + [[nodiscard]] auto get_family_indices() const -> std::array + { + return { m_graphics_queue_family_index, m_present_queue_family_index }; + } + +private: + void initialize_physical_device(); + + void initialize_logical_device(); + + void initialize_queue(const Surface &surface); + + [[nodiscard]] auto find_suitable_queue_family() const -> uint32_t; + + // logical device + memory::NullOnMove m_physical_device = VK_NULL_HANDLE; + + memory::NullOnMove m_device = VK_NULL_HANDLE; + + memory::NullOnMove m_queue = VK_NULL_HANDLE; + + uint32_t m_graphics_queue_family_index = VK_QUEUE_FAMILY_IGNORED; + + uint32_t m_present_queue_family_index = VK_QUEUE_FAMILY_IGNORED; +}; + +} // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/context.cpp b/modules/renderer/private/vk/context/instance.cpp similarity index 60% rename from modules/renderer/private/vk/context.cpp rename to modules/renderer/private/vk/context/instance.cpp index baba864..9b56234 100644 --- a/modules/renderer/private/vk/context.cpp +++ b/modules/renderer/private/vk/context/instance.cpp @@ -1,8 +1,8 @@ -#include -#include +#include +#include #if defined(_WIN32) - + #error "Unsupported platform (currently)" #elif defined(__unix__) #include namespace { @@ -99,70 +99,35 @@ auto parse_message_severity(VkDebugUtilsMessageSeverityFlagBitsEXT message_sever -> app::SystemDiagnosis::Severity; auto validation_layers_callback( - VkDebugUtilsMessageSeverityFlagBitsEXT const message_severity, - VkDebugUtilsMessageTypeFlagsEXT const message_types, - VkDebugUtilsMessengerCallbackDataEXT const *const callback_data, - void *const vulkan_user_data + VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, + VkDebugUtilsMessageTypeFlagsEXT message_types, + VkDebugUtilsMessengerCallbackDataEXT const *callback_data, + void *vulkan_user_data ) -> VkBool32; -Context::Context(const ecs::Entity &surface_entity, Ref system_stats) - : m_stats(std::move(system_stats)) +Instance::Instance() { - ensure(m_stats, "Failed to create Vulkan Context: null stats"); - load_library(); load_global_functions(); initialize_instance(); load_instance_functions(); - initialize_debug_messenger(); - - initialize_physical_device(); - initialize_logical_device(); - load_device_functions(); - - initialize_surface(surface_entity); - initialize_queue(); - initialize_swapchain(); } -Context::~Context() +Instance::~Instance() { - try + if (m_instance) { - 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); - vk_destroy_device(m_device, nullptr); - } - - if (m_instance) - { - vk_destroy_surface_khr(m_instance, m_surface, nullptr); - vk_destroy_debug_messenger(m_instance, m_debug_messenger, nullptr); - // TODO(Light): fix this issue - // @ref: - // https://git.light7734.com/light7734/light/commit/f268724034a2ceb63b90dc13aedf86a1eecac62e - // vk_destroy_instance(m_instance, nullptr); - } - } - catch (const std::exception &exp) - { - log_err("Exception: {}", exp.what()); + vk_destroy_debug_messenger(m_instance, m_debug_messenger, nullptr); + vk_destroy_instance(m_instance, nullptr); } + + unload_library(); } - -void Context::initialize_instance() +void Instance::initialize_instance() { auto app_info = VkApplicationInfo { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, @@ -206,11 +171,10 @@ void Context::initialize_instance() } vkc(vk_create_instance(&instance_info, nullptr, &m_instance)); - ensure(m_instance, "Failed to create vulkan instance"); } -void Context::initialize_debug_messenger() +void Instance::initialize_debug_messenger() { const auto info = VkDebugUtilsMessengerCreateInfoEXT { .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, @@ -227,7 +191,7 @@ void Context::initialize_debug_messenger() .pfnUserCallback = &validation_layers_callback, - .pUserData = &m_stats, + .pUserData = {}, }; ensure( @@ -236,242 +200,7 @@ void Context::initialize_debug_messenger() ); } -void Context::initialize_physical_device() -{ - auto count = 0u; - vkc(vk_enumerate_physical_devices(m_instance, &count, nullptr)); - ensure(count != 0u, "Failed to find any physical devices with Vulkan support"); - - auto devices = std::vector(count); - vkc(vk_enumerate_physical_devices(m_instance, &count, devices.data())); - - for (auto &device : devices) - { - auto properties = VkPhysicalDeviceProperties {}; - auto features = VkPhysicalDeviceFeatures {}; - - vk_get_physical_device_properties(device, &properties); - vk_get_physical_device_features(device, &features); - - if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU - && features.geometryShader) - { - m_physical_device = device; - } - } - ensure(m_physical_device, "Failed to find any suitable Vulkan physical device"); -} - -void Context::initialize_logical_device() -{ - const float priorities = .0f; - - auto queue_info = VkDeviceQueueCreateInfo { - .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, - .queueFamilyIndex = find_suitable_queue_family(), - .queueCount = 1u, - .pQueuePriorities = &priorities, - }; - - auto physical_device_features = VkPhysicalDeviceFeatures {}; - - auto extensions = std::vector { - VK_KHR_SWAPCHAIN_EXTENSION_NAME, - }; - - auto device_info = VkDeviceCreateInfo { - .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &queue_info, - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data(), - .pEnabledFeatures = &physical_device_features, - }; - - ensuree - !vk_create_device(m_physical_device, &device_info, nullptr, &m_device), - "Failed to create logical vulkan device" - ); -} - -void Context::initialize_surface(const ecs::Entity &surface_entity) -{ - const auto &component = surface_entity.get(); - - auto create_info = VkXlibSurfaceCreateInfoKHR { - .sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, - .dpy = component.get_native_data().display, - .window = component.get_native_data().window, - }; - - vkc(vk_create_xlib_surface_khr(m_instance, &create_info, nullptr, &m_surface)); - - const auto &[width, height] = component.get_resolution(); - m_framebuffer_size = { - .width = width, - .height = height, - }; -} - -void Context::initialize_queue() -{ - vk_get_device_queue(m_device, find_suitable_queue_family(), 0, &m_queue); - - auto count = uint32_t { 0u }; - vk_get_physical_device_queue_family_properties(m_physical_device, &count, nullptr); - - auto properties = std::vector(count); - vk_get_physical_device_queue_family_properties(m_physical_device, &count, properties.data()); - - for (auto idx = uint32_t { 0u }; const auto &property : properties) - { - if (property.queueFlags & VK_QUEUE_GRAPHICS_BIT) - { - m_graphics_queue_family_index = idx; - } - - auto has_presentation_support = VkBool32 { false }; - vkc(vk_get_physical_device_surface_support( - m_physical_device, - idx, - m_surface, - &has_presentation_support - )); - if (has_presentation_support) - { - m_present_queue_family_index = idx; - } - - ++idx; - - if (m_graphics_queue_family_index != VK_QUEUE_FAMILY_IGNORED - && m_present_queue_family_index != VK_QUEUE_FAMILY_IGNORED) - { - break; - } - } - - ensure( - m_graphics_queue_family_index != VK_QUEUE_FAMILY_IGNORED, - "Failed to find graphics queue family" - ); - - ensure( - m_present_queue_family_index != VK_QUEUE_FAMILY_IGNORED, - "Failed to find presentation queue family" - ); -} - - -void Context::initialize_swapchain() -{ - auto capabilities = VkSurfaceCapabilitiesKHR {}; - vkc(vk_get_physical_device_surface_capabilities(m_physical_device, m_surface, &capabilities)); - - auto count = uint32_t { 0 }; - vkc(vk_get_physical_device_surface_formats(m_physical_device, m_surface, &count, nullptr)); - - auto formats = std::vector(count); - vkc( - vk_get_physical_device_surface_formats(m_physical_device, m_surface, &count, formats.data()) - ); - ensure(!formats.empty(), "Surface has no formats!"); - - // TODO(Light): parameterize - constexpr auto desired_swapchain_image_count = uint32_t { 3 }; - const auto surface_format = formats.front(); - - const auto queue_indices = std::array { - m_graphics_queue_family_index, - m_present_queue_family_index, - }; - - auto create_info = VkSwapchainCreateInfoKHR { - .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, - .surface = m_surface, - .minImageCount = get_optimal_swapchain_image_count( - capabilities, - desired_swapchain_image_count - ), - .imageFormat = surface_format.format, - .imageColorSpace = surface_format.colorSpace, - .imageExtent = m_framebuffer_size, - .imageArrayLayers = 1u, - .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, - .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = queue_indices.size(), - .pQueueFamilyIndices = queue_indices.data(), - .preTransform = capabilities.currentTransform, - .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, - .presentMode = VK_PRESENT_MODE_FIFO_RELAXED_KHR, // TODO(Light): parameterize - .clipped = VK_TRUE, - .oldSwapchain = nullptr, - }; - - vkc(vk_create_swapchain_khr(m_device, &create_info, nullptr, &m_swapchain)); - vkc(vk_device_wait_idle(m_device)); - - auto image_count = uint32_t { 0u }; - vk_get_swapchain_images_khr(m_device, m_swapchain, &image_count, nullptr); - - m_swapchain_images.resize(image_count); - m_swapchain_image_views.resize(image_count); - vk_get_swapchain_images_khr(m_device, m_swapchain, &image_count, m_swapchain_images.data()); - - for (auto [image, view] : std::views::zip(m_swapchain_images, m_swapchain_image_views)) - { - auto create_info = VkImageViewCreateInfo { - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .image = image, - .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = surface_format.format, - .components = VkComponentMapping { - .r = VK_COMPONENT_SWIZZLE_IDENTITY, - .g = VK_COMPONENT_SWIZZLE_IDENTITY, - .b = VK_COMPONENT_SWIZZLE_IDENTITY, - .a = VK_COMPONENT_SWIZZLE_IDENTITY, - }, - .subresourceRange = VkImageSubresourceRange { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0u, - .levelCount = 1u, - .baseArrayLayer = 0u, - .layerCount = 1u, - } - }; - - vkc(vk_create_image_view(m_device, &create_info, nullptr, &view)); - } -} - -auto Context::get_optimal_swapchain_image_count( - VkSurfaceCapabilitiesKHR capabilities, - uint32_t desired_image_count -) -> uint32_t -{ - const auto min_image_count = capabilities.minImageCount; - const auto max_image_count = capabilities.maxImageCount; - - const auto has_max_limit = max_image_count != 0; - - // Desired image count is in range - if ((!has_max_limit || max_image_count >= desired_image_count) - && min_image_count <= desired_image_count) - { - return desired_image_count; - } - - // Fall-back to 2 if in ange - if (min_image_count <= 2 && max_image_count >= 2) - { - return 2; - } - - // Fall-back to min_image_count - return min_image_count; -} - -void Context::load_library() +void Instance::load_library() { library = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL); ensure(library, "Failed to dlopen libvulkan.so"); @@ -483,7 +212,18 @@ void Context::load_library() ensure(vk_get_instance_proc_address, "Failed to load vulkan function: vkGetInstanceProcAddr"); } -void Context::load_global_functions() +void Instance::unload_library() +{ + if (!library) + { + return; + } + + // dlclose(library); + // library = nullptr; +} + +void Instance::load_global_functions() { constexpr auto load_fn = [](T &pfn, const char *fn_name) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) @@ -497,7 +237,7 @@ void Context::load_global_functions() load_fn(vk_enumerate_instance_layer_properties, "vkEnumerateInstanceLayerProperties"); } -void Context::load_instance_functions() +void Instance::load_instance_functions() { const auto load_fn = [&](T &pfn, const char *fn_name) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) @@ -541,11 +281,11 @@ void Context::load_instance_functions() load_fn(vk_destroy_surface_khr, "vkDestroySurfaceKHR"); } -void Context::load_device_functions() +void Instance::load_device_functions_impl(VkDevice device) { const auto load_fn = [&](T &pfn, const char *fn_name) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - pfn = reinterpret_cast(vk_get_device_proc_address(m_device, fn_name)); + pfn = reinterpret_cast(vk_get_device_proc_address(device, fn_name)); ensure(pfn, "Failed to load vulkan device function: {}", fn_name); // log_trc("Loaded device function: {}", fn_name); }; @@ -592,28 +332,6 @@ void Context::load_device_functions() load_fn(vk_cmd_set_scissors, "vkCmdSetScissor"); } -[[nodiscard]] auto Context::find_suitable_queue_family() const -> uint32_t -{ - auto count = 0u; - vk_get_physical_device_queue_family_properties(m_physical_device, &count, nullptr); - ensure(count != 0u, "Failed to find any physical devices with Vulkan support"); - - auto families = std::vector(count); - vk_get_physical_device_queue_family_properties(m_physical_device, &count, families.data()); - - const auto required_flags = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT; - for (auto idx = 0u; auto &family : families) - { - if ((family.queueFlags & required_flags) == required_flags) - { - return idx; - } - } - - ensure(false, "Failed to find a suitable Vulkan queue family"); - return 0; -} - auto parse_message_type(VkDebugUtilsMessageTypeFlagsEXT message_types) -> const char * { if (message_types == VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) @@ -660,7 +378,11 @@ auto validation_layers_callback( void *const vulkan_user_data ) -> VkBool32 { + std::cout << callback_data->pMessage << std::endl; + return VK_FALSE; + log_dbg("VALIDATION: {}", callback_data->pMessage); + ensure(vulkan_user_data, "Validation layers's user data is not set!"); auto stats = *(Ref *)vulkan_user_data; // NOLINT @@ -690,5 +412,4 @@ auto validation_layers_callback( return static_cast(VK_FALSE); } - } // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/context.hpp b/modules/renderer/private/vk/context/instance.hpp similarity index 70% rename from modules/renderer/private/vk/context.hpp rename to modules/renderer/private/vk/context/instance.hpp index 0d0664d..8b587a1 100644 --- a/modules/renderer/private/vk/context.hpp +++ b/modules/renderer/private/vk/context/instance.hpp @@ -5,16 +5,17 @@ #include #include -// -#include -#include -#include -#include - namespace lt::renderer::vk { -using memory::NullOnMove; - +inline void vkc(VkResult result) +{ + if (result) + { + throw std::runtime_error { + std::format("Vulkan call failed with result: {}", std::to_underlying(result)) + }; + } +} // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) // global functions extern PFN_vkGetInstanceProcAddr vk_get_instance_proc_address; @@ -97,118 +98,64 @@ extern PFN_vkCreateXlibSurfaceKHR vk_create_xlib_surface_khr; extern PFN_vkDestroySurfaceKHR vk_destroy_surface_khr; // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) -inline void vkc(VkResult result) -{ - if (result) - { - throw std::runtime_error { - std::format("Vulkan call failed with result: {}", std::to_underlying(result)) - }; - } -} - -class Context +/** + * Responsible for dynamically loading Vulkan library/functions. + * + * @note: The delayed vkInstance destruction is due to a work-around for libx11: + * @ref: + * https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers/commit/0017308648b6bf8eef10ef0ffb9470576c0c2e9e + * https://www.xfree86.org/4.7.0/DRI11.html + * https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers/issues/1894 + */ +class Instance { public: - Context(const ecs::Entity &surface_entity, Ref system_stats); - - ~Context(); - - Context(Context &&other) noexcept = default; - - auto operator=(Context &&other) noexcept -> Context & = default; - - Context(const Context &) = delete; - - auto operator=(const Context &) -> Context & = delete; - - [[nodiscard]] auto instance() -> VkInstance + static auto get() -> VkInstance { - return m_instance; + return Instance::instance().m_instance; } - [[nodiscard]] auto physical_device() -> VkPhysicalDevice + static auto load_device_functions(VkDevice device) { - return m_physical_device; + instance().load_device_functions_impl(device); } - [[nodiscard]] auto device() -> VkDevice - { - return m_device; - } + Instance(Instance &&) = delete; - auto queue() -> VkQueue - { - return m_queue; - }; + Instance(const Instance &) = delete; - auto debug_messenger() -> VkDebugUtilsMessengerEXT - { - return m_debug_messenger; - }; + auto operator=(const Instance &) -> Instance & = delete; - [[nodiscard]] auto get_stats() const -> const app::SystemStats & - { - return *m_stats; - } + auto operator=(Instance &&) -> Instance & = delete; private: + static auto instance() -> Instance & + { + static auto instance = Instance {}; + return instance; + } + + Instance(); + + ~Instance(); + void initialize_instance(); void initialize_debug_messenger(); - void initialize_physical_device(); - - void initialize_logical_device(); - - void initialize_queue(); - - void initialize_surface(const ecs::Entity &surface_entity); - - void initialize_swapchain(); - void load_library(); + void unload_library(); + void load_global_functions(); void load_instance_functions(); - void load_device_functions(); + void load_device_functions_impl(VkDevice device); - auto get_optimal_swapchain_image_count( - VkSurfaceCapabilitiesKHR capabilities, - uint32_t desired_image_count = 3 - ) -> uint32_t; + VkInstance m_instance = VK_NULL_HANDLE; - [[nodiscard]] auto find_suitable_queue_family() const -> uint32_t; - - Ref m_registry; - - NullOnMove m_instance = VK_NULL_HANDLE; - - NullOnMove m_physical_device = VK_NULL_HANDLE; - - NullOnMove m_device = VK_NULL_HANDLE; - - NullOnMove m_queue = VK_NULL_HANDLE; - - NullOnMove m_debug_messenger = VK_NULL_HANDLE; - - NullOnMove m_surface = VK_NULL_HANDLE; - - VkExtent2D m_framebuffer_size {}; - - uint32_t m_graphics_queue_family_index = VK_QUEUE_FAMILY_IGNORED; - - uint32_t m_present_queue_family_index = VK_QUEUE_FAMILY_IGNORED; - - NullOnMove m_swapchain = VK_NULL_HANDLE; - - std::vector m_swapchain_images; - - std::vector m_swapchain_image_views; - - Ref m_stats; + VkDebugUtilsMessengerEXT m_debug_messenger = VK_NULL_HANDLE; }; } // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/context/surface.cpp b/modules/renderer/private/vk/context/surface.cpp new file mode 100644 index 0000000..b446d4d --- /dev/null +++ b/modules/renderer/private/vk/context/surface.cpp @@ -0,0 +1,34 @@ +#include +#include + +namespace lt::renderer::vk { + +Surface::Surface(const ecs::Entity &surface_entity) +{ + const auto &component = surface_entity.get(); + + auto create_info = VkXlibSurfaceCreateInfoKHR { + .sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, + .dpy = component.get_native_data().display, + .window = component.get_native_data().window, + }; + + auto *instance = Instance::get(); + vkc(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() +{ + if (Instance::get()) + { + vk_destroy_surface_khr(Instance::get(), m_surface, nullptr); + } +} + +} // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/context/surface.hpp b/modules/renderer/private/vk/context/surface.hpp new file mode 100644 index 0000000..a7a5091 --- /dev/null +++ b/modules/renderer/private/vk/context/surface.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +namespace lt::renderer::vk { + +class Surface +{ +public: + Surface(const ecs::Entity &surface_entity); + + ~Surface(); + + Surface(Surface &&) = default; + + Surface(const Surface &) = delete; + + auto operator=(Surface &&) -> Surface & = default; + + auto operator=(const Surface &) const -> Surface & = delete; + + [[nodiscard]] auto vk() const -> VkSurfaceKHR + { + return m_surface; + } + + [[nodiscard]] auto get_framebuffer_size() const -> VkExtent2D + { + return m_framebuffer_size; + } + +private: + memory::NullOnMove m_surface = VK_NULL_HANDLE; + + VkExtent2D m_framebuffer_size {}; +}; + +} // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/context/swapchain.cpp b/modules/renderer/private/vk/context/swapchain.cpp new file mode 100644 index 0000000..f47ec96 --- /dev/null +++ b/modules/renderer/private/vk/context/swapchain.cpp @@ -0,0 +1,135 @@ +#include +#include + +namespace lt::renderer::vk { + +Swapchain::Swapchain(const Device &device, const Surface &surface): m_device(device.vk()) +{ + auto *physical_device = device.physical(); + + auto capabilities = VkSurfaceCapabilitiesKHR {}; + vkc(vk_get_physical_device_surface_capabilities(physical_device, surface.vk(), &capabilities)); + + auto count = uint32_t { 0 }; + vkc(vk_get_physical_device_surface_formats(physical_device, surface.vk(), &count, nullptr)); + + auto formats = std::vector(count); + vkc(vk_get_physical_device_surface_formats( + physical_device, + surface.vk(), + &count, + formats.data() + )); + ensure(!formats.empty(), "Surface has no formats!"); + + // TODO(Light): parameterize + constexpr auto desired_swapchain_image_count = uint32_t { 3 }; + const auto surface_format = formats.front(); + const auto queue_indices = device.get_family_indices(); + + auto create_info = VkSwapchainCreateInfoKHR { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = surface.vk(), + .minImageCount = get_optimal_image_count(capabilities, desired_swapchain_image_count), + .imageFormat = surface_format.format, + .imageColorSpace = surface_format.colorSpace, + .imageExtent = surface.get_framebuffer_size(), + .imageArrayLayers = 1u, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = queue_indices.size(), + .pQueueFamilyIndices = queue_indices.data(), + .preTransform = capabilities.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = VK_PRESENT_MODE_FIFO_RELAXED_KHR, // TODO(Light): parameterize + .clipped = VK_TRUE, + .oldSwapchain = nullptr, + }; + + vkc(vk_create_swapchain_khr(device.vk(), &create_info, nullptr, &m_swapchain)); + vkc(vk_device_wait_idle(device.vk())); + + 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()); + + for (auto [image, view] : std::views::zip(m_swapchain_images, m_swapchain_image_views)) + { + auto create_info = VkImageViewCreateInfo { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = surface_format.format, + .components = VkComponentMapping { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = VkImageSubresourceRange { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0u, + .levelCount = 1u, + .baseArrayLayer = 0u, + .layerCount = 1u, + } + }; + + 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()); + } +} + +[[nodiscard]] auto Swapchain::get_optimal_image_count( + VkSurfaceCapabilitiesKHR capabilities, + uint32_t desired_image_count +) const -> uint32_t +{ + const auto min_image_count = capabilities.minImageCount; + const auto max_image_count = capabilities.maxImageCount; + + const auto has_max_limit = max_image_count != 0; + + // Desired image count is in range + if ((!has_max_limit || max_image_count >= desired_image_count) + && min_image_count <= desired_image_count) + { + return desired_image_count; + } + + // Fall-back to 2 if in ange + if (min_image_count <= 2 && max_image_count >= 2) + { + return 2; + } + + // Fall-back to min_image_count + return min_image_count; +} + + +} // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/context/swapchain.hpp b/modules/renderer/private/vk/context/swapchain.hpp new file mode 100644 index 0000000..04847b6 --- /dev/null +++ b/modules/renderer/private/vk/context/swapchain.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +namespace lt::renderer::vk { + +class Swapchain +{ +public: + Swapchain(const Device &device, const Surface &surface); + + ~Swapchain(); + + Swapchain(Swapchain &&) = default; + + Swapchain(const Swapchain &) = delete; + + auto operator=(Swapchain &&) -> Swapchain & = default; + + auto operator=(const Swapchain &) const -> Swapchain & = delete; + +private: + [[nodiscard]] auto get_optimal_image_count( + VkSurfaceCapabilitiesKHR capabilities, + uint32_t desired_image_count + ) const -> uint32_t; + + memory::NullOnMove m_device; + + memory::NullOnMove m_swapchain = VK_NULL_HANDLE; + + std::vector m_swapchain_images; + + std::vector m_swapchain_image_views; +}; + +} // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/pipeline.cpp b/modules/renderer/private/vk/pipeline.cpp new file mode 100644 index 0000000..8d35d8b --- /dev/null +++ b/modules/renderer/private/vk/pipeline.cpp @@ -0,0 +1,18 @@ +#include + +namespace lt::renderer::vk { + +Pipeline::Pipeline(CreateInfo info): m_context(std::move(info.context)) +{ + ensure(m_context, "Failed to create vk pipeline: null context"); +} + +Pipeline::~Pipeline() +{ + if (m_context) + { + return; + } +} + +} // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/pipeline.hpp b/modules/renderer/private/vk/pipeline.hpp new file mode 100644 index 0000000..9f4b7b0 --- /dev/null +++ b/modules/renderer/private/vk/pipeline.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +namespace lt::renderer::vk { + +class Pipeline +{ +public: + struct CreateInfo + { + Ref context; + }; + + Pipeline(CreateInfo info); + + ~Pipeline(); + + Pipeline(Pipeline &&) = default; + + Pipeline(const Pipeline &) = delete; + + auto operator=(Pipeline &&) -> Pipeline & = default; + + auto operator=(const Pipeline &) -> Pipeline & = delete; + +private: + VkPipeline m_pipeline = {}; + + VkPipelineLayout m_pipeline_layout = {}; + + Ref m_context; +}; + +}; // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/pipeline.test.cpp b/modules/renderer/private/vk/pipeline.test.cpp new file mode 100644 index 0000000..8ad6961 --- /dev/null +++ b/modules/renderer/private/vk/pipeline.test.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include + +using namespace lt; + +using std::ignore; +using test::Case; +using test::expect_throw; +using test::expect_true; +using test::Suite; + +using renderer::vk::Context; +using renderer::vk::Pipeline; + +constexpr auto resolution = math::uvec2 { 800, 600 }; + +class VkPipelineTest +{ +public: + VkPipelineTest() + { + m_registry = create_ref(); + + m_surface_system = create_ref(m_registry); + + m_surface_entity = create_scope(m_registry, m_registry->create_entity()); + m_surface_entity->add(surface::SurfaceComponent::CreateInfo { + .title = "", + .resolution = resolution, + }); + + m_context = create_ref(*m_surface_entity, m_stats); + } + + [[nodiscard]] auto context() -> Ref + { + return m_context; + } + + [[nodiscard]] auto device() -> VkDevice + { + return m_context->device().vk(); + } + +private: + Ref m_stats; + + Ref m_registry; + + Ref m_surface_system; + + Scope m_surface_entity; + + Ref m_context; +}; + +Suite raii = [] { + Case { "happy path won't throw" } = [] { + auto fixture = VkPipelineTest {}; + std::ignore = Pipeline { { .context = fixture.context() } }; + }; + + Case { "unhappy path throws" } = [] { + expect_throw([] { std::ignore = Pipeline { { .context = nullptr } }; }); + }; +}; diff --git a/modules/renderer/private/vk/surface.hpp b/modules/renderer/private/vk/surface.hpp deleted file mode 100644 index 392f955..0000000 --- a/modules/renderer/private/vk/surface.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#define VK_NO_PROTOTYPES -#define VK_USE_PLATFORM_XLIB_KHR -#include -#include - -// -#include - -namespace lt::renderer::vk { - -class Surface -{ -public: - Surface(ecs::Entity entity) - { - } - - ~Surface(); - -private: - VkSurfaceKHR m_surface = VK_NULL_HANDLE; -}; - -} // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/swapchain.hpp b/modules/renderer/private/vk/swapchain.hpp deleted file mode 100644 index 2bd40a4..0000000 --- a/modules/renderer/private/vk/swapchain.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#define VK_NO_PROTOTYPES -#include - -namespace lt::renderer::vk { - -class Swapchain -{ -public: - Swapchain() - { - } - -private: - VkSwapchainKHR m_swapchain {}; - - std::vector images; - std::vector image_views; -}; - -} // namespace lt::renderer::vk diff --git a/modules/renderer/public/system.hpp b/modules/renderer/public/system.hpp index cdb97a8..f3f693f 100644 --- a/modules/renderer/public/system.hpp +++ b/modules/renderer/public/system.hpp @@ -2,8 +2,7 @@ #include #include -#include -#include +#include namespace lt::renderer { @@ -19,8 +18,10 @@ public: [[nodiscard]] System(CreateInfo info) : m_registry(std::move(info.registry)) - , m_context(info.surface_entity, std::move(info.system_stats)) + , m_stats(info.system_stats) + , m_context(info.surface_entity, info.system_stats) { + ensure(m_stats, "Failed to initialize system: null stats"); ensure(m_registry, "Failed to initialize renderer system: null registry"); } @@ -50,7 +51,7 @@ public: [[nodiscard]] auto get_stats() const -> const app::SystemStats & { - return m_context.get_stats(); + return *m_stats; } [[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override @@ -61,7 +62,7 @@ public: private: Ref m_registry; - renderer::Validation m_validation; + Ref m_stats; vk::Context m_context;