diff --git a/modules/renderer/CMakeLists.txt b/modules/renderer/CMakeLists.txt index 7741f23..35f0146 100644 --- a/modules/renderer/CMakeLists.txt +++ b/modules/renderer/CMakeLists.txt @@ -1,5 +1,6 @@ add_library_module(renderer system.cpp + vk/debug/messenger.cpp vk/context/instance.cpp vk/context/surface.cpp vk/context/device.cpp @@ -12,6 +13,7 @@ target_link_libraries(renderer PUBLIC app ecs + memory PRIVATE surface pthread @@ -19,6 +21,8 @@ PRIVATE add_test_module(renderer system.test.cpp + vk/test_utils.cpp + vk/debug/messenger.test.cpp vk/context/instance.test.cpp vk/context/surface.test.cpp vk/context/device.test.cpp @@ -26,3 +30,8 @@ add_test_module(renderer vk/context/context.test.cpp vk/pipeline.test.cpp ) +target_link_libraries(renderer_tests +PRIVATE + surface + pthread +) diff --git a/modules/renderer/private/system.test.cpp b/modules/renderer/private/system.test.cpp index f5e997a..04f587f 100644 --- a/modules/renderer/private/system.test.cpp +++ b/modules/renderer/private/system.test.cpp @@ -108,7 +108,7 @@ private: ); }; -Suite raii = [] { +Suite raii = "raii"_suite = [] { Case { "happy path won't throw" } = [&] { ignore = create_system(); }; diff --git a/modules/renderer/private/vk/context/context.cpp b/modules/renderer/private/vk/context/context.cpp index 4c92712..18ef46e 100644 --- a/modules/renderer/private/vk/context/context.cpp +++ b/modules/renderer/private/vk/context/context.cpp @@ -1,4 +1,3 @@ -#include #include #include diff --git a/modules/renderer/private/vk/context/context.test.cpp b/modules/renderer/private/vk/context/context.test.cpp new file mode 100644 index 0000000..e69de29 diff --git a/modules/renderer/private/vk/context/device.cpp b/modules/renderer/private/vk/context/device.cpp index 2bd2eea..1e5a16f 100644 --- a/modules/renderer/private/vk/context/device.cpp +++ b/modules/renderer/private/vk/context/device.cpp @@ -1,9 +1,14 @@ #include +#include +#include +#include namespace lt::renderer::vk { Device::Device(const Surface &surface) { + ensure(surface.vk(), "Failed to initialize vk::Device: null vulkan surface"); + initialize_physical_device(); initialize_logical_device(); Instance::load_device_functions(m_device); diff --git a/modules/renderer/private/vk/context/device.hpp b/modules/renderer/private/vk/context/device.hpp index f3992cd..157904a 100644 --- a/modules/renderer/private/vk/context/device.hpp +++ b/modules/renderer/private/vk/context/device.hpp @@ -1,15 +1,14 @@ #pragma once #include -#include -#include +#include namespace lt::renderer::vk { class Device { public: - Device(const Surface &surface); + Device(const class Surface &surface); ~Device(); @@ -41,7 +40,7 @@ private: void initialize_logical_device(); - void initialize_queue(const Surface &surface); + void initialize_queue(const class Surface &surface); [[nodiscard]] auto find_suitable_queue_family() const -> uint32_t; diff --git a/modules/renderer/private/vk/context/device.test.cpp b/modules/renderer/private/vk/context/device.test.cpp new file mode 100644 index 0000000..50e6244 --- /dev/null +++ b/modules/renderer/private/vk/context/device.test.cpp @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include +#include + +using namespace lt; +using renderer::vk::Device; +using renderer::vk::Surface; +using test::Case; +using test::expect_ne; +using test::expect_throw; +using test::Suite; + +constexpr auto resolution = math::uvec2 { 800u, 600u }; + +Suite raii = "device_raii"_suite = [] { + Case { "happy path won't throw" } = [] { + auto registry = create_ref(); + auto surface_system = surface::System { registry }; + auto entity = ecs::Entity { registry, registry->create_entity() }; + entity.add(surface::SurfaceComponent::CreateInfo { + .resolution = resolution, + .visible = true, + }); + + auto surface = Surface { entity }; + auto device = Device { surface }; + }; + + Case { "many won't freeze/throw" } = [] { + auto registry = create_ref(); + auto surface_system = surface::System { registry }; + auto entity = ecs::Entity { registry, registry->create_entity() }; + entity.add(surface::SurfaceComponent::CreateInfo { + .resolution = resolution, + .visible = true, + }); + auto surface = Surface { entity }; + + // it takes a loong time to initialize vulkan + setup device + for (auto idx : std::views::iota(0, 10)) + { + Device { surface }; + } + }; + + Case { "unhappy path throws" } = [] { + auto registry = create_ref(); + auto surface_system = surface::System { registry }; + auto entity = ecs::Entity { registry, registry->create_entity() }; + entity.add(surface::SurfaceComponent::CreateInfo { + .resolution = resolution, + .visible = true, + }); + + auto moved_out_surface = Surface { entity }; + auto surface = std::move(moved_out_surface); + + expect_throw([&] { Device { moved_out_surface }; }); + }; + + Case { "post construct has correct state" } = [] { + auto registry = create_ref(); + auto surface_system = surface::System { registry }; + auto entity = ecs::Entity { registry, registry->create_entity() }; + entity.add(surface::SurfaceComponent::CreateInfo { + .resolution = resolution, + .visible = true, + }); + + auto surface = Surface { entity }; + auto device = Device { surface }; + + for (auto &index : device.get_family_indices()) + { + expect_ne(index, VK_QUEUE_FAMILY_IGNORED); + } + test::expect_true(device.physical()); + test::expect_true(device.vk()); + }; + + Case { "post destruct has correct state" } = [] { + auto registry = create_ref(); + auto surface_system = surface::System { registry }; + auto entity = ecs::Entity { registry, registry->create_entity() }; + entity.add(surface::SurfaceComponent::CreateInfo { + .resolution = resolution, + .visible = true, + }); + + auto surface = Surface { entity }; + + { + auto device = Device { surface }; + } + }; +}; diff --git a/modules/renderer/private/vk/context/instance.cpp b/modules/renderer/private/vk/context/instance.cpp index a544edd..96186a5 100644 --- a/modules/renderer/private/vk/context/instance.cpp +++ b/modules/renderer/private/vk/context/instance.cpp @@ -1,5 +1,6 @@ #include #include +#include #if defined(_WIN32) #error "Unsupported platform (currently)" @@ -113,17 +114,11 @@ Instance::Instance() initialize_instance(); load_instance_functions(); - initialize_debug_messenger(); } Instance::~Instance() { - if (m_instance) - { - vk_destroy_debug_messenger(m_instance, m_debug_messenger, nullptr); - vk_destroy_instance(m_instance, nullptr); - } - + vk_destroy_instance(m_instance, nullptr); unload_library(); } @@ -143,12 +138,83 @@ void Instance::initialize_instance() VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_XLIB_SURFACE_EXTENSION_NAME, }; + + const char *layer_name = "VK_LAYER_KHRONOS_validation"; + const auto setting_validate_core = VkBool32 { VK_TRUE }; + const auto setting_validate_sync = VkBool32 { VK_TRUE }; + 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 }; + auto setting_report_flags = std::array { + "info", "warn", "perf", "error", "verbose", + }; + + const auto settings = std::array({ + { + .pLayerName = layer_name, + .pSettingName = "validate_core", + .type = VK_LAYER_SETTING_TYPE_BOOL32_EXT, + .valueCount = 1, + .pValues = &setting_validate_core, + }, + { + .pLayerName = layer_name, + .pSettingName = "validate_sync", + .type = VK_LAYER_SETTING_TYPE_BOOL32_EXT, + .valueCount = 1, + .pValues = &setting_validate_sync, + }, + { + .pLayerName = layer_name, + .pSettingName = "thread_safety", + .type = VK_LAYER_SETTING_TYPE_BOOL32_EXT, + .valueCount = 1, + .pValues = &setting_thread_safety, + }, + { + .pLayerName = layer_name, + .pSettingName = "debug_action", + .type = VK_LAYER_SETTING_TYPE_STRING_EXT, + .valueCount = 1, + .pValues = static_cast(&setting_debug_action), + }, + { + .pLayerName = layer_name, + .pSettingName = "report_flags", + .type = VK_LAYER_SETTING_TYPE_STRING_EXT, + .valueCount = setting_report_flags.size(), + .pValues = static_cast(setting_report_flags.data()), + }, + { + .pLayerName = layer_name, + .pSettingName = "enable_message_limit", + .type = VK_LAYER_SETTING_TYPE_BOOL32_EXT, + .valueCount = 1, + .pValues = &setting_enable_message_limit, + }, + { + .pLayerName = layer_name, + .pSettingName = "duplicate_message_limit", + .type = VK_LAYER_SETTING_TYPE_UINT32_EXT, + .valueCount = 1u, + .pValues = &setting_duplicate_message_limit, + }, + }); + + const VkLayerSettingsCreateInfoEXT layer_settings_create_info = { + .sType = VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT, + .settingCount = settings.size(), + .pSettings = settings.data() + }; + auto layers = std::vector { "VK_LAYER_KHRONOS_validation", }; auto instance_info = VkInstanceCreateInfo { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pNext = &layer_settings_create_info, .pApplicationInfo = &app_info, .enabledLayerCount = static_cast(layers.size()), .ppEnabledLayerNames = layers.data(), @@ -174,32 +240,6 @@ void Instance::initialize_instance() ensure(m_instance, "Failed to create vulkan instance"); } -void Instance::initialize_debug_messenger() -{ - const auto info = VkDebugUtilsMessengerCreateInfoEXT { - .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, - - .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT - | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT - | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT - | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, - - .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT - | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT - | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, - - - .pfnUserCallback = &validation_layers_callback, - - .pUserData = {}, - }; - - ensure( - !vk_create_debug_messenger(m_instance, &info, nullptr, &m_debug_messenger), - "Failed to create vulkan debug utils messenger" - ); -} - void Instance::load_library() { library = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL); diff --git a/modules/renderer/private/vk/context/instance.hpp b/modules/renderer/private/vk/context/instance.hpp index 467a3bc..157d465 100644 --- a/modules/renderer/private/vk/context/instance.hpp +++ b/modules/renderer/private/vk/context/instance.hpp @@ -1,103 +1,9 @@ #pragma once -#define VK_NO_PROTOTYPES -#define VK_USE_PLATFORM_XLIB_KHR -#include -#include +#include namespace lt::renderer::vk { -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; -extern PFN_vkCreateInstance vk_create_instance; -extern PFN_vkEnumerateInstanceExtensionProperties vk_enumerate_instance_extension_properties; -extern PFN_vkEnumerateInstanceLayerProperties vk_enumerate_instance_layer_properties; - -// instance functions -extern PFN_vkDestroyInstance vk_destroy_instance; -extern PFN_vkEnumeratePhysicalDevices vk_enumerate_physical_devices; -extern PFN_vkGetPhysicalDeviceProperties vk_get_physical_device_properties; -extern PFN_vkGetPhysicalDeviceQueueFamilyProperties vk_get_physical_device_queue_family_properties; -extern PFN_vkCreateDevice vk_create_device; -extern PFN_vkGetDeviceProcAddr vk_get_device_proc_address; -extern PFN_vkDestroyDevice vk_destroy_device; -extern PFN_vkGetPhysicalDeviceFeatures vk_get_physical_device_features; -extern PFN_vkEnumerateDeviceExtensionProperties vk_enumerate_device_extension_properties; - -// extension instance functions -extern PFN_vkCmdBeginDebugUtilsLabelEXT vk_cmd_begin_debug_label; -extern PFN_vkCmdEndDebugUtilsLabelEXT vk_cmd_end_debug_label; -extern PFN_vkCmdInsertDebugUtilsLabelEXT vk_cmd_insert_debug_label; -extern PFN_vkCreateDebugUtilsMessengerEXT vk_create_debug_messenger; -extern PFN_vkDestroyDebugUtilsMessengerEXT vk_destroy_debug_messenger; -extern PFN_vkQueueBeginDebugUtilsLabelEXT vk_queue_begin_debug_label; -extern PFN_vkQueueEndDebugUtilsLabelEXT vk_queue_end_debug_label; -extern PFN_vkQueueInsertDebugUtilsLabelEXT vk_queue_insert_debug_label; -extern PFN_vkSetDebugUtilsObjectNameEXT vk_set_debug_object_name; -extern PFN_vkSetDebugUtilsObjectTagEXT vk_set_debug_object_tag; -extern PFN_vkSubmitDebugUtilsMessageEXT vk_submit_debug_message; - -// surface instance functions -extern PFN_vkGetPhysicalDeviceSurfaceSupportKHR vk_get_physical_device_surface_support; -extern PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vk_get_physical_device_surface_capabilities; -extern PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vk_get_physical_device_surface_formats; -extern PFN_vkCreateXlibSurfaceKHR vk_create_xlib_surface_khr; -extern PFN_vkDestroySurfaceKHR vk_destroy_surface_khr; - -// device functions -extern PFN_vkGetDeviceQueue vk_get_device_queue; -extern PFN_vkCreateCommandPool vk_create_command_pool; -extern PFN_vkDestroyCommandPool vk_destroy_command_pool; -extern PFN_vkAllocateCommandBuffers vk_allocate_command_buffers; -extern PFN_vkFreeCommandBuffers vk_free_command_buffers; -extern PFN_vkBeginCommandBuffer vk_begin_command_buffer; -extern PFN_vkEndCommandBuffer vk_end_command_buffer; -extern PFN_vkCmdPipelineBarrier vk_cmd_pipeline_barrier; -extern PFN_vkQueueSubmit vk_queue_submit; -extern PFN_vkQueueWaitIdle vk_queue_wait_idle; -extern PFN_vkDeviceWaitIdle vk_device_wait_idle; -extern PFN_vkCreateFence vk_create_fence; -extern PFN_vkDestroyFence vk_destroy_fence; -extern PFN_vkWaitForFences vk_wait_for_fences; -extern PFN_vkResetFences vk_reset_fences; -extern PFN_vkCreateSemaphore vk_create_semaphore; -extern PFN_vkDestroySemaphore vk_destroy_semaphore; -extern PFN_vkCreateSwapchainKHR vk_create_swapchain_khr; -extern PFN_vkDestroySwapchainKHR vk_destroy_swapchain_khr; -extern PFN_vkGetSwapchainImagesKHR vk_get_swapchain_images_khr; -extern PFN_vkAcquireNextImageKHR vk_acquire_next_image_khr; -extern PFN_vkQueuePresentKHR vk_queue_present_khr; -extern PFN_vkCreateImageView vk_create_image_view; -extern PFN_vkDestroyImageView vk_destroy_image_view; -extern PFN_vkCreateRenderPass vk_create_render_pass; -extern PFN_vkDestroyRenderPass vk_destroy_render_pass; -extern PFN_vkCreateFramebuffer vk_create_frame_buffer; -extern PFN_vkDestroyFramebuffer vk_destroy_frame_buffer; -extern PFN_vkCreateShaderModule vk_create_shader_module; -extern PFN_vkDestroyShaderModule vk_destroy_shader_module; -extern PFN_vkCreatePipelineLayout vk_create_pipeline_layout; -extern PFN_vkDestroyPipelineLayout vk_destroy_pipeline_layout; -extern PFN_vkCreateGraphicsPipelines vk_create_graphics_pipelines; -extern PFN_vkDestroyPipeline vk_destroy_pipeline; -extern PFN_vkCmdBeginRenderPass vk_cmd_begin_render_pass; -extern PFN_vkCmdEndRenderPass vk_cmd_end_render_pass; -extern PFN_vkCmdBindPipeline vk_cmd_bind_pipeline; -extern PFN_vkCmdDraw vk_cmd_draw; -extern PFN_vkCmdSetViewport vk_cmd_set_viewport; -extern PFN_vkCmdSetScissor vk_cmd_set_scissors; - -// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) - /** * Responsible for dynamically loading Vulkan library/functions. * @@ -141,8 +47,6 @@ private: void initialize_instance(); - void initialize_debug_messenger(); - void load_library(); void unload_library(); @@ -154,8 +58,6 @@ private: void load_device_functions_impl(VkDevice device); VkInstance m_instance = VK_NULL_HANDLE; - - VkDebugUtilsMessengerEXT m_debug_messenger = VK_NULL_HANDLE; }; } // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/context/instance.test.cpp b/modules/renderer/private/vk/context/instance.test.cpp new file mode 100644 index 0000000..68caf5e --- /dev/null +++ b/modules/renderer/private/vk/context/instance.test.cpp @@ -0,0 +1,95 @@ +#include +#include + +using namespace lt; +using renderer::vk::Instance; +using test::Case; +using test::expect_not_nullptr; +using test::Suite; + +// NOLINTNEXTLINE +Suite raii = "raii"_suite = [] { + Case { "post singleton insantiation state is correct" } = [] { + expect_not_nullptr(Instance::get()); + + using namespace renderer::vk; + expect_not_nullptr(vk_get_instance_proc_address); + expect_not_nullptr(vk_create_instance); + expect_not_nullptr(vk_enumerate_instance_extension_properties); + expect_not_nullptr(vk_enumerate_instance_layer_properties); + + expect_not_nullptr(vk_destroy_instance); + expect_not_nullptr(vk_enumerate_physical_devices); + expect_not_nullptr(vk_get_physical_device_properties); + expect_not_nullptr(vk_get_physical_device_queue_family_properties); + expect_not_nullptr(vk_create_device); + expect_not_nullptr(vk_get_device_proc_address); + expect_not_nullptr(vk_destroy_device); + expect_not_nullptr(vk_get_physical_device_features); + expect_not_nullptr(vk_enumerate_device_extension_properties); + + expect_not_nullptr(vk_cmd_begin_debug_label); + expect_not_nullptr(vk_cmd_end_debug_label); + expect_not_nullptr(vk_cmd_insert_debug_label); + expect_not_nullptr(vk_create_debug_messenger); + expect_not_nullptr(vk_destroy_debug_messenger); + expect_not_nullptr(vk_queue_begin_debug_label); + expect_not_nullptr(vk_queue_end_debug_label); + expect_not_nullptr(vk_queue_insert_debug_label); + expect_not_nullptr(vk_set_debug_object_name); + expect_not_nullptr(vk_set_debug_object_tag); + expect_not_nullptr(vk_submit_debug_message); + + expect_not_nullptr(vk_get_physical_device_surface_support); + expect_not_nullptr(vk_get_physical_device_surface_capabilities); + expect_not_nullptr(vk_get_physical_device_surface_formats); + expect_not_nullptr(vk_create_xlib_surface_khr); + expect_not_nullptr(vk_destroy_surface_khr); + }; + + Case { "post load device functions state is correct" } = [] { + using namespace renderer::vk; + expect_not_nullptr(Instance::get()); + + expect_not_nullptr(vk_get_device_queue); + expect_not_nullptr(vk_create_command_pool); + expect_not_nullptr(vk_destroy_command_pool); + expect_not_nullptr(vk_allocate_command_buffers); + expect_not_nullptr(vk_free_command_buffers); + expect_not_nullptr(vk_begin_command_buffer); + expect_not_nullptr(vk_end_command_buffer); + expect_not_nullptr(vk_cmd_pipeline_barrier); + expect_not_nullptr(vk_queue_submit); + expect_not_nullptr(vk_queue_wait_idle); + expect_not_nullptr(vk_device_wait_idle); + expect_not_nullptr(vk_create_fence); + expect_not_nullptr(vk_destroy_fence); + expect_not_nullptr(vk_wait_for_fences); + expect_not_nullptr(vk_reset_fences); + expect_not_nullptr(vk_create_semaphore); + expect_not_nullptr(vk_destroy_semaphore); + expect_not_nullptr(vk_create_swapchain_khr); + expect_not_nullptr(vk_destroy_swapchain_khr); + expect_not_nullptr(vk_get_swapchain_images_khr); + expect_not_nullptr(vk_acquire_next_image_khr); + expect_not_nullptr(vk_queue_present_khr); + expect_not_nullptr(vk_create_image_view); + expect_not_nullptr(vk_destroy_image_view); + expect_not_nullptr(vk_create_render_pass); + expect_not_nullptr(vk_destroy_render_pass); + expect_not_nullptr(vk_create_frame_buffer); + expect_not_nullptr(vk_destroy_frame_buffer); + expect_not_nullptr(vk_create_shader_module); + expect_not_nullptr(vk_destroy_shader_module); + expect_not_nullptr(vk_create_pipeline_layout); + expect_not_nullptr(vk_destroy_pipeline_layout); + expect_not_nullptr(vk_create_graphics_pipelines); + expect_not_nullptr(vk_destroy_pipeline); + expect_not_nullptr(vk_cmd_begin_render_pass); + expect_not_nullptr(vk_cmd_end_render_pass); + expect_not_nullptr(vk_cmd_bind_pipeline); + expect_not_nullptr(vk_cmd_draw); + expect_not_nullptr(vk_cmd_set_viewport); + expect_not_nullptr(vk_cmd_set_scissors); + }; +}; diff --git a/modules/renderer/private/vk/context/surface.cpp b/modules/renderer/private/vk/context/surface.cpp index 27f7e57..d5833dd 100644 --- a/modules/renderer/private/vk/context/surface.cpp +++ b/modules/renderer/private/vk/context/surface.cpp @@ -1,4 +1,3 @@ -#include #include #include @@ -8,8 +7,8 @@ Surface::Surface(const ecs::Entity &surface_entity) { const auto &component = surface_entity.get(); - esnure(component.get_native_data().display, "Failed to initialize usrface: null x-display"); - esnure(component.get_native_data().window, "Failed to initialize usrface: null x-window"); + ensure(component.get_native_data().display, "Failed to initialize vk::Surface: null x-display"); + ensure(component.get_native_data().window, "Failed to initialize vk::Surface: null x-window"); auto create_info = VkXlibSurfaceCreateInfoKHR { .sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, diff --git a/modules/renderer/private/vk/context/surface.test.cpp b/modules/renderer/private/vk/context/surface.test.cpp new file mode 100644 index 0000000..9e4f812 --- /dev/null +++ b/modules/renderer/private/vk/context/surface.test.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include + +using ::lt::ecs::Entity; +using ::lt::ecs::Registry; +using ::lt::renderer::vk::Surface; +using ::lt::surface::SurfaceComponent; +using ::lt::surface::System; + +Suite raii = "surface"_suite = [] { + Case { "happy path won't throw" } = [&] { + auto observer = ValidationObserver {}; + + auto registry = lt::create_ref(); + auto entity = Entity { registry, registry->create_entity() }; + auto surface_system = System(registry); + + entity.add(SurfaceComponent::CreateInfo { + .title = "", + .resolution = constants::resolution, + .visible = true, + }); + + const auto surface = Surface { entity }; + const auto &[x, y] = surface.get_framebuffer_size(); + + expect_eq(x, constants::resolution.x); + expect_eq(y, constants::resolution.y); + expect_not_nullptr(surface.vk()); + expect_false(observer.had_any_messages()); + }; + + Case { "unhappy path throws" } = [&] { + auto observer = ValidationObserver {}; + auto registry = lt::create_ref(); + auto entity = Entity { registry, registry->create_entity() }; + + entity.add(SurfaceComponent::CreateInfo { + .title = "", + .resolution = constants::resolution, + .visible = true, + }); + + expect_throw([&] { Surface { entity }; }); + expect_false(observer.had_any_messages()); + }; +}; diff --git a/modules/renderer/private/vk/context/swapchain.cpp b/modules/renderer/private/vk/context/swapchain.cpp index f47ec96..8c94de3 100644 --- a/modules/renderer/private/vk/context/swapchain.cpp +++ b/modules/renderer/private/vk/context/swapchain.cpp @@ -1,5 +1,9 @@ #include +#include +#include +#include #include +#include namespace lt::renderer::vk { diff --git a/modules/renderer/private/vk/context/swapchain.hpp b/modules/renderer/private/vk/context/swapchain.hpp index 04847b6..3ddc0ed 100644 --- a/modules/renderer/private/vk/context/swapchain.hpp +++ b/modules/renderer/private/vk/context/swapchain.hpp @@ -1,15 +1,14 @@ #pragma once #include -#include -#include +#include namespace lt::renderer::vk { class Swapchain { public: - Swapchain(const Device &device, const Surface &surface); + Swapchain(const class Device &device, const class Surface &surface); ~Swapchain(); diff --git a/modules/renderer/private/vk/context/swapchain.test.cpp b/modules/renderer/private/vk/context/swapchain.test.cpp new file mode 100644 index 0000000..e69de29 diff --git a/modules/renderer/private/vk/debug/messenger.cpp b/modules/renderer/private/vk/debug/messenger.cpp new file mode 100644 index 0000000..e69de29 diff --git a/modules/renderer/private/vk/debug/messenger.hpp b/modules/renderer/private/vk/debug/messenger.hpp new file mode 100644 index 0000000..6386e97 --- /dev/null +++ b/modules/renderer/private/vk/debug/messenger.hpp @@ -0,0 +1,143 @@ +#pragma once + +#include +#include +#include + +namespace lt::renderer::vk { + +class Messenger +{ +public: + // NOLINTNEXTLINE(performance-enum-size) + enum Severity : decltype(std::to_underlying( + VK_DEBUG_UTILS_MESSAGE_SEVERITY_FLAG_BITS_MAX_ENUM_EXT + )) + { + verbose = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, + info = VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT, + warning = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT, + error = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + + all_severity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + }; + + // NOLINTNEXTLINE(performance-enum-size) + enum Type : decltype(std::to_underlying(VK_DEBUG_UTILS_MESSAGE_TYPE_FLAG_BITS_MAX_ENUM_EXT)) + { + general = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT, + validation = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, + performance = VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + + // address_binding = VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT, + + /** @note: does not include address binding yet. */ + all_type = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + }; + + using CallbackData_T = const VkDebugUtilsMessengerCallbackDataEXT *; + + using Callback_T = std::function; + + struct CreateInfo + { + Severity severity; + + Type type; + + Callback_T callback; + + void *user_data; + }; + + explicit Messenger(CreateInfo info) + : m_callback(std::move(info.callback)) + , m_user_data(info.user_data) + { + ensure(m_callback, "Failed to initialize vk::Messenger: null callback"); + ensure(info.severity != Severity {}, "Failed to initialize vk::Messenger: null severity"); + ensure(info.type != Type {}, "Failed to initialize vk::Messenger: null type"); + + // Instance may not be initialized yet + // Making it get initialized inside a call to vk_create_debug_messenger + // Which would invoke a nullptr... + if (!vk_create_debug_messenger) + { + Instance::get(); + } + + const auto vulkan_info = VkDebugUtilsMessengerCreateInfoEXT { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .messageSeverity = info.severity, + .messageType = info.type, + .pfnUserCallback = &validation_layers_callback, + .pUserData = this, + }; + + ensure( + !vk_create_debug_messenger(Instance::get(), &vulkan_info, nullptr, &m_debug_messenger), + "Failed to create vulkan debug utils messenger" + ); + } + + ~Messenger() + { + vk_destroy_debug_messenger(Instance::get(), m_debug_messenger, nullptr); + } + + Messenger(Messenger &&) = default; + + Messenger(const Messenger &) = delete; + + auto operator=(Messenger &&) -> Messenger & = default; + + auto operator=(const Messenger &) const -> Messenger & = delete; + + +private: + static auto validation_layers_callback( + VkDebugUtilsMessageSeverityFlagBitsEXT const message_severity, + VkDebugUtilsMessageTypeFlagsEXT const message_type, + VkDebugUtilsMessengerCallbackDataEXT const *const callback_data, + void *const vulkan_user_data + ) -> VkBool32 + { + auto *messenger = (Messenger *)vulkan_user_data; // NOLINT + ensure(messenger, "Null vulkan_user_data received in messenger callback"); + + messenger->validation_layers_callback_impl(message_severity, message_type, callback_data); + return VK_FALSE; + } + + void validation_layers_callback_impl( + VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, + VkDebugUtilsMessageTypeFlagsEXT message_type, + const VkDebugUtilsMessengerCallbackDataEXT *callback_data + ) + { + m_callback( + static_cast(message_severity), + static_cast(message_type), + callback_data, + m_user_data + ); + } + + memory::NullOnMove m_debug_messenger = VK_NULL_HANDLE; + + Callback_T m_callback; + + void *m_user_data; +}; + +} // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/debug/messenger.test.cpp b/modules/renderer/private/vk/debug/messenger.test.cpp new file mode 100644 index 0000000..17c918c --- /dev/null +++ b/modules/renderer/private/vk/debug/messenger.test.cpp @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace lt; +using renderer::vk::Messenger; +using renderer::vk::Surface; +using enum Messenger::Severity; +using enum Messenger::Type; + +void noop_callback( + Messenger::Severity message_severity, + Messenger::Type message_type, + Messenger::CallbackData_T vulkan_data, + void *user_data +) +{ +} + +Suite raii = "messenger_raii"_suite = [] { + Case { "happy path won't throw" } = [] { + Messenger( + Messenger::CreateInfo { + .severity = all_severity, + .type = all_type, + .callback = &noop_callback, + } + ); + }; + + Case { "unhappy path throws" } = [] { + expect_throw([] { + Messenger( + Messenger::CreateInfo { + .severity = all_severity, + .type = all_type, + .callback = {}, + } + ); + }); + + expect_throw([] { + Messenger( + Messenger::CreateInfo { + .severity = {}, + .type = all_type, + .callback = &noop_callback, + } + ); + }); + + expect_throw([] { + Messenger( + Messenger::CreateInfo { + .severity = all_severity, + .type = {}, + .callback = &noop_callback, + } + ); + }); + }; +}; + +// NOLINTNEXTLINE(cppcoreguidelines-interfaces-global-init) +Suite callback = "messenger_callback"_suite = [] { + Case { "callback gets called with correct arguments" } = [] { + auto total_messages = 0u; + auto first_message_is_severity = false; + auto second_message_is_type = false; + auto third_message_is_user_callback = false; + auto all_messages_are_error = true; + auto all_messages_are_validation = true; + auto user_data_is_always_69 = true; + + auto callback = [&](Messenger::Severity message_severity, + Messenger::Type message_type, + Messenger::CallbackData_T vulkan_data, + void *user_data) { + ++total_messages; + auto message = std::string_view { vulkan_data->pMessage }; + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + user_data_is_always_69 = user_data_is_always_69 && *(size_t *)user_data == 69u; + + all_messages_are_validation = all_messages_are_validation && message_type == validation; + all_messages_are_error = all_messages_are_error && message_severity == error; + + if (total_messages == 1) + { + first_message_is_severity = message.starts_with( + "vkCreateDebugUtilsMessengerEXT(): pCreateInfo->messageSeverity is zero." + ); + return; + } + + + if (total_messages == 2) + { + second_message_is_type = message.starts_with( + "vkCreateDebugUtilsMessengerEXT(): pCreateInfo->messageType is zero." + ); + return; + } + + if (total_messages == 3) + { + third_message_is_user_callback = message.starts_with( + "vkCreateDebugUtilsMessengerEXT(): pCreateInfo->pfnUserCallback is NULL." + ); + return; + } + }; + + auto user_data = size_t { 69 }; + auto messenger = Messenger( + Messenger::CreateInfo { + .severity = all_severity, + .type = all_type, + .callback = callback, + .user_data = &user_data, + } + ); + + { + auto *messenger = VkDebugUtilsMessengerEXT {}; + auto info = VkDebugUtilsMessengerCreateInfoEXT { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + }; + renderer::vk::vk_create_debug_messenger( + renderer::vk::Instance::get(), + &info, + nullptr, + &messenger + ); + } + + expect_eq(total_messages, 3u); + expect_true(first_message_is_severity); + expect_true(second_message_is_type); + expect_true(third_message_is_user_callback); + expect_true(all_messages_are_error); + expect_true(all_messages_are_validation); + expect_true(user_data_is_always_69); + }; +}; diff --git a/modules/renderer/private/vk/debug/validation.hpp b/modules/renderer/private/vk/debug/validation.hpp new file mode 100644 index 0000000..3f7d900 --- /dev/null +++ b/modules/renderer/private/vk/debug/validation.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace lt::renderer::vk { + +inline void vkc(VkResult result) +{ + if (result) + { + throw std::runtime_error { + std::format("Vulkan call failed with result: {}", std::to_underlying(result)) + }; + } +} + +} // namespace lt::renderer::vk diff --git a/modules/renderer/private/vk/pipeline.test.cpp b/modules/renderer/private/vk/pipeline.test.cpp index 8ad6961..c2d2e0d 100644 --- a/modules/renderer/private/vk/pipeline.test.cpp +++ b/modules/renderer/private/vk/pipeline.test.cpp @@ -1,22 +1,14 @@ -#include #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: @@ -29,7 +21,7 @@ public: m_surface_entity = create_scope(m_registry, m_registry->create_entity()); m_surface_entity->add(surface::SurfaceComponent::CreateInfo { .title = "", - .resolution = resolution, + .resolution = constants::resolution, }); m_context = create_ref(*m_surface_entity, m_stats); @@ -57,7 +49,7 @@ private: Ref m_context; }; -Suite raii = [] { +Suite raii = "raii"_suite = [] { Case { "happy path won't throw" } = [] { auto fixture = VkPipelineTest {}; std::ignore = Pipeline { { .context = fixture.context() } }; diff --git a/modules/renderer/private/vk/test_utils.cpp b/modules/renderer/private/vk/test_utils.cpp new file mode 100644 index 0000000..cd57168 --- /dev/null +++ b/modules/renderer/private/vk/test_utils.cpp @@ -0,0 +1 @@ +#include diff --git a/modules/renderer/private/vk/test_utils.hpp b/modules/renderer/private/vk/test_utils.hpp new file mode 100644 index 0000000..19f2b8a --- /dev/null +++ b/modules/renderer/private/vk/test_utils.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +using ::lt::test::Case; +using ::lt::test::expect_eq; +using ::lt::test::expect_false; +using ::lt::test::expect_not_nullptr; +using ::lt::test::expect_throw; +using ::lt::test::expect_true; +using ::lt::test::Suite; +using ::std::ignore; + +namespace constants { + +constexpr auto resolution = lt::math::uvec2 { 800u, 600u }; + +} + +class ValidationObserver +{ + using Messenger = lt::renderer::vk::Messenger; + using enum Messenger::Type; + using enum Messenger::Severity; + +public: + ValidationObserver() + : m_messenger( + Messenger::CreateInfo { + .severity = all_severity, + .type = lt::renderer::vk::Messenger::all_type, + .callback = &callback, + .user_data = &m_had_any_messages, + } + ) + { + } + + [[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; + + 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; +}; + +template<> +struct std::formatter +{ + constexpr auto parse(std::format_parse_context &context) + { + return context.begin(); + } + + auto format(const VkExtent2D &val, std::format_context &context) const + { + return std::format_to(context.out(), "{}, {}", val.width, val.height); + } +}; + +inline auto operator==(VkExtent2D lhs, VkExtent2D rhs) -> bool +{ + return lhs.width == rhs.width && lhs.height == rhs.height; +} diff --git a/modules/renderer/private/vk/vulkan.hpp b/modules/renderer/private/vk/vulkan.hpp new file mode 100644 index 0000000..de96918 --- /dev/null +++ b/modules/renderer/private/vk/vulkan.hpp @@ -0,0 +1,91 @@ +#pragma once + +#define VK_NO_PROTOTYPES +#define VK_USE_PLATFORM_XLIB_KHR +#include +#include + +namespace lt::renderer::vk { + +// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) +// global functions +extern PFN_vkGetInstanceProcAddr vk_get_instance_proc_address; +extern PFN_vkCreateInstance vk_create_instance; +extern PFN_vkEnumerateInstanceExtensionProperties vk_enumerate_instance_extension_properties; +extern PFN_vkEnumerateInstanceLayerProperties vk_enumerate_instance_layer_properties; + +// instance functions +extern PFN_vkDestroyInstance vk_destroy_instance; +extern PFN_vkEnumeratePhysicalDevices vk_enumerate_physical_devices; +extern PFN_vkGetPhysicalDeviceProperties vk_get_physical_device_properties; +extern PFN_vkGetPhysicalDeviceQueueFamilyProperties vk_get_physical_device_queue_family_properties; +extern PFN_vkCreateDevice vk_create_device; +extern PFN_vkGetDeviceProcAddr vk_get_device_proc_address; +extern PFN_vkDestroyDevice vk_destroy_device; +extern PFN_vkGetPhysicalDeviceFeatures vk_get_physical_device_features; +extern PFN_vkEnumerateDeviceExtensionProperties vk_enumerate_device_extension_properties; + +// extension instance functions +extern PFN_vkCmdBeginDebugUtilsLabelEXT vk_cmd_begin_debug_label; +extern PFN_vkCmdEndDebugUtilsLabelEXT vk_cmd_end_debug_label; +extern PFN_vkCmdInsertDebugUtilsLabelEXT vk_cmd_insert_debug_label; +extern PFN_vkCreateDebugUtilsMessengerEXT vk_create_debug_messenger; +extern PFN_vkDestroyDebugUtilsMessengerEXT vk_destroy_debug_messenger; +extern PFN_vkQueueBeginDebugUtilsLabelEXT vk_queue_begin_debug_label; +extern PFN_vkQueueEndDebugUtilsLabelEXT vk_queue_end_debug_label; +extern PFN_vkQueueInsertDebugUtilsLabelEXT vk_queue_insert_debug_label; +extern PFN_vkSetDebugUtilsObjectNameEXT vk_set_debug_object_name; +extern PFN_vkSetDebugUtilsObjectTagEXT vk_set_debug_object_tag; +extern PFN_vkSubmitDebugUtilsMessageEXT vk_submit_debug_message; + +// surface instance functions +extern PFN_vkGetPhysicalDeviceSurfaceSupportKHR vk_get_physical_device_surface_support; +extern PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vk_get_physical_device_surface_capabilities; +extern PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vk_get_physical_device_surface_formats; +extern PFN_vkCreateXlibSurfaceKHR vk_create_xlib_surface_khr; +extern PFN_vkDestroySurfaceKHR vk_destroy_surface_khr; + +// device functions +extern PFN_vkGetDeviceQueue vk_get_device_queue; +extern PFN_vkCreateCommandPool vk_create_command_pool; +extern PFN_vkDestroyCommandPool vk_destroy_command_pool; +extern PFN_vkAllocateCommandBuffers vk_allocate_command_buffers; +extern PFN_vkFreeCommandBuffers vk_free_command_buffers; +extern PFN_vkBeginCommandBuffer vk_begin_command_buffer; +extern PFN_vkEndCommandBuffer vk_end_command_buffer; +extern PFN_vkCmdPipelineBarrier vk_cmd_pipeline_barrier; +extern PFN_vkQueueSubmit vk_queue_submit; +extern PFN_vkQueueWaitIdle vk_queue_wait_idle; +extern PFN_vkDeviceWaitIdle vk_device_wait_idle; +extern PFN_vkCreateFence vk_create_fence; +extern PFN_vkDestroyFence vk_destroy_fence; +extern PFN_vkWaitForFences vk_wait_for_fences; +extern PFN_vkResetFences vk_reset_fences; +extern PFN_vkCreateSemaphore vk_create_semaphore; +extern PFN_vkDestroySemaphore vk_destroy_semaphore; +extern PFN_vkCreateSwapchainKHR vk_create_swapchain_khr; +extern PFN_vkDestroySwapchainKHR vk_destroy_swapchain_khr; +extern PFN_vkGetSwapchainImagesKHR vk_get_swapchain_images_khr; +extern PFN_vkAcquireNextImageKHR vk_acquire_next_image_khr; +extern PFN_vkQueuePresentKHR vk_queue_present_khr; +extern PFN_vkCreateImageView vk_create_image_view; +extern PFN_vkDestroyImageView vk_destroy_image_view; +extern PFN_vkCreateRenderPass vk_create_render_pass; +extern PFN_vkDestroyRenderPass vk_destroy_render_pass; +extern PFN_vkCreateFramebuffer vk_create_frame_buffer; +extern PFN_vkDestroyFramebuffer vk_destroy_frame_buffer; +extern PFN_vkCreateShaderModule vk_create_shader_module; +extern PFN_vkDestroyShaderModule vk_destroy_shader_module; +extern PFN_vkCreatePipelineLayout vk_create_pipeline_layout; +extern PFN_vkDestroyPipelineLayout vk_destroy_pipeline_layout; +extern PFN_vkCreateGraphicsPipelines vk_create_graphics_pipelines; +extern PFN_vkDestroyPipeline vk_destroy_pipeline; +extern PFN_vkCmdBeginRenderPass vk_cmd_begin_render_pass; +extern PFN_vkCmdEndRenderPass vk_cmd_end_render_pass; +extern PFN_vkCmdBindPipeline vk_cmd_bind_pipeline; +extern PFN_vkCmdDraw vk_cmd_draw; +extern PFN_vkCmdSetViewport vk_cmd_set_viewport; +extern PFN_vkCmdSetScissor vk_cmd_set_scissors; +// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) + +} // namespace lt::renderer::vk