From 61473c275820b5f226a6f79a6e07b12608638862 Mon Sep 17 00:00:00 2001 From: light7734 Date: Tue, 7 Oct 2025 16:09:50 +0330 Subject: [PATCH] test(renderer): overhaul tests & fix many bugs --- modules/renderer/CMakeLists.txt | 53 +++-- .../private/backend/vk/context/context.cpp | 41 ---- .../private/backend/vk/context/context.hpp | 66 ------ .../private/backend/vk/context/device.hpp | 2 +- .../backend/vk/context/instance.test.cpp | 90 ++++++++ .../private/backend/vk/context/surface.cpp | 10 +- .../private/backend/vk/context/surface.hpp | 2 +- .../renderer/private/backend/vk/messenger.cpp | 32 ++- .../renderer/private/backend/vk/messenger.hpp | 3 +- .../private/backend/vk/messenger.test.cpp | 1 - .../private/backend/vk/renderer/pass.cpp | 14 +- .../private/backend/vk/renderer/pass.hpp | 8 +- .../private/backend/vk/renderer/renderer.cpp | 9 +- .../private/backend/vk/renderer/renderer.hpp | 4 +- .../private/backend/vk/test_utils.cpp | 1 - .../private/backend/vk/test_utils.hpp | 133 ++---------- .../private/frontend/context/context.cpp | 18 -- .../private/frontend/context/context.hpp | 46 ---- .../private/frontend/context/context.test.cpp | 56 ----- .../private/frontend/context/device.cpp | 21 ++ .../private/frontend/context/device.hpp | 6 + .../private/frontend/context/device.test.cpp | 115 +++------- .../renderer/private/frontend/context/gpu.cpp | 18 ++ .../renderer/private/frontend/context/gpu.hpp | 6 + .../private/frontend/context/instance.cpp | 17 ++ .../private/frontend/context/instance.hpp | 4 + .../frontend/context/instance.test.cpp | 95 --------- .../private/frontend/context/surface.cpp | 24 +++ .../private/frontend/context/surface.hpp | 13 ++ .../private/frontend/context/surface.test.cpp | 96 ++++++--- .../private/frontend/context/swapchain.cpp | 23 ++ .../private/frontend/context/swapchain.hpp | 10 + .../renderer/private/frontend/messenger.cpp | 9 +- .../private/frontend/messenger.test.cpp | 0 .../private/frontend/renderer/pass.cpp | 29 +++ .../private/frontend/renderer/pass.hpp | 13 ++ .../private/frontend/renderer/pass.test.cpp | 80 +++++-- .../private/frontend/renderer/renderer.cpp | 28 ++- .../private/frontend/renderer/renderer.hpp | 12 +- .../frontend/renderer/renderer.test.cpp | 110 ++++++---- modules/renderer/private/system.cpp | 91 ++++++-- modules/renderer/private/system.test.cpp | 177 +++++----------- modules/renderer/private/test/constants.hpp | 5 + modules/renderer/private/test/formatters.hpp | 0 modules/renderer/private/test/utils.cpp | 1 + modules/renderer/private/test/utils.hpp | 199 ++++++++++++++++++ modules/renderer/public/api.hpp | 10 +- .../renderer/public/components/messenger.hpp | 4 +- .../renderer/public/frontend/messenger.hpp | 2 +- modules/renderer/public/system.hpp | 55 ++++- 50 files changed, 1030 insertions(+), 832 deletions(-) delete mode 100644 modules/renderer/private/backend/vk/context/context.cpp delete mode 100644 modules/renderer/private/backend/vk/context/context.hpp create mode 100644 modules/renderer/private/backend/vk/context/instance.test.cpp delete mode 100644 modules/renderer/private/backend/vk/test_utils.cpp delete mode 100644 modules/renderer/private/frontend/context/context.cpp delete mode 100644 modules/renderer/private/frontend/context/context.hpp delete mode 100644 modules/renderer/private/frontend/context/context.test.cpp create mode 100644 modules/renderer/private/frontend/context/device.cpp create mode 100644 modules/renderer/private/frontend/context/gpu.cpp create mode 100644 modules/renderer/private/frontend/context/instance.cpp create mode 100644 modules/renderer/private/frontend/context/surface.cpp create mode 100644 modules/renderer/private/frontend/context/swapchain.cpp create mode 100644 modules/renderer/private/frontend/messenger.test.cpp create mode 100644 modules/renderer/private/frontend/renderer/pass.cpp create mode 100644 modules/renderer/private/test/constants.hpp create mode 100644 modules/renderer/private/test/formatters.hpp create mode 100644 modules/renderer/private/test/utils.cpp create mode 100644 modules/renderer/private/test/utils.hpp diff --git a/modules/renderer/CMakeLists.txt b/modules/renderer/CMakeLists.txt index c7aaa6f..5ef45e9 100644 --- a/modules/renderer/CMakeLists.txt +++ b/modules/renderer/CMakeLists.txt @@ -3,7 +3,6 @@ add_library_module(renderer # Vulkan - backend backend/vk/messenger.cpp - backend/vk/context/context.cpp backend/vk/context/device.cpp backend/vk/context/gpu.cpp backend/vk/context/instance.cpp @@ -11,11 +10,15 @@ add_library_module(renderer backend/vk/context/swapchain.cpp backend/vk/renderer/pass.cpp backend/vk/renderer/renderer.cpp - # Vulkan - frontend frontend/messenger.cpp - frontend/context/context.cpp + frontend/context/device.cpp + frontend/context/gpu.cpp + frontend/context/instance.cpp + frontend/context/surface.cpp + frontend/context/swapchain.cpp frontend/renderer/renderer.cpp + frontend/renderer/pass.cpp ) target_link_libraries(renderer @@ -31,21 +34,29 @@ PRIVATE pthread ) -# 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 -# vk/context/swapchain.test.cpp -# vk/context/context.test.cpp -# vk/renderer/pass.test.cpp -# vk/renderer/renderer.test.cpp -# vk/pipeline.test.cpp -# ) -# target_link_libraries(renderer_tests -# PRIVATE -# surface -# pthread -# ) +add_test_module(renderer + test/utils.cpp + + system.test.cpp + + # general backend tests through the frontend + frontend/messenger.test.cpp + frontend/context/surface.test.cpp + frontend/context/device.test.cpp + frontend/context/swapchain.test.cpp + frontend/renderer/pass.test.cpp + frontend/renderer/renderer.test.cpp + + # backend specific tests -- vk + backend/vk/context/instance.test.cpp + + # backend specific tests -- dx + + # backend specific tests -- mt +) + +target_link_libraries(renderer_tests +PRIVATE + surface + pthread +) diff --git a/modules/renderer/private/backend/vk/context/context.cpp b/modules/renderer/private/backend/vk/context/context.cpp deleted file mode 100644 index 394e20a..0000000 --- a/modules/renderer/private/backend/vk/context/context.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -namespace lt::renderer::vk { - -Context::Context(const ecs::Entity &surface_entity) - : m_instance(Instance::get()) - , m_surface(memory::create_scope(m_instance, surface_entity)) - , m_gpu(memory::create_scope(m_instance)) - , m_device(memory::create_scope(m_gpu.get(), m_surface.get())) - , m_swapchain(memory::create_scope(m_surface.get(), m_gpu.get(), m_device.get())) -{ - ensure( - static_cast(m_instance)->vk(), - "Failed to create vulkan context: null instance" - ); - - ensure( - static_cast(m_surface.get())->vk(), - "Failed to create vulkan context: null surface" - ); - - ensure(static_cast(m_gpu.get())->vk(), "Failed to create vulkan context: null gpu"); - - ensure( - static_cast(m_device.get())->vk(), - "Failed to create vulkan context: null device" - ); - - ensure( - static_cast(m_swapchain.get())->vk(), - "Failed to create vulkan context: null swapchain" - ); -} - -} // namespace lt::renderer::vk diff --git a/modules/renderer/private/backend/vk/context/context.hpp b/modules/renderer/private/backend/vk/context/context.hpp deleted file mode 100644 index 4cd4f26..0000000 --- a/modules/renderer/private/backend/vk/context/context.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace lt::renderer::vk { - -using memory::NullOnMove; - -/** A layer of glue between main graphics objects. */ -class Context: public IContext -{ -public: - Context(const ecs::Entity &surface_entity); - - [[nodiscard]] auto instance() const -> IInstance * override - { - return m_instance; - } - - [[nodiscard]] auto gpu() const -> IGpu * override - { - return m_gpu.get(); - } - - [[nodiscard]] auto device() const -> IDevice * override - { - return m_device.get(); - } - - [[nodiscard]] auto swapchain() const -> ISwapchain * override - { - return m_swapchain.get(); - } - - [[nodiscard]] auto surface() const -> ISurface * override - { - return m_surface.get(); - } - - void recreate_swapchain() override - { - m_swapchain.reset(); - m_swapchain = memory::create_scope( - m_surface.get(), - m_gpu.get(), - m_device.get() - ); - } - -private: - IInstance *m_instance; - - memory::Scope m_surface; - - memory::Scope m_gpu; - - memory::Scope m_device; - - memory::Scope m_swapchain; -}; - -} // namespace lt::renderer::vk diff --git a/modules/renderer/private/backend/vk/context/device.hpp b/modules/renderer/private/backend/vk/context/device.hpp index 0d523b2..a0ecaf8 100644 --- a/modules/renderer/private/backend/vk/context/device.hpp +++ b/modules/renderer/private/backend/vk/context/device.hpp @@ -91,7 +91,7 @@ public: [[nodiscard]] auto acquire_image( VkSwapchainKHR swapchain, VkSemaphore semaphore, - uint64_t timeout = 1'000'000 + uint64_t timeout = 100'000'000 ) -> std::optional; [[nodiscard]] auto get_swapchain_images(VkSwapchainKHR swapchain) const -> std::vector; diff --git a/modules/renderer/private/backend/vk/context/instance.test.cpp b/modules/renderer/private/backend/vk/context/instance.test.cpp new file mode 100644 index 0000000..badef9c --- /dev/null +++ b/modules/renderer/private/backend/vk/context/instance.test.cpp @@ -0,0 +1,90 @@ +#include +#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_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); + + // TODO(Light): add test for platform-dependant functions + // 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/backend/vk/context/surface.cpp b/modules/renderer/private/backend/vk/context/surface.cpp index 6e15a73..ea39570 100644 --- a/modules/renderer/private/backend/vk/context/surface.cpp +++ b/modules/renderer/private/backend/vk/context/surface.cpp @@ -32,15 +32,9 @@ Surface::~Surface() m_instance->destroy_surface(m_surface); } -[[nodiscard]] auto Surface::get_framebuffer_size() const -> VkExtent2D +[[nodiscard]] auto Surface::get_framebuffer_size() const -> math::uvec2 { - const auto &[width, height] = // - m_surface_entity.get().get_resolution(); - - return { - .width = width, - .height = height, - }; + return m_surface_entity.get().get_resolution(); } } // namespace lt::renderer::vk diff --git a/modules/renderer/private/backend/vk/context/surface.hpp b/modules/renderer/private/backend/vk/context/surface.hpp index 31aae37..2373210 100644 --- a/modules/renderer/private/backend/vk/context/surface.hpp +++ b/modules/renderer/private/backend/vk/context/surface.hpp @@ -30,7 +30,7 @@ public: return m_surface; } - [[nodiscard]] auto get_framebuffer_size() const -> VkExtent2D; + [[nodiscard]] auto get_framebuffer_size() const -> math::uvec2 override; private: class Instance *m_instance {}; diff --git a/modules/renderer/private/backend/vk/messenger.cpp b/modules/renderer/private/backend/vk/messenger.cpp index 152ec34..d28f335 100644 --- a/modules/renderer/private/backend/vk/messenger.cpp +++ b/modules/renderer/private/backend/vk/messenger.cpp @@ -4,10 +4,24 @@ namespace lt::renderer::vk { Messenger::Messenger(IInstance *instance, ecs::Entity entity) : m_instance(static_cast(instance)) - , m_entity(std::move(entity)) + + // Move this to heap for pointer-stability of .pUserData + , m_entity(memory::create_scope(std::move(entity))) { - const auto &component = m_entity.get(); + const auto &component = m_entity->get(); + + ensure( + component.get_severities() != MessageSeverity::none, + "Failed to create vk::Messenger: severities == none" + ); + + ensure( + component.get_types() != MessageType::none, + "Failed to create vk::Messenger: types == none" + ); + + ensure(component.get_callback(), "Failed to create vk::Messenger: null callback"); m_debug_messenger = m_instance->create_messenger( VkDebugUtilsMessengerCreateInfoEXT { @@ -15,7 +29,7 @@ Messenger::Messenger(IInstance *instance, ecs::Entity entity) .messageSeverity = to_native_severity(component.get_severities()), .messageType = to_native_type(component.get_types()), .pfnUserCallback = &native_callback, - .pUserData = this, + .pUserData = m_entity.get(), } ); } @@ -41,8 +55,8 @@ Messenger::~Messenger() { ensure(vulkan_user_data, "Null vulkan_user_data received in messenger callback"); - auto *messenger = (Messenger *)vulkan_user_data; // NOLINT - auto &component = messenger->m_entity.get(); + auto *messenger = std::bit_cast(vulkan_user_data); + auto &component = messenger->get(); component.get_callback()( from_native_severity(severity), from_native_type(type), @@ -102,22 +116,22 @@ Messenger::~Messenger() if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { - flags &= std::to_underlying(error); + flags |= std::to_underlying(error); } if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { - flags &= std::to_underlying(warning); + flags |= std::to_underlying(warning); } if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { - flags &= std::to_underlying(info); + flags |= std::to_underlying(info); } if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { - flags &= std::to_underlying(verbose); + flags |= std::to_underlying(verbose); } return static_cast(flags); diff --git a/modules/renderer/private/backend/vk/messenger.hpp b/modules/renderer/private/backend/vk/messenger.hpp index 38ff399..a395c40 100644 --- a/modules/renderer/private/backend/vk/messenger.hpp +++ b/modules/renderer/private/backend/vk/messenger.hpp @@ -24,7 +24,6 @@ public: auto operator=(const Messenger &) const -> Messenger & = delete; - private: static auto native_callback( VkDebugUtilsMessageSeverityFlagBitsEXT severity, @@ -45,7 +44,7 @@ private: memory::NullOnMove m_instance {}; - ecs::Entity m_entity; + memory::Scope m_entity; VkDebugUtilsMessengerEXT m_debug_messenger = VK_NULL_HANDLE; diff --git a/modules/renderer/private/backend/vk/messenger.test.cpp b/modules/renderer/private/backend/vk/messenger.test.cpp index 7136b88..d4a2631 100644 --- a/modules/renderer/private/backend/vk/messenger.test.cpp +++ b/modules/renderer/private/backend/vk/messenger.test.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include diff --git a/modules/renderer/private/backend/vk/renderer/pass.cpp b/modules/renderer/private/backend/vk/renderer/pass.cpp index 4f04e3e..7b7d3af 100644 --- a/modules/renderer/private/backend/vk/renderer/pass.cpp +++ b/modules/renderer/private/backend/vk/renderer/pass.cpp @@ -5,11 +5,12 @@ namespace lt::renderer::vk { Pass::Pass( - IContext &context, - lt::assets::ShaderAsset vertex_shader, - lt::assets::ShaderAsset fragment_shader + IDevice *device, + ISwapchain *swapchain, + const lt::assets::ShaderAsset &vertex_shader, + const lt::assets::ShaderAsset &fragment_shader ) - : m_device(static_cast(context.device())) + : m_device(static_cast(device)) { auto *vertex_module = create_module( vertex_shader.unpack(lt::assets::ShaderAsset::BlobTag::code) @@ -113,7 +114,7 @@ Pass::Pass( ); auto attachment_description = VkAttachmentDescription { - .format = static_cast(context.swapchain())->get_format(), + .format = static_cast(swapchain)->get_format(), .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, @@ -176,8 +177,7 @@ Pass::Pass( } ); - m_framebuffers = static_cast(context.swapchain()) - ->create_framebuffers_for_pass(m_pass); + m_framebuffers = static_cast(swapchain)->create_framebuffers_for_pass(m_pass); m_device->destroy_shader_module(vertex_module); diff --git a/modules/renderer/private/backend/vk/renderer/pass.hpp b/modules/renderer/private/backend/vk/renderer/pass.hpp index 204044c..670a440 100644 --- a/modules/renderer/private/backend/vk/renderer/pass.hpp +++ b/modules/renderer/private/backend/vk/renderer/pass.hpp @@ -3,7 +3,6 @@ #include #include #include -#include #include namespace lt::renderer::vk { @@ -12,9 +11,10 @@ class Pass: public IPass { public: Pass( - IContext &context, - lt::assets::ShaderAsset vertex_shader, - lt::assets::ShaderAsset fragment_shader + class IDevice *device, + class ISwapchain *swapchain, + const lt::assets::ShaderAsset &vertex_shader, + const lt::assets::ShaderAsset &fragment_shader ); ~Pass() override; diff --git a/modules/renderer/private/backend/vk/renderer/renderer.cpp b/modules/renderer/private/backend/vk/renderer/renderer.cpp index fba6165..0e0b025 100644 --- a/modules/renderer/private/backend/vk/renderer/renderer.cpp +++ b/modules/renderer/private/backend/vk/renderer/renderer.cpp @@ -4,9 +4,9 @@ namespace lt::renderer::vk { -Renderer::Renderer(IContext &context, uint32_t max_frames_in_flight) - : m_device(static_cast(context.device())) - , m_swapchain(static_cast(context.swapchain())) +Renderer::Renderer(IDevice *device, ISwapchain *swapchain, uint32_t max_frames_in_flight) + : m_device(static_cast(device)) + , m_swapchain(static_cast(swapchain)) , m_resolution(m_swapchain->get_resolution()) , m_max_frames_in_flight(max_frames_in_flight) { @@ -15,7 +15,8 @@ Renderer::Renderer(IContext &context, uint32_t max_frames_in_flight) // TODO(Light): HARDCODED PASS!!! m_pass = memory::create_ref( - context, + m_device, + m_swapchain, assets::ShaderAsset { "./data/test_assets/triangle.vert.asset" }, assets::ShaderAsset { "./data/test_assets/triangle.frag.asset" } ); diff --git a/modules/renderer/private/backend/vk/renderer/renderer.hpp b/modules/renderer/private/backend/vk/renderer/renderer.hpp index 2fc4fbd..247b437 100644 --- a/modules/renderer/private/backend/vk/renderer/renderer.hpp +++ b/modules/renderer/private/backend/vk/renderer/renderer.hpp @@ -2,11 +2,9 @@ #include #include -#include #include #include #include -#include #include #include @@ -15,7 +13,7 @@ namespace lt::renderer::vk { class Renderer: public IRenderer { public: - Renderer(IContext &context, uint32_t max_frames_in_flight); + Renderer(class IDevice *device, class ISwapchain *swapchain, uint32_t max_frames_in_flight); ~Renderer() override; diff --git a/modules/renderer/private/backend/vk/test_utils.cpp b/modules/renderer/private/backend/vk/test_utils.cpp deleted file mode 100644 index cd57168..0000000 --- a/modules/renderer/private/backend/vk/test_utils.cpp +++ /dev/null @@ -1 +0,0 @@ -#include diff --git a/modules/renderer/private/backend/vk/test_utils.hpp b/modules/renderer/private/backend/vk/test_utils.hpp index 53369a6..874bd96 100644 --- a/modules/renderer/private/backend/vk/test_utils.hpp +++ b/modules/renderer/private/backend/vk/test_utils.hpp @@ -1,115 +1,18 @@ -#pragma once - -#include -#include -#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 = static_cast(warning | error), - .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; - for (auto idx = 0; idx < vulkan_data->objectCount; ++idx) - { - auto object = vulkan_data->pObjects[idx]; - std::println( - "0x{:x}({}) = {}", - object.objectHandle, - string_VkObjectType(object.objectType), - object.pObjectName ? object.pObjectName : "unnamed" - ); - } - - std::println("Validation message: {}", vulkan_data->pMessage); - - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) - *(bool *)user_data = true; - } - - Messenger m_messenger; - bool m_had_any_messages = false; -}; - -[[nodiscard]] inline auto create_context() - -> std::pair -{ - using lt::surface::SurfaceComponent; - - auto registry = lt::memory::create_ref(); - auto entity = lt::ecs::Entity { registry, registry->create_entity() }; - auto surface_system = lt::surface::System(registry); - entity.add(SurfaceComponent::CreateInfo { - .title = "", - .resolution = constants::resolution, - }); - - return { lt::renderer::vk::Context { entity }, std::move(surface_system) }; -} - -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; -} +// 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/frontend/context/context.cpp b/modules/renderer/private/frontend/context/context.cpp deleted file mode 100644 index 8aeef26..0000000 --- a/modules/renderer/private/frontend/context/context.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include -#include -#include - -namespace lt::renderer { - -auto IContext::create(API target_api, const ecs::Entity &surface_entity) - -> memory::Scope -{ - switch (target_api) - { - case API::Vulkan: return memory::create_scope(surface_entity); - default: throw std::runtime_error { "Invalid API" }; - } -} - -} // namespace lt::renderer diff --git a/modules/renderer/private/frontend/context/context.hpp b/modules/renderer/private/frontend/context/context.hpp deleted file mode 100644 index b7e5c9c..0000000 --- a/modules/renderer/private/frontend/context/context.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - - -namespace lt::renderer { - -class IContext -{ -public: - static auto create(API target_api, const ecs::Entity &surface_entity) - -> memory::Scope; - IContext() = default; - - virtual ~IContext() = default; - - IContext(IContext &&) = default; - - IContext(const IContext &) = delete; - - auto operator=(IContext &&) -> IContext & = default; - - auto operator=(const IContext &) -> IContext & = delete; - - virtual void recreate_swapchain() = 0; - - [[nodiscard]] virtual auto instance() const -> IInstance * = 0; - - [[nodiscard]] virtual auto surface() const -> ISurface * = 0; - - [[nodiscard]] virtual auto gpu() const -> IGpu * = 0; - - [[nodiscard]] virtual auto device() const -> IDevice * = 0; - - [[nodiscard]] virtual auto swapchain() const -> ISwapchain * = 0; -}; - - -} // namespace lt::renderer diff --git a/modules/renderer/private/frontend/context/context.test.cpp b/modules/renderer/private/frontend/context/context.test.cpp deleted file mode 100644 index e28fad0..0000000 --- a/modules/renderer/private/frontend/context/context.test.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using lt::renderer::API; -using lt::renderer::IContext; -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 Fixture -{ -public: - Fixture() - : m_registry(lt::memory::create_ref()) - , m_surface_system(lt::memory::create_scope(m_registry)) - , m_surface_entity(m_registry, m_registry->create_entity()) - { - } - - auto get_surface_entity() -> lt::ecs::Entity - { - return m_surface_entity; - } - -private: - lt::memory::Ref m_registry; - lt::memory::Scope m_surface_system; - lt::ecs::Entity m_surface_entity; -}; - -Suite raii = "context_raii"_suite = [] { - Case { "Happy path won't throw" } = [] { - auto fixture = Fixture {}; - IContext::create(API::Vulkan, fixture.get_surface_entity()); - }; -}; diff --git a/modules/renderer/private/frontend/context/device.cpp b/modules/renderer/private/frontend/context/device.cpp new file mode 100644 index 0000000..e8fc3a0 --- /dev/null +++ b/modules/renderer/private/frontend/context/device.cpp @@ -0,0 +1,21 @@ +#include +#include + +namespace lt::renderer { + +[[nodiscard]] /* static */ auto IDevice::create(Api target_api, IGpu *gpu, ISurface *surface) + -> memory::Scope +{ + ensure(gpu, "Failed to create renderer::IDevice: null gpu"); + ensure(surface, "Failed to create renderer::IDevice: null surface"); + + switch (target_api) + { + case Api::vulkan: return memory::create_scope(gpu, surface); + case Api::none: + case Api::metal: + case Api::direct_x: throw std::runtime_error { "Invalid API" }; + } +} + +} // namespace lt::renderer diff --git a/modules/renderer/private/frontend/context/device.hpp b/modules/renderer/private/frontend/context/device.hpp index add80f8..afa7634 100644 --- a/modules/renderer/private/frontend/context/device.hpp +++ b/modules/renderer/private/frontend/context/device.hpp @@ -1,10 +1,16 @@ #pragma once +#include +#include + namespace lt::renderer { class IDevice { public: + [[nodiscard]] static auto create(Api target_api, class IGpu *gpu, class ISurface *surface) + -> memory::Scope; + IDevice() = default; virtual ~IDevice() = default; diff --git a/modules/renderer/private/frontend/context/device.test.cpp b/modules/renderer/private/frontend/context/device.test.cpp index 244155d..bf74b73 100644 --- a/modules/renderer/private/frontend/context/device.test.cpp +++ b/modules/renderer/private/frontend/context/device.test.cpp @@ -1,100 +1,55 @@ #include #include -#include -#include +#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 = memory::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 = memory::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 }; - } + auto fixture = Fixture_SurfaceGpu {}; + std::ignore = lt::renderer::IDevice::create( + constants::api, + fixture.gpu(), + fixture.surface() + ); }; Case { "unhappy path throws" } = [] { - auto registry = memory::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 fixture = Fixture_SurfaceGpu {}; + + expect_throw([&] { + ignore = lt::renderer::IDevice::create(constants::api, nullptr, fixture.surface()); }); - 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 = memory::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, + expect_throw([&] { + ignore = lt::renderer::IDevice::create(constants::api, fixture.gpu(), nullptr); }); - 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 = memory::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, + expect_throw([&] { + ignore = lt::renderer::IDevice::create( + lt::renderer::Api::none, + fixture.gpu(), + fixture.surface() + ); }); - auto surface = Surface { entity }; + expect_throw([&] { + ignore = lt::renderer::IDevice::create( + lt::renderer::Api::direct_x, + fixture.gpu(), + fixture.surface() + ); + }); - { - auto device = Device { surface }; - } + expect_throw([&] { + ignore = lt::renderer::IDevice::create( + lt::renderer::Api::metal, + fixture.gpu(), + fixture.surface() + ); + }); }; }; diff --git a/modules/renderer/private/frontend/context/gpu.cpp b/modules/renderer/private/frontend/context/gpu.cpp new file mode 100644 index 0000000..b9c120d --- /dev/null +++ b/modules/renderer/private/frontend/context/gpu.cpp @@ -0,0 +1,18 @@ +#include +#include + +namespace lt::renderer { + +[[nodiscard]] /* static */ auto IGpu::create(Api target_api, IInstance *instance) + -> memory::Scope +{ + switch (target_api) + { + case Api::vulkan: return memory::create_scope(instance); + case Api::none: + case Api::metal: + case Api::direct_x: throw std::runtime_error { "Invalid API" }; + } +} + +} // namespace lt::renderer diff --git a/modules/renderer/private/frontend/context/gpu.hpp b/modules/renderer/private/frontend/context/gpu.hpp index b18cd82..47ede20 100644 --- a/modules/renderer/private/frontend/context/gpu.hpp +++ b/modules/renderer/private/frontend/context/gpu.hpp @@ -1,10 +1,16 @@ #pragma once +#include +#include + namespace lt::renderer { class IGpu { public: + [[nodiscard]] static auto create(Api target_api, class IInstance *instance) + -> memory::Scope; + IGpu() = default; virtual ~IGpu() = default; diff --git a/modules/renderer/private/frontend/context/instance.cpp b/modules/renderer/private/frontend/context/instance.cpp new file mode 100644 index 0000000..f8b6bc4 --- /dev/null +++ b/modules/renderer/private/frontend/context/instance.cpp @@ -0,0 +1,17 @@ +#include +#include + +namespace lt::renderer { + +[[nodiscard]] /* static */ auto IInstance::get(Api target_api) -> IInstance * +{ + switch (target_api) + { + case Api::vulkan: return vk::Instance::get(); + case Api::none: + case Api::metal: + case Api::direct_x: throw std::runtime_error { "Invalid API" }; + } +} + +} // namespace lt::renderer diff --git a/modules/renderer/private/frontend/context/instance.hpp b/modules/renderer/private/frontend/context/instance.hpp index 2d384f4..2da83d5 100644 --- a/modules/renderer/private/frontend/context/instance.hpp +++ b/modules/renderer/private/frontend/context/instance.hpp @@ -1,10 +1,14 @@ #pragma once +#include + namespace lt::renderer { class IInstance { public: + [[nodiscard]] static auto get(Api target_api) -> IInstance *; + IInstance() = default; virtual ~IInstance() = default; diff --git a/modules/renderer/private/frontend/context/instance.test.cpp b/modules/renderer/private/frontend/context/instance.test.cpp index 68caf5e..e69de29 100644 --- a/modules/renderer/private/frontend/context/instance.test.cpp +++ b/modules/renderer/private/frontend/context/instance.test.cpp @@ -1,95 +0,0 @@ -#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/frontend/context/surface.cpp b/modules/renderer/private/frontend/context/surface.cpp new file mode 100644 index 0000000..ef8447e --- /dev/null +++ b/modules/renderer/private/frontend/context/surface.cpp @@ -0,0 +1,24 @@ + +#include +#include + +namespace lt::renderer { + +[[nodiscard]] /* static */ auto ISurface::create( + Api target_api, + IInstance *instance, + const ecs::Entity &surface_entity +) -> memory::Scope +{ + ensure(instance, "Failed to create renderer::ISurface: null instance"); + + switch (target_api) + { + case Api::vulkan: return memory::create_scope(instance, surface_entity); + case Api::none: + case Api::metal: + case Api::direct_x: throw std::runtime_error { "Invalid API" }; + } +} + +} // namespace lt::renderer diff --git a/modules/renderer/private/frontend/context/surface.hpp b/modules/renderer/private/frontend/context/surface.hpp index e6931ef..60f6492 100644 --- a/modules/renderer/private/frontend/context/surface.hpp +++ b/modules/renderer/private/frontend/context/surface.hpp @@ -1,10 +1,21 @@ #pragma once +#include +#include +#include +#include + namespace lt::renderer { class ISurface { public: + [[nodiscard]] static auto create( + Api target_api, + class IInstance *instance, + const ecs::Entity &surface_entity + ) -> memory::Scope; + ISurface() = default; virtual ~ISurface() = default; @@ -16,6 +27,8 @@ public: auto operator=(ISurface &&) -> ISurface & = default; auto operator=(const ISurface &) -> ISurface & = delete; + + [[nodiscard]] virtual auto get_framebuffer_size() const -> math::uvec2 = 0; }; } // namespace lt::renderer diff --git a/modules/renderer/private/frontend/context/surface.test.cpp b/modules/renderer/private/frontend/context/surface.test.cpp index cb1d9a8..9ebeb91 100644 --- a/modules/renderer/private/frontend/context/surface.test.cpp +++ b/modules/renderer/private/frontend/context/surface.test.cpp @@ -1,52 +1,88 @@ #include -#include -#include -#include +#include +#include +#include #include #include #include -using ::lt::ecs::Entity; +using ::lt::ecs::EntityId; 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 fixture = Fixture_SurfaceSystem {}; - auto registry = lt::memory::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(); + const auto surface = lt::renderer::ISurface::create( + constants::api, + lt::renderer::IInstance::get(constants::api), + fixture.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::memory::create_ref(); - auto entity = Entity { registry, registry->create_entity() }; + auto registry = lt::memory::create_ref(); + auto entity = lt::ecs::Entity { registry, registry->create_entity() }; + auto system = lt::surface::System(registry); - entity.add(SurfaceComponent::CreateInfo { - .title = "", - .resolution = constants::resolution, - .visible = true, + expect_throw([&] { + std::ignore = lt::renderer::ISurface::create( + constants::api, + lt::renderer::IInstance::get(constants::api), + entity + ); }); - expect_throw([&] { Surface { entity }; }); - expect_false(observer.had_any_messages()); + system.create_surface_component( + entity.id(), + lt::surface::SurfaceComponent::CreateInfo { + .title = "", + .resolution = constants::resolution, + } + ); + + expect_throw([&] { + std::ignore = lt::renderer::ISurface::create(constants::api, nullptr, entity); + }); + + expect_throw([&] { + std::ignore = lt::renderer::ISurface::create( + lt::renderer::Api::none, + lt::renderer::IInstance::get(constants::api), + entity + ); + }); + + expect_throw([&] { + std::ignore = lt::renderer::ISurface::create( + lt::renderer::Api::direct_x, + lt::renderer::IInstance::get(constants::api), + entity + ); + }); + + expect_throw([&] { + std::ignore = lt::renderer::ISurface::create( + lt::renderer::Api::metal, + lt::renderer::IInstance::get(constants::api), + entity + ); + }); + + // Ensure base creation info is non-throwing + std::ignore = lt::renderer::ISurface::create( + constants::api, + lt::renderer::IInstance::get(constants::api), + entity + ); + }; + + // TODO(Light): add torture tests + Case { "torture tests" } = [] { }; }; diff --git a/modules/renderer/private/frontend/context/swapchain.cpp b/modules/renderer/private/frontend/context/swapchain.cpp new file mode 100644 index 0000000..33582cd --- /dev/null +++ b/modules/renderer/private/frontend/context/swapchain.cpp @@ -0,0 +1,23 @@ +#include +#include + +namespace lt::renderer { + +[[nodiscard]] /* static */ auto ISwapchain::create( + Api target_api, + ISurface *surface, + IGpu *gpu, + IDevice *device +) -> memory::Scope +{ + switch (target_api) + { + case Api::vulkan: return memory::create_scope(surface, gpu, device); + case Api::none: + case Api::metal: + case Api::direct_x: throw std::runtime_error { "Invalid API" }; + } +} + + +} // namespace lt::renderer diff --git a/modules/renderer/private/frontend/context/swapchain.hpp b/modules/renderer/private/frontend/context/swapchain.hpp index 3eef813..ac35e8d 100644 --- a/modules/renderer/private/frontend/context/swapchain.hpp +++ b/modules/renderer/private/frontend/context/swapchain.hpp @@ -1,10 +1,20 @@ #pragma once +#include +#include + namespace lt::renderer { class ISwapchain { public: + [[nodiscard]] static auto create( + Api target_api, + class ISurface *surface, + class IGpu *gpu, + class IDevice *device + ) -> memory::Scope; + ISwapchain() = default; virtual ~ISwapchain() = default; diff --git a/modules/renderer/private/frontend/messenger.cpp b/modules/renderer/private/frontend/messenger.cpp index 40019bb..5e91aeb 100644 --- a/modules/renderer/private/frontend/messenger.cpp +++ b/modules/renderer/private/frontend/messenger.cpp @@ -5,16 +5,17 @@ namespace lt::renderer { [[nodiscard]] /* static */ auto IMessenger::create( - API target_api, + Api target_api, IInstance *instance, ecs::Entity entity ) -> memory::Scope { switch (target_api) { - case API::Vulkan: return memory::create_scope(instance, std::move(entity)); - case API::Metal: - case API::DirectX: throw std::runtime_error { "Invalid API" }; + case Api::vulkan: return memory::create_scope(instance, std::move(entity)); + case Api::none: + case Api::metal: + case Api::direct_x: throw std::runtime_error { "Invalid API" }; } } } // namespace lt::renderer diff --git a/modules/renderer/private/frontend/messenger.test.cpp b/modules/renderer/private/frontend/messenger.test.cpp new file mode 100644 index 0000000..e69de29 diff --git a/modules/renderer/private/frontend/renderer/pass.cpp b/modules/renderer/private/frontend/renderer/pass.cpp new file mode 100644 index 0000000..ba002da --- /dev/null +++ b/modules/renderer/private/frontend/renderer/pass.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +namespace lt::renderer { + +[[nodiscard]] /* static */ auto IPass::create( + lt::renderer::Api target_api, + IDevice *device, + ISwapchain *swapchain, + const lt::assets::ShaderAsset &vertex_shader, + const lt::assets::ShaderAsset &fragment_shader +) -> memory::Scope +{ + ensure(device, "Failed to create renderer::IPass: null device"); + ensure(swapchain, "Failed to create renderer::IPass: null swapchain"); + + switch (target_api) + { + case Api::vulkan: + return memory::create_scope(device, swapchain, vertex_shader, fragment_shader); + case Api::none: + case Api::metal: + case Api::direct_x: throw std::runtime_error { "Invalid API" }; + } +} + +} // namespace lt::renderer diff --git a/modules/renderer/private/frontend/renderer/pass.hpp b/modules/renderer/private/frontend/renderer/pass.hpp index 854aafd..de59511 100644 --- a/modules/renderer/private/frontend/renderer/pass.hpp +++ b/modules/renderer/private/frontend/renderer/pass.hpp @@ -1,12 +1,25 @@ #pragma once +#include #include +namespace lt::assets { +class ShaderAsset; +} + namespace lt::renderer { class IPass { public: + [[nodiscard]] static auto create( + lt::renderer::Api target_api, + class IDevice *device, + class ISwapchain *swapchain, + const class lt::assets::ShaderAsset &vertex_shader, + const class lt::assets::ShaderAsset &fragment_shader + ) -> memory::Scope; + IPass() = default; virtual ~IPass() = default; diff --git a/modules/renderer/private/frontend/renderer/pass.test.cpp b/modules/renderer/private/frontend/renderer/pass.test.cpp index 64ffefe..30b24b0 100644 --- a/modules/renderer/private/frontend/renderer/pass.test.cpp +++ b/modules/renderer/private/frontend/renderer/pass.test.cpp @@ -1,20 +1,74 @@ -#include -#include - -using ::lt::assets::ShaderAsset; -using ::lt::renderer::vk::Pass; +#include +#include +#include Suite raii = "pass_raii"_suite = [] { Case { "happy path won't throw" } = [] { - auto observer = ValidationObserver {}; - auto [context, _] = create_context(); + auto fixture = Fixture_RendererSystem {}; + auto &system = fixture.renderer_system(); - std::ignore = Pass { - context, - ShaderAsset { "./data/test_assets/triangle.vert.asset" }, - ShaderAsset { "./data/test_assets/triangle.frag.asset" }, - }; + std::ignore = lt::renderer::IPass::create( + constants::api, + system.get_device(), + system.get_swapchain(), + lt::assets::ShaderAsset { "./data/test_assets/triangle.vert.asset" }, + lt::assets::ShaderAsset { "./data/test_assets/triangle.frag.asset" } + ); + }; - expect_false(observer.had_any_messages()); + Case { "unhappy path throws" } = [] { + auto fixture = Fixture_RendererSystem {}; + auto &system = fixture.renderer_system(); + + expect_throw([&] { + std::ignore = lt::renderer::IPass::create( + constants::api, + nullptr, + system.get_swapchain(), + lt::assets::ShaderAsset { "./data/test_assets/triangle.vert.asset" }, + lt::assets::ShaderAsset { "./data/test_assets/triangle.frag.asset" } + ); + }); + + + expect_throw([&] { + std::ignore = lt::renderer::IPass::create( + constants::api, + system.get_device(), + nullptr, + lt::assets::ShaderAsset { "./data/test_assets/triangle.vert.asset" }, + lt::assets::ShaderAsset { "./data/test_assets/triangle.frag.asset" } + ); + }); + + expect_throw([&] { + std::ignore = lt::renderer::IPass::create( + lt::renderer::Api::none, + system.get_device(), + system.get_swapchain(), + lt::assets::ShaderAsset { "./data/test_assets/triangle.vert.asset" }, + lt::assets::ShaderAsset { "./data/test_assets/triangle.frag.asset" } + ); + }); + + expect_throw([&] { + std::ignore = lt::renderer::IPass::create( + lt::renderer::Api::direct_x, + system.get_device(), + system.get_swapchain(), + lt::assets::ShaderAsset { "./data/test_assets/triangle.vert.asset" }, + lt::assets::ShaderAsset { "./data/test_assets/triangle.frag.asset" } + ); + }); + + expect_throw([&] { + std::ignore = lt::renderer::IPass::create( + lt::renderer::Api::metal, + system.get_device(), + system.get_swapchain(), + lt::assets::ShaderAsset { "./data/test_assets/triangle.vert.asset" }, + lt::assets::ShaderAsset { "./data/test_assets/triangle.frag.asset" } + ); + }); }; }; diff --git a/modules/renderer/private/frontend/renderer/renderer.cpp b/modules/renderer/private/frontend/renderer/renderer.cpp index 5a957e7..adfd2f3 100644 --- a/modules/renderer/private/frontend/renderer/renderer.cpp +++ b/modules/renderer/private/frontend/renderer/renderer.cpp @@ -5,13 +5,33 @@ namespace lt::renderer { -auto IRenderer::create(API target_api, IContext &context, uint32_t max_frames_in_flight) - -> memory::Scope +[[nodiscard]] /* static */ auto IRenderer::create( + Api target_api, + IDevice *device, + ISwapchain *swapchain, + uint32_t max_frames_in_flight +) -> memory::Scope { + ensure(device, "Failed to create renderer::IRenderer: null device"); + ensure(swapchain, "Failed to create renderer::IRenderer: null swapchain"); + ensure( + std::clamp(max_frames_in_flight, frames_in_flight_lower_limit, frames_in_flight_upper_limit) + == max_frames_in_flight, + "Failed to initialize renderer::System: max_frames_in_flight ({}) not within bounds ({} -> " + "{}) ", + max_frames_in_flight, + frames_in_flight_lower_limit, + frames_in_flight_upper_limit + ); + + switch (target_api) { - case API::Vulkan: return memory::create_scope(context, max_frames_in_flight); - default: throw std::runtime_error { "Invalid API" }; + case Api::vulkan: + return memory::create_scope(device, swapchain, max_frames_in_flight); + case Api::none: + case Api::metal: + case Api::direct_x: throw std::runtime_error { "Invalid API" }; } } diff --git a/modules/renderer/private/frontend/renderer/renderer.hpp b/modules/renderer/private/frontend/renderer/renderer.hpp index 1c85640..a019741 100644 --- a/modules/renderer/private/frontend/renderer/renderer.hpp +++ b/modules/renderer/private/frontend/renderer/renderer.hpp @@ -8,6 +8,10 @@ namespace lt::renderer { class IRenderer { public: + static constexpr auto frames_in_flight_upper_limit = 5u; + + static constexpr auto frames_in_flight_lower_limit = 1u; + enum class DrawResult : uint8_t { success = 0, @@ -15,8 +19,12 @@ public: error, }; - static auto create(API target_api, class IContext &context, uint32_t max_frames_in_flight) - -> memory::Scope; + [[nodiscard]] static auto create( + Api target_api, + class IDevice *device, + class ISwapchain *swapchain, + uint32_t max_frames_in_flight + ) -> memory::Scope; IRenderer() = default; diff --git a/modules/renderer/private/frontend/renderer/renderer.test.cpp b/modules/renderer/private/frontend/renderer/renderer.test.cpp index 0e4d2a3..2d6c0b6 100644 --- a/modules/renderer/private/frontend/renderer/renderer.test.cpp +++ b/modules/renderer/private/frontend/renderer/renderer.test.cpp @@ -1,74 +1,96 @@ #include -#include -#include - -using ::lt::assets::ShaderAsset; -using ::lt::renderer::vk::Pass; -using ::lt::renderer::vk::Renderer; +#include +#include Suite raii = "renderer_raii"_suite = [] { Case { "happy path won't throw" } = [] { - auto observer = ValidationObserver {}; - auto [context, _] = create_context(); - - std::ignore = Renderer( - context, - lt::memory::create_ref( - context, - ShaderAsset { "./data/test_assets/triangle.vert.asset" }, - ShaderAsset { "./data/test_assets/triangle.frag.asset" } - ) + auto fixture = FixtureDeviceSwapchain {}; + ignore = lt::renderer::IRenderer::create( + constants::api, + fixture.device(), + fixture.swapchain(), + constants::frames_in_flight ); + }; - expect_false(observer.had_any_messages()); + Case { "unhappy path throws" } = [] { + auto fixture = FixtureDeviceSwapchain {}; + + expect_throw([&] { + ignore = lt::renderer::IRenderer::create( + constants::api, + nullptr, + fixture.swapchain(), + constants::frames_in_flight + ); + }); + + expect_throw([&] { + ignore = lt::renderer::IRenderer::create( + constants::api, + fixture.device(), + nullptr, + constants::frames_in_flight + ); + }); + + expect_throw([&] { + ignore = lt::renderer::IRenderer::create( + constants::api, + fixture.device(), + nullptr, + lt::renderer::IRenderer::frames_in_flight_upper_limit + 1 + ); + }); + + expect_throw([&] { + ignore = lt::renderer::IRenderer::create( + constants::api, + fixture.device(), + nullptr, + lt::renderer::IRenderer::frames_in_flight_lower_limit - 1 + ); + }); }; }; Suite draw = "renderer_draw"_suite = [] { - Case { "renderer draw" } = [] { - auto observer = ValidationObserver {}; - auto [context, _] = create_context(); + using enum lt::renderer::IRenderer::DrawResult; - auto renderer = Renderer( - context, - lt::memory::create_ref( - context, - ShaderAsset { "./data/test_assets/triangle.vert.asset" }, - ShaderAsset { "./data/test_assets/triangle.frag.asset" } - ) + Case { "renderer draw" } = [] { + auto fixture = FixtureDeviceSwapchain {}; + auto renderer = lt::renderer::IRenderer::create( + constants::api, + fixture.device(), + fixture.swapchain(), + constants::frames_in_flight ); for (auto frame_idx : std::views::iota(0u, 30u)) { - expect_true(renderer.draw(frame_idx % Renderer::max_frames_in_flight)); + expect_eq(renderer->draw(frame_idx % constants::frames_in_flight), success); } - expect_false(observer.had_any_messages()); }; Case { "post swapchain replacement renderer draw" } = [] { - auto observer = ValidationObserver {}; - auto [context, _] = create_context(); - auto pass = lt::memory::create_ref( - context, - ShaderAsset { "./data/test_assets/triangle.vert.asset" }, - ShaderAsset { "./data/test_assets/triangle.frag.asset" } + auto fixture = FixtureDeviceSwapchain {}; + auto renderer = lt::renderer::IRenderer::create( + constants::api, + fixture.device(), + fixture.swapchain(), + constants::frames_in_flight ); - auto renderer = Renderer { context, pass }; - for (auto frame_idx : std::views::iota(0u, 15u)) { - expect_true(renderer.draw(frame_idx % Renderer::max_frames_in_flight)); + expect_eq(renderer->draw(frame_idx % constants::frames_in_flight), success); } - context.recreate_swapchain(); - renderer.replace_swapchain(context.swapchain()); - pass->replace_swapchain(context.swapchain()); + fixture.recreate_swapchain(); + renderer->replace_swapchain(fixture.swapchain()); for (auto frame_idx : std::views::iota(0u, 15u)) { - expect_true(renderer.draw(frame_idx % Renderer::max_frames_in_flight)); + expect_eq(renderer->draw(frame_idx % constants::frames_in_flight), success); } - - expect_false(observer.had_any_messages()); }; }; diff --git a/modules/renderer/private/system.cpp b/modules/renderer/private/system.cpp index 87d1c4c..7db7d2e 100644 --- a/modules/renderer/private/system.cpp +++ b/modules/renderer/private/system.cpp @@ -1,5 +1,9 @@ #include -#include +#include +#include +#include +#include +#include #include #include #include @@ -9,20 +13,65 @@ namespace lt::renderer { System::System(CreateInfo info) - : m_api(info.config.target_api) + : m_surface_entity(info.surface_entity) + , m_api(info.config.target_api) , m_registry(std::move(info.registry)) - , m_context(IContext::create(m_api, info.surface_entity)) - , m_surface_entity(info.surface_entity) + , m_instance(IInstance::get(m_api)) , m_max_frames_in_flight(info.config.max_frames_in_flight) { - // ensure(m_stats, "Failed to initialize system: null stats"); - ensure(m_registry, "Failed to initialize renderer system: null registry"); + ensure(m_registry, "Failed to initialize renderer::System: null registry"); + ensure( + std::clamp( + info.config.max_frames_in_flight, + frames_in_flight_lower_limit, + frames_in_flight_upper_limit + ) == info.config.max_frames_in_flight, + "Failed to initialize renderer::System: max_frames_in_flight ({}) not within bounds ({} -> " + "{}) ", + info.config.max_frames_in_flight, + frames_in_flight_lower_limit, + frames_in_flight_upper_limit + ); - m_renderer = IRenderer::create(m_api, *m_context, info.config.max_frames_in_flight); + if (info.messenger_info.has_value()) + { + ensure( + create_messenger_component(m_registry->create_entity(), info.messenger_info.value()), + "Failed to initialize renderer::System: failed to create messenger component" + ); + } + else + { + log_wrn( + "Creating renderer::System without a default messenger component, this is not " + "recommended" + ); + } + + m_surface = ISurface::create(m_api, m_instance, m_surface_entity); + m_gpu = IGpu::create(m_api, m_instance); + m_device = IDevice::create(m_api, m_gpu.get(), m_surface.get()); + m_swapchain = ISwapchain::create(m_api, m_surface.get(), m_gpu.get(), m_device.get()); + m_renderer = { IRenderer::create( + m_api, + m_device.get(), + m_swapchain.get(), + info.config.max_frames_in_flight + ) }; } System::~System() { + auto entities_to_remove = std::vector {}; + for (auto &[entity, surface] : m_registry->view()) + { + entities_to_remove.emplace_back(entity); + } + + for (auto entity : entities_to_remove) + { + m_registry->remove(entity); + } } void System::on_register() @@ -41,37 +90,41 @@ void System::tick(app::TickInfo tick) { if (std::holds_alternative(event)) { - m_context->recreate_swapchain(); - m_renderer->replace_swapchain(m_context->swapchain()); - // m_pass->replace_swapchain(m_context->swapchain()); + m_swapchain.reset(); + m_swapchain = ISwapchain::create(m_api, m_surface.get(), m_gpu.get(), m_device.get()); + m_renderer->replace_swapchain(m_swapchain.get()); } } if (m_renderer->draw(m_frame_idx) != IRenderer::DrawResult::success) { - m_context->recreate_swapchain(); - m_renderer->replace_swapchain(m_context->swapchain()); - // m_pass->replace_swapchain(m_context->swapchain()); + m_swapchain.reset(); + m_swapchain = ISwapchain::create(m_api, m_surface.get(), m_gpu.get(), m_device.get()); + m_renderer->replace_swapchain(m_swapchain.get()); + std::ignore = m_renderer->draw(m_frame_idx); // drop the frame if failed twice } m_frame_idx = (m_frame_idx + 1) % m_max_frames_in_flight; } -void System::create_messenger_component(ecs::EntityId entity, MessengerComponent::CreateInfo info) +[[nodiscard]] auto System::create_messenger_component( + ecs::EntityId entity, + MessengerComponent::CreateInfo info +) -> bool try { auto &component = m_registry->add(entity, std::move(info)); - component.m_implementation = IMessenger::create( - m_api, - m_context->instance(), - { m_registry, entity } - ); + component.m_implementation = IMessenger::create(m_api, m_instance, { m_registry, entity }); + // component.m_user_data = info.user_data; + return true; } catch (const std::exception &exp) { log_err("Failed to create renderer::MessengerComponent:"); log_err("\twhat: {}", exp.what()); + m_registry->remove(entity); + return false; } diff --git a/modules/renderer/private/system.test.cpp b/modules/renderer/private/system.test.cpp index 0c286bc..3b437cb 100644 --- a/modules/renderer/private/system.test.cpp +++ b/modules/renderer/private/system.test.cpp @@ -1,9 +1,8 @@ #include #include -#include +#include #include -#include -#include +#include #include #include #include @@ -15,10 +14,9 @@ using test::expect_throw; using test::expect_true; using test::Suite; +using lt::renderer::MessageSeverity; using renderer::System; -constexpr auto resolution = math::uvec2 { 800, 600 }; - struct SurfaceContext { surface::System system; @@ -31,140 +29,67 @@ struct RendererContext System system; }; -[[nodiscard]] auto create_surface() -> SurfaceContext -{ - using surface::SurfaceComponent; - - auto surface_registry = memory::create_ref(); - auto surface_entity = surface_registry->create_entity(); - auto surface_system = surface::System(surface_registry); - surface_registry->add( - surface_entity, - SurfaceComponent::CreateInfo { - .title = "", - .resolution = resolution, - } - ); - - return { - .system = std::move(surface_system), - .entity = ecs::Entity { surface_registry, surface_entity }, - }; -} - -[[nodiscard]] auto create_system() -> std::pair -{ - auto surface_context = create_surface(); - auto &[surface_system, surface_entity] = surface_context; - auto registry = memory::create_ref(); - auto stats = memory::create_ref(); - - return { - std::move(surface_context), - RendererContext { - .registry = registry, - .system = System( - { - .registry = registry, - .surface_entity = surface_entity, - .system_stats = stats, - } - ), - }, - }; -} - -class SystemTest -{ -public: - SystemTest() - { - m_surface_entity->add(surface::SurfaceComponent::CreateInfo { - .title = "", - .resolution = resolution, - }); - } - - [[nodiscard]] auto registry() const -> memory::Ref - { - return m_registry; - } - - [[nodiscard]] auto surface_entity() const -> const ecs::Entity & - { - return *m_surface_entity; - } - - [[nodiscard]] auto stats() const -> memory::Ref - { - return m_stats; - } - -private: - memory::Ref m_stats = memory::create_ref(); - - memory::Ref m_registry = memory::create_ref(); - - memory::Ref m_surface_system = memory::create_ref( - m_registry - ); - - memory::Scope m_surface_entity = memory::create_scope( - m_registry, - m_registry->create_entity() - ); -}; Suite raii = "raii"_suite = [] { - Case { "happy path won't throw" } = [&] { - ignore = create_system(); + Case { "happy path won't throw" } = [] { + ignore = Fixture_RendererSystem {}; }; - Case { "happy path has no validation errors" } = [&] { - 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 { "happy path has no errors" } = [] { + auto fixture = Fixture_RendererSystem {}; + expect_false(fixture.has_any_messages_of(MessageSeverity::error)); + expect_false(fixture.has_any_messages_of(MessageSeverity::warning)); }; Case { "unhappy path throws" } = [] { - auto fixture = SystemTest {}; + auto fixture = Fixture_SurfaceSystem {}; auto empty_entity = ecs::Entity { fixture.registry(), fixture.registry()->create_entity() }; + auto info = fixture.renderer_system_create_info(); - expect_throw([&] { - ignore = System( - { - .registry = {}, - .surface_entity = fixture.surface_entity(), - .system_stats = fixture.stats(), - } - ); + expect_throw([=] mutable { + info.registry = nullptr; + ignore = System { info }; }); - expect_throw([&] { - ignore = System( - System::CreateInfo { - .registry = fixture.registry(), - .surface_entity = empty_entity, - .system_stats = fixture.stats(), - } - ); + expect_throw([=] mutable { + info.surface_entity = ecs::Entity({}, {}); + ignore = System { info }; }); - expect_throw([&] { - ignore = System( - System::CreateInfo { - .registry = fixture.registry(), - .surface_entity = fixture.surface_entity(), - .system_stats = {}, - } - ); + expect_throw([=] mutable { + info.config.target_api = lt::renderer::Api::none; + ignore = System { info }; }); + + // unsupported Apis + expect_throw([=] mutable { + info.config.target_api = lt::renderer::Api::direct_x; + ignore = System { info }; + }); + + expect_throw([=] mutable { + info.config.target_api = lt::renderer::Api::metal; + ignore = System { info }; + }); + + expect_throw([=] mutable { + constexpr auto limit = lt::renderer::System::frames_in_flight_upper_limit; + info.config.max_frames_in_flight = limit + 1u; + ignore = System { info }; + }); + + expect_throw([=] mutable { + constexpr auto limit = lt::renderer::System::frames_in_flight_lower_limit; + info.config.max_frames_in_flight = limit - 1u; + ignore = System { info }; + }); + + expect_throw([=] mutable { + info.messenger_info = lt::renderer::MessengerComponent::CreateInfo {}; + ignore = System { info }; + }); + + // Make sure the base info is not at fault for unhappiness. + ignore = System { info }; }; }; diff --git a/modules/renderer/private/test/constants.hpp b/modules/renderer/private/test/constants.hpp new file mode 100644 index 0000000..14755d9 --- /dev/null +++ b/modules/renderer/private/test/constants.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace constants { + +}; diff --git a/modules/renderer/private/test/formatters.hpp b/modules/renderer/private/test/formatters.hpp new file mode 100644 index 0000000..e69de29 diff --git a/modules/renderer/private/test/utils.cpp b/modules/renderer/private/test/utils.cpp new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/modules/renderer/private/test/utils.cpp @@ -0,0 +1 @@ + diff --git a/modules/renderer/private/test/utils.hpp b/modules/renderer/private/test/utils.hpp new file mode 100644 index 0000000..e6ceb7a --- /dev/null +++ b/modules/renderer/private/test/utils.hpp @@ -0,0 +1,199 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#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 api = lt::renderer::Api::vulkan; +constexpr auto resolution = lt::math::uvec2 { 800u, 600u }; +constexpr auto frames_in_flight = uint32_t { 3u }; + +} // namespace constants + +class Fixture_SurfaceSystem +{ +public: + Fixture_SurfaceSystem() + { + m_system.create_surface_component( + m_entity.id(), + lt::surface::SurfaceComponent::CreateInfo { + .title = "", + .resolution = constants::resolution, + } + ); + } + + [[nodiscard]] auto renderer_system_create_info() -> lt::renderer::System::CreateInfo + { + return lt::renderer::System::CreateInfo{ + .config = lt::renderer::System::Configuration{ + .target_api = constants::api, + .max_frames_in_flight = constants::frames_in_flight, + }, + .registry = registry(), + .surface_entity = surface_entity(), + } ; + } + + [[nodiscard]] auto registry() -> lt::memory::Ref & + { + return m_registry; + } + + [[nodiscard]] auto surface_entity() -> lt::ecs::Entity & + { + return m_entity; + } + + [[nodiscard]] auto surface_system() -> lt::surface::System & + { + return m_system; + } + +private: + lt::memory::Ref m_registry = lt::memory::create_ref(); + + lt::ecs::Entity m_entity { m_registry, m_registry->create_entity() }; + + lt::surface::System m_system = lt::surface::System(m_registry); +}; + +class Fixture_SurfaceGpu: public Fixture_SurfaceSystem +{ +public: + Fixture_SurfaceGpu() = default; + + [[nodiscard]] auto surface() -> lt::renderer::ISurface * + { + return m_surface.get(); + } + + [[nodiscard]] auto gpu() -> lt::renderer::IGpu * + { + return m_gpu.get(); + } + +private: + lt::memory::Scope m_surface { lt::renderer::ISurface::create( + constants::api, + lt::renderer::IInstance::get(constants::api), + surface_entity() + ) }; + + lt::memory::Scope m_gpu { + lt::renderer::IGpu::create(constants::api, lt::renderer::IInstance::get(constants::api)) + }; +}; + +class FixtureDeviceSwapchain: public Fixture_SurfaceGpu +{ +public: + FixtureDeviceSwapchain() = default; + + [[nodiscard]] auto device() -> lt::renderer::IDevice * + { + return m_device.get(); + } + + [[nodiscard]] auto swapchain() -> lt::renderer::ISwapchain * + { + return m_swapchain.get(); + } + + void recreate_swapchain() + { + m_swapchain.reset(); + m_swapchain = lt::renderer::ISwapchain::create( + constants::api, + surface(), + gpu(), + m_device.get() + ); + } + +private: + lt::memory::Scope m_device { + lt::renderer::IDevice::create(constants::api, gpu(), surface()) + }; + + lt::memory::Scope m_swapchain { + lt::renderer::ISwapchain::create(constants::api, surface(), gpu(), m_device.get()) + }; +}; + +class Fixture_RendererSystem: public Fixture_SurfaceSystem +{ +public: + Fixture_RendererSystem() = default; + + [[nodiscard]] auto renderer_system() -> lt::renderer::System & + { + return m_system; + } + + [[nodiscard]] auto has_any_messages() const -> bool + { + return m_has_any_messages; + } + + [[nodiscard]] auto has_any_messages_of(lt::renderer::MessageSeverity severity) const -> uint32_t + { + return m_severity_counter.contains(severity); + } + +private: + static void messenger_callback( + lt::renderer::MessageSeverity severity, + lt::renderer::MessageType type, + lt::renderer::MessageData data, + std::any &user_data + ) + { + std::ignore = type; + std::ignore = data; + + auto *fixture = std::any_cast(user_data); + fixture->m_has_any_messages = true; + ++fixture->m_severity_counter[severity]; + } + + std::unordered_map m_severity_counter; + + bool m_has_any_messages {}; + + lt::renderer::System m_system = lt::renderer::System::CreateInfo { + .config = { + .target_api = constants::api, + .max_frames_in_flight = constants::frames_in_flight, + }, + .registry = registry(), + .surface_entity = surface_entity(), + .messenger_info = lt::renderer::MessengerComponent::CreateInfo { + .severities = lt::renderer::MessageSeverity::all, + .types = lt::renderer::MessageType::all, + .callback = &messenger_callback, + .user_data = this, + } + }; +}; diff --git a/modules/renderer/public/api.hpp b/modules/renderer/public/api.hpp index a0aa2ba..373be79 100644 --- a/modules/renderer/public/api.hpp +++ b/modules/renderer/public/api.hpp @@ -2,11 +2,13 @@ namespace lt::renderer { -enum class API : uint8_t +enum class Api : uint8_t { - Vulkan, - DirectX, - Metal, + none = 0, + + vulkan, + direct_x, + metal, }; } diff --git a/modules/renderer/public/components/messenger.hpp b/modules/renderer/public/components/messenger.hpp index 46d6c44..34ea09e 100644 --- a/modules/renderer/public/components/messenger.hpp +++ b/modules/renderer/public/components/messenger.hpp @@ -29,7 +29,7 @@ enum class MessageType : uint8_t all = general | validation | performance, }; -struct MessengerCallbackData +struct MessageData { std::string message; }; @@ -37,7 +37,7 @@ struct MessengerCallbackData using Callback_T = std::function; diff --git a/modules/renderer/public/frontend/messenger.hpp b/modules/renderer/public/frontend/messenger.hpp index ba37e7a..88cc70c 100644 --- a/modules/renderer/public/frontend/messenger.hpp +++ b/modules/renderer/public/frontend/messenger.hpp @@ -9,7 +9,7 @@ namespace lt::renderer { class IMessenger { public: - [[nodiscard]] static auto create(API target_api, class IInstance *instance, ecs::Entity entity) + [[nodiscard]] static auto create(Api target_api, class IInstance *instance, ecs::Entity entity) -> memory::Scope; IMessenger() = default; diff --git a/modules/renderer/public/system.hpp b/modules/renderer/public/system.hpp index d7d7400..54f33de 100644 --- a/modules/renderer/public/system.hpp +++ b/modules/renderer/public/system.hpp @@ -7,15 +7,22 @@ #include #include #include +#include namespace lt::renderer { class System: public app::ISystem { public: + /** config.max_frames_in_flight should not be higher than this value. */ + static constexpr auto frames_in_flight_upper_limit = IRenderer::frames_in_flight_upper_limit; + + /** config.max_frames_in_flight should not be lower than this value. */ + static constexpr auto frames_in_flight_lower_limit = IRenderer::frames_in_flight_lower_limit; + struct Configuration { - API target_api; + Api target_api; uint32_t max_frames_in_flight; }; @@ -27,6 +34,8 @@ public: memory::Ref registry; ecs::Entity surface_entity; + + std::optional messenger_info; }; System(CreateInfo info); @@ -47,7 +56,35 @@ public: void tick(app::TickInfo tick) override; - void create_messenger_component(ecs::EntityId entity, MessengerComponent::CreateInfo info); + [[nodiscard]] auto get_surface() -> class ISurface * + { + return m_surface.get(); + } + + [[nodiscard]] auto get_gpu() -> class IGpu * + { + return m_gpu.get(); + } + + [[nodiscard]] auto get_device() -> class IDevice * + { + return m_device.get(); + } + + [[nodiscard]] auto get_swapchain() -> class ISwapchain * + { + return m_swapchain.get(); + } + + [[nodiscard]] auto get_renderer() -> class IRenderer * + { + return m_renderer.get(); + } + + [[nodiscard]] auto create_messenger_component( + ecs::EntityId entity, + MessengerComponent::CreateInfo info + ) -> bool; [[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override { @@ -55,13 +92,23 @@ public: } private: - API m_api; + Api m_api; memory::Ref m_registry; ecs::Entity m_surface_entity; - memory::Scope m_context; + memory::Scope m_messenger; + + class IInstance *m_instance; + + memory::Scope m_surface; + + memory::Scope m_gpu; + + memory::Scope m_device; + + memory::Scope m_swapchain; memory::Scope m_renderer;