feat(renderer): contextn, test utils, refactors & changes

This commit is contained in:
light7734 2025-09-30 14:04:22 +03:30
parent 405c707e23
commit 5148b8836c
Signed by: light7734
GPG key ID: 8C30176798F1A6BA
23 changed files with 836 additions and 155 deletions

View file

@ -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
)

View file

@ -108,7 +108,7 @@ private:
);
};
Suite raii = [] {
Suite raii = "raii"_suite = [] {
Case { "happy path won't throw" } = [&] {
ignore = create_system();
};

View file

@ -1,4 +1,3 @@
#include <ranges>
#include <renderer/vk/context/context.hpp>
#include <renderer/vk/context/instance.hpp>

View file

@ -1,9 +1,14 @@
#include <renderer/vk/context/device.hpp>
#include <renderer/vk/context/instance.hpp>
#include <renderer/vk/context/surface.hpp>
#include <renderer/vk/debug/validation.hpp>
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);

View file

@ -1,15 +1,14 @@
#pragma once
#include <memory/pointer_types/null_on_move.hpp>
#include <renderer/vk/context/instance.hpp>
#include <renderer/vk/context/surface.hpp>
#include <renderer/vk/vulkan.hpp>
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;

View file

@ -0,0 +1,99 @@
#include <ranges>
#include <renderer/vk/context/device.hpp>
#include <renderer/vk/context/surface.hpp>
#include <surface/components.hpp>
#include <surface/system.hpp>
#include <test/test.hpp>
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<ecs::Registry>();
auto surface_system = surface::System { registry };
auto entity = ecs::Entity { registry, registry->create_entity() };
entity.add<surface::SurfaceComponent>(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<ecs::Registry>();
auto surface_system = surface::System { registry };
auto entity = ecs::Entity { registry, registry->create_entity() };
entity.add<surface::SurfaceComponent>(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<ecs::Registry>();
auto surface_system = surface::System { registry };
auto entity = ecs::Entity { registry, registry->create_entity() };
entity.add<surface::SurfaceComponent>(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<ecs::Registry>();
auto surface_system = surface::System { registry };
auto entity = ecs::Entity { registry, registry->create_entity() };
entity.add<surface::SurfaceComponent>(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<ecs::Registry>();
auto surface_system = surface::System { registry };
auto entity = ecs::Entity { registry, registry->create_entity() };
entity.add<surface::SurfaceComponent>(surface::SurfaceComponent::CreateInfo {
.resolution = resolution,
.visible = true,
});
auto surface = Surface { entity };
{
auto device = Device { surface };
}
};
};

View file

@ -1,5 +1,6 @@
#include <app/system.hpp>
#include <renderer/vk/context/instance.hpp>
#include <renderer/vk/debug/validation.hpp>
#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<const char *, 5> {
"info", "warn", "perf", "error", "verbose",
};
const auto settings = std::array<VkLayerSettingEXT, 7>({
{
.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<const void *>(&setting_debug_action),
},
{
.pLayerName = layer_name,
.pSettingName = "report_flags",
.type = VK_LAYER_SETTING_TYPE_STRING_EXT,
.valueCount = setting_report_flags.size(),
.pValues = static_cast<const void *>(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<const char *> {
"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<uint32_t>(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);

View file

@ -1,103 +1,9 @@
#pragma once
#define VK_NO_PROTOTYPES
#define VK_USE_PLATFORM_XLIB_KHR
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_xlib.h>
#include <renderer/vk/vulkan.hpp>
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

View file

@ -0,0 +1,95 @@
#include <renderer/vk/context/instance.hpp>
#include <test/test.hpp>
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);
};
};

View file

@ -1,4 +1,3 @@
#include <lt_debug/assertions.hpp>
#include <renderer/vk/context/surface.hpp>
#include <surface/components.hpp>
@ -8,8 +7,8 @@ Surface::Surface(const ecs::Entity &surface_entity)
{
const auto &component = surface_entity.get<surface::SurfaceComponent>();
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,

View file

@ -0,0 +1,51 @@
#include <renderer/vk/context/surface.hpp>
#include <renderer/vk/debug/messenger.hpp>
#include <renderer/vk/test_utils.hpp>
#include <surface/components.hpp>
#include <surface/system.hpp>
#include <test/test.hpp>
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<Registry>();
auto entity = Entity { registry, registry->create_entity() };
auto surface_system = System(registry);
entity.add<SurfaceComponent>(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<Registry>();
auto entity = Entity { registry, registry->create_entity() };
entity.add<SurfaceComponent>(SurfaceComponent::CreateInfo {
.title = "",
.resolution = constants::resolution,
.visible = true,
});
expect_throw([&] { Surface { entity }; });
expect_false(observer.had_any_messages());
};
};

View file

@ -1,5 +1,9 @@
#include <ranges>
#include <renderer/vk/context/device.hpp>
#include <renderer/vk/context/instance.hpp>
#include <renderer/vk/context/surface.hpp>
#include <renderer/vk/context/swapchain.hpp>
#include <renderer/vk/debug/validation.hpp>
namespace lt::renderer::vk {

View file

@ -1,15 +1,14 @@
#pragma once
#include <memory/pointer_types/null_on_move.hpp>
#include <renderer/vk/context/device.hpp>
#include <renderer/vk/context/instance.hpp>
#include <renderer/vk/vulkan.hpp>
namespace lt::renderer::vk {
class Swapchain
{
public:
Swapchain(const Device &device, const Surface &surface);
Swapchain(const class Device &device, const class Surface &surface);
~Swapchain();

View file

@ -0,0 +1,143 @@
#pragma once
#include <memory/pointer_types/null_on_move.hpp>
#include <renderer/vk/context/instance.hpp>
#include <renderer/vk/vulkan.hpp>
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<void(
Severity message_severity,
Type message_type,
CallbackData_T vulkan_data,
void *user_data
)>;
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<Severity>(message_severity),
static_cast<Type>(message_type),
callback_data,
m_user_data
);
}
memory::NullOnMove<VkDebugUtilsMessengerEXT> m_debug_messenger = VK_NULL_HANDLE;
Callback_T m_callback;
void *m_user_data;
};
} // namespace lt::renderer::vk

View file

@ -0,0 +1,149 @@
#include <renderer/vk/context/surface.hpp>
#include <renderer/vk/debug/messenger.hpp>
#include <renderer/vk/test_utils.hpp>
#include <surface/components.hpp>
#include <surface/system.hpp>
#include <test/expects.hpp>
#include <test/test.hpp>
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);
};
};

View file

@ -0,0 +1,17 @@
#pragma once
#include <renderer/vk/vulkan.hpp>
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

View file

@ -1,22 +1,14 @@
#include <ranges>
#include <renderer/vk/context/context.hpp>
#include <renderer/vk/pipeline.hpp>
#include <renderer/vk/test_utils.hpp>
#include <surface/system.hpp>
#include <test/test.hpp>
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<ecs::Entity>(m_registry, m_registry->create_entity());
m_surface_entity->add<surface::SurfaceComponent>(surface::SurfaceComponent::CreateInfo {
.title = "",
.resolution = resolution,
.resolution = constants::resolution,
});
m_context = create_ref<Context>(*m_surface_entity, m_stats);
@ -57,7 +49,7 @@ private:
Ref<Context> m_context;
};
Suite raii = [] {
Suite raii = "raii"_suite = [] {
Case { "happy path won't throw" } = [] {
auto fixture = VkPipelineTest {};
std::ignore = Pipeline { { .context = fixture.context() } };

View file

@ -0,0 +1 @@
#include <renderer/vk/test_utils.hpp>

View file

@ -0,0 +1,87 @@
#pragma once
#include <ranges>
#include <renderer/vk/context/surface.hpp>
#include <renderer/vk/debug/messenger.hpp>
#include <surface/components.hpp>
#include <surface/system.hpp>
#include <test/test.hpp>
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<VkExtent2D>
{
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;
}

View file

@ -0,0 +1,91 @@
#pragma once
#define VK_NO_PROTOTYPES
#define VK_USE_PLATFORM_XLIB_KHR
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_xlib.h>
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